blob: 9c38fe8fe7f5a9eb1c51a9b89dae44494c516617 [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
Georgios Katsikas80e0b9f2018-07-21 20:29:18 +020046import com.google.common.base.Strings;
47
Georgios Katsikas70671b32018-07-02 18:47:27 +020048import static com.google.common.base.Preconditions.checkArgument;
49import static com.google.common.base.Preconditions.checkNotNull;
50import static org.slf4j.LoggerFactory.getLogger;
51
52/**
53 * Manages rules on commodity server devices, by
54 * converting ONOS FlowRule objetcs into
55 * network interface card (NIC) rules and vice versa.
56 */
57public class FlowRuleProgrammableServerImpl extends BasicServerDriver
58 implements FlowRuleProgrammable {
59
60 private final Logger log = getLogger(getClass());
61
62 /**
63 * Resource endpoints of the server agent (REST server-side).
64 */
Georgios Katsikas30bede52018-07-28 14:46:07 +020065 private static final String RULE_MANAGEMENT_URL = BASE_URL + SLASH + "rules";
Georgios Katsikas70671b32018-07-02 18:47:27 +020066
67 /**
68 * Parameters to be exchanged with the server's agent.
69 */
70 private static final String PARAM_RULES = "rules";
Georgios Katsikas70671b32018-07-02 18:47:27 +020071 private static final String PARAM_CPU_ID = "cpuId";
72 private static final String PARAM_CPU_RULES = "cpuRules";
73 private static final String PARAM_RULE_ID = "ruleId";
74 private static final String PARAM_RULE_CONTENT = "ruleContent";
75
76 @Override
77 public Collection<FlowEntry> getFlowEntries() {
78 DeviceId deviceId = getHandler().data().deviceId();
79 checkNotNull(deviceId, DEVICE_ID_NULL);
80
81 // Expected FlowEntries installed through ONOS
82 FlowRuleService flowService = getHandler().get(FlowRuleService.class);
83 Iterable<FlowEntry> flowEntries = flowService.getFlowEntries(deviceId);
84
85 // Hit the path that provides the server's flow rules
86 InputStream response = null;
87 try {
88 response = getController().get(deviceId, RULE_MANAGEMENT_URL, JSON);
89 } catch (ProcessingException pEx) {
90 log.error("Failed to get flow entries from device: {}", deviceId);
91 return Collections.EMPTY_LIST;
92 }
93
94 // Load the JSON into objects
95 ObjectMapper mapper = new ObjectMapper();
96 ObjectNode objNode = null;
97 try {
98 Map<String, Object> jsonMap = mapper.readValue(response, Map.class);
99 JsonNode jsonNode = mapper.convertValue(jsonMap, JsonNode.class);
100 objNode = (ObjectNode) jsonNode;
101 } catch (IOException ioEx) {
102 log.error("Failed to get flow entries from device: {}", deviceId);
103 return Collections.EMPTY_LIST;
104 }
105
106 if (objNode == null) {
107 log.error("Failed to get flow entries from device: {}", deviceId);
108 return Collections.EMPTY_LIST;
109 }
110
111 JsonNode scsNode = objNode.path(PARAM_RULES);
112
113 // Here we store the trully installed rules
114 Collection<FlowEntry> actualFlowEntries =
115 Sets.<FlowEntry>newConcurrentHashSet();
116
117 for (JsonNode scNode : scsNode) {
118 String scId = get(scNode, PARAM_ID);
119 String rxFilter = get(
120 scNode.path(NIC_PARAM_RX_FILTER), NIC_PARAM_RX_METHOD);
121
122 // Only Flow-based RxFilter is permitted
123 if (RxFilter.getByName(rxFilter) != RxFilter.FLOW) {
124 log.warn("Device with Rx filter {} is not managed by this driver",
125 rxFilter.toString().toUpperCase());
126 continue;
127 }
128
129 // Each device might have multiple NICs
130 for (JsonNode nicNode : scNode.path(PARAM_NICS)) {
Georgios Katsikas70671b32018-07-02 18:47:27 +0200131 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
Georgios Katsikas80e0b9f2018-07-21 20:29:18 +0200284 String nic = null;
Georgios Katsikas70671b32018-07-02 18:47:27 +0200285
286 for (FlowRule rule : rules) {
287 NicFlowRule nicRule = (NicFlowRule) rule;
288 long coreIndex = nicRule.cpuCoreIndex();
289
290 // Keep the ID of the target NIC
Georgios Katsikas80e0b9f2018-07-21 20:29:18 +0200291 if (nic == null) {
292 nic = findNicInterfaceWithPort(deviceId, nicRule.interfaceNumber());
293 checkArgument(
294 !Strings.isNullOrEmpty(nic),
295 "Attempted to install rules on an invalid NIC");
Georgios Katsikas70671b32018-07-02 18:47:27 +0200296 }
297
298 // Create a JSON array for this CPU core
299 if (!cpuObjSet.containsKey(coreIndex)) {
300 cpuObjSet.put(coreIndex, mapper.createArrayNode());
301 }
302
303 // The array of rules that corresponds to this CPU core
304 ArrayNode ruleArrayNode = cpuObjSet.get(coreIndex);
305
306 // Each rule has an ID and a content
307 ObjectNode ruleNode = mapper.createObjectNode();
308 ruleNode.put("ruleId", nicRule.id().value());
309 ruleNode.put("ruleContent", nicRule.ruleBody());
310
311 ruleArrayNode.add(ruleNode);
312 }
313
314 ObjectNode nicObjNode = mapper.createObjectNode();
Georgios Katsikas80e0b9f2018-07-21 20:29:18 +0200315 nicObjNode.put("nicName", nic);
Georgios Katsikas70671b32018-07-02 18:47:27 +0200316
317 ArrayNode cpusArrayNode = nicObjNode.putArray(PARAM_CPUS);
318
319 // Convert the map of CPU cores to arrays of rules to JSON
320 for (Map.Entry<Long, ArrayNode> entry : cpuObjSet.entrySet()) {
321 long coreIndex = entry.getKey();
322 ArrayNode ruleArrayNode = entry.getValue();
323
324 ObjectNode cpuObjNode = mapper.createObjectNode();
325 cpuObjNode.put("cpuId", coreIndex);
326 cpuObjNode.putArray(PARAM_CPU_RULES).addAll(ruleArrayNode);
327
328 cpusArrayNode.add(cpuObjNode);
329 }
330
331 scsObjNode.putArray(PARAM_NICS).add(nicObjNode);
332
333 // Create the object node to host all the data
334 ObjectNode sendObjNode = mapper.createObjectNode();
335 sendObjNode.putArray(PARAM_RULES).add(scsObjNode);
336
337 // Post the NIC rules to the server
338 int response = getController().post(
339 deviceId, RULE_MANAGEMENT_URL,
340 new ByteArrayInputStream(sendObjNode.toString().getBytes()), JSON);
341
342 // Upon an error, return an empty set of rules
343 if (!checkStatusCode(response)) {
344 log.error("Failed to install flow rules on device {}", deviceId);
345 return Collections.EMPTY_LIST;
346 }
347
348 log.info("Successfully installed {} flow rules on device {}",
349 rules.size(), deviceId);
350
351 // .. or all of them
352 return rules;
353 }
354
355 /**
356 * Removes a FlowRule from a server device.
357 *
358 * @param deviceId target server device ID
359 * @param ruleId NIC rule ID to be removed
360 * @return boolean removal status
361 */
362 private boolean removeNicFlowRule(DeviceId deviceId, long ruleId) {
Georgios Katsikas30bede52018-07-28 14:46:07 +0200363 int response = -1;
Georgios Katsikas70671b32018-07-02 18:47:27 +0200364
Georgios Katsikas30bede52018-07-28 14:46:07 +0200365 // Try to remove the rule, although server might be unreachable
366 try {
367 response = getController().delete(deviceId,
368 RULE_MANAGEMENT_URL + SLASH + Long.toString(ruleId), null, JSON);
369 } catch (Exception ex) {
370 log.error("Failed to remove flow rule {} from device {}", ruleId, deviceId);
Georgios Katsikas70671b32018-07-02 18:47:27 +0200371 return false;
372 }
373
Georgios Katsikas30bede52018-07-28 14:46:07 +0200374 if (!checkStatusCode(response)) {
375 log.error("Failed to remove flow rule {} from device {}", ruleId, deviceId);
376 return false;
377 }
Georgios Katsikas70671b32018-07-02 18:47:27 +0200378
Georgios Katsikas30bede52018-07-28 14:46:07 +0200379 log.info("Successfully removed flow rule {} from device {}", ruleId, deviceId);
Georgios Katsikas70671b32018-07-02 18:47:27 +0200380 return true;
381 }
382
383}