Network topology and inventory init
[transportpce.git] / inventory / src / main / java / org / opendaylight / transportpce / inventory / job / PeriodicDeviceBackupJob.java
1 /*
2  * Copyright © 2017 AT&T and others. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.transportpce.inventory.job;
10
11 import com.fasterxml.jackson.core.JsonProcessingException;
12 import com.fasterxml.jackson.databind.ObjectMapper;
13 import com.google.common.base.Strings;
14 import com.google.common.io.Files;
15
16 import java.io.File;
17 import java.io.IOException;
18 import java.nio.charset.StandardCharsets;
19 import java.text.SimpleDateFormat;
20 import java.util.Date;
21 import java.util.Optional;
22 import java.util.concurrent.ExecutionException;
23
24 import org.apache.karaf.scheduler.Job;
25 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
26 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
27 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
28 import org.opendaylight.mdsal.dom.api.DOMSchemaService;
29 import org.opendaylight.transportpce.common.InstanceIdentifiers;
30 import org.opendaylight.transportpce.common.Timeouts;
31 import org.opendaylight.transportpce.common.device.DeviceTransactionManager;
32 import org.opendaylight.yang.gen.v1.http.org.openroadm.device.rev170206.org.openroadm.device.container.OrgOpenroadmDevice;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
34 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
35 import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
36 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
37 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 /**
42  * Class which periodically backups the device into a file implements {@link Job}
43  * interface which is automatically registered via whitboard pattern into karaf
44  *  environment. This job will persist an {@link OrgOpenroadmDevice} capable with
45  *  the models provided in transportpce-models
46  */
47 public class PeriodicDeviceBackupJob implements Runnable {
48
49     private static final String TIMESTAMP_FORMAT = "yyyy-MM-dd_HH-mm-ss-SSS";
50     private static final String ORG_OPENROADM_DEVICE = "org-openroadm-device";
51     private static final Logger LOG = LoggerFactory.getLogger(PeriodicDeviceBackupJob.class);
52
53     private final DataBroker dataBroker;
54     private final DeviceTransactionManager deviceTransactionManager;
55
56     private final SchemaContext schemaContext;
57     private final SimpleDateFormat filenameFormatter;
58
59     /**
60      * Folder property, where the file is placed.
61      */
62     private String folder;
63     /**
64      * Prefix of device file.
65      */
66     private String filePrefix;
67
68     /**
69      * Constructor with injected fields.
70      *
71      * @param dataBroker the datatabroker
72      * @param domSchemaService the DOM schema service
73      * @param deviceTransactionManager the device transaction manager
74      */
75     public PeriodicDeviceBackupJob(DataBroker dataBroker, DOMSchemaService domSchemaService,
76             DeviceTransactionManager deviceTransactionManager) {
77         this.dataBroker = dataBroker;
78         this.schemaContext = domSchemaService.getGlobalContext();
79         this.deviceTransactionManager = deviceTransactionManager;
80         this.filenameFormatter = new SimpleDateFormat(TIMESTAMP_FORMAT);
81     }
82
83     @Override
84     public void run() {
85         LOG.info("Running periodical device backup into {}", folder);
86         try {
87             backupAllDevices();
88         } catch (InterruptedException | ExecutionException e) {
89             LOG.warn("Unable to read netconf topology from the operational datastore", e);
90         }
91     }
92
93     /**
94      * Gets the folder.
95      *
96      * @return the folder
97      */
98     public String getFolder() {
99         return folder;
100     }
101
102     /**
103      * Sets the folder.
104      *
105      * @param folder the folder to set
106      */
107     public void setFolder(String folder) {
108         this.folder = folder;
109     }
110
111     /**
112      * Gets the filePrefix.
113      *
114      * @return the filePrefix
115      */
116     public String getFilePrefix() {
117         return filePrefix;
118     }
119
120     /**
121      * Sets the filePrefix.
122      *
123      * @param filePrefix the filePrefix to set
124      */
125     public void setFilePrefix(String filePrefix) {
126         this.filePrefix = filePrefix;
127     }
128
129     /**
130      * Stores all devices into files.
131      *
132      * @throws InterruptedException interrupted exception
133      * @throws ExecutionException execution exception
134      */
135     private void backupAllDevices() throws InterruptedException, ExecutionException {
136         ReadOnlyTransaction newReadOnlyTransaction = dataBroker.newReadOnlyTransaction();
137         Optional<Topology> topology = newReadOnlyTransaction
138                 .read(LogicalDatastoreType.OPERATIONAL, InstanceIdentifiers.NETCONF_TOPOLOGY_II).get().toJavaUtil();
139         if (!topology.isPresent()) {
140             LOG.warn("Netconf topology was not found in datastore");
141             return;
142         }
143
144         for (Node node : topology.get().getNode()) {
145             String nodeId = getNodeId(node);
146             if (Strings.isNullOrEmpty(nodeId)) {
147                 LOG.debug("{} node is not a roadm device");
148                 continue;
149             }
150             storeRoadmDevice(nodeId);
151         }
152     }
153
154     /**
155      * Stores a single ROADM device into a file.
156      *
157      * @param nodeId the node ID
158      */
159     private void storeRoadmDevice(String nodeId) {
160         InstanceIdentifier<OrgOpenroadmDevice> deviceIi = InstanceIdentifier.create(OrgOpenroadmDevice.class);
161         Optional<OrgOpenroadmDevice> deviceObject =
162                 deviceTransactionManager.getDataFromDevice(nodeId, LogicalDatastoreType.OPERATIONAL, deviceIi,
163                         Timeouts.DEVICE_READ_TIMEOUT, Timeouts.DEVICE_READ_TIMEOUT_UNIT);
164         if (!deviceObject.isPresent()) {
165             LOG.warn("Device object {} is not present.", nodeId);
166             return;
167         }
168
169         // TODO: Serialization should be done via XMLDataObjectConverter when devices use the same model as
170         // this project
171         /*
172          * XMLDataObjectConverter createWithSchemaContext =
173          * XMLDataObjectConverter.createWithSchemaContext(schemaContext, nodeSerializer); Writer
174          * writerFromDataObject = createWithSchemaContext.writerFromDataObject(deviceObject.get(),
175          * OrgOpenroadmDevice.class, createWithSchemaContext.dataContainer());
176          */
177         StringBuilder serializedDevice = new StringBuilder();
178         try {
179             serializedDevice.append(new ObjectMapper().writeValueAsString(deviceObject.get()));
180         } catch (JsonProcessingException e1) {
181             LOG.error(e1.getMessage(), e1);
182         }
183
184         String prepareFileName = prepareFileName(nodeId);
185         File parent = new File(folder);
186         if (!parent.exists() && !parent.isDirectory() && !parent.mkdirs()) {
187             LOG.error("Could not create empty directory {}", folder);
188             throw new IllegalStateException(String.format("Could not create empty directory %s", folder));
189         }
190
191         File file = new File(parent, filePrefix.concat(prepareFileName));
192         if (file.exists()) {
193             throw new IllegalStateException(String.format("The file %s already exists", file));
194         }
195         try {
196             Files.asCharSink(file, StandardCharsets.UTF_8).write(serializedDevice.toString());
197         } catch (IOException e) {
198             LOG.error("Could not write device backup into file {}", file);
199         }
200     }
201
202     private String prepareFileName(String nodeId) {
203         String format = filenameFormatter.format(new Date());
204         StringBuilder sb = new StringBuilder(format);
205         sb.append("__").append(nodeId).append("__").append(".xml");
206         return sb.toString();
207     }
208
209     /**
210      * If the {@link Node} in the {@link Topology} is a {@link NetconfNode}, has
211      * capabilities of {@link PeriodicDeviceBackupJob#ORG_OPENROADM_DEVICE} and the
212      * key is not null returns the identifier of {@link OrgOpenroadmDevice} node.
213      *
214      * @param node inside the {@link Topology}
215      * @return node key
216      */
217     private static String getNodeId(Node node) {
218         if (node == null) {
219             LOG.trace("The node is null");
220             return "";
221         }
222         NetconfNode netconfNode = node.getAugmentation(NetconfNode.class);
223         if (netconfNode == null) {
224             LOG.trace("The node {} has not properties of NetconfNode", node);
225             return "";
226         }
227         if (netconfNode.getAvailableCapabilities() == null
228                 || netconfNode.getAvailableCapabilities().getAvailableCapability() == null) {
229             LOG.trace("No available capabilities");
230             return "";
231         }
232         long count = netconfNode.getAvailableCapabilities().getAvailableCapability().stream()
233                 .filter(cp -> cp.getCapability() != null && cp.getCapability().contains(ORG_OPENROADM_DEVICE)).count();
234         if (count < 1) {
235             LOG.trace("The node {} has not capabilities of OpenROADMDevice", node);
236             return "";
237         }
238         if (node.getKey() == null || node.getKey().getNodeId() == null) {
239             LOG.trace("Node {} has invalid key", node);
240             return "";
241         }
242         if ("controller-config".equalsIgnoreCase(node.getKey().getNodeId().getValue())) {
243             LOG.info("Got controller-config instead of roadm-node");
244             return "";
245         }
246         return node.getKey().getNodeId().getValue();
247     }
248
249 }