blob: f316fc07dd2271c7036c5d0ab7f6293bedc8374c [file] [log] [blame]
alessio0a0f3342019-10-28 16:58:01 +01001/*
2 * Copyright 2018-present 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 * This work was partially supported by EC H2020 project METRO-HAUL (761727).
17 */
18
19package org.onosproject.drivers.odtn.openconfig;
20
21import com.google.common.collect.ImmutableList;
22import org.apache.commons.configuration.HierarchicalConfiguration;
23import org.onlab.util.Frequency;
24import org.onlab.util.Spectrum;
25import org.onosproject.drivers.odtn.impl.DeviceConnectionCache;
26import org.onosproject.drivers.odtn.impl.FlowRuleParser;
27import org.onosproject.drivers.utilities.XmlConfigParser;
28import org.onosproject.net.PortNumber;
29import org.onosproject.net.OchSignal;
30import org.onosproject.net.OchSignalType;
31import org.onosproject.net.ChannelSpacing;
32import org.onosproject.net.GridType;
33import org.onosproject.net.DeviceId;
34import org.onosproject.net.device.DeviceService;
35import org.onosproject.net.driver.AbstractHandlerBehaviour;
36import org.onosproject.net.flow.FlowRule;
37import org.onosproject.net.flow.DefaultFlowRule;
38import org.onosproject.net.flow.FlowEntry;
39import org.onosproject.net.flow.DefaultFlowEntry;
40import org.onosproject.net.flow.TrafficTreatment;
41import org.onosproject.net.flow.DefaultTrafficTreatment;
42import org.onosproject.net.flow.TrafficSelector;
43import org.onosproject.net.flow.DefaultTrafficSelector;
44import org.onosproject.net.flow.FlowRuleProgrammable;
45import org.onosproject.net.flow.criteria.Criteria;
46import org.onosproject.net.flow.instructions.Instructions;
47import org.onosproject.netconf.DatastoreId;
48import org.onosproject.netconf.NetconfController;
49import org.onosproject.netconf.NetconfException;
50import org.onosproject.netconf.NetconfSession;
51import org.onosproject.odtn.behaviour.OdtnDeviceDescriptionDiscovery;
52import org.slf4j.Logger;
53import org.slf4j.LoggerFactory;
54import org.w3c.dom.Document;
55import org.xml.sax.InputSource;
56
57import javax.xml.namespace.NamespaceContext;
58import javax.xml.parsers.DocumentBuilder;
59import javax.xml.parsers.DocumentBuilderFactory;
60import javax.xml.xpath.XPath;
61import javax.xml.xpath.XPathFactory;
62import java.io.ByteArrayInputStream;
63import java.io.StringReader;
64import java.util.ArrayList;
65import java.util.Collection;
66import java.util.Iterator;
67import java.util.List;
68import java.util.stream.Collectors;
69
70import static com.google.common.base.Preconditions.checkNotNull;
71
72/**
73 * Implementation of FlowRuleProgrammable interface for
74 * OpenConfig terminal devices.
75 */
76public class ClientLineTerminalDeviceFlowRuleProgrammable
77 extends AbstractHandlerBehaviour implements FlowRuleProgrammable {
78
79 private static final Logger log =
80 LoggerFactory.getLogger(ClientLineTerminalDeviceFlowRuleProgrammable.class);
81
82 private static final String RPC_TAG_NETCONF_BASE =
83 "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">";
84
85 private static final String RPC_CLOSE_TAG = "</rpc>";
86
87 private static final String PREFIX_PORT = "port-";
88 private static final String PREFIX_CHANNEL = "channel-";
89 private static final String DEFAULT_OPERATIONAL_MODE = "0";
90 private static final String DEFAULT_TARGET_POWER = "0";
91 private static final String DEFAULT_ASSIGNMENT_INDEX = "1";
92 private static final String DEFAULT_ALLOCATION_INDEX = "10";
93 private static final int DEFAULT_RULE_PRIORITY = 10;
94 private static final long DEFAULT_RULE_COOKIE = 1234L;
95 private static final String OPERATION_DISABLE = "DISABLED";
96 private static final String OPERATION_ENABLE = "ENABLED";
97 private static final String OC_TYPE_PROT_OTN = "oc-opt-types:PROT_OTN";
98 private static final String OC_TYPE_PROT_ETH = "oc-opt-types:PROT_ETHERNET";
99
100
101 /**
102 * Apply the flow entries specified in the collection rules.
103 *
104 * @param rules A collection of Flow Rules to be applied
105 * @return The collection of added Flow Entries
106 */
107 @Override
108 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
109 NetconfSession session = getNetconfSession();
110 if (session == null) {
111 openConfigError("null session");
112 return ImmutableList.of();
113 }
114
115 // Apply the rules on the device
116 Collection<FlowRule> added = rules.stream()
117 .map(r -> new TerminalDeviceFlowRule(r, getLinePorts()))
118 .filter(xc -> applyFlowRule(session, xc))
119 .collect(Collectors.toList());
120
121 for (FlowRule flowRule : added) {
122 log.info("OpenConfig added flowrule {}", flowRule);
123 getConnectionCache().add(did(), ((TerminalDeviceFlowRule) flowRule).connectionName(), flowRule);
124 }
125
126 //Print out number of rules sent to the device (without receiving errors)
127 openConfigLog("applyFlowRules added {}", added.size());
128
129 return added;
130 }
131
132 /**
133 * Get the flow entries that are present on the device.
134 *
135 * @return A collection of Flow Entries
136 */
137 @Override
138 public Collection<FlowEntry> getFlowEntries() {
139 Collection<FlowEntry> fetched = fetchConnectionsFromDevice().stream()
140 .map(fr -> new DefaultFlowEntry(fr, FlowEntry.FlowEntryState.ADDED, 0, 0, 0))
141 .collect(Collectors.toList());
142
143 //Print out number of rules actually found on the device that are also included in the cache
144 openConfigLog("getFlowEntries fetched connections {}", fetched.size());
145
146 return fetched;
147 }
148
149 /**
150 * Remove the specified flow rules.
151 *
152 * @param rules A collection of Flow Rules to be removed
153 * @return The collection of removed Flow Entries
154 */
155 @Override
156 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
157 NetconfSession session = getNetconfSession();
158 if (session == null) {
159 openConfigError("null session");
160 return ImmutableList.of();
161 }
162 List<FlowRule> removed = new ArrayList<>();
163 for (FlowRule r : rules) {
164 try {
165 TerminalDeviceFlowRule termFlowRule = new TerminalDeviceFlowRule(r, getLinePorts());
166 removeFlowRule(session, termFlowRule);
167 getConnectionCache().remove(did(), r);
168 removed.add(r);
169 } catch (Exception e) {
170 openConfigError("Error {}", e);
171 continue;
172 }
173 }
174
175 //Print out number of removed rules from the device (without receiving errors)
176 openConfigLog("removeFlowRules removed {}", removed.size());
177
178 return removed;
179 }
180
181 private DeviceConnectionCache getConnectionCache() {
182 return DeviceConnectionCache.init();
183 }
184
185 // Context so XPath expressions are aware of XML namespaces
186 private static final NamespaceContext NS_CONTEXT = new NamespaceContext() {
187 @Override
188 public String getNamespaceURI(String prefix) {
189 if (prefix.equals("oc-platform-types")) {
190 return "http://openconfig.net/yang/platform-types";
191 }
192 if (prefix.equals("oc-opt-term")) {
193 return "http://openconfig.net/yang/terminal-device";
194 }
195 return null;
196 }
197
198 @Override
199 public Iterator getPrefixes(String val) {
200 return null;
201 }
202
203 @Override
204 public String getPrefix(String uri) {
205 return null;
206 }
207 };
208
209
210 /**
211 * Helper method to get the device id.
212 */
213 private DeviceId did() {
214 return data().deviceId();
215 }
216
217 /**
218 * Helper method to log from this class adding DeviceId.
219 */
220 private void openConfigLog(String format, Object... arguments) {
221 log.info("OPENCONFIG {}: " + format, did(), arguments);
222 }
223
224 /**
225 * Helper method to log an error from this class adding DeviceId.
226 */
227 private void openConfigError(String format, Object... arguments) {
228 log.error("OPENCONFIG {}: " + format, did(), arguments);
229 }
230
231
232 /**
233 * Helper method to get the Netconf Session.
234 */
235 private NetconfSession getNetconfSession() {
236 NetconfController controller =
237 checkNotNull(handler().get(NetconfController.class));
238 return controller.getNetconfDevice(did()).getSession();
239 }
240
241
242 /**
243 * Construct a String with a Netconf filtered get RPC Message.
244 *
245 * @param filter A valid XML tree with the filter to apply in the get
246 * @return a String containing the RPC XML Document
247 */
248 private String filteredGetBuilder(String filter) {
249 StringBuilder rpc = new StringBuilder(RPC_TAG_NETCONF_BASE);
250 rpc.append("<get>");
251 rpc.append("<filter type='subtree'>");
252 rpc.append(filter);
253 rpc.append("</filter>");
254 rpc.append("</get>");
255 rpc.append(RPC_CLOSE_TAG);
256 return rpc.toString();
257 }
258
259 /**
260 * Construct a get request to retrieve Components and their
261 * properties (for the ONOS port, index).
262 *
263 * @return The filt content to send to the device.
264 */
265 private String getComponents() {
266 StringBuilder filt = new StringBuilder();
267 filt.append("<components xmlns='http://openconfig.net/yang/platform'>");
268 filt.append(" <component>");
269 filt.append(" <name/>");
270 filt.append(" <properties/>");
271 filt.append(" </component>");
272 filt.append("</components>");
273 return filteredGetBuilder(filt.toString());
274 }
275
276 /**
277 * Construct a get request to retrieve Optical Channels and
278 * the line port they are using.
279 * <p>
280 * This method is used to query the device so we can find the
281 * OpticalChannel component name that used a given line port.
282 *
283 * @return The filt content to send to the device.
284 */
285 private String getOpticalChannels() {
286 StringBuilder filt = new StringBuilder();
287 filt.append("<components xmlns='http://openconfig.net/yang/platform'>");
288 filt.append(" <component>");
289 filt.append(" <name/>");
290 filt.append(" <state/>");
291 filt.append(" <oc-opt-term:optical-channel xmlns:oc-opt-term"
292 + " = 'http://openconfig.net/yang/terminal-device'>");
293 filt.append(" <oc-opt-term:config>");
294 filt.append(" <oc-opt-term:line-port/>");
295 filt.append(" </oc-opt-term:config>");
296 filt.append(" </oc-opt-term:optical-channel>");
297 filt.append(" </component>");
298 filt.append("</components>");
299 return filteredGetBuilder(filt.toString());
300 }
301
302 /**
303 * Get the OpenConfig component name for the OpticalChannel component
304 * associated to the passed port number (typically a line side port, already
305 * mapped to ONOS port).
306 *
307 * @param session The netconf session to the device.
308 * @param portNumber ONOS port number of the Line port ().
309 * @return the channel component name or null
310 */
311 private String getOpticalChannel(NetconfSession session,
312 PortNumber portNumber) {
313 try {
314 checkNotNull(session);
315 checkNotNull(portNumber);
316 XPath xp = XPathFactory.newInstance().newXPath();
317 xp.setNamespaceContext(NS_CONTEXT);
318
319 // Get the port name for a given port number
320 // We could iterate the port annotations too, no need to
321 // interact with device.
322 String xpGetPortName =
323 "/rpc-reply/data/components/"
324 +
325 "component[./properties/property[name='onos-index']/config/value ='" +
326 portNumber.toLong() + "']/"
327 + "name/text()";
328
329 // Get all the components and their properties
330 String compReply = session.rpc(getComponents()).get();
331 DocumentBuilderFactory builderFactory =
332 DocumentBuilderFactory.newInstance();
333 DocumentBuilder builder = builderFactory.newDocumentBuilder();
334 Document document =
335 builder.parse(new InputSource(new StringReader(compReply)));
336 String portName = xp.evaluate(xpGetPortName, document);
337 String xpGetOptChannelName =
338 "/rpc-reply/data/components/"
339 + "component[./optical-channel/config/line-port='" + portName +
340 "']/name/text()";
341
342 String optChannelReply = session.rpc(getOpticalChannels()).get();
343 document =
344 builder.parse(new InputSource(new StringReader(optChannelReply)));
345 return xp.evaluate(xpGetOptChannelName, document);
346 } catch (Exception e) {
347 openConfigError("Exception {}", e);
348 return null;
349 }
350 }
351
352 private void setLogicalChannel(NetconfSession session, String operation, String logChannel)
353 throws NetconfException {
354 StringBuilder sb = new StringBuilder();
355
356 sb.append("<terminal-device xmlns='http://openconfig.net/yang/terminal-device'>");
357 sb.append("<logical-channels>");
358 sb.append("<channel>");
359 sb.append("<index>" + logChannel + "</index>");
360 sb.append("<config>");
361 sb.append("<admin-state>" + operation + "</admin-state>");
362 sb.append("</config>");
363 sb.append("</channel>");
364 sb.append("</logical-channels>");
365 sb.append("</terminal-device>");
366
367 boolean ok =
368 session.editConfig(DatastoreId.RUNNING, null, sb.toString());
369 if (!ok) {
370 throw new NetconfException("error writing the logical channel");
371 }
372 }
373
374 private void setOpticalChannelFrequency(NetconfSession session, String optChannel, Frequency freq)
375 throws NetconfException {
376 StringBuilder sb = new StringBuilder();
377
378 sb.append("<components xmlns='http://openconfig.net/yang/platform'>");
379 sb.append("<component>");
380 sb.append("<name>" + PREFIX_CHANNEL + optChannel + "</name>");
381 sb.append("<oc-opt-term:optical-channel xmlns:oc-opt-term='http://openconfig.net/yang/terminal-device'>");
382 sb.append("<oc-opt-term:config>");
383 sb.append("<oc-opt-term:frequency>" + (long) freq.asMHz() + "</oc-opt-term:frequency>");
384 sb.append("<oc-opt-term:target-output-power>" + DEFAULT_TARGET_POWER + "</oc-opt-term:target-output-power>");
385 sb.append("<oc-opt-term:operational-mode>" + DEFAULT_OPERATIONAL_MODE + "</oc-opt-term:operational-mode>");
386 sb.append("<oc-opt-term:line-port>" + PREFIX_PORT + optChannel + "</oc-opt-term:line-port>");
387 sb.append("</oc-opt-term:config>");
388 sb.append("</oc-opt-term:optical-channel>");
389 sb.append("</component>");
390 sb.append("</components>");
391
392 boolean ok =
393 session.editConfig(DatastoreId.RUNNING, null, sb.toString());
394 if (!ok) {
395 throw new NetconfException("error writing channel frequency");
396 }
397 }
398
399 private void setLogicalChannelAssignment(NetconfSession session, String operation, String client, String line,
400 String assignmentIndex, String allocationIndex)
401 throws NetconfException {
402 StringBuilder sb = new StringBuilder();
403
404 sb.append("<terminal-device xmlns='http://openconfig.net/yang/terminal-device'>");
405 sb.append("<logical-channels>");
406 sb.append("<channel>");
407 sb.append("<index>" + client + "</index>");
408 sb.append("<config>");
409 sb.append("<admin-state>" + operation + "</admin-state>");
410 sb.append("</config>");
411 sb.append("<logical-channel-assignments>");
412 sb.append("<assignment>");
413 sb.append("<index>" + assignmentIndex + "</index>");
414 sb.append("<config>");
415 sb.append("<logical-channel>" + line + "</logical-channel>");
416 sb.append("<allocation>" + allocationIndex + "</allocation>");
417 sb.append("</config>");
418 sb.append("</assignment>");
419 sb.append("</logical-channel-assignments>");
420 sb.append("</channel>");
421 sb.append("</logical-channels>");
422 sb.append("</terminal-device>");
423
424 boolean ok =
425 session.editConfig(DatastoreId.RUNNING, null, sb.toString());
426 if (!ok) {
427 throw new NetconfException("error writing logical channel assignment");
428 }
429 }
430
431 /**
432 * Apply a single flowrule to the device.
433 *
434 * --- Directionality details:
435 * Driver supports ADD (INGRESS) and DROP (EGRESS) rules generated by OpticalCircuit/OpticalConnectivity intents
436 * the format of the rules are checked in class TerminalDeviceFlowRule
437 *
438 * However, the physical transponder is always bidirectional as specified in OpenConfig YANG models
439 * therefore ADD and DROP rules are mapped in the same xml that ENABLE (and tune) a transponder port.
440 *
441 * If the intent is generated as bidirectional both ADD and DROP flowrules are generated for each device, thus
442 * the same xml is sent twice to the device.
443 *
444 * @param session The Netconf session.
445 * @param rule Flow Rules to be applied.
446 * @return true if no Netconf errors are received from the device when xml is sent
447 * @throws NetconfException if exchange goes wrong
448 */
449 protected boolean applyFlowRule(NetconfSession session, TerminalDeviceFlowRule rule) {
450
451 //Configuration of LINE side, used for OpticalConnectivity intents
452 //--- configure central frequency
453 //--- enable the line port
454 if (rule.type == TerminalDeviceFlowRule.Type.LINE_INGRESS ||
455 rule.type == TerminalDeviceFlowRule.Type.LINE_EGRESS) {
456
457 FlowRuleParser frp = new FlowRuleParser(rule);
458 String componentName = frp.getPortNumber().toString();
459 Frequency centralFrequency = frp.getCentralFrequency();
460
461 StringBuilder componentConf = new StringBuilder();
462
463 log.info("Sending LINE FlowRule to device {} LINE port {}, frequency {}",
464 did(), componentName, centralFrequency);
465
466 try {
467 setOpticalChannelFrequency(session, componentName, centralFrequency);
468 } catch (NetconfException e) {
469 log.error("Error writing central frequency in the component");
470 return false;
471 }
472
473 try {
474 setLogicalChannel(session, OPERATION_ENABLE, componentName);
475 } catch (NetconfException e) {
476 log.error("Error enabling the logical channel");
477 return false;
478 }
479 }
480
481 //Configuration of CLIENT side, used for OpticalCircuit intents
482 //--- associate the client port to the line port
483 //--- enable the client port
484 //
485 //Assumes only one "assignment" per logical-channel with index 1
486 //TODO check the OTN mapping of client ports into the line port frame specified by parameter "<allocation>"
487 if (rule.type == TerminalDeviceFlowRule.Type.CLIENT_INGRESS ||
488 rule.type == TerminalDeviceFlowRule.Type.CLIENT_EGRESS) {
489
490 String clientPortName;
491 String linePortName;
492 if (rule.type == TerminalDeviceFlowRule.Type.CLIENT_INGRESS) {
493 clientPortName = rule.inPort().toString();
494 linePortName = rule.outPort().toString();
495 } else {
496 clientPortName = rule.outPort().toString();
497 linePortName = rule.inPort().toString();
498 }
499
500 log.info("Sending CLIENT FlowRule to device {} CLIENT port: {}, LINE port {}",
501 did(), clientPortName, linePortName);
502
503 try {
504 setLogicalChannelAssignment(session, OPERATION_ENABLE, clientPortName, linePortName,
505 DEFAULT_ASSIGNMENT_INDEX, DEFAULT_ALLOCATION_INDEX);
506 } catch (NetconfException e) {
507 log.error("Error setting the logical channel assignment");
508 return false;
509 }
510 }
511
512 return true;
513 }
514
515 protected boolean removeFlowRule(NetconfSession session, TerminalDeviceFlowRule rule)
516 throws NetconfException {
517
518 //Configuration of LINE side, used for OpticalConnectivity intents
519 //--- configure central frequency to ZERO
520 //--- disable the line port
521 if (rule.type == TerminalDeviceFlowRule.Type.LINE_INGRESS ||
522 rule.type == TerminalDeviceFlowRule.Type.LINE_EGRESS) {
523
524 FlowRuleParser frp = new FlowRuleParser(rule);
525 String componentName = frp.getPortNumber().toString();
526
527 log.info("Removing LINE FlowRule device {} line port {}",
528 did(), componentName);
529
530 try {
531 setLogicalChannel(session, OPERATION_DISABLE, componentName);
532 } catch (NetconfException e) {
533 log.error("Error disabling the logical channel line side");
534 return false;
535 }
536 }
537
538 //Configuration of CLIENT side, used for OpticalCircuit intents
539 //--- configure central frequency to ZERO
540 //--- disable the line port
541 if (rule.type == TerminalDeviceFlowRule.Type.CLIENT_INGRESS ||
542 rule.type == TerminalDeviceFlowRule.Type.CLIENT_EGRESS) {
543
544 String clientPortName;
545 String linePortName;
546 if (rule.type == TerminalDeviceFlowRule.Type.CLIENT_INGRESS) {
547 clientPortName = rule.inPort().toString();
548 linePortName = rule.outPort().toString();
549 } else {
550 clientPortName = rule.outPort().toString();
551 linePortName = rule.inPort().toString();
552 }
553
554 log.debug("Removing CLIENT FlowRule device {} client port: {}, line port {}",
555 did(), clientPortName, linePortName);
556
557 try {
558 setLogicalChannelAssignment(session, OPERATION_DISABLE, clientPortName, linePortName,
559 DEFAULT_ASSIGNMENT_INDEX, DEFAULT_ALLOCATION_INDEX);
560 } catch (NetconfException e) {
561 log.error("Error disabling the logical channel assignment");
562 return false;
563 }
564 }
565
566 return true;
567 }
568
569 private List<FlowRule> fetchLineConnectionFromDevice(String channel, Frequency centralFreq) {
570 List<FlowRule> confirmedRules = new ArrayList<>();
571 FlowRule cacheAddRule;
572 FlowRule cacheDropRule;
573 NetconfSession session = getNetconfSession();
574
575 log.debug("fetchOpticalConnectionsFromDevice {} frequency {}", did(), centralFreq);
576
577 //Build the corresponding flow rule as expected
578 //Selector including port and ochSignal
579 //Treatment including port
580 PortNumber inputPortNumber = PortNumber.portNumber(channel);
581 PortNumber outputPortNumber = PortNumber.portNumber(channel);
582
583 log.debug("fetchOpticalConnectionsFromDevice {} port {}-{}", did(), inputPortNumber, outputPortNumber);
584
585 TrafficSelector selectorDrop = DefaultTrafficSelector.builder()
586 .matchInPort(inputPortNumber)
587 .add(Criteria.matchOchSignalType(OchSignalType.FIXED_GRID))
588 .add(Criteria.matchLambda(toOchSignal(centralFreq, 50.0)))
589 .build();
590
591 TrafficTreatment treatmentDrop = DefaultTrafficTreatment.builder()
592 .setOutput(outputPortNumber)
593 .build();
594
595 TrafficSelector selectorAdd = DefaultTrafficSelector.builder()
596 .matchInPort(inputPortNumber)
597 .build();
598
599 TrafficTreatment treatmentAdd = DefaultTrafficTreatment.builder()
600 .add(Instructions.modL0Lambda(toOchSignal(centralFreq, 50.0)))
601 .setOutput(outputPortNumber)
602 .build();
603
604 //Retrieved rules and cached rules are considered equal if both selector and treatment are equal
605 cacheAddRule = null;
606 cacheDropRule = null;
607 if (getConnectionCache().size(did()) != 0) {
608 cacheDropRule = getConnectionCache().get(did()).stream()
609 .filter(r -> (r.selector().equals(selectorDrop) && r.treatment().equals(treatmentDrop)))
610 .findFirst()
611 .orElse(null);
612
613 cacheAddRule = getConnectionCache().get(did()).stream()
614 .filter(r -> (r.selector().equals(selectorAdd) && r.treatment().equals(treatmentAdd)))
615 .findFirst()
616 .orElse(null);
617 }
618
619 //Include the DROP rule to the retrieved rules if found in cache
620 if ((cacheDropRule != null)) {
621 confirmedRules.add(cacheDropRule);
622 log.debug("fetchOpticalConnectionsFromDevice {} DROP LINE rule included in the cache {}",
623 did(), cacheDropRule);
624 } else {
625 log.warn("fetchOpticalConnectionsFromDevice {} DROP LINE rule not included in cache", did());
626 }
627
628 //Include the ADD rule to the retrieved rules if found in cache
629 if ((cacheAddRule != null)) {
630 confirmedRules.add(cacheAddRule);
631 log.debug("fetchOpticalConnectionsFromDevice {} ADD LINE rule included in the cache {}",
632 did(), cacheAddRule.selector());
633 } else {
634 log.warn("fetchOpticalConnectionsFromDevice {} ADD LINE rule not included in cache", did());
635 }
636
637 //If neither Add or Drop rules are present in the cache, remove configuration from the device
638 if ((cacheDropRule == null) && (cacheAddRule == null)) {
639 log.warn("fetchOpticalConnectionsFromDevice {} ADD and DROP rule not included in the cache", did());
640
641 FlowRule deviceDropRule = DefaultFlowRule.builder()
642 .forDevice(data().deviceId())
643 .makePermanent()
644 .withSelector(selectorDrop)
645 .withTreatment(treatmentDrop)
646 .withCookie(DEFAULT_RULE_COOKIE)
647 .withPriority(DEFAULT_RULE_PRIORITY)
648 .build();
649
650 FlowRule deviceAddRule = DefaultFlowRule.builder()
651 .forDevice(data().deviceId())
652 .makePermanent()
653 .withSelector(selectorAdd)
654 .withTreatment(treatmentAdd)
655 .withCookie(DEFAULT_RULE_COOKIE)
656 .withPriority(DEFAULT_RULE_PRIORITY)
657 .build();
658
659 try {
660 //TODO this is not required if allowExternalFlowRules
661 TerminalDeviceFlowRule addRule = new TerminalDeviceFlowRule(deviceAddRule, getLinePorts());
662 removeFlowRule(session, addRule);
663
664 TerminalDeviceFlowRule dropRule = new TerminalDeviceFlowRule(deviceDropRule, getLinePorts());
665 removeFlowRule(session, dropRule);
666 } catch (NetconfException e) {
667 openConfigError("Error removing LINE rule from device", e);
668 }
669 }
670 return confirmedRules;
671 }
672
673 private List<FlowRule> fetchClientConnectionFromDevice(PortNumber clientPortNumber, PortNumber linePortNumber) {
674 List<FlowRule> confirmedRules = new ArrayList<>();
675 FlowRule cacheAddRule;
676 FlowRule cacheDropRule;
677 NetconfSession session = getNetconfSession();
678
679 //Build the corresponding flow rule as expected
680 //Selector including port
681 //Treatment including port
682
683 log.debug("fetchClientConnectionsFromDevice {} client {} line {}", did(), clientPortNumber, linePortNumber);
684
685 TrafficSelector selectorDrop = DefaultTrafficSelector.builder()
686 .matchInPort(linePortNumber)
687 .build();
688
689 TrafficTreatment treatmentDrop = DefaultTrafficTreatment.builder()
690 .setOutput(clientPortNumber)
691 .build();
692
693 TrafficSelector selectorAdd = DefaultTrafficSelector.builder()
694 .matchInPort(clientPortNumber)
695 .build();
696
697 TrafficTreatment treatmentAdd = DefaultTrafficTreatment.builder()
698 .setOutput(linePortNumber)
699 .build();
700
701 //Retrieved rules and cached rules are considered equal if both selector and treatment are equal
702 cacheAddRule = null;
703 cacheDropRule = null;
704 if (getConnectionCache().size(did()) != 0) {
705 cacheDropRule = getConnectionCache().get(did()).stream()
706 .filter(r -> (r.selector().equals(selectorDrop) && r.treatment().equals(treatmentDrop)))
707 .findFirst()
708 .orElse(null);
709
710 cacheAddRule = getConnectionCache().get(did()).stream()
711 .filter(r -> (r.selector().equals(selectorAdd) && r.treatment().equals(treatmentAdd)))
712 .findFirst()
713 .orElse(null);
714 }
715
716 //Include the DROP rule to the retrieved rules if found in cache
717 if ((cacheDropRule != null)) {
718 confirmedRules.add(cacheDropRule);
719 log.debug("fetchClientConnectionsFromDevice {} DROP CLIENT rule in the cache {}",
720 did(), cacheDropRule);
721 } else {
722 log.warn("fetchClientConnectionsFromDevice {} DROP CLIENT rule not found in cache", did());
723 }
724
725 //Include the ADD rule to the retrieved rules if found in cache
726 if ((cacheAddRule != null)) {
727 confirmedRules.add(cacheAddRule);
728 log.debug("fetchClientConnectionsFromDevice {} ADD CLIENT rule in the cache {}",
729 did(), cacheAddRule);
730 } else {
731 log.warn("fetchClientConnectionsFromDevice {} ADD CLIENT rule not found in cache", did());
732 }
733
734 if ((cacheDropRule == null) && (cacheAddRule == null)) {
735 log.warn("fetchClientConnectionsFromDevice {} ADD and DROP rule not included in the cache", did());
736
737 FlowRule deviceDropRule = DefaultFlowRule.builder()
738 .forDevice(data().deviceId())
739 .makePermanent()
740 .withSelector(selectorDrop)
741 .withTreatment(treatmentDrop)
742 .withCookie(DEFAULT_RULE_COOKIE)
743 .withPriority(DEFAULT_RULE_PRIORITY)
744 .build();
745
746 FlowRule deviceAddRule = DefaultFlowRule.builder()
747 .forDevice(data().deviceId())
748 .makePermanent()
749 .withSelector(selectorAdd)
750 .withTreatment(treatmentAdd)
751 .withCookie(DEFAULT_RULE_COOKIE)
752 .withPriority(DEFAULT_RULE_PRIORITY)
753 .build();
754
755 try {
756 //TODO this is not required if allowExternalFlowRules
757 TerminalDeviceFlowRule addRule = new TerminalDeviceFlowRule(deviceAddRule, getLinePorts());
758 removeFlowRule(session, addRule);
759
760 TerminalDeviceFlowRule dropRule = new TerminalDeviceFlowRule(deviceDropRule, getLinePorts());
761 removeFlowRule(session, dropRule);
762 } catch (NetconfException e) {
763 openConfigError("Error removing CLIENT rule from device", e);
764 }
765 }
766 return confirmedRules;
767 }
768
769 /**
770 * Fetches list of connections from device.
771 *
772 * TODO manage allow external flow rules (allowExternalFlowRules)
773 * Currently removes from the device all connections that are not currently present in the DeviceConnectionCache.
774 *
775 * @return connections that are present on the device and in the DeviceConnectionCache.
776 */
777 private List<FlowRule> fetchConnectionsFromDevice() {
778 List<FlowRule> confirmedRules = new ArrayList<>();
779 String reply;
780 FlowRule cacheAddRule;
781 FlowRule cacheDropRule;
782 NetconfSession session = getNetconfSession();
783
784 //Get relevant information from the device
785 StringBuilder requestFilter = new StringBuilder();
786 requestFilter.append("<components xmlns='http://openconfig.net/yang/platform'>");
787 requestFilter.append(" <component>");
788 requestFilter.append(" <name/>");
789 requestFilter.append(" <oc-opt-term:optical-channel " +
790 "xmlns:oc-opt-term='http://openconfig.net/yang/terminal-device'>");
791 requestFilter.append(" <oc-opt-term:config/>");
792 requestFilter.append(" </oc-opt-term:optical-channel>");
793 requestFilter.append(" </component>");
794 requestFilter.append("</components>");
795 requestFilter.append("<terminal-device xmlns='http://openconfig.net/yang/terminal-device'>");
796 requestFilter.append(" <logical-channels>");
797 requestFilter.append(" <channel>");
798 requestFilter.append(" <index/>");
799 requestFilter.append(" <config>");
800 requestFilter.append(" <admin-state/>");
801 requestFilter.append(" <logical-channel-type/>");
802 requestFilter.append(" </config>");
803 requestFilter.append(" <logical-channel-assignments>");
804 requestFilter.append(" <assignment>");
805 requestFilter.append(" <config>");
806 requestFilter.append(" <logical-channel/>");
807 requestFilter.append(" </config>");
808 requestFilter.append(" </assignment>");
809 requestFilter.append(" </logical-channel-assignments>");
810 requestFilter.append(" </channel>");
811 requestFilter.append(" </logical-channels>");
812 requestFilter.append("</terminal-device>");
813
814 try {
815 reply = session.get(requestFilter.toString(), null);
816 //log.debug("TRANSPONDER CONNECTIONS - fetchConnectionsFromDevice {} reply {}", did(), reply);
817 } catch (NetconfException e) {
818 log.error("Failed to retrieve configuration details for device {}", handler().data().deviceId(), e);
819 return ImmutableList.of();
820 }
821
822 HierarchicalConfiguration cfg = XmlConfigParser.loadXml(new ByteArrayInputStream(reply.getBytes()));
823
824 List<HierarchicalConfiguration> logicalChannels =
825 cfg.configurationsAt("data.terminal-device.logical-channels.channel");
826
827 List<HierarchicalConfiguration> components =
828 cfg.configurationsAt("data.components.component");
829
830 //Retrieve the ENABLED line ports
831 List<String> enabledOpticalChannels = logicalChannels.stream()
832 .filter(r -> r.getString("config.logical-channel-type").equals(OC_TYPE_PROT_OTN))
833 .filter(r -> r.getString("config.admin-state").equals(OPERATION_ENABLE))
834 .map(r -> r.getString("index"))
835 .collect(Collectors.toList());
836
837 log.debug("fetchConnectionsFromDevice {} enabledOpticalChannelsIndex {}", did(), enabledOpticalChannels);
838
839 if (enabledOpticalChannels.size() != 0) {
840 for (String channel : enabledOpticalChannels) {
841 log.debug("fetchOpticalConnectionsFromDevice {} channel {}", did(), channel);
842
843 //Retrieve the corresponding central frequency from the associated component
844 //TODO correlate the components instead of relying on naming
845 Frequency centralFreq = components.stream()
846 .filter(c -> c.getString("name").equals(PREFIX_CHANNEL + channel))
847 .map(c -> c.getDouble("optical-channel.config.frequency"))
848 .map(c -> Frequency.ofMHz(c))
849 .findFirst()
850 .orElse(null);
851
852 confirmedRules.addAll(fetchLineConnectionFromDevice(channel, centralFreq));
853 }
854 }
855
856 //Retrieve the ENABLED client ports
857 List<String> enabledClientChannels = logicalChannels.stream()
858 .filter(r -> r.getString("config.logical-channel-type").equals(OC_TYPE_PROT_ETH))
859 .filter(r -> r.getString("config.admin-state").equals(OPERATION_ENABLE))
860 .map(r -> r.getString("index"))
861 .collect(Collectors.toList());
862
863 log.debug("fetchClientConnectionsFromDevice {} enabledClientChannelsIndex {}", did(), enabledClientChannels);
864
865 if (enabledClientChannels.size() != 0) {
866 for (String clientPort : enabledClientChannels) {
867
868 log.debug("fetchClientConnectionsFromDevice {} channel {}", did(), clientPort);
869
870 String linePort = logicalChannels.stream()
871 .filter(r -> r.getString("config.logical-channel-type").equals(OC_TYPE_PROT_ETH))
872 .filter(r -> r.getString("config.admin-state").equals(OPERATION_ENABLE))
873 .filter(r -> r.getString("index").equals(clientPort))
874 .map(r -> r.getString("logical-channel-assignments.assignment.config.logical-channel"))
875 .findFirst()
876 .orElse(null);
877
878 //Build the corresponding flow rule as expected
879 //Selector including port
880 //Treatment including port
881 PortNumber clientPortNumber = PortNumber.portNumber(clientPort);
882 PortNumber linePortNumber = PortNumber.portNumber(linePort);
883
884 confirmedRules.addAll(fetchClientConnectionFromDevice(clientPortNumber, linePortNumber));
885 }
886 }
887
888 //Returns rules that are both on the device and on the cache
889 if (confirmedRules.size() != 0) {
890 log.info("fetchConnectionsFromDevice {} number of confirmed rules {}", did(), confirmedRules.size());
891 return confirmedRules;
892 } else {
893 return ImmutableList.of();
894 }
895 }
896
897 /**
898 * Convert start and end frequencies to OCh signal.
899 *
900 * FIXME: supports channel spacing 50 and 100
901 *
902 * @param central central frequency as double in THz
903 * @param width width of the channel arounf the central frequency as double in GHz
904 * @return OCh signal
905 */
906 public static OchSignal toOchSignal(Frequency central, double width) {
907 int slots = (int) (width / ChannelSpacing.CHL_12P5GHZ.frequency().asGHz());
908 int multiplier = 0;
909
910 double centralAsGHz = central.asGHz();
911
912 if (width == 50) {
913 multiplier = (int) ((centralAsGHz - Spectrum.CENTER_FREQUENCY.asGHz())
914 / ChannelSpacing.CHL_50GHZ.frequency().asGHz());
915
916 return new OchSignal(GridType.DWDM, ChannelSpacing.CHL_50GHZ, multiplier, slots);
917 }
918
919 if (width == 100) {
920 multiplier = (int) ((centralAsGHz - Spectrum.CENTER_FREQUENCY.asGHz())
921 / ChannelSpacing.CHL_100GHZ.frequency().asGHz());
922
923 return new OchSignal(GridType.DWDM, ChannelSpacing.CHL_100GHZ, multiplier, slots);
924 }
925
926 return null;
927 }
928
929 private List<PortNumber> getLinePorts() {
930 List<PortNumber> linePorts;
931
932 DeviceService deviceService = this.handler().get(DeviceService.class);
933 linePorts = deviceService.getPorts(data().deviceId()).stream()
934 .filter(p -> p.annotations().value(OdtnDeviceDescriptionDiscovery.PORT_TYPE)
935 .equals(OdtnDeviceDescriptionDiscovery.OdtnPortType.LINE.value()))
936 .map(p -> p.number())
937 .collect(Collectors.toList());
938
939 return linePorts;
940
941 }
942}