blob: ca9392f24c233063a7640a9495323430c28253e6 [file] [log] [blame]
Frank Wang0e805082017-07-21 14:37:35 +08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Frank Wang0e805082017-07-21 14:37:35 +08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Andrea Campanella0288c872017-08-07 18:32:51 +020017package org.onosproject.drivers.p4runtime;
Frank Wang0e805082017-07-21 14:37:35 +080018
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020019import com.google.common.collect.ImmutableList;
20import com.google.common.collect.Lists;
Frank Wang0e805082017-07-21 14:37:35 +080021import com.google.common.collect.Maps;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020022import io.grpc.StatusRuntimeException;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080023import org.onlab.util.SharedExecutors;
24import org.onosproject.drivers.p4runtime.mirror.P4RuntimeTableMirror;
25import org.onosproject.drivers.p4runtime.mirror.TimedEntry;
Frank Wang0e805082017-07-21 14:37:35 +080026import org.onosproject.net.flow.DefaultFlowEntry;
27import org.onosproject.net.flow.FlowEntry;
28import org.onosproject.net.flow.FlowRule;
29import org.onosproject.net.flow.FlowRuleProgrammable;
Carmelo Cascone87892e22017-11-13 16:01:29 -080030import org.onosproject.net.pi.model.PiCounterId;
Frank Wang0e805082017-07-21 14:37:35 +080031import org.onosproject.net.pi.model.PiPipelineInterpreter;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020032import org.onosproject.net.pi.model.PiPipelineModel;
Carmelo Cascone87892e22017-11-13 16:01:29 -080033import org.onosproject.net.pi.model.PiTableId;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020034import org.onosproject.net.pi.model.PiTableModel;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020035import org.onosproject.net.pi.runtime.PiCounterCellData;
36import org.onosproject.net.pi.runtime.PiCounterCellId;
Frank Wang0e805082017-07-21 14:37:35 +080037import org.onosproject.net.pi.runtime.PiTableEntry;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080038import org.onosproject.net.pi.runtime.PiTableEntryHandle;
39import org.onosproject.net.pi.service.PiFlowRuleTranslator;
40import org.onosproject.net.pi.service.PiTranslatedEntity;
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080041import org.onosproject.net.pi.service.PiTranslationException;
Frank Wang0e805082017-07-21 14:37:35 +080042import org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType;
Frank Wang0e805082017-07-21 14:37:35 +080043
44import java.util.Collection;
45import java.util.Collections;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020046import java.util.List;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020047import java.util.Map;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080048import java.util.Optional;
Carmelo Cascone7f75be42017-09-07 14:37:02 +020049import java.util.Set;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020050import java.util.concurrent.ConcurrentMap;
51import java.util.concurrent.ExecutionException;
52import java.util.concurrent.locks.Lock;
53import java.util.concurrent.locks.ReentrantLock;
Carmelo Casconefe99be92017-09-11 21:55:54 +020054import java.util.stream.Collectors;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020055
56import static com.google.common.collect.Lists.newArrayList;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080057import static java.util.Collections.singleton;
Andrea Campanella0288c872017-08-07 18:32:51 +020058import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.APPLY;
59import static org.onosproject.drivers.p4runtime.P4RuntimeFlowRuleProgrammable.Operation.REMOVE;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020060import static org.onosproject.net.flow.FlowEntry.FlowEntryState.ADDED;
Carmelo Cascone87892e22017-11-13 16:01:29 -080061import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.DELETE;
62import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.INSERT;
63import static org.onosproject.p4runtime.api.P4RuntimeClient.WriteOperationType.MODIFY;
Frank Wang0e805082017-07-21 14:37:35 +080064
65/**
Carmelo Casconee3a7c742017-09-01 01:25:52 +020066 * Implementation of the flow rule programmable behaviour for P4Runtime.
Frank Wang0e805082017-07-21 14:37:35 +080067 */
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080068public class P4RuntimeFlowRuleProgrammable
69 extends AbstractP4RuntimeHandlerBehaviour
70 implements FlowRuleProgrammable {
Frank Wang0e805082017-07-21 14:37:35 +080071
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080072 // When updating an existing rule, if true, we issue a DELETE operation
73 // before inserting the new one, otherwise we issue a MODIFY operation. This
74 // is useful fore devices that do not support MODIFY operations for table
75 // entries.
Carmelo Casconefe99be92017-09-11 21:55:54 +020076 // TODO: make this attribute configurable by child drivers (e.g. BMv2 or Tofino)
Carmelo Cascone2308e522017-08-25 02:35:12 +020077 private boolean deleteEntryBeforeUpdate = true;
78
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080079 // If true, we ignore re-installing rules that are already exists the
80 // device, i.e. same match key and action.
81 // FIXME: can remove this check as soon as the multi-apply-per-same-flow rule bug is fixed.
82 private boolean checkStoreBeforeUpdate = true;
Carmelo Cascone2308e522017-08-25 02:35:12 +020083
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080084 // If true, we avoid querying the device and return what's already known by
85 // the ONOS store.
Yi Tseng63a82df2017-11-17 16:41:40 -080086 private boolean ignoreDeviceWhenGet = true;
Carmelo Casconefe99be92017-09-11 21:55:54 +020087
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080088 /* If true, we read all direct counters of a table with one request.
89 Otherwise, we send as many requests as the number of table entries. */
90 // FIXME: set to true as soon as the feature is implemented in P4Runtime.
Carmelo Cascone7f75be42017-09-07 14:37:02 +020091 private boolean readAllDirectCounters = false;
92
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020093 // Needed to synchronize operations over the same table entry.
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -080094 // FIXME: locks should be removed when unused (hint use cache with timeout)
95 private static final ConcurrentMap<PiTableEntryHandle, Lock>
96 ENTRY_LOCKS = Maps.newConcurrentMap();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020097
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +020098 private PiPipelineModel pipelineModel;
99 private PiPipelineInterpreter interpreter;
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800100 private P4RuntimeTableMirror tableMirror;
101 private PiFlowRuleTranslator translator;
Frank Wang0e805082017-07-21 14:37:35 +0800102
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200103 @Override
104 protected boolean setupBehaviour() {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200105
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200106 if (!super.setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +0800107 return false;
108 }
109
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200110 if (!device.is(PiPipelineInterpreter.class)) {
111 log.warn("Unable to get interpreter of {}", deviceId);
Frank Wang0e805082017-07-21 14:37:35 +0800112 return false;
113 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200114 interpreter = device.as(PiPipelineInterpreter.class);
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200115 pipelineModel = pipeconf.pipelineModel();
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800116 tableMirror = handler().get(P4RuntimeTableMirror.class);
117 translator = piTranslationService.flowRuleTranslator();
Frank Wang0e805082017-07-21 14:37:35 +0800118 return true;
119 }
120
121 @Override
122 public Collection<FlowEntry> getFlowEntries() {
123
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200124 if (!setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +0800125 return Collections.emptyList();
126 }
127
Carmelo Casconefe99be92017-09-11 21:55:54 +0200128 if (ignoreDeviceWhenGet) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800129 return getFlowEntriesFromMirror();
Carmelo Casconefe99be92017-09-11 21:55:54 +0200130 }
131
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800132 final ImmutableList.Builder<FlowEntry> result = ImmutableList.builder();
133 final List<PiTableEntry> inconsistentEntries = Lists.newArrayList();
Frank Wang0e805082017-07-21 14:37:35 +0800134
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200135 for (PiTableModel tableModel : pipelineModel.tables()) {
136
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800137 final PiTableId piTableId = tableModel.id();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200138
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800139 // Read table entries.
140 final Collection<PiTableEntry> installedEntries;
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200141 try {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800142 // TODO: optimize by dumping entries and counters in parallel
143 // From ALL tables with the same request.
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200144 installedEntries = client.dumpTable(piTableId, pipeconf).get();
145 } catch (InterruptedException | ExecutionException e) {
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200146 if (!(e.getCause() instanceof StatusRuntimeException)) {
147 // gRPC errors are logged in the client.
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800148 log.error("Exception while dumping table {} of {}",
149 piTableId, deviceId, e);
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200150 }
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800151 continue; // next table
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200152 }
153
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800154 if (installedEntries.size() == 0) {
155 continue; // next table
156 }
157
158 // Read table direct counters (if any).
159 final Map<PiTableEntry, PiCounterCellData> counterCellMap;
160 if (interpreter.mapTableCounter(piTableId).isPresent()) {
161 PiCounterId piCounterId = interpreter.mapTableCounter(piTableId).get();
162 counterCellMap = readEntryCounters(piCounterId, installedEntries);
163 } else {
Carmelo Cascone7f75be42017-09-07 14:37:02 +0200164 counterCellMap = Collections.emptyMap();
165 }
166
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800167 // Forge flow entries with counter values.
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200168 for (PiTableEntry installedEntry : installedEntries) {
169
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800170 final FlowEntry flowEntry = forgeFlowEntry(
171 installedEntry, counterCellMap.get(installedEntry));
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200172
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800173 if (flowEntry == null) {
174 // Entry is on device but unknown to translation service or
175 // device mirror. Inconsistent. Mark for removal.
176 // TODO: make this behaviour configurable
177 // In some cases it's fine for the device to have rules
178 // that were not installed by us.
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200179 inconsistentEntries.add(installedEntry);
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800180 } else {
181 result.add(flowEntry);
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200182 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200183 }
184 }
185
186 if (inconsistentEntries.size() > 0) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800187 // Async clean up inconsistent entries.
188 SharedExecutors.getSingleThreadExecutor().execute(
189 () -> cleanUpInconsistentEntries(inconsistentEntries));
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200190 }
191
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800192 return result.build();
Frank Wang0e805082017-07-21 14:37:35 +0800193 }
194
195 @Override
196 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200197 return processFlowRules(rules, APPLY);
Frank Wang0e805082017-07-21 14:37:35 +0800198 }
199
200 @Override
201 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200202 return processFlowRules(rules, REMOVE);
Frank Wang0e805082017-07-21 14:37:35 +0800203 }
204
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800205 private FlowEntry forgeFlowEntry(PiTableEntry entry,
206 PiCounterCellData cellData) {
207 final PiTableEntryHandle handle = PiTableEntryHandle
208 .of(deviceId, entry);
209 final Optional<PiTranslatedEntity<FlowRule, PiTableEntry>>
210 translatedEntity = translator.lookup(handle);
211 final TimedEntry<PiTableEntry> timedEntry = tableMirror.get(handle);
212
213 if (!translatedEntity.isPresent()) {
214 log.debug("Handle not found in store: {}", handle);
215 return null;
216 }
217
218 if (timedEntry == null) {
219 log.debug("Handle not found in device mirror: {}", handle);
220 return null;
221 }
222
223 if (cellData != null) {
224 return new DefaultFlowEntry(translatedEntity.get().original(),
225 ADDED, timedEntry.lifeSec(), cellData.bytes(),
226 cellData.bytes());
227 } else {
228 return new DefaultFlowEntry(translatedEntity.get().original(),
229 ADDED, timedEntry.lifeSec(), 0, 0);
230 }
231 }
232
233 private Collection<FlowEntry> getFlowEntriesFromMirror() {
234 return tableMirror.getAll(deviceId).stream()
235 .map(timedEntry -> forgeFlowEntry(
236 timedEntry.entry(), null))
237 .collect(Collectors.toList());
238 }
239
240 private void cleanUpInconsistentEntries(Collection<PiTableEntry> piEntries) {
241 log.warn("Found {} entries from {} not on translation store, removing them...",
242 piEntries.size(), deviceId);
243 piEntries.forEach(entry -> {
244 log.debug(entry.toString());
245 applyEntry(PiTableEntryHandle.of(deviceId, entry),
246 entry, null, REMOVE);
247 });
248 }
249
250 private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules,
251 Operation driverOperation) {
Frank Wang0e805082017-07-21 14:37:35 +0800252
Carmelo Casconee3a7c742017-09-01 01:25:52 +0200253 if (!setupBehaviour()) {
Frank Wang0e805082017-07-21 14:37:35 +0800254 return Collections.emptyList();
255 }
256
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800257 final ImmutableList.Builder<FlowRule> result = ImmutableList.builder();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200258
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800259 // TODO: send writes in bulk (e.g. all entries to insert, modify or delete).
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200260 // Instead of calling the client for each one of them.
261
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800262 for (FlowRule ruleToApply : rules) {
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200263
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800264 final PiTableEntry piEntryToApply;
Frank Wang0e805082017-07-21 14:37:35 +0800265 try {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800266 piEntryToApply = translator.translate(ruleToApply, pipeconf);
Carmelo Cascone326ad2d2017-11-28 18:09:13 -0800267 } catch (PiTranslationException e) {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800268 log.warn("Unable to translate flow rule for pipeconf '{}': {} - {}",
269 pipeconf.id(), e.getMessage(), ruleToApply);
270 // Next rule.
271 continue;
Frank Wang0e805082017-07-21 14:37:35 +0800272 }
Frank Wang0e805082017-07-21 14:37:35 +0800273
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800274 final PiTableEntryHandle handle = PiTableEntryHandle
275 .of(deviceId, piEntryToApply);
Frank Wang0e805082017-07-21 14:37:35 +0800276
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800277 // Serialize operations over the same match key/table/device ID.
278 final Lock lock = ENTRY_LOCKS.computeIfAbsent(handle, k -> new ReentrantLock());
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200279 lock.lock();
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200280 try {
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800281 if (applyEntry(handle, piEntryToApply,
282 ruleToApply, driverOperation)) {
283 result.add(ruleToApply);
Frank Wang0e805082017-07-21 14:37:35 +0800284 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200285 } finally {
286 lock.unlock();
287 }
288 }
289
Carmelo Cascone6a0b5a32017-11-20 23:08:32 -0800290 return result.build();
291 }
292
293 /**
294 * Applies the given entry to the device, and returns true if the operation
295 * was successful, false otherwise.
296 */
297 private boolean applyEntry(PiTableEntryHandle handle,
298 PiTableEntry piEntryToApply,
299 FlowRule ruleToApply,
300 Operation driverOperation) {
301 // Depending on the driver operation, and if a matching rule exists on
302 // the device, decide which P4 Runtime write operation to perform for
303 // this entry.
304 final TimedEntry<PiTableEntry> piEntryOnDevice = tableMirror.get(handle);
305 final WriteOperationType p4Operation;
306 if (driverOperation == APPLY) {
307 if (piEntryOnDevice == null) {
308 // Entry is first-timer.
309 p4Operation = INSERT;
310 } else {
311 if (checkStoreBeforeUpdate
312 && piEntryToApply.action().equals(piEntryOnDevice.entry().action())) {
313 log.debug("Ignoring re-apply of existing entry: {}", piEntryToApply);
314 p4Operation = null;
315 } else if (deleteEntryBeforeUpdate) {
316 // Some devices return error when updating existing
317 // entries. If requested, remove entry before
318 // re-inserting the modified one.
319 applyEntry(handle, piEntryOnDevice.entry(), null, REMOVE);
320 p4Operation = INSERT;
321 } else {
322 p4Operation = MODIFY;
323 }
324 }
325 } else {
326 p4Operation = DELETE;
327 }
328
329 if (p4Operation != null) {
330 if (writeEntry(piEntryToApply, p4Operation)) {
331 updateStores(handle, piEntryToApply, ruleToApply, p4Operation);
332 return true;
333 } else {
334 return false;
335 }
336 } else {
337 // If no operation, let's pretend we applied the rule to the device.
338 return true;
339 }
340 }
341
342 /**
343 * Performs a write operation on the device.
344 */
345 private boolean writeEntry(PiTableEntry entry,
346 WriteOperationType p4Operation) {
347 try {
348 if (client.writeTableEntries(
349 newArrayList(entry), p4Operation, pipeconf).get()) {
350 return true;
351 } else {
352 log.warn("Unable to {} table entry in {}: {}",
353 p4Operation.name(), deviceId, entry);
354 }
355 } catch (InterruptedException | ExecutionException e) {
356 log.warn("Exception while performing {} table entry operation:",
357 p4Operation, e);
358 }
359 return false;
360 }
361
362 private void updateStores(PiTableEntryHandle handle,
363 PiTableEntry entry,
364 FlowRule rule,
365 WriteOperationType p4Operation) {
366 switch (p4Operation) {
367 case INSERT:
368 case MODIFY:
369 tableMirror.put(handle, entry);
370 translator.learn(handle, new PiTranslatedEntity<>(rule, entry, handle));
371 break;
372 case DELETE:
373 tableMirror.remove(handle);
374 translator.forget(handle);
375 break;
376 default:
377 throw new IllegalArgumentException(
378 "Unknown operation " + p4Operation.name());
379 }
380 }
381
382 private Map<PiTableEntry, PiCounterCellData> readEntryCounters(
383 PiCounterId counterId, Collection<PiTableEntry> tableEntries) {
384 Collection<PiCounterCellData> cellDatas;
385 try {
386 if (readAllDirectCounters) {
387 cellDatas = client.readAllCounterCells(
388 singleton(counterId), pipeconf).get();
389 } else {
390 Set<PiCounterCellId> cellIds = tableEntries.stream()
391 .map(entry -> PiCounterCellId.ofDirect(counterId, entry))
392 .collect(Collectors.toSet());
393 cellDatas = client.readCounterCells(cellIds, pipeconf).get();
394 }
395 return cellDatas.stream()
396 .collect(Collectors.toMap(c -> c.cellId().tableEntry(), c -> c));
397 } catch (InterruptedException | ExecutionException e) {
398 if (!(e.getCause() instanceof StatusRuntimeException)) {
399 // gRPC errors are logged in the client.
400 log.error("Exception while reading counter '{}' from {}: {}",
401 counterId, deviceId, e);
402 }
403 return Collections.emptyMap();
404 }
Carmelo Cascone0b22d8f2017-07-31 07:22:27 +0200405 }
406
407 enum Operation {
408 APPLY, REMOVE
Frank Wang0e805082017-07-21 14:37:35 +0800409 }
Carmelo Cascone87892e22017-11-13 16:01:29 -0800410}