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