blob: 7177dc048b6ace44359bd2c417dc51d1090d8f0b [file] [log] [blame]
Georgios Katsikas70671b32018-07-02 18:47:27 +02001/*
2 * Copyright 2018 Open Networking Foundation
3 *
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
17package org.onosproject.drivers.server;
18
19import com.fasterxml.jackson.databind.JsonNode;
20import com.fasterxml.jackson.databind.ObjectMapper;
21import com.fasterxml.jackson.databind.node.ArrayNode;
22import com.fasterxml.jackson.databind.node.ObjectNode;
23import com.google.common.collect.Sets;
24
25import org.slf4j.Logger;
26
27import org.onosproject.drivers.server.devices.nic.NicFlowRule;
28import org.onosproject.drivers.server.devices.nic.NicRxFilter.RxFilter;
29import org.onosproject.net.DeviceId;
30import org.onosproject.net.flow.DefaultFlowEntry;
31import org.onosproject.net.flow.FlowEntry;
32import org.onosproject.net.flow.FlowRule;
33import org.onosproject.net.flow.FlowRuleProgrammable;
34import org.onosproject.net.flow.FlowRuleService;
35
36import java.io.ByteArrayInputStream;
37import java.io.IOException;
38import java.io.InputStream;
39import java.util.Collection;
40import java.util.Collections;
41import java.util.Map;
42import java.util.Set;
43import java.util.concurrent.ConcurrentHashMap;
44import javax.ws.rs.ProcessingException;
45
46import static com.google.common.base.Preconditions.checkArgument;
47import static com.google.common.base.Preconditions.checkNotNull;
48import static org.slf4j.LoggerFactory.getLogger;
49
50/**
51 * Manages rules on commodity server devices, by
52 * converting ONOS FlowRule objetcs into
53 * network interface card (NIC) rules and vice versa.
54 */
55public class FlowRuleProgrammableServerImpl extends BasicServerDriver
56 implements FlowRuleProgrammable {
57
58 private final Logger log = getLogger(getClass());
59
60 /**
61 * Resource endpoints of the server agent (REST server-side).
62 */
63 private static final String RULE_MANAGEMENT_URL = BASE_URL + "/rules";
64
65 /**
66 * Parameters to be exchanged with the server's agent.
67 */
68 private static final String PARAM_RULES = "rules";
69 private static final String PARAM_NIC_ID = "nicId";
70 private static final String PARAM_CPU_ID = "cpuId";
71 private static final String PARAM_CPU_RULES = "cpuRules";
72 private static final String PARAM_RULE_ID = "ruleId";
73 private static final String PARAM_RULE_CONTENT = "ruleContent";
74
75 @Override
76 public Collection<FlowEntry> getFlowEntries() {
77 DeviceId deviceId = getHandler().data().deviceId();
78 checkNotNull(deviceId, DEVICE_ID_NULL);
79
80 // Expected FlowEntries installed through ONOS
81 FlowRuleService flowService = getHandler().get(FlowRuleService.class);
82 Iterable<FlowEntry> flowEntries = flowService.getFlowEntries(deviceId);
83
84 // Hit the path that provides the server's flow rules
85 InputStream response = null;
86 try {
87 response = getController().get(deviceId, RULE_MANAGEMENT_URL, JSON);
88 } catch (ProcessingException pEx) {
89 log.error("Failed to get flow entries from device: {}", deviceId);
90 return Collections.EMPTY_LIST;
91 }
92
93 // Load the JSON into objects
94 ObjectMapper mapper = new ObjectMapper();
95 ObjectNode objNode = null;
96 try {
97 Map<String, Object> jsonMap = mapper.readValue(response, Map.class);
98 JsonNode jsonNode = mapper.convertValue(jsonMap, JsonNode.class);
99 objNode = (ObjectNode) jsonNode;
100 } catch (IOException ioEx) {
101 log.error("Failed to get flow entries from device: {}", deviceId);
102 return Collections.EMPTY_LIST;
103 }
104
105 if (objNode == null) {
106 log.error("Failed to get flow entries from device: {}", deviceId);
107 return Collections.EMPTY_LIST;
108 }
109
110 JsonNode scsNode = objNode.path(PARAM_RULES);
111
112 // Here we store the trully installed rules
113 Collection<FlowEntry> actualFlowEntries =
114 Sets.<FlowEntry>newConcurrentHashSet();
115
116 for (JsonNode scNode : scsNode) {
117 String scId = get(scNode, PARAM_ID);
118 String rxFilter = get(
119 scNode.path(NIC_PARAM_RX_FILTER), NIC_PARAM_RX_METHOD);
120
121 // Only Flow-based RxFilter is permitted
122 if (RxFilter.getByName(rxFilter) != RxFilter.FLOW) {
123 log.warn("Device with Rx filter {} is not managed by this driver",
124 rxFilter.toString().toUpperCase());
125 continue;
126 }
127
128 // Each device might have multiple NICs
129 for (JsonNode nicNode : scNode.path(PARAM_NICS)) {
130 String nicId = get(nicNode, PARAM_NIC_ID);
131 JsonNode cpusNode = nicNode.path(PARAM_CPUS);
132
133 // Each NIC can dispatch to multiple CPU cores
134 for (JsonNode cpuNode : cpusNode) {
135 String cpuId = get(cpuNode, PARAM_CPU_ID);
136 JsonNode rulesNode = cpuNode.path(PARAM_CPU_RULES);
137
138 // Multiple rules might correspond to each CPU core
139 for (JsonNode ruleNode : rulesNode) {
140 long ruleId = ruleNode.path(PARAM_RULE_ID).asLong();
141 String ruleContent = get(ruleNode, PARAM_RULE_CONTENT);
142
143 // Search for this rule ID in ONOS's store
144 FlowRule r = findRuleInFlowEntries(flowEntries, ruleId);
145
146 // Local rule, not present in the controller => Ignore
147 if (r == null) {
148 continue;
149 // Rule trully present in the data plane => Add
150 } else {
151 actualFlowEntries.add(
152 new DefaultFlowEntry(
153 r, FlowEntry.FlowEntryState.ADDED, 0, 0, 0));
154 }
155 }
156 }
157 }
158 }
159
160 return actualFlowEntries;
161 }
162
163 @Override
164 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
165 DeviceId deviceId = getHandler().data().deviceId();
166 checkNotNull(deviceId, DEVICE_ID_NULL);
167
168 // Set of truly-installed rules to be reported
169 Set<FlowRule> installedRules = Sets.<FlowRule>newConcurrentHashSet();
170
171 // Splits the rule set into multiple ones, grouped by traffic class ID
172 Map<String, Set<FlowRule>> rulesPerTc = groupRules(rules);
173
174 // Install NIC rules on a per-traffic class basis
175 for (Map.Entry<String, Set<FlowRule>> entry : rulesPerTc.entrySet()) {
176 String tcId = entry.getKey();
177 Set<FlowRule> tcRuleSet = entry.getValue();
178
179 installedRules.addAll(
180 installNicFlowRules(deviceId, tcId, tcRuleSet)
181 );
182 }
183
184 return installedRules;
185 }
186
187 @Override
188 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
189 DeviceId deviceId = getHandler().data().deviceId();
190 checkNotNull(deviceId, DEVICE_ID_NULL);
191
192 // Set of truly-removed rules to be reported
193 Set<FlowRule> removedRules = Sets.<FlowRule>newConcurrentHashSet();
194
195 // for (FlowRule rule : rules) {
196 rules.forEach(rule -> {
197 if (removeNicFlowRule(deviceId, rule.id().value())) {
198 removedRules.add(rule);
199 }
200 });
201
202 return removedRules;
203 }
204
205 /**
206 * Groups a set of FlowRules by their traffic class ID.
207 *
208 * @param rules set of NIC rules to install
209 * @return a map of traffic class IDs to their set of NIC rules
210 */
211 private Map<String, Set<FlowRule>> groupRules(Collection<FlowRule> rules) {
212 Map<String, Set<FlowRule>> rulesPerTc =
213 new ConcurrentHashMap<String, Set<FlowRule>>();
214
215 rules.forEach(rule -> {
216 if (!(rule instanceof FlowEntry)) {
217 NicFlowRule nicRule = (NicFlowRule) rule;
218 String tcId = nicRule.trafficClassId();
219
220 // Create a bucket of flow rules for this traffic class
221 if (!rulesPerTc.containsKey(tcId)) {
222 rulesPerTc.put(tcId, Sets.<FlowRule>newConcurrentHashSet());
223 }
224
225 Set<FlowRule> tcRuleSet = rulesPerTc.get(tcId);
226 tcRuleSet.add(nicRule);
227 }
228 });
229
230 return rulesPerTc;
231 }
232
233 /**
234 * Searches for a flow rule with certain ID.
235 *
236 * @param flowEntries a list of FlowEntries
237 * @param ruleId a desired rule ID
238 * @return a FlowRule that corresponds to the desired ID or null
239 */
240 private FlowRule findRuleInFlowEntries(
241 Iterable<FlowEntry> flowEntries, long ruleId) {
242 for (FlowEntry fe : flowEntries) {
243 if (fe.id().value() == ruleId) {
244 return (FlowRule) fe;
245 }
246 }
247
248 return null;
249 }
250
251 /**
252 * Installs a set of FlowRules of the same traffic class ID
253 * on a server device.
254 *
255 * @param deviceId target server device ID
256 * @param trafficClassId traffic class ID of the NIC rules
257 * @param rules set of NIC rules to install
258 * @return a set of successfully installed NIC rules
259 */
260 private Collection<FlowRule> installNicFlowRules(
261 DeviceId deviceId, String trafficClassId,
262 Collection<FlowRule> rules) {
263 if (rules.isEmpty()) {
264 return Collections.EMPTY_LIST;
265 }
266
267 ObjectMapper mapper = new ObjectMapper();
268
269 // Create the object node to host the list of rules
270 ObjectNode scsObjNode = mapper.createObjectNode();
271
272 // Add the service chain's traffic class ID that requested these rules
273 scsObjNode.put(BasicServerDriver.PARAM_ID, trafficClassId);
274
275 // Create the object node to host the Rx filter method
276 ObjectNode methodObjNode = mapper.createObjectNode();
277 methodObjNode.put(BasicServerDriver.NIC_PARAM_RX_METHOD, "flow");
278 scsObjNode.put(BasicServerDriver.NIC_PARAM_RX_FILTER, methodObjNode);
279
280 // Map each core to an array of rule IDs and rules
281 Map<Long, ArrayNode> cpuObjSet =
282 new ConcurrentHashMap<Long, ArrayNode>();
283
284 String nicId = null;
285
286 for (FlowRule rule : rules) {
287 NicFlowRule nicRule = (NicFlowRule) rule;
288 long coreIndex = nicRule.cpuCoreIndex();
289
290 // Keep the ID of the target NIC
291 if (nicId == null) {
292 long nicIfaceNb = nicRule.interfaceNumber();
293 checkArgument(nicIfaceNb > 0,
294 "Attempted to install NIC rules on an invalid NIC ID: "
295 + nicIfaceNb);
296 // NIC IDs in the dataplane start from 0
297 nicId = Long.toString(nicIfaceNb - 1);
298 }
299
300 // Create a JSON array for this CPU core
301 if (!cpuObjSet.containsKey(coreIndex)) {
302 cpuObjSet.put(coreIndex, mapper.createArrayNode());
303 }
304
305 // The array of rules that corresponds to this CPU core
306 ArrayNode ruleArrayNode = cpuObjSet.get(coreIndex);
307
308 // Each rule has an ID and a content
309 ObjectNode ruleNode = mapper.createObjectNode();
310 ruleNode.put("ruleId", nicRule.id().value());
311 ruleNode.put("ruleContent", nicRule.ruleBody());
312
313 ruleArrayNode.add(ruleNode);
314 }
315
316 ObjectNode nicObjNode = mapper.createObjectNode();
317 // TODO: Fix this as it might cause issues
318 nicObjNode.put("nicId", "fd" + nicId);
319
320 ArrayNode cpusArrayNode = nicObjNode.putArray(PARAM_CPUS);
321
322 // Convert the map of CPU cores to arrays of rules to JSON
323 for (Map.Entry<Long, ArrayNode> entry : cpuObjSet.entrySet()) {
324 long coreIndex = entry.getKey();
325 ArrayNode ruleArrayNode = entry.getValue();
326
327 ObjectNode cpuObjNode = mapper.createObjectNode();
328 cpuObjNode.put("cpuId", coreIndex);
329 cpuObjNode.putArray(PARAM_CPU_RULES).addAll(ruleArrayNode);
330
331 cpusArrayNode.add(cpuObjNode);
332 }
333
334 scsObjNode.putArray(PARAM_NICS).add(nicObjNode);
335
336 // Create the object node to host all the data
337 ObjectNode sendObjNode = mapper.createObjectNode();
338 sendObjNode.putArray(PARAM_RULES).add(scsObjNode);
339
340 // Post the NIC rules to the server
341 int response = getController().post(
342 deviceId, RULE_MANAGEMENT_URL,
343 new ByteArrayInputStream(sendObjNode.toString().getBytes()), JSON);
344
345 // Upon an error, return an empty set of rules
346 if (!checkStatusCode(response)) {
347 log.error("Failed to install flow rules on device {}", deviceId);
348 return Collections.EMPTY_LIST;
349 }
350
351 log.info("Successfully installed {} flow rules on device {}",
352 rules.size(), deviceId);
353
354 // .. or all of them
355 return rules;
356 }
357
358 /**
359 * Removes a FlowRule from a server device.
360 *
361 * @param deviceId target server device ID
362 * @param ruleId NIC rule ID to be removed
363 * @return boolean removal status
364 */
365 private boolean removeNicFlowRule(DeviceId deviceId, long ruleId) {
366 // Remove rule with ID from this server
367 int response = getController().delete(deviceId,
368 RULE_MANAGEMENT_URL + "/" + Long.toString(ruleId), null, JSON);
369
370 if (!checkStatusCode(response)) {
371 log.error("Failed to remove flow rule {} from device {}",
372 ruleId, deviceId);
373 return false;
374 }
375
376 log.info("Successfully removed flow rule {} from device {}",
377 ruleId, deviceId);
378
379 return true;
380 }
381
382}