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 -
77 - List<FlowRule> appliedFlowRules = Lists.newArrayList();
78 56
79 - for (FlowRule rule : rules) { 57 + DeviceId deviceId = handler().data().deviceId();
80 58
81 - Bmv2TableEntry entry; 59 + List<FlowEntry> entryList = Lists.newArrayList();
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())) { 69 + }
92 - tableRulesMap.put(rule.tableId(), Sets.newHashSet());
93 - }
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( 75 + }
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 - }
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); 81 + }
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 - }
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 -
153 - try {
154 - deviceClient.deleteTableEntry(tableName, entryId);
155 - } catch (Bmv2RuntimeException e) {
156 - log.error("Unable to delete flow rule", e);
157 - continue;
158 - }
159 -
160 - /* remove from local store */
161 - tableEntryIdsMap.remove(rule);
162 - tableRulesMap.get(rule.tableId()).remove(rule);
163 - deviceEntriesMap.remove(rule);
164 100
165 - removedFlowRules.add(rule); 101 + try {
102 + bmv2Entry = TRANSLATOR.translate(rule);
103 + } catch (Bmv2FlowRuleTranslatorException e) {
104 + LOG.error("Unable to translate flow rule: {}", e.getMessage());
105 + continue;
166 } 106 }
167 - }
168 107
169 - return Collections.unmodifiableCollection(removedFlowRules); 108 + String tableName = bmv2Entry.tableName();
170 - } 109 + Triple<DeviceId, String, Bmv2MatchKey> entryKey = Triple.of(deviceId, tableName, bmv2Entry.matchKey());
171 110
172 - private Bmv2TableEntry parseFlowRule(FlowRule flowRule) { 111 + /*
173 - 112 + From here on threads are synchronized over entryKey, i.e. serialize operations
174 - // TODO make it pipeline dependant, i.e. implement mapping 113 + over the same matchKey of a specific table and device.
175 - 114 + */
176 - Bmv2TableEntry.Builder entryBuilder = Bmv2TableEntry.builder(); 115 + ENTRIES_MAP.compute(entryKey, (key, value) -> {
177 - 116 + try {
178 - // Check selector 117 + if (operation == Operation.APPLY) {
179 - ExtensionCriterion ec = 118 + // Apply entry
180 - (ExtensionCriterion) flowRule 119 + long entryId;
181 - .selector().getCriterion(Criterion.Type.EXTENSION); 120 + if (value == null) {
182 - Preconditions.checkState( 121 + // New entry
183 - flowRule.selector().criteria().size() == 1 122 + entryId = deviceClient.addTableEntry(bmv2Entry);
184 - && ec != null, 123 + } else {
185 - "Selector must have only 1 criterion of type EXTENSION"); 124 + // Existing entry
186 - ExtensionSelector es = ec.extensionSelector(); 125 + entryId = value.getKey();
187 - Preconditions.checkState( 126 + // FIXME: check if priority or timeout changed
188 - es.type() == ExtensionSelectorType.ExtensionSelectorTypes.P4_BMV2_MATCH_KEY.type(), 127 + // In this case we should to re-add the entry (not modify)
189 - "ExtensionSelectorType must be P4_BMV2_MATCH_KEY"); 128 + deviceClient.modifyTableEntry(tableName, entryId, bmv2Entry.action());
190 - 129 + }
191 - // Selector OK, get Bmv2MatchKey 130 + // TODO: evaluate flow entry life, bytes and packets
192 - entryBuilder.withMatchKey(((Bmv2ExtensionSelector) es).matchKey()); 131 + FlowEntry flowEntry = new DefaultFlowEntry(
193 - 132 + rule, FlowEntry.FlowEntryState.ADDED, 0, 0, 0);
194 - // Check treatment 133 + value = Pair.of(entryId, flowEntry);
195 - Instruction inst = flowRule.treatment().allInstructions().get(0); 134 + } else {
196 - Preconditions.checkState( 135 + // Remove entry
197 - flowRule.treatment().allInstructions().size() == 1 136 + if (value == null) {
198 - && inst.type() == Instruction.Type.EXTENSION, 137 + // Entry not found in map, how come?
199 - "Treatment must have only 1 instruction of type EXTENSION"); 138 + LOG.debug("Trying to remove entry, but entry ID not found: " + entryKey);
200 - ExtensionTreatment et = 139 + } else {
201 - ((Instructions.ExtensionInstructionWrapper) inst) 140 + deviceClient.deleteTableEntry(tableName, value.getKey());
202 - .extensionInstruction(); 141 + value = null;
203 - 142 + }
204 - Preconditions.checkState( 143 + }
205 - et.type() == ExtensionTreatmentType.ExtensionTreatmentTypes.P4_BMV2_ACTION.type(), 144 + // If here, no exceptions... things went well :)
206 - "ExtensionTreatmentType must be P4_BMV2_ACTION"); 145 + processedFlowRules.add(rule);
207 - 146 + } catch (Bmv2RuntimeException e) {
208 - // Treatment OK, get Bmv2Action 147 + LOG.error("Unable to " + operation.name().toLowerCase() + " flow rule", e);
209 - entryBuilder.withAction(((Bmv2ExtensionTreatment) et).getAction()); 148 + } catch (Exception e) {
210 - 149 + LOG.error("Uncaught exception while processing flow rule", e);
211 - // Table name 150 + }
212 - entryBuilder.withTableName(parseTableName(flowRule.tableId())); 151 + return value;
213 - 152 + });
214 - if (!flowRule.isPermanent()) {
215 - entryBuilder.withTimeout(flowRule.timeout());
216 } 153 }
217 154
218 - entryBuilder.withPriority(flowRule.priority()); 155 + return processedFlowRules;
219 -
220 - return entryBuilder.build();
221 - }
222 -
223 - private String parseTableName(int tableId) {
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
......