blob: acf97baa1d054c86aae312b21ab981c000fbae47 [file] [log] [blame]
Aaron Kruglikov309068e2017-03-17 15:25:54 -07001/*
2 * Copyright 2017-present Open Networking Laboratory
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
Thomas Vachuska59d24eb2017-03-22 10:57:34 -070017package org.onosproject.netconf.client.impl;
Aaron Kruglikov309068e2017-03-17 15:25:54 -070018
19import com.google.common.annotations.Beta;
20import org.apache.felix.scr.annotations.Activate;
21import org.apache.felix.scr.annotations.Component;
Aaron Kruglikov309068e2017-03-17 15:25:54 -070022import org.apache.felix.scr.annotations.Deactivate;
23import org.apache.felix.scr.annotations.Reference;
24import org.apache.felix.scr.annotations.ReferenceCardinality;
25import org.onosproject.config.DynamicConfigEvent;
26import org.onosproject.config.DynamicConfigListener;
27import org.onosproject.config.DynamicConfigService;
Sithara Punnassery0b51d432017-03-28 01:09:28 -070028import org.onosproject.config.ResourceIdParser;
Aaron Kruglikov309068e2017-03-17 15:25:54 -070029import org.onosproject.config.Filter;
30import org.onosproject.mastership.MastershipService;
31import org.onosproject.net.DeviceId;
Thomas Vachuska59d24eb2017-03-22 10:57:34 -070032import org.onosproject.netconf.NetconfController;
33import org.onosproject.netconf.NetconfException;
Aaron Kruglikov309068e2017-03-17 15:25:54 -070034import org.onosproject.netconf.client.NetconfTranslator;
35import org.onosproject.netconf.client.NetconfTranslator.OperationType;
36import org.onosproject.yang.model.DataNode;
Bharat saraswalb0dbe6b2017-03-24 22:51:06 +053037import org.onosproject.yang.model.InnerNode;
Sithara Punnassery425837f2017-03-21 12:58:00 -070038import org.onosproject.yang.model.LeafNode;
Sithara Punnasserybda82502017-03-22 19:08:19 -070039import org.onosproject.yang.model.ListKey;
Bharat saraswalb0dbe6b2017-03-24 22:51:06 +053040import org.onosproject.yang.model.NodeKey;
Aaron Kruglikov309068e2017-03-17 15:25:54 -070041import org.onosproject.yang.model.ResourceId;
42import org.onosproject.yang.runtime.DefaultResourceData;
Sithara Punnassery326b61d2017-03-24 14:15:04 -070043import org.onlab.util.AbstractAccumulator;
44import org.onlab.util.Accumulator;
Aaron Kruglikov309068e2017-03-17 15:25:54 -070045import org.slf4j.Logger;
46import org.slf4j.LoggerFactory;
47
48import java.io.IOException;
Thomas Vachuska59d24eb2017-03-22 10:57:34 -070049import java.net.URI;
50import java.net.URISyntaxException;
Sithara Punnassery0b51d432017-03-28 01:09:28 -070051import java.util.ArrayList;
Bharat saraswalb0dbe6b2017-03-24 22:51:06 +053052import java.util.Iterator;
Sithara Punnassery0b51d432017-03-28 01:09:28 -070053import java.util.LinkedHashMap;
Bharat saraswalb0dbe6b2017-03-24 22:51:06 +053054import java.util.List;
55import java.util.Map;
Sithara Punnassery326b61d2017-03-24 14:15:04 -070056import java.util.Timer;
Bharat saraswalb0dbe6b2017-03-24 22:51:06 +053057
58import static com.google.common.base.Preconditions.checkNotNull;
Aaron Kruglikov309068e2017-03-17 15:25:54 -070059
Sithara Punnassery425837f2017-03-21 12:58:00 -070060
Aaron Kruglikov309068e2017-03-17 15:25:54 -070061@Beta
62@Component(immediate = true)
Sithara Punnassery425837f2017-03-21 12:58:00 -070063public class NetconfActiveComponent implements DynamicConfigListener {
Aaron Kruglikov309068e2017-03-17 15:25:54 -070064
Sithara Punnassery425837f2017-03-21 12:58:00 -070065 private static final Logger log = LoggerFactory.getLogger(NetconfActiveComponent.class);
Aaron Kruglikov309068e2017-03-17 15:25:54 -070066 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Sithara Punnassery425837f2017-03-21 12:58:00 -070067 protected DynamicConfigService cfgService;
Aaron Kruglikov309068e2017-03-17 15:25:54 -070068
69 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
70 protected NetconfTranslator netconfTranslator;
71
72 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
73 protected MastershipService mastershipService;
74
Sithara Punnassery425837f2017-03-21 12:58:00 -070075 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
76 protected NetconfController controller;
77
Sithara Punnassery326b61d2017-03-24 14:15:04 -070078 private final Accumulator<DynamicConfigEvent> accumulator = new InternalEventAccummulator();
79 private static final String DEVNMSPACE = "ne-l3vpn-api";
80 private static final String DEVICES = "devices";
81 private static final String DEVICE = "device";
82 private static final String DEVICE_ID = "deviceid";
83
84 //Symbolic constants for use with the accumulator
85 private static final int MAX_EVENTS = 1000;
86 private static final int MAX_BATCH_MS = 5000;
87 private static final int MAX_IDLE_MS = 1000;
Bharat saraswalb0dbe6b2017-03-24 22:51:06 +053088
Sithara Punnassery0b51d432017-03-28 01:09:28 -070089 private ResourceId defParent = new ResourceId.Builder()
Bharat saraswalb0dbe6b2017-03-24 22:51:06 +053090 .addBranchPointSchema(DEVICES, DEVNMSPACE)
91 .addBranchPointSchema(DEVICE, DEVNMSPACE)
Sithara Punnassery0b51d432017-03-28 01:09:28 -070092 .addBranchPointSchema(DEVICE_ID, DEVNMSPACE)
Aaron Kruglikov309068e2017-03-17 15:25:54 -070093 .build();
Thomas Vachuska59d24eb2017-03-22 10:57:34 -070094
Sithara Punnassery0b51d432017-03-28 01:09:28 -070095 //TODO remove this hack after store ordering is fixed
96 private static final String EXCPATH = "root|devices#ne-l3vpn-api|" +
97 "device#ne-l3vpn-api$deviceid#ne-l3vpn-api#netconf:172.16.5.11:22|" +
98 "l3vpn#ne-l3vpn-api|l3vpncomm#ne-l3vpn-api|l3vpnInstances#ne-l3vpn-api|" +
99 "l3vpnInstance#ne-l3vpn-api$vrfName#ne-l3vpn-api#vrf2|l3vpnIfs#ne-l3vpn-api";
100 //end of hack
101
Aaron Kruglikov309068e2017-03-17 15:25:54 -0700102 @Activate
103 protected void activate() {
Sithara Punnassery425837f2017-03-21 12:58:00 -0700104 cfgService.addListener(this);
Aaron Kruglikov309068e2017-03-17 15:25:54 -0700105 log.info("Started");
106 }
107
108 @Deactivate
109 protected void deactivate() {
Sithara Punnassery425837f2017-03-21 12:58:00 -0700110 cfgService.removeListener(this);
Aaron Kruglikov309068e2017-03-17 15:25:54 -0700111 log.info("Stopped");
112 }
113
114 @Override
115 public boolean isRelevant(DynamicConfigEvent event) {
Sithara Punnassery0b51d432017-03-28 01:09:28 -0700116 String resId = ResourceIdParser.parseResId(event.subject());
117 String refId = ResourceIdParser.parseResId(defParent);
118 return (resId.substring(0, (refId.length() - 1)).compareTo(refId) == 0);
Aaron Kruglikov309068e2017-03-17 15:25:54 -0700119 }
Sithara Punnassery425837f2017-03-21 12:58:00 -0700120
121 public boolean isMaster(DeviceId deviceId) {
Thomas Vachuska59d24eb2017-03-22 10:57:34 -0700122 return mastershipService.isLocalMaster(deviceId);
Sithara Punnassery425837f2017-03-21 12:58:00 -0700123 }
124
Sithara Punnassery326b61d2017-03-24 14:15:04 -0700125
Aaron Kruglikov309068e2017-03-17 15:25:54 -0700126 @Override
127 public void event(DynamicConfigEvent event) {
Sithara Punnassery326b61d2017-03-24 14:15:04 -0700128 accumulator.add(event);
Aaron Kruglikov309068e2017-03-17 15:25:54 -0700129 }
130
131 /**
132 * Performs the delete operation corresponding to the passed event.
Thomas Vachuska59d24eb2017-03-22 10:57:34 -0700133 *
134 * @param node a relevant dataNode
135 * @param deviceId the deviceId of the device to be updated
Aaron Kruglikov309068e2017-03-17 15:25:54 -0700136 * @param resourceId the resourceId of the root of the subtree to be edited
137 * @return true if the update succeeds false otherwise
138 */
Sithara Punnassery0b51d432017-03-28 01:09:28 -0700139 private boolean configDelete(DataNode node, DeviceId deviceId, ResourceId resourceId) {
Aaron Kruglikov309068e2017-03-17 15:25:54 -0700140 return parseAndEdit(node, deviceId, resourceId, OperationType.DELETE);
141 }
142
143 /**
144 * Performs the update operation corresponding to the passed event.
Thomas Vachuska59d24eb2017-03-22 10:57:34 -0700145 *
146 * @param node a relevant dataNode
147 * @param deviceId the deviceId of the device to be updated
Aaron Kruglikov309068e2017-03-17 15:25:54 -0700148 * @param resourceId the resourceId of the root of the subtree to be edited
149 * @return true if the update succeeds false otherwise
150 */
151 private boolean configUpdate(DataNode node, DeviceId deviceId, ResourceId resourceId) {
152 return parseAndEdit(node, deviceId, resourceId, OperationType.REPLACE);
153 }
154
155 /**
156 * Parses the incoming event and pushes configuration to the effected
157 * device.
Thomas Vachuska59d24eb2017-03-22 10:57:34 -0700158 *
159 * @param node the dataNode effecting a particular device of which this node
160 * is master
161 * @param deviceId the deviceId of the device to be modified
162 * @param resourceId the resourceId of the root of the subtree to be edited
Aaron Kruglikov309068e2017-03-17 15:25:54 -0700163 * @param operationType the type of editing to be performed
164 * @return true if the operation succeeds, false otherwise
165 */
166 private boolean parseAndEdit(DataNode node, DeviceId deviceId,
167 ResourceId resourceId,
168 NetconfTranslator.OperationType operationType) {
Sithara Punnassery0b51d432017-03-28 01:09:28 -0700169 //FIXME separate edit and delete, delete can proceed with a null node
Bharat saraswalb0dbe6b2017-03-24 22:51:06 +0530170 DefaultResourceData.Builder builder = DefaultResourceData.builder();
Sithara Punnassery0b51d432017-03-28 01:09:28 -0700171 if (node != null) {
172 //add all low level nodes of devices
173 Iterator<Map.Entry<NodeKey, DataNode>> it = ((InnerNode) node)
174 .childNodes().entrySet().iterator();
175 while (it.hasNext()) {
176 DataNode n = it.next().getValue();
177 if (!n.key().schemaId().name().equals("deviceid")) {
178 builder.addDataNode(n);
179 }
Bharat saraswalb0dbe6b2017-03-24 22:51:06 +0530180 }
181 }
182 //add resouce id //TODO: check if it is correct
Aaron Kruglikov309068e2017-03-17 15:25:54 -0700183 try {
184 return netconfTranslator.editDeviceConfig(
Bharat saraswalb0dbe6b2017-03-24 22:51:06 +0530185 deviceId, builder.build(), operationType);
Aaron Kruglikov309068e2017-03-17 15:25:54 -0700186 } catch (IOException e) {
187 e.printStackTrace();
188 return false;
189 }
190 }
191
192 /**
Sithara Punnassery425837f2017-03-21 12:58:00 -0700193 * Retrieves device id from Data node.
194 *
195 * @param node the node associated with the event
Aaron Kruglikov309068e2017-03-17 15:25:54 -0700196 * @return the deviceId of the effected device
197 */
Sithara Punnasserybda82502017-03-22 19:08:19 -0700198 @Beta
Sithara Punnassery425837f2017-03-21 12:58:00 -0700199 public DeviceId getDeviceId(DataNode node) {
200 String[] temp;
201 String ip, port;
202 if (node.type() == DataNode.Type.SINGLE_INSTANCE_LEAF_VALUE_NODE) {
203 temp = ((LeafNode) node).asString().split("\\:");
204 if (temp.length != 3) {
205 throw new RuntimeException(new NetconfException("Invalid device id form, cannot apply"));
206 }
207 ip = temp[1];
208 port = temp[2];
Sithara Punnasserybda82502017-03-22 19:08:19 -0700209 } else if (node.type() == DataNode.Type.MULTI_INSTANCE_NODE) {
210 ListKey key = (ListKey) node.key();
211 temp = key.keyLeafs().get(0).leafValAsString().split("\\:");
212 if (temp.length != 3) {
213 throw new RuntimeException(new NetconfException("Invalid device id form, cannot apply"));
214 }
215 ip = temp[1];
216 port = temp[2];
Sithara Punnassery425837f2017-03-21 12:58:00 -0700217 } else {
218 throw new RuntimeException(new NetconfException("Invalid device id type, cannot apply"));
219 }
220 try {
221 return DeviceId.deviceId(new URI("netconf", ip + ":" + port, (String) null));
222 } catch (URISyntaxException var4) {
223 throw new IllegalArgumentException("Unable to build deviceID for device " + ip + ":" + port, var4);
224 }
225 }
226
227 /**
228 * Inititates a Netconf connection to the device.
229 *
230 * @param deviceId of the added device
231 */
232 private void initiateConnection(DeviceId deviceId) {
233 if (controller.getNetconfDevice(deviceId) == null) {
234 try {
235 //if (this.isReachable(deviceId)) {
Thomas Vachuska59d24eb2017-03-22 10:57:34 -0700236 this.controller.connectDevice(deviceId);
Sithara Punnassery425837f2017-03-21 12:58:00 -0700237 //}
238 } catch (Exception ex) {
Thomas Vachuska59d24eb2017-03-22 10:57:34 -0700239 throw new RuntimeException(new NetconfException("Unable to connect to NETCONF device on " +
Sithara Punnassery326b61d2017-03-24 14:15:04 -0700240 deviceId, ex));
241 }
242 }
243 }
244
Sithara Punnassery0b51d432017-03-28 01:09:28 -0700245 /**
246 * Retrieves device key from Resource id.
247 *
248 * @param path associated with the event
249 * @return the deviceId of the effected device
250 */
251 @Beta
252 public ResourceId getDeviceKey(ResourceId path) {
253 String resId = ResourceIdParser.parseResId(path);
254 String[] el = resId.split(ResourceIdParser.EL_CHK);
255 if (el.length < 2) {
256 throw new RuntimeException(new NetconfException("Invalid resource id, cannot apply"));
257 }
258 if (!el[1].contains((ResourceIdParser.KEY_SEP))) {
259 throw new RuntimeException(new NetconfException("Invalid device id key, cannot apply"));
260 }
261 String[] keys = el[1].split(ResourceIdParser.KEY_CHK);
262 if (keys.length < 2) {
263 throw new RuntimeException(new NetconfException("Invalid device id key, cannot apply"));
264 }
265 String[] parts = keys[1].split(ResourceIdParser.NM_CHK);
266 if (parts.length < 3) {
267 throw new RuntimeException(new NetconfException("Invalid device id key, cannot apply"));
268 }
269 if (parts[2].split("\\:").length != 3) {
270 throw new RuntimeException(new NetconfException("Invalid device id form, cannot apply"));
271 }
272 return (new ResourceId.Builder()
273 .addBranchPointSchema(el[0].split(ResourceIdParser.NM_CHK)[0],
274 el[0].split(ResourceIdParser.NM_CHK)[1])
275 .addBranchPointSchema(keys[0].split(ResourceIdParser.NM_CHK)[0],
276 keys[0].split(ResourceIdParser.NM_CHK)[1])
277 .addKeyLeaf(parts[0], parts[1], parts[2])
278 .build());
279
280 }
281
282 /**
283 * Retrieves device id from Resource id.
284 *
285 * @param path associated with the event
286 * @return the deviceId of the effected device
287 */
288 @Beta
289 public DeviceId getDeviceId(ResourceId path) {
290 String resId = ResourceIdParser.parseResId(path);
291 String[] el = resId.split(ResourceIdParser.EL_CHK);
292 if (el.length < 2) {
293 throw new RuntimeException(new NetconfException("Invalid resource id, cannot apply"));
294 }
295 if (!el[2].contains((ResourceIdParser.KEY_SEP))) {
296 throw new RuntimeException(new NetconfException("Invalid device id key, cannot apply"));
297 }
298 String[] keys = el[2].split(ResourceIdParser.KEY_CHK);
299 if (keys.length < 2) {
300 throw new RuntimeException(new NetconfException("Invalid device id key, cannot apply"));
301 }
302 String[] parts = keys[1].split(ResourceIdParser.NM_CHK);
303 if (parts.length < 3) {
304 throw new RuntimeException(new NetconfException("Invalid device id key, cannot apply"));
305 }
306 String[] temp = parts[2].split("\\:");
307 String ip, port;
308 if (temp.length != 3) {
309 throw new RuntimeException(new NetconfException("Invalid device id form, cannot apply"));
310 }
311 ip = temp[1];
312 port = temp[2];
313 try {
314 return DeviceId.deviceId(new URI("netconf", ip + ":" + port, (String) null));
315 } catch (URISyntaxException ex) {
316 throw new IllegalArgumentException("Unable to build deviceID for device " + ip + ":" + port, ex);
317 }
318 }
319
Sithara Punnassery326b61d2017-03-24 14:15:04 -0700320 /* Accumulates events to allow processing after a desired number of events were accumulated.
321 */
322 private class InternalEventAccummulator extends AbstractAccumulator<DynamicConfigEvent> {
323 protected InternalEventAccummulator() {
324 super(new Timer("dyncfgevt-timer"), MAX_EVENTS, MAX_BATCH_MS, MAX_IDLE_MS);
325 }
326 @Override
327 public void processItems(List<DynamicConfigEvent> events) {
Sithara Punnassery0b51d432017-03-28 01:09:28 -0700328 Map<ResourceId, List<DynamicConfigEvent>> evtmap = new LinkedHashMap<>();
329 Boolean handleEx = false; //hack
330 ResourceId exId = null; //hack
Sithara Punnassery326b61d2017-03-24 14:15:04 -0700331 for (DynamicConfigEvent e : events) {
Sithara Punnassery0b51d432017-03-28 01:09:28 -0700332 checkNotNull(e, "process:Event cannot be null");
333 ResourceId cur = e.subject();
334 //TODO remove this hack after store ordering is fixed
335 if (ResourceIdParser.parseResId(cur).compareTo(EXCPATH) == 0) {
336 if (e.type() == DynamicConfigEvent.Type.NODE_ADDED) {
337 log.info("Received an event for the xempted path ADD {}", e.type());
338 handleEx = true;
339 exId = cur;
340 } else {
341 log.warn("Received an event for the xempted path {}, NOT handled!!", e.type());
342 }
343 } else { //actual code
344 ResourceId key = getDeviceKey(e.subject());
345 List<DynamicConfigEvent> el = evtmap.get(key);
346 if (el == null) {
347 el = new ArrayList<>();
348 }
349 el.add(e);
350 evtmap.put(key, el);
Sithara Punnassery326b61d2017-03-24 14:15:04 -0700351 }
Sithara Punnassery425837f2017-03-21 12:58:00 -0700352 }
Sithara Punnassery0b51d432017-03-28 01:09:28 -0700353 evtmap.forEach((k, v) -> {
354 log.info("Current deviceKey {}", k);
355 DeviceId curDevice = getDeviceId(k);
356 if (!isMaster(curDevice)) {
357 log.info("NetConfListener: not master, ignoring config for device {}", k);
358 return;
359 }
360 initiateConnection(curDevice);
361 for (DynamicConfigEvent curEvt : v) {
362 switch (curEvt.type()) {
363 case NODE_ADDED:
364 case NODE_UPDATED:
365 case NODE_REPLACED:
366 Filter filt = new Filter();
367 DataNode node = cfgService.readNode(k, filt);
368 configUpdate(node, curDevice, k);
369 break;
370 case NODE_DELETED:
371 log.info("NetConfListener: RXD DELETE EVT for {}", k);
372 configDelete(null, curDevice, k); //TODO curEvt.subject());
373 break;
374 case UNKNOWN_OPRN:
375 default:
376 log.warn("NetConfListener: unknown event: {}", curEvt.type());
377 break;
378 }
379 }
380 });
381 //TODO remove this hack after store ordering is fixed
382 if (handleEx) {
383 log.info("Handling exception path {}", exId);
384 DeviceId exDevice = getDeviceId(exId);
385 if (!isMaster(exDevice)) {
386 log.info("NetConfListener: not master, ignoring config for expath {}", exId);
387 return;
388 }
389 initiateConnection(exDevice);
390 Filter filt = new Filter();
391 DataNode exnode = cfgService.readNode(exId, filt);
392 configUpdate(exnode, exDevice, exId);
393 } //end of hack
Sithara Punnassery425837f2017-03-21 12:58:00 -0700394 }
Aaron Kruglikov309068e2017-03-17 15:25:54 -0700395 }
396}