blob: 3dc7d59003abae06af48df80fac7f52988ce0aa6 [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)) {
254 NicFlowRule nicRule = (NicFlowRule) rule;
255 String tcId = nicRule.trafficClassId();
256
257 // Create a bucket of flow rules for this traffic class
258 if (!rulesPerTc.containsKey(tcId)) {
259 rulesPerTc.put(tcId, Sets.<FlowRule>newConcurrentHashSet());
260 }
261
262 Set<FlowRule> tcRuleSet = rulesPerTc.get(tcId);
263 tcRuleSet.add(nicRule);
264 }
265 });
266
267 return rulesPerTc;
268 }
269
270 /**
271 * Searches for a flow rule with certain ID.
272 *
273 * @param flowEntries a list of FlowEntries
274 * @param ruleId a desired rule ID
275 * @return a FlowRule that corresponds to the desired ID or null
276 */
277 private FlowRule findRuleInFlowEntries(
278 Iterable<FlowEntry> flowEntries, long ruleId) {
279 for (FlowEntry fe : flowEntries) {
280 if (fe.id().value() == ruleId) {
281 return (FlowRule) fe;
282 }
283 }
284
285 return null;
286 }
287
288 /**
289 * Installs a set of FlowRules of the same traffic class ID
290 * on a server device.
291 *
292 * @param deviceId target server device ID
293 * @param trafficClassId traffic class ID of the NIC rules
294 * @param rules set of NIC rules to install
295 * @return a set of successfully installed NIC rules
296 */
297 private Collection<FlowRule> installNicFlowRules(
298 DeviceId deviceId, String trafficClassId,
299 Collection<FlowRule> rules) {
300 if (rules.isEmpty()) {
301 return Collections.EMPTY_LIST;
302 }
303
304 ObjectMapper mapper = new ObjectMapper();
305
306 // Create the object node to host the list of rules
307 ObjectNode scsObjNode = mapper.createObjectNode();
308
309 // Add the service chain's traffic class ID that requested these rules
310 scsObjNode.put(BasicServerDriver.PARAM_ID, trafficClassId);
311
312 // Create the object node to host the Rx filter method
313 ObjectNode methodObjNode = mapper.createObjectNode();
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200314 methodObjNode.put(BasicServerDriver.NIC_PARAM_RX_METHOD, PARAM_RX_FILTER_FD);
Georgios Katsikas70671b32018-07-02 18:47:27 +0200315 scsObjNode.put(BasicServerDriver.NIC_PARAM_RX_FILTER, methodObjNode);
316
317 // Map each core to an array of rule IDs and rules
318 Map<Long, ArrayNode> cpuObjSet =
319 new ConcurrentHashMap<Long, ArrayNode>();
320
Georgios Katsikas80e0b9f2018-07-21 20:29:18 +0200321 String nic = null;
Georgios Katsikas70671b32018-07-02 18:47:27 +0200322
323 for (FlowRule rule : rules) {
324 NicFlowRule nicRule = (NicFlowRule) rule;
325 long coreIndex = nicRule.cpuCoreIndex();
326
327 // Keep the ID of the target NIC
Georgios Katsikas80e0b9f2018-07-21 20:29:18 +0200328 if (nic == null) {
329 nic = findNicInterfaceWithPort(deviceId, nicRule.interfaceNumber());
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200330 checkArgument(!Strings.isNullOrEmpty(nic),
331 "Attempted to install rules in an invalid NIC");
Georgios Katsikas70671b32018-07-02 18:47:27 +0200332 }
333
334 // Create a JSON array for this CPU core
335 if (!cpuObjSet.containsKey(coreIndex)) {
336 cpuObjSet.put(coreIndex, mapper.createArrayNode());
337 }
338
339 // The array of rules that corresponds to this CPU core
340 ArrayNode ruleArrayNode = cpuObjSet.get(coreIndex);
341
342 // Each rule has an ID and a content
343 ObjectNode ruleNode = mapper.createObjectNode();
344 ruleNode.put("ruleId", nicRule.id().value());
345 ruleNode.put("ruleContent", nicRule.ruleBody());
346
347 ruleArrayNode.add(ruleNode);
348 }
349
350 ObjectNode nicObjNode = mapper.createObjectNode();
Georgios Katsikas80e0b9f2018-07-21 20:29:18 +0200351 nicObjNode.put("nicName", nic);
Georgios Katsikas70671b32018-07-02 18:47:27 +0200352
353 ArrayNode cpusArrayNode = nicObjNode.putArray(PARAM_CPUS);
354
355 // Convert the map of CPU cores to arrays of rules to JSON
356 for (Map.Entry<Long, ArrayNode> entry : cpuObjSet.entrySet()) {
357 long coreIndex = entry.getKey();
358 ArrayNode ruleArrayNode = entry.getValue();
359
360 ObjectNode cpuObjNode = mapper.createObjectNode();
361 cpuObjNode.put("cpuId", coreIndex);
362 cpuObjNode.putArray(PARAM_CPU_RULES).addAll(ruleArrayNode);
363
364 cpusArrayNode.add(cpuObjNode);
365 }
366
367 scsObjNode.putArray(PARAM_NICS).add(nicObjNode);
368
369 // Create the object node to host all the data
370 ObjectNode sendObjNode = mapper.createObjectNode();
371 sendObjNode.putArray(PARAM_RULES).add(scsObjNode);
372
373 // Post the NIC rules to the server
374 int response = getController().post(
375 deviceId, RULE_MANAGEMENT_URL,
376 new ByteArrayInputStream(sendObjNode.toString().getBytes()), JSON);
377
378 // Upon an error, return an empty set of rules
379 if (!checkStatusCode(response)) {
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200380 log.error("Failed to install NIC flow rules in device {}", deviceId);
Georgios Katsikas70671b32018-07-02 18:47:27 +0200381 return Collections.EMPTY_LIST;
382 }
383
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200384 log.info("Successfully installed {} NIC flow rules in device {}",
Georgios Katsikas70671b32018-07-02 18:47:27 +0200385 rules.size(), deviceId);
386
387 // .. or all of them
388 return rules;
389 }
390
391 /**
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200392 * Removes a batch of FlowRules from a server device
393 * using a single REST command.
Georgios Katsikas70671b32018-07-02 18:47:27 +0200394 *
395 * @param deviceId target server device ID
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200396 * @param ruleIds a batch of comma-separated NIC rule IDs to be removed
Georgios Katsikas70671b32018-07-02 18:47:27 +0200397 * @return boolean removal status
398 */
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200399 private boolean removeNicFlowRuleBatch(DeviceId deviceId, String ruleIds) {
Georgios Katsikas30bede52018-07-28 14:46:07 +0200400 int response = -1;
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200401 long ruleCount = ruleIds.chars().filter(ch -> ch == ',').count() + 1;
Georgios Katsikas70671b32018-07-02 18:47:27 +0200402
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200403 // Try to remove the rules, although server might be unreachable
Georgios Katsikas30bede52018-07-28 14:46:07 +0200404 try {
405 response = getController().delete(deviceId,
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200406 RULE_MANAGEMENT_URL + SLASH + ruleIds, null, JSON);
Georgios Katsikas30bede52018-07-28 14:46:07 +0200407 } catch (Exception ex) {
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200408 log.error("Failed to remove NIC flow rule batch with {} rules from device {}", ruleCount, deviceId);
Georgios Katsikas70671b32018-07-02 18:47:27 +0200409 return false;
410 }
411
Georgios Katsikas30bede52018-07-28 14:46:07 +0200412 if (!checkStatusCode(response)) {
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200413 log.error("Failed to remove NIC flow rule batch with {} rules from device {}", ruleCount, deviceId);
Georgios Katsikas30bede52018-07-28 14:46:07 +0200414 return false;
415 }
Georgios Katsikas70671b32018-07-02 18:47:27 +0200416
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200417 log.info("Successfully removed NIC flow rule batch with {} rules from device {}", ruleCount, deviceId);
Georgios Katsikas70671b32018-07-02 18:47:27 +0200418 return true;
419 }
420
Georgios Katsikas042a0fc2018-08-16 18:49:07 +0200421 /**
422 * Returns how many rules this driver can delete at once.
423 *
424 * @param deviceId the device's ID to delete rules from
425 * @return rule deletion batch size
426 */
427 private int getRuleDeleteBatchSizeProperty(DeviceId deviceId) {
428 Driver driver = getHandler().get(DriverService.class).getDriver(deviceId);
429 return Integer.parseInt(driver.getProperty(RULE_DELETE_BATCH_SIZE_PROPERTY));
430 }
431
Georgios Katsikas70671b32018-07-02 18:47:27 +0200432}