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