blob: ee50f7fb816b03959ca98877558322b2e70ba218 [file] [log] [blame]
Sudeep Desai6f136d42019-11-25 14:42:43 +05301/*
2 * Copyright 2019-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.onlab.util.Frequency;
23import org.onosproject.drivers.odtn.impl.DeviceConnectionCache;
24import org.onosproject.drivers.odtn.impl.FlowRuleParser;
25import org.onosproject.net.DeviceId;
26import org.onosproject.net.Port;
27import org.onosproject.net.PortNumber;
28import org.onosproject.net.device.DeviceService;
29import org.onosproject.net.driver.AbstractHandlerBehaviour;
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.netconf.NetconfController;
35import org.onosproject.netconf.NetconfException;
36import org.onosproject.netconf.NetconfSession;
37import org.slf4j.Logger;
38import org.slf4j.LoggerFactory;
39import org.w3c.dom.Document;
40import org.xml.sax.InputSource;
41
42import javax.xml.namespace.NamespaceContext;
43import javax.xml.parsers.DocumentBuilder;
44import javax.xml.parsers.DocumentBuilderFactory;
45import javax.xml.xpath.XPath;
46import javax.xml.xpath.XPathFactory;
47import java.io.StringReader;
48import java.util.ArrayList;
49import java.util.Collection;
50import java.util.Iterator;
51import java.util.List;
52
53import static com.google.common.base.Preconditions.checkNotNull;
54import static org.onosproject.odtn.behaviour.OdtnDeviceDescriptionDiscovery.OC_NAME;
55
56/**
57 * Implementation of FlowRuleProgrammable interface for
58 * OpenConfig terminal devices.
59 */
60public abstract class AbstractTerminalDeviceFlowRuleProgrammable
61 extends AbstractHandlerBehaviour implements FlowRuleProgrammable {
62
63 private static final Logger log =
64 LoggerFactory.getLogger(AbstractTerminalDeviceFlowRuleProgrammable.class);
65
66 private static final String RPC_TAG_NETCONF_BASE =
67 "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">";
68
69 private static final String RPC_CLOSE_TAG = "</rpc>";
70
71
72 /**
73 * Apply the flow entries specified in the collection rules.
74 *
75 * @param rules A collection of Flow Rules to be applied
76 * @return The collection of added Flow Entries
77 */
78 @Override
79 public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
80 NetconfSession session = getNetconfSession();
81 if (session == null) {
82 openConfigError("null session");
83 return ImmutableList.of();
84 }
85 List<FlowRule> added = new ArrayList<>();
86 for (FlowRule r : rules) {
87 try {
88 String connectionId = applyFlowRule(session, r);
89 getConnectionCache().add(did(), connectionId, r);
90 added.add(r);
91 } catch (Exception e) {
92 openConfigError("Error {}", e);
93 continue;
94 }
95 }
96 openConfigLog("applyFlowRules added {}", added.size());
97 return added;
98 }
99
100 /**
101 * Get the flow entries that are present on the device.
102 *
103 * @return A collection of Flow Entries
104 */
105 @Override
106 public Collection<FlowEntry> getFlowEntries() {
107 DeviceConnectionCache cache = getConnectionCache();
108 if (cache.get(did()) == null) {
109 return ImmutableList.of();
110 }
111
112 List<FlowEntry> entries = new ArrayList<>();
113 for (FlowRule r : cache.get(did())) {
114 entries.add(
115 new DefaultFlowEntry(r, FlowEntry.FlowEntryState.ADDED, 0, 0, 0));
116 }
117 return entries;
118 }
119
120 /**
121 * Remove the specified flow rules.
122 *
123 * @param rules A collection of Flow Rules to be removed
124 * @return The collection of removed Flow Entries
125 */
126 @Override
127 public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
128 NetconfSession session = getNetconfSession();
129 if (session == null) {
130 openConfigError("null session");
131 return ImmutableList.of();
132 }
133 List<FlowRule> removed = new ArrayList<>();
134 for (FlowRule r : rules) {
135 try {
136 String connectionId = removeFlowRule(session, r);
137 getConnectionCache().remove(did(), connectionId);
138 removed.add(r);
139 } catch (Exception e) {
140 openConfigError("Error {}", e);
141 continue;
142 }
143 }
144 openConfigLog("removedFlowRules removed {}", removed.size());
145 return removed;
146 }
147
148 private DeviceConnectionCache getConnectionCache() {
149 return DeviceConnectionCache.init();
150 }
151
152 // Context so XPath expressions are aware of XML namespaces
153 private static final NamespaceContext NS_CONTEXT = new NamespaceContext() {
154 @Override
155 public String getNamespaceURI(String prefix) {
156 if (prefix.equals("oc-platform-types")) {
157 return "http://openconfig.net/yang/platform-types";
158 }
159 if (prefix.equals("oc-opt-term")) {
160 return "http://openconfig.net/yang/terminal-device";
161 }
162 return null;
163 }
164
165 @Override
166 public Iterator getPrefixes(String val) {
167 return null;
168 }
169
170 @Override
171 public String getPrefix(String uri) {
172 return null;
173 }
174 };
175
176
177 /**
178 * Helper method to get the device id.
179 */
180 private DeviceId did() {
181 return data().deviceId();
182 }
183
184 /**
185 * Helper method to log from this class adding DeviceId.
186 */
187 private void openConfigLog(String format, Object... arguments) {
188 log.info("OPENCONFIG {}: " + format, did(), arguments);
189 }
190
191 /**
192 * Helper method to log an error from this class adding DeviceId.
193 */
194 private void openConfigError(String format, Object... arguments) {
195 log.error("OPENCONFIG {}: " + format, did(), arguments);
196 }
197
198
199 /**
200 * Helper method to get the Netconf Session.
201 */
202 private NetconfSession getNetconfSession() {
203 NetconfController controller =
204 checkNotNull(handler().get(NetconfController.class));
205 return controller.getNetconfDevice(did()).getSession();
206 }
207
208
209 /**
210 * Construct a String with a Netconf filtered get RPC Message.
211 *
212 * @param filter A valid XML tree with the filter to apply in the get
213 * @return a String containing the RPC XML Document
214 */
215 private String filteredGetBuilder(String filter) {
216 StringBuilder rpc = new StringBuilder(RPC_TAG_NETCONF_BASE);
217 rpc.append("<get>");
218 rpc.append("<filter type='subtree'>");
219 rpc.append(filter);
220 rpc.append("</filter>");
221 rpc.append("</get>");
222 rpc.append(RPC_CLOSE_TAG);
223 return rpc.toString();
224 }
225
226 /**
227 * Construct a get request to retrieve Components and their
228 * properties (for the ONOS port, index).
229 *
230 * @return The filt content to send to the device.
231 */
232 private String getComponents() {
233 StringBuilder filt = new StringBuilder();
234 filt.append("<components xmlns='http://openconfig.net/yang/platform'>");
235 filt.append(" <component>");
236 filt.append(" <name/>");
237 filt.append(" <properties/>");
238 filt.append(" </component>");
239 filt.append("</components>");
240 return filteredGetBuilder(filt.toString());
241 }
242
243
244 /**
245 * Construct a get request to retrieve Optical Channels and
246 * the line port they are using.
247 * <p>
248 * This method is used to query the device so we can find the
249 * OpticalChannel component name that used a given line port.
250 *
251 * @return The filt content to send to the device.
252 */
253 private String getOpticalChannels() {
254 StringBuilder filt = new StringBuilder();
255 filt.append("<components xmlns='http://openconfig.net/yang/platform'>");
256 filt.append(" <component>");
257 filt.append(" <name/>");
258 filt.append(" <state/>");
259 filt.append(" <oc-opt-term:optical-channel xmlns:oc-opt-term"
260 + " = 'http://openconfig.net/yang/terminal-device'>");
261 filt.append(" <oc-opt-term:config>");
262 filt.append(" <oc-opt-term:line-port/>");
263 filt.append(" </oc-opt-term:config>");
264 filt.append(" </oc-opt-term:optical-channel>");
265 filt.append(" </component>");
266 filt.append("</components>");
267 return filteredGetBuilder(filt.toString());
268 }
269
270
271 /**
272 * Get the OpenConfig component name for the OpticalChannel component
273 * associated to the passed port number (typically a line side port, already
274 * mapped to ONOS port).
275 *
276 * @param session The netconf session to the device.
277 * @param portNumber ONOS port number of the Line port ().
278 * @return the channel component name or null
279 */
280 protected String getOpticalChannel(NetconfSession session,
281 PortNumber portNumber) {
282 try {
283 checkNotNull(session);
284 checkNotNull(portNumber);
285 XPath xp = XPathFactory.newInstance().newXPath();
286 xp.setNamespaceContext(NS_CONTEXT);
287
288 // Get the port name for a given port number
289 // We could iterate the port annotations too, no need to
290 // interact with device.
291 String xpGetPortName =
292 "/rpc-reply/data/components/"
293 +
294 "component[./properties/property[name='onos-index']/config/value ='" +
295 portNumber.toLong() + "']/"
296 + "name/text()";
297
298 // Get all the components and their properties
299 String compReply = session.rpc(getComponents()).get();
300 DocumentBuilderFactory builderFactory =
301 DocumentBuilderFactory.newInstance();
302 DocumentBuilder builder = builderFactory.newDocumentBuilder();
303 Document document =
304 builder.parse(new InputSource(new StringReader(compReply)));
305 String portName = xp.evaluate(xpGetPortName, document);
306 String xpGetOptChannelName =
307 "/rpc-reply/data/components/"
308 + "component[./optical-channel/config/line-port='" + portName +
309 "']/name/text()";
310
311 String optChannelReply = session.rpc(getOpticalChannels()).get();
312 document =
313 builder.parse(new InputSource(new StringReader(optChannelReply)));
314 return xp.evaluate(xpGetOptChannelName, document);
315 } catch (Exception e) {
316 openConfigError("Exception {}", e);
317 return null;
318 }
319 }
320
321 /**
322 * Get the OpenConfig component name for the OpticalChannel component.
323 *
324 * @param portNumber ONOS port number of the Line port ().
325 * @return the channel component name or null
326 */
327 private String getOpticalChannel(PortNumber portNumber) {
328 Port clientPort = handler().get(DeviceService.class).getPort(did(), portNumber);
329 return clientPort.annotations().value(OC_NAME);
330 }
331
332
333 public abstract void setOpticalChannelFrequency(NetconfSession session,
334 String optChannel, Frequency freq)
335 throws NetconfException;
336
337
338 /**
339 * Apply the flowrule.
340 * <p>
341 * Note: only bidirectional are supported as of now,
342 * given OpenConfig note (below). In consequence, only the
343 * TX rules are actually mapped to netconf ops.
344 * <p>
345 * https://github.com/openconfig/public/blob/master/release/models
346 * /optical-transport/openconfig-terminal-device.yang
347 * <p>
348 * Directionality:
349 * To maintain simplicity in the model, the configuration is
350 * described from client-to-line direction. The assumption is that
351 * equivalent reverse configuration is implicit, resulting in
352 * the same line-to-client configuration.
353 *
354 * @param session The Netconf session.
355 * @param r Flow Rules to be applied.
356 * @return the optical channel + the frequency or just channel as identifier fo the config installed on the device
357 * @throws NetconfException if exchange goes wrong
358 */
359 protected String applyFlowRule(NetconfSession session, FlowRule r) throws NetconfException {
360 FlowRuleParser frp = new FlowRuleParser(r);
361 if (!frp.isReceiver()) {
362 String optChannel = getOpticalChannel(frp.getPortNumber());
363 setOpticalChannelFrequency(session, optChannel,
364 frp.getCentralFrequency());
365 return optChannel + ":" + frp.getCentralFrequency().asGHz();
366 }
367 return String.valueOf(frp.getCentralFrequency().asGHz());
368 }
369
370
371 protected String removeFlowRule(NetconfSession session, FlowRule r)
372 throws NetconfException {
373 FlowRuleParser frp = new FlowRuleParser(r);
374 if (!frp.isReceiver()) {
375 String optChannel = getOpticalChannel(frp.getPortNumber());
376 setOpticalChannelFrequency(session, optChannel, Frequency.ofMHz(0));
377 return optChannel + ":" + frp.getCentralFrequency().asGHz();
378 }
379 return String.valueOf(frp.getCentralFrequency().asGHz());
380 }
381}