Carmelo Cascone

Translator-based Bmv2 flow rule driver

Removed old parsing logic. Now it uses Bmv2FlowRuleTranslator to
translate ONOS flow rule into Bmv2 model-dependent table entries.

Change-Id: I1febc23b334acade027e806c8a8c266acc061277
1 /* 1 /*
2 - * Copyright 2014-2016 Open Networking Laboratory 2 + * Copyright 2016-present Open Networking Laboratory
3 * 3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License. 5 * you may not use this file except in compliance with the License.
...@@ -16,221 +16,146 @@ ...@@ -16,221 +16,146 @@
16 16
17 package org.onosproject.drivers.bmv2; 17 package org.onosproject.drivers.bmv2;
18 18
19 -import com.google.common.base.Preconditions;
20 import com.google.common.collect.Lists; 19 import com.google.common.collect.Lists;
21 import com.google.common.collect.Maps; 20 import com.google.common.collect.Maps;
22 -import com.google.common.collect.Sets; 21 +import org.apache.commons.lang3.tuple.Pair;
23 -import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector; 22 +import org.apache.commons.lang3.tuple.Triple;
24 -import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment; 23 +import org.onosproject.bmv2.api.runtime.Bmv2MatchKey;
25 -import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
26 import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException; 24 import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
25 +import org.onosproject.bmv2.api.runtime.Bmv2TableEntry;
27 import org.onosproject.bmv2.ctl.Bmv2ThriftClient; 26 import org.onosproject.bmv2.ctl.Bmv2ThriftClient;
27 +import org.onosproject.drivers.bmv2.translators.Bmv2DefaultFlowRuleTranslator;
28 +import org.onosproject.drivers.bmv2.translators.Bmv2FlowRuleTranslator;
29 +import org.onosproject.drivers.bmv2.translators.Bmv2FlowRuleTranslatorException;
30 +import org.onosproject.net.DeviceId;
28 import org.onosproject.net.driver.AbstractHandlerBehaviour; 31 import org.onosproject.net.driver.AbstractHandlerBehaviour;
29 import org.onosproject.net.flow.DefaultFlowEntry; 32 import org.onosproject.net.flow.DefaultFlowEntry;
30 import org.onosproject.net.flow.FlowEntry; 33 import org.onosproject.net.flow.FlowEntry;
31 import org.onosproject.net.flow.FlowRule; 34 import org.onosproject.net.flow.FlowRule;
32 import org.onosproject.net.flow.FlowRuleProgrammable; 35 import org.onosproject.net.flow.FlowRuleProgrammable;
33 -import org.onosproject.net.flow.criteria.Criterion;
34 -import org.onosproject.net.flow.criteria.ExtensionCriterion;
35 -import org.onosproject.net.flow.criteria.ExtensionSelector;
36 -import org.onosproject.net.flow.criteria.ExtensionSelectorType;
37 -import org.onosproject.net.flow.instructions.ExtensionTreatment;
38 -import org.onosproject.net.flow.instructions.ExtensionTreatmentType;
39 -import org.onosproject.net.flow.instructions.Instruction;
40 -import org.onosproject.net.flow.instructions.Instructions;
41 import org.slf4j.Logger; 36 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory; 37 import org.slf4j.LoggerFactory;
43 38
44 import java.util.Collection; 39 import java.util.Collection;
45 import java.util.Collections; 40 import java.util.Collections;
46 import java.util.List; 41 import java.util.List;
47 -import java.util.Map; 42 +import java.util.concurrent.ConcurrentMap;
48 -import java.util.Set;
49 43
50 public class Bmv2FlowRuleDriver extends AbstractHandlerBehaviour 44 public class Bmv2FlowRuleDriver extends AbstractHandlerBehaviour
51 implements FlowRuleProgrammable { 45 implements FlowRuleProgrammable {
52 46
53 - private final Logger log = 47 + private static final Logger LOG =
54 - LoggerFactory.getLogger(this.getClass()); 48 + LoggerFactory.getLogger(Bmv2FlowRuleDriver.class);
55 - 49 + // There's no Bmv2 client method to poll flow entries from the device device. gitNeed a local store.
56 - // Bmv2 doesn't support proper table dump, use a local store 50 + private static final ConcurrentMap<Triple<DeviceId, String, Bmv2MatchKey>, Pair<Long, FlowEntry>>
57 - // FIXME: synchronize entries with device 51 + ENTRIES_MAP = Maps.newConcurrentMap();
58 - private final Map<FlowRule, FlowEntry> deviceEntriesMap = Maps.newHashMap(); 52 + private static final Bmv2FlowRuleTranslator TRANSLATOR = new Bmv2DefaultFlowRuleTranslator();
59 - private final Map<Integer, Set<FlowRule>> tableRulesMap = Maps.newHashMap();
60 - private final Map<FlowRule, Long> tableEntryIdsMap = Maps.newHashMap();
61 53
62 @Override 54 @Override
63 public Collection<FlowEntry> getFlowEntries() { 55 public Collection<FlowEntry> getFlowEntries() {
64 - return Collections.unmodifiableCollection(
65 - deviceEntriesMap.values());
66 - }
67 -
68 - @Override
69 - public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
70 - Bmv2ThriftClient deviceClient;
71 - try {
72 - deviceClient = getDeviceClient();
73 - } catch (Bmv2RuntimeException e) {
74 - return Collections.emptyList();
75 - }
76 56
77 - List<FlowRule> appliedFlowRules = Lists.newArrayList(); 57 + DeviceId deviceId = handler().data().deviceId();
78 58
79 - for (FlowRule rule : rules) { 59 + List<FlowEntry> entryList = Lists.newArrayList();
80 -
81 - Bmv2TableEntry entry;
82 60
83 - try { 61 + // FIXME: improve this, e.g. might store a separate Map<DeviceId, Collection<FlowEntry>>
84 - entry = parseFlowRule(rule); 62 + ENTRIES_MAP.forEach((key, value) -> {
85 - } catch (IllegalStateException e) { 63 + if (key.getLeft() == deviceId && value != null) {
86 - log.error("Unable to parse flow rule", e); 64 + entryList.add(value.getRight());
87 - continue;
88 } 65 }
66 + });
89 67
90 - // Instantiate flowrule set for table if it does not exist 68 + return Collections.unmodifiableCollection(entryList);
91 - if (!tableRulesMap.containsKey(rule.tableId())) {
92 - tableRulesMap.put(rule.tableId(), Sets.newHashSet());
93 } 69 }
94 70
95 - if (tableRulesMap.get(rule.tableId()).contains(rule)) { 71 + @Override
96 - /* Rule is already installed in the table */ 72 + public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
97 - long entryId = tableEntryIdsMap.get(rule);
98 73
99 - try { 74 + return processFlowRules(rules, Operation.APPLY);
100 - deviceClient.modifyTableEntry(
101 - entry.tableName(), entryId, entry.action());
102 -
103 - // Replace stored rule as treatment, etc. might have changed
104 - // Java Set doesn't replace on add, remove first
105 - tableRulesMap.get(rule.tableId()).remove(rule);
106 - tableRulesMap.get(rule.tableId()).add(rule);
107 - tableEntryIdsMap.put(rule, entryId);
108 - deviceEntriesMap.put(rule, new DefaultFlowEntry(
109 - rule, FlowEntry.FlowEntryState.ADDED, 0, 0, 0));
110 - } catch (Bmv2RuntimeException e) {
111 - log.error("Unable to update flow rule", e);
112 - continue;
113 } 75 }
114 76
115 - } else { 77 + @Override
116 - /* Rule is new */ 78 + public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
117 - try {
118 - long entryId = deviceClient.addTableEntry(entry);
119 79
120 - tableRulesMap.get(rule.tableId()).add(rule); 80 + return processFlowRules(rules, Operation.REMOVE);
121 - tableEntryIdsMap.put(rule, entryId);
122 - deviceEntriesMap.put(rule, new DefaultFlowEntry(
123 - rule, FlowEntry.FlowEntryState.ADDED, 0, 0, 0));
124 - } catch (Bmv2RuntimeException e) {
125 - log.error("Unable to add flow rule", e);
126 - continue;
127 - }
128 } 81 }
129 82
130 - appliedFlowRules.add(rule); 83 + private Collection<FlowRule> processFlowRules(Collection<FlowRule> rules, Operation operation) {
131 - }
132 84
133 - return Collections.unmodifiableCollection(appliedFlowRules); 85 + DeviceId deviceId = handler().data().deviceId();
134 - }
135 86
136 - @Override
137 - public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
138 Bmv2ThriftClient deviceClient; 87 Bmv2ThriftClient deviceClient;
139 try { 88 try {
140 - deviceClient = getDeviceClient(); 89 + deviceClient = Bmv2ThriftClient.of(deviceId);
141 } catch (Bmv2RuntimeException e) { 90 } catch (Bmv2RuntimeException e) {
91 + LOG.error("Failed to connect to Bmv2 device", e);
142 return Collections.emptyList(); 92 return Collections.emptyList();
143 } 93 }
144 94
145 - List<FlowRule> removedFlowRules = Lists.newArrayList(); 95 + List<FlowRule> processedFlowRules = Lists.newArrayList();
146 96
147 for (FlowRule rule : rules) { 97 for (FlowRule rule : rules) {
148 98
149 - if (tableEntryIdsMap.containsKey(rule)) { 99 + Bmv2TableEntry bmv2Entry;
150 - long entryId = tableEntryIdsMap.get(rule);
151 - String tableName = parseTableName(rule.tableId());
152 100
153 try { 101 try {
154 - deviceClient.deleteTableEntry(tableName, entryId); 102 + bmv2Entry = TRANSLATOR.translate(rule);
155 - } catch (Bmv2RuntimeException e) { 103 + } catch (Bmv2FlowRuleTranslatorException e) {
156 - log.error("Unable to delete flow rule", e); 104 + LOG.error("Unable to translate flow rule: {}", e.getMessage());
157 continue; 105 continue;
158 } 106 }
159 107
160 - /* remove from local store */ 108 + String tableName = bmv2Entry.tableName();
161 - tableEntryIdsMap.remove(rule); 109 + Triple<DeviceId, String, Bmv2MatchKey> entryKey = Triple.of(deviceId, tableName, bmv2Entry.matchKey());
162 - tableRulesMap.get(rule.tableId()).remove(rule);
163 - deviceEntriesMap.remove(rule);
164 110
165 - removedFlowRules.add(rule); 111 + /*
166 - } 112 + From here on threads are synchronized over entryKey, i.e. serialize operations
113 + over the same matchKey of a specific table and device.
114 + */
115 + ENTRIES_MAP.compute(entryKey, (key, value) -> {
116 + try {
117 + if (operation == Operation.APPLY) {
118 + // Apply entry
119 + long entryId;
120 + if (value == null) {
121 + // New entry
122 + entryId = deviceClient.addTableEntry(bmv2Entry);
123 + } else {
124 + // Existing entry
125 + entryId = value.getKey();
126 + // FIXME: check if priority or timeout changed
127 + // In this case we should to re-add the entry (not modify)
128 + deviceClient.modifyTableEntry(tableName, entryId, bmv2Entry.action());
129 + }
130 + // TODO: evaluate flow entry life, bytes and packets
131 + FlowEntry flowEntry = new DefaultFlowEntry(
132 + rule, FlowEntry.FlowEntryState.ADDED, 0, 0, 0);
133 + value = Pair.of(entryId, flowEntry);
134 + } else {
135 + // Remove entry
136 + if (value == null) {
137 + // Entry not found in map, how come?
138 + LOG.debug("Trying to remove entry, but entry ID not found: " + entryKey);
139 + } else {
140 + deviceClient.deleteTableEntry(tableName, value.getKey());
141 + value = null;
167 } 142 }
168 -
169 - return Collections.unmodifiableCollection(removedFlowRules);
170 } 143 }
171 - 144 + // If here, no exceptions... things went well :)
172 - private Bmv2TableEntry parseFlowRule(FlowRule flowRule) { 145 + processedFlowRules.add(rule);
173 - 146 + } catch (Bmv2RuntimeException e) {
174 - // TODO make it pipeline dependant, i.e. implement mapping 147 + LOG.error("Unable to " + operation.name().toLowerCase() + " flow rule", e);
175 - 148 + } catch (Exception e) {
176 - Bmv2TableEntry.Builder entryBuilder = Bmv2TableEntry.builder(); 149 + LOG.error("Uncaught exception while processing flow rule", e);
177 -
178 - // Check selector
179 - ExtensionCriterion ec =
180 - (ExtensionCriterion) flowRule
181 - .selector().getCriterion(Criterion.Type.EXTENSION);
182 - Preconditions.checkState(
183 - flowRule.selector().criteria().size() == 1
184 - && ec != null,
185 - "Selector must have only 1 criterion of type EXTENSION");
186 - ExtensionSelector es = ec.extensionSelector();
187 - Preconditions.checkState(
188 - es.type() == ExtensionSelectorType.ExtensionSelectorTypes.P4_BMV2_MATCH_KEY.type(),
189 - "ExtensionSelectorType must be P4_BMV2_MATCH_KEY");
190 -
191 - // Selector OK, get Bmv2MatchKey
192 - entryBuilder.withMatchKey(((Bmv2ExtensionSelector) es).matchKey());
193 -
194 - // Check treatment
195 - Instruction inst = flowRule.treatment().allInstructions().get(0);
196 - Preconditions.checkState(
197 - flowRule.treatment().allInstructions().size() == 1
198 - && inst.type() == Instruction.Type.EXTENSION,
199 - "Treatment must have only 1 instruction of type EXTENSION");
200 - ExtensionTreatment et =
201 - ((Instructions.ExtensionInstructionWrapper) inst)
202 - .extensionInstruction();
203 -
204 - Preconditions.checkState(
205 - et.type() == ExtensionTreatmentType.ExtensionTreatmentTypes.P4_BMV2_ACTION.type(),
206 - "ExtensionTreatmentType must be P4_BMV2_ACTION");
207 -
208 - // Treatment OK, get Bmv2Action
209 - entryBuilder.withAction(((Bmv2ExtensionTreatment) et).getAction());
210 -
211 - // Table name
212 - entryBuilder.withTableName(parseTableName(flowRule.tableId()));
213 -
214 - if (!flowRule.isPermanent()) {
215 - entryBuilder.withTimeout(flowRule.timeout());
216 } 150 }
217 - 151 + return value;
218 - entryBuilder.withPriority(flowRule.priority()); 152 + });
219 -
220 - return entryBuilder.build();
221 } 153 }
222 154
223 - private String parseTableName(int tableId) { 155 + return processedFlowRules;
224 - // TODO: map tableId with tableName according to P4 JSON
225 - return "table" + String.valueOf(tableId);
226 } 156 }
227 157
228 - private Bmv2ThriftClient getDeviceClient() throws Bmv2RuntimeException { 158 + private enum Operation {
229 - try { 159 + APPLY, REMOVE
230 - return Bmv2ThriftClient.of(handler().data().deviceId());
231 - } catch (Bmv2RuntimeException e) {
232 - log.error("Failed to connect to Bmv2 device", e);
233 - throw e;
234 - }
235 } 160 }
236 } 161 }
...\ No newline at end of file ...\ No newline at end of file
......