blob: 34947cb4f4c2073b10df24757054ebef2cac81a0 [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
Georgios Katsikas70671b32018-07-02 18:47:27 +020025import org.onosproject.drivers.server.devices.nic.NicFlowRule;
26import org.onosproject.drivers.server.devices.nic.NicRxFilter.RxFilter;
27import org.onosproject.net.DeviceId;
Georgios Katsikas042a0fc2018-08-16 18:49:07 +020028import org.onosproject.net.driver.Driver;
29import org.onosproject.net.driver.DriverService;
Georgios Katsikas70671b32018-07-02 18:47:27 +020030import 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
Georgios Katsikas042a0fc2018-08-16 18:49:07 +020036import org.slf4j.Logger;
37
Georgios Katsikas70671b32018-07-02 18:47:27 +020038import java.io.ByteArrayInputStream;
39import java.io.IOException;
40import java.io.InputStream;
41import java.util.Collection;
42import java.util.Collections;
Georgios Katsikas042a0fc2018-08-16 18:49:07 +020043import java.util.List;
Georgios Katsikas70671b32018-07-02 18:47:27 +020044import java.util.Map;
45import java.util.Set;
46import java.util.concurrent.ConcurrentHashMap;
47import javax.ws.rs.ProcessingException;
48
Georgios Katsikas80e0b9f2018-07-21 20:29:18 +020049import com.google.common.base.Strings;
50
Georgios Katsikas70671b32018-07-02 18:47:27 +020051import static com.google.common.base.Preconditions.checkArgument;
52import static com.google.common.base.Preconditions.checkNotNull;
53import static org.slf4j.LoggerFactory.getLogger;
54
55/**
56 * Manages rules on commodity server devices, by
57 * converting ONOS FlowRule objetcs into
58 * network interface card (NIC) rules and vice versa.
59 */
60public class FlowRuleProgrammableServerImpl extends BasicServerDriver
61 implements FlowRuleProgrammable {
62
63 private final Logger log = getLogger(getClass());
64
65 /**
66 * Resource endpoints of the server agent (REST server-side).
67 */
Georgios Katsikas30bede52018-07-28 14:46:07 +020068 private static final String RULE_MANAGEMENT_URL = BASE_URL + SLASH + "rules";
Georgios Katsikas70671b32018-07-02 18:47:27 +020069
70 /**
71 * Parameters to be exchanged with the server's agent.
72 */
73 private static final String PARAM_RULES = "rules";
Georgios Katsikas70671b32018-07-02 18:47:27 +020074 private static final String PARAM_CPU_ID = "cpuId";
75 private static final String PARAM_CPU_RULES = "cpuRules";
76 private static final String PARAM_RULE_ID = "ruleId";
77 private static final String PARAM_RULE_CONTENT = "ruleContent";
Georgios Katsikas042a0fc2018-08-16 18:49:07 +020078 private static final String PARAM_RX_FILTER_FD = "flow";
79
80 /**
81 * Driver's property to specify how many rules the controller can remove at once.
82 */
83 private static final String RULE_DELETE_BATCH_SIZE_PROPERTY = "ruleDeleteBatchSize";
Georgios Katsikas70671b32018-07-02 18:47:27 +020084
85 @Override
86 public Collection<FlowEntry> getFlowEntries() {
87 DeviceId deviceId = getHandler().data().deviceId();
88 checkNotNull(deviceId, DEVICE_ID_NULL);
89
90 // Expected FlowEntries installed through ONOS
91 FlowRuleService flowService = getHandler().get(FlowRuleService.class);
92 Iterable<FlowEntry> flowEntries = flowService.getFlowEntries(deviceId);
93
94 // Hit the path that provides the server's flow rules
95 InputStream response = null;
96 try {
97 response = getController().get(deviceId, RULE_MANAGEMENT_URL, JSON);
98 } catch (ProcessingException pEx) {
Georgios Katsikas042a0fc2018-08-16 18:49:07 +020099 log.error("Failed to get NIC flow entries from device: {}", deviceId);
Georgios Katsikas70671b32018-07-02 18:47:27 +0200100 return Collections.EMPTY_LIST;
101 }
102
103 // Load the JSON into objects
104 ObjectMapper mapper = new ObjectMapper();
105 ObjectNode objNode = null;
106 try {
107 Map<String, Object> jsonMap = mapper.readValue(response, Map.class);
108 JsonNode jsonNode = mapper.convertValue(jsonMap, JsonNode.class);
109 objNode = (ObjectNode) jsonNode;
110 } catch (IOException ioEx) {
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200111 log.error("Failed to get NIC flow entries from device: {}", deviceId);
Georgios Katsikas70671b32018-07-02 18:47:27 +0200112 return Collections.EMPTY_LIST;
113 }
114
115 if (objNode == null) {
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200116 log.error("Failed to get NIC flow entries from device: {}", deviceId);
Georgios Katsikas70671b32018-07-02 18:47:27 +0200117 return Collections.EMPTY_LIST;
118 }
119
120 JsonNode scsNode = objNode.path(PARAM_RULES);
121
122 // Here we store the trully installed rules
123 Collection<FlowEntry> actualFlowEntries =
124 Sets.<FlowEntry>newConcurrentHashSet();
125
126 for (JsonNode scNode : scsNode) {
127 String scId = get(scNode, PARAM_ID);
128 String rxFilter = get(
129 scNode.path(NIC_PARAM_RX_FILTER), NIC_PARAM_RX_METHOD);
130
131 // Only Flow-based RxFilter is permitted
132 if (RxFilter.getByName(rxFilter) != RxFilter.FLOW) {
133 log.warn("Device with Rx filter {} is not managed by this driver",
134 rxFilter.toString().toUpperCase());
135 continue;
136 }
137
138 // Each device might have multiple NICs
139 for (JsonNode nicNode : scNode.path(PARAM_NICS)) {
Georgios Katsikas70671b32018-07-02 18:47:27 +0200140 JsonNode cpusNode = nicNode.path(PARAM_CPUS);
141
142 // Each NIC can dispatch to multiple CPU cores
143 for (JsonNode cpuNode : cpusNode) {
144 String cpuId = get(cpuNode, PARAM_CPU_ID);
145 JsonNode rulesNode = cpuNode.path(PARAM_CPU_RULES);
146
147 // Multiple rules might correspond to each CPU core
148 for (JsonNode ruleNode : rulesNode) {
149 long ruleId = ruleNode.path(PARAM_RULE_ID).asLong();
150 String ruleContent = get(ruleNode, PARAM_RULE_CONTENT);
151
152 // Search for this rule ID in ONOS's store
153 FlowRule r = findRuleInFlowEntries(flowEntries, ruleId);
154
155 // Local rule, not present in the controller => Ignore
156 if (r == null) {
157 continue;
158 // Rule trully present in the data plane => Add
159 } else {
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200160 actualFlowEntries.add(new DefaultFlowEntry(
Georgios Katsikas70671b32018-07-02 18:47:27 +0200161 r, FlowEntry.FlowEntryState.ADDED, 0, 0, 0));
162 }
163 }
164 }
165 }
166 }
167
168 return actualFlowEntries;
169 }
170
171 @Override
172 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
173 DeviceId deviceId = getHandler().data().deviceId();
174 checkNotNull(deviceId, DEVICE_ID_NULL);
175
176 // Set of truly-installed rules to be reported
177 Set<FlowRule> installedRules = Sets.<FlowRule>newConcurrentHashSet();
178
179 // Splits the rule set into multiple ones, grouped by traffic class ID
180 Map<String, Set<FlowRule>> rulesPerTc = groupRules(rules);
181
182 // Install NIC rules on a per-traffic class basis
183 for (Map.Entry<String, Set<FlowRule>> entry : rulesPerTc.entrySet()) {
184 String tcId = entry.getKey();
185 Set<FlowRule> tcRuleSet = entry.getValue();
186
187 installedRules.addAll(
188 installNicFlowRules(deviceId, tcId, tcRuleSet)
189 );
190 }
191
192 return installedRules;
193 }
194
195 @Override
196 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
197 DeviceId deviceId = getHandler().data().deviceId();
198 checkNotNull(deviceId, DEVICE_ID_NULL);
199
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200200 int ruleDeleteBatchSize = getRuleDeleteBatchSizeProperty(deviceId);
201
Georgios Katsikas70671b32018-07-02 18:47:27 +0200202 // Set of truly-removed rules to be reported
203 Set<FlowRule> removedRules = Sets.<FlowRule>newConcurrentHashSet();
204
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200205 List<FlowRule> ruleList = (List) rules;
206 int ruleCount = rules.size();
207 int ruleStart = 0;
208 int processed = 0;
209 int batchNb = 1;
210 while (processed < ruleCount) {
211 String ruleIds = "";
212
213 for (int i = ruleStart; i < ruleCount; i++) {
214 // Batch completed
215 if (i >= (batchNb * ruleDeleteBatchSize)) {
216 break;
217 }
218
219 // TODO: Turn this string into a list and modify removeNicFlowRuleBatch()
220 // Create a comma-separated sequence of rule IDs
221 ruleIds += Long.toString(ruleList.get(i).id().value()) + ",";
222
223 processed++;
Georgios Katsikas70671b32018-07-02 18:47:27 +0200224 }
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200225
226 // Remove last comma
227 ruleIds = ruleIds.substring(0, ruleIds.length() - 1);
228
229 // Remove the entire batch of rules at once
230 if (removeNicFlowRuleBatch(deviceId, ruleIds)) {
231 removedRules.addAll(ruleList.subList(ruleStart, processed));
232 }
233
234 // Prepare for the next batch (if any)
235 batchNb++;
236 ruleStart += ruleDeleteBatchSize;
237 }
Georgios Katsikas70671b32018-07-02 18:47:27 +0200238
239 return removedRules;
240 }
241
242 /**
243 * Groups a set of FlowRules by their traffic class ID.
244 *
245 * @param rules set of NIC rules to install
246 * @return a map of traffic class IDs to their set of NIC rules
247 */
248 private Map<String, Set<FlowRule>> groupRules(Collection<FlowRule> rules) {
249 Map<String, Set<FlowRule>> rulesPerTc =
250 new ConcurrentHashMap<String, Set<FlowRule>>();
251
252 rules.forEach(rule -> {
253 if (!(rule instanceof FlowEntry)) {
Georgios Katsikas6dc11c12018-12-20 08:43:29 +0100254 NicFlowRule nicRule = null;
Georgios Katsikas70671b32018-07-02 18:47:27 +0200255
Georgios Katsikas6dc11c12018-12-20 08:43:29 +0100256 // Only NicFlowRules are accepted
257 try {
258 nicRule = (NicFlowRule) rule;
259 } catch (ClassCastException cEx) {
260 log.warn("Skipping rule not crafted for NIC: {}", rule);
Georgios Katsikas70671b32018-07-02 18:47:27 +0200261 }
262
Georgios Katsikas6dc11c12018-12-20 08:43:29 +0100263 if (nicRule != null) {
264 String tcId = nicRule.trafficClassId();
265
266 // Create a bucket of flow rules for this traffic class
267 if (!rulesPerTc.containsKey(tcId)) {
268 rulesPerTc.put(tcId, Sets.<FlowRule>newConcurrentHashSet());
269 }
270
271 Set<FlowRule> tcRuleSet = rulesPerTc.get(tcId);
272 tcRuleSet.add(nicRule);
273 }
Georgios Katsikas70671b32018-07-02 18:47:27 +0200274 }
275 });
276
277 return rulesPerTc;
278 }
279
280 /**
281 * Searches for a flow rule with certain ID.
282 *
283 * @param flowEntries a list of FlowEntries
284 * @param ruleId a desired rule ID
285 * @return a FlowRule that corresponds to the desired ID or null
286 */
287 private FlowRule findRuleInFlowEntries(
288 Iterable<FlowEntry> flowEntries, long ruleId) {
289 for (FlowEntry fe : flowEntries) {
290 if (fe.id().value() == ruleId) {
291 return (FlowRule) fe;
292 }
293 }
294
295 return null;
296 }
297
298 /**
299 * Installs a set of FlowRules of the same traffic class ID
300 * on a server device.
301 *
302 * @param deviceId target server device ID
303 * @param trafficClassId traffic class ID of the NIC rules
304 * @param rules set of NIC rules to install
305 * @return a set of successfully installed NIC rules
306 */
307 private Collection<FlowRule> installNicFlowRules(
308 DeviceId deviceId, String trafficClassId,
309 Collection<FlowRule> rules) {
310 if (rules.isEmpty()) {
311 return Collections.EMPTY_LIST;
312 }
313
314 ObjectMapper mapper = new ObjectMapper();
315
316 // Create the object node to host the list of rules
317 ObjectNode scsObjNode = mapper.createObjectNode();
318
319 // Add the service chain's traffic class ID that requested these rules
320 scsObjNode.put(BasicServerDriver.PARAM_ID, trafficClassId);
321
322 // Create the object node to host the Rx filter method
323 ObjectNode methodObjNode = mapper.createObjectNode();
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200324 methodObjNode.put(BasicServerDriver.NIC_PARAM_RX_METHOD, PARAM_RX_FILTER_FD);
Georgios Katsikas70671b32018-07-02 18:47:27 +0200325 scsObjNode.put(BasicServerDriver.NIC_PARAM_RX_FILTER, methodObjNode);
326
327 // Map each core to an array of rule IDs and rules
328 Map<Long, ArrayNode> cpuObjSet =
329 new ConcurrentHashMap<Long, ArrayNode>();
330
Georgios Katsikas80e0b9f2018-07-21 20:29:18 +0200331 String nic = null;
Georgios Katsikas70671b32018-07-02 18:47:27 +0200332
333 for (FlowRule rule : rules) {
334 NicFlowRule nicRule = (NicFlowRule) rule;
Georgios Katsikas6dc11c12018-12-20 08:43:29 +0100335 if (nicRule.isFullWildcard() && (rules.size() > 1)) {
336 log.warn("Skipping wildcard rule: {}", nicRule);
337 continue;
338 }
339
Georgios Katsikas70671b32018-07-02 18:47:27 +0200340 long coreIndex = nicRule.cpuCoreIndex();
341
342 // Keep the ID of the target NIC
Georgios Katsikas80e0b9f2018-07-21 20:29:18 +0200343 if (nic == null) {
344 nic = findNicInterfaceWithPort(deviceId, nicRule.interfaceNumber());
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200345 checkArgument(!Strings.isNullOrEmpty(nic),
346 "Attempted to install rules in an invalid NIC");
Georgios Katsikas70671b32018-07-02 18:47:27 +0200347 }
348
349 // Create a JSON array for this CPU core
350 if (!cpuObjSet.containsKey(coreIndex)) {
351 cpuObjSet.put(coreIndex, mapper.createArrayNode());
352 }
353
354 // The array of rules that corresponds to this CPU core
355 ArrayNode ruleArrayNode = cpuObjSet.get(coreIndex);
356
357 // Each rule has an ID and a content
358 ObjectNode ruleNode = mapper.createObjectNode();
359 ruleNode.put("ruleId", nicRule.id().value());
360 ruleNode.put("ruleContent", nicRule.ruleBody());
361
362 ruleArrayNode.add(ruleNode);
363 }
364
365 ObjectNode nicObjNode = mapper.createObjectNode();
Georgios Katsikas80e0b9f2018-07-21 20:29:18 +0200366 nicObjNode.put("nicName", nic);
Georgios Katsikas70671b32018-07-02 18:47:27 +0200367
368 ArrayNode cpusArrayNode = nicObjNode.putArray(PARAM_CPUS);
369
370 // Convert the map of CPU cores to arrays of rules to JSON
371 for (Map.Entry<Long, ArrayNode> entry : cpuObjSet.entrySet()) {
372 long coreIndex = entry.getKey();
373 ArrayNode ruleArrayNode = entry.getValue();
374
375 ObjectNode cpuObjNode = mapper.createObjectNode();
376 cpuObjNode.put("cpuId", coreIndex);
377 cpuObjNode.putArray(PARAM_CPU_RULES).addAll(ruleArrayNode);
378
379 cpusArrayNode.add(cpuObjNode);
380 }
381
382 scsObjNode.putArray(PARAM_NICS).add(nicObjNode);
383
384 // Create the object node to host all the data
385 ObjectNode sendObjNode = mapper.createObjectNode();
386 sendObjNode.putArray(PARAM_RULES).add(scsObjNode);
387
388 // Post the NIC rules to the server
389 int response = getController().post(
390 deviceId, RULE_MANAGEMENT_URL,
391 new ByteArrayInputStream(sendObjNode.toString().getBytes()), JSON);
392
393 // Upon an error, return an empty set of rules
394 if (!checkStatusCode(response)) {
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200395 log.error("Failed to install NIC flow rules in device {}", deviceId);
Georgios Katsikas70671b32018-07-02 18:47:27 +0200396 return Collections.EMPTY_LIST;
397 }
398
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200399 log.info("Successfully installed {} NIC flow rules in device {}",
Georgios Katsikas70671b32018-07-02 18:47:27 +0200400 rules.size(), deviceId);
401
402 // .. or all of them
403 return rules;
404 }
405
406 /**
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200407 * Removes a batch of FlowRules from a server device
408 * using a single REST command.
Georgios Katsikas70671b32018-07-02 18:47:27 +0200409 *
410 * @param deviceId target server device ID
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200411 * @param ruleIds a batch of comma-separated NIC rule IDs to be removed
Georgios Katsikas70671b32018-07-02 18:47:27 +0200412 * @return boolean removal status
413 */
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200414 private boolean removeNicFlowRuleBatch(DeviceId deviceId, String ruleIds) {
Georgios Katsikas30bede52018-07-28 14:46:07 +0200415 int response = -1;
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200416 long ruleCount = ruleIds.chars().filter(ch -> ch == ',').count() + 1;
Georgios Katsikas70671b32018-07-02 18:47:27 +0200417
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200418 // Try to remove the rules, although server might be unreachable
Georgios Katsikas30bede52018-07-28 14:46:07 +0200419 try {
420 response = getController().delete(deviceId,
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200421 RULE_MANAGEMENT_URL + SLASH + ruleIds, null, JSON);
Georgios Katsikas30bede52018-07-28 14:46:07 +0200422 } catch (Exception ex) {
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200423 log.error("Failed to remove NIC flow rule batch with {} rules from device {}", ruleCount, deviceId);
Georgios Katsikas70671b32018-07-02 18:47:27 +0200424 return false;
425 }
426
Georgios Katsikas30bede52018-07-28 14:46:07 +0200427 if (!checkStatusCode(response)) {
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200428 log.error("Failed to remove NIC flow rule batch with {} rules from device {}", ruleCount, deviceId);
Georgios Katsikas30bede52018-07-28 14:46:07 +0200429 return false;
430 }
Georgios Katsikas70671b32018-07-02 18:47:27 +0200431
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200432 log.info("Successfully removed NIC flow rule batch with {} rules from device {}", ruleCount, deviceId);
Georgios Katsikas70671b32018-07-02 18:47:27 +0200433 return true;
434 }
435
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200436 /**
437 * Returns how many rules this driver can delete at once.
438 *
439 * @param deviceId the device's ID to delete rules from
440 * @return rule deletion batch size
441 */
442 private int getRuleDeleteBatchSizeProperty(DeviceId deviceId) {
443 Driver driver = getHandler().get(DriverService.class).getDriver(deviceId);
444 return Integer.parseInt(driver.getProperty(RULE_DELETE_BATCH_SIZE_PROPERTY));
445 }
446
Georgios Katsikas70671b32018-07-02 18:47:27 +0200447}