2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2018 AT&T Intellectual Property. All rights
7 * ================================================================================
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 * ============LICENSE_END=========================================================
22 package org.onap.ccsdk.sli.northbound.daeximoffsitebackup;
24 import com.google.common.util.concurrent.FluentFuture;
25 import com.google.common.util.concurrent.Futures;
26 import com.google.common.util.concurrent.ListenableFuture;
28 import java.io.FileInputStream;
29 import java.io.FileOutputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.OutputStream;
33 import java.net.HttpURLConnection;
35 import java.time.Instant;
36 import java.time.ZoneId;
37 import java.time.format.DateTimeFormatter;
38 import java.util.Arrays;
39 import java.util.Base64;
40 import java.util.Collection;
41 import java.util.List;
42 import java.util.Properties;
43 import java.util.concurrent.ExecutionException;
44 import java.util.concurrent.ExecutorService;
45 import java.util.concurrent.Executors;
46 import java.util.zip.ZipEntry;
47 import java.util.zip.ZipInputStream;
48 import java.util.zip.ZipOutputStream;
49 import javax.annotation.Nonnull;
50 import org.eclipse.jdt.annotation.NonNull;
51 import org.onap.ccsdk.sli.core.utils.common.EnvProperties;
52 import org.opendaylight.mdsal.binding.api.DataBroker;
53 import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
54 import org.opendaylight.mdsal.binding.api.RpcProviderService;
55 import org.opendaylight.mdsal.binding.api.WriteTransaction;
56 import org.opendaylight.mdsal.common.api.CommitInfo;
57 import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.BackupDataInput;
58 import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.BackupDataOutput;
59 import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.BackupDataOutputBuilder;
60 import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.DaeximOffsiteBackupService;
61 import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.RetrieveDataInput;
62 import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.RetrieveDataOutput;
63 import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.RetrieveDataOutputBuilder;
64 import org.opendaylight.yangtools.concepts.ObjectRegistration;
65 import org.opendaylight.yangtools.yang.common.RpcResult;
66 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
70 public class DaeximOffsiteBackupProvider implements AutoCloseable, DaeximOffsiteBackupService, DataTreeChangeListener {
71 private static final Logger LOG = LoggerFactory.getLogger(DaeximOffsiteBackupProvider.class);
73 private static String DAEXIM_DIR;
74 private static String CREDENTIALS;
75 private static String NEXUS_URL;
76 private static String POD_NAME;
77 private static String OPERATIONAL_JSON;
78 private static String MODELS_JSON;
79 private static String CONFIG_JSON;
80 private static String PROPERTIES_FILE = System.getenv("SDNC_CONFIG_DIR") + "/daexim-offsite-backup.properties";
82 private static final String BACKUP_ARCHIVE = "odl_backup.zip";
83 private static final String appName = "daexim-offsite-backup";
85 private final ExecutorService executor;
86 private Properties properties;
87 private DataBroker dataBroker;
88 private RpcProviderService rpcRegistry;
89 private ObjectRegistration<DaeximOffsiteBackupService> rpcRegistration;
91 public DaeximOffsiteBackupProvider(DataBroker dataBroker,
92 RpcProviderService rpcProviderRegistry) {
93 LOG.info("Creating provider for " + appName);
94 this.executor = Executors.newFixedThreadPool(1);
95 this.dataBroker = dataBroker;
96 this.rpcRegistry = rpcProviderRegistry;
100 public void initialize() {
101 LOG.info("Initializing provider for " + appName);
102 // Create the top level containers
105 DaeximOffsiteBackupUtil.loadProperties();
106 } catch (Exception e) {
107 LOG.error("Caught Exception while trying to load properties file", e);
109 rpcRegistration = rpcRegistry.registerRpcImplementation(DaeximOffsiteBackupService.class, this);
110 LOG.info("Initialization complete for " + appName);
113 private void loadProperties() {
114 LOG.info("Loading properties from " + PROPERTIES_FILE);
115 if(properties == null)
116 properties = new EnvProperties();
117 File propertiesFile = new File(PROPERTIES_FILE);
118 if(!propertiesFile.exists()) {
119 LOG.warn("Properties file (" + PROPERTIES_FILE + ") not found. Using default properties.");
120 properties.put("daeximDirectory", "/opt/opendaylight/current/daexim/");
121 properties.put("credentials", "admin:enc:YWRtaW4xMjM=");
122 properties.put("nexusUrl", "http://localhost:8081/nexus/content/repositories/");
123 properties.put("podName", "UNKNOWN_ODL");
124 properties.put("file.operational", "odl_backup_operational.json");
125 properties.put("file.models", "odl_backup_models.json");
126 properties.put("file.config", "odl_backup_config.json");
129 FileInputStream fileInputStream;
131 fileInputStream = new FileInputStream(propertiesFile);
132 properties.load(fileInputStream);
133 fileInputStream.close();
134 LOG.info(properties.size() + " properties loaded.");
135 LOG.info("daeximDirectory: " + properties.getProperty("daeximDirectory"));
136 LOG.info("nexusUrl: " + properties.getProperty("nexusUrl"));
137 LOG.info("podName: " + properties.getProperty("podName"));
138 LOG.info("file.operational: " + properties.getProperty("file.operational"));
139 LOG.info("file.models: " + properties.getProperty("file.models"));
140 LOG.info("file.config: " + properties.getProperty("file.config"));
141 } catch(IOException e) {
142 LOG.error("Error loading properties.", e);
146 private void applyProperties() {
147 LOG.info("Applying properties...");
148 if(POD_NAME == null || POD_NAME.isEmpty()) {
149 LOG.warn("MY_POD_NAME environment variable not set. Using value from properties.");
150 POD_NAME = properties.getProperty("podName");
152 DAEXIM_DIR = properties.getProperty("daeximDirectory");
153 NEXUS_URL = properties.getProperty("nexusUrl");
155 OPERATIONAL_JSON = properties.getProperty("file.operational");
156 MODELS_JSON = properties.getProperty("file.models");
157 CONFIG_JSON = properties.getProperty("file.config");
159 if(!properties.getProperty("credentials").contains(":")) { //Entire thing is encoded
160 CREDENTIALS = new String(Base64.getDecoder().decode(properties.getProperty("credentials")));
163 String[] credentials = properties.getProperty("credentials").split(":", 2);
164 if(credentials[1].startsWith("enc:")) { // Password is encoded
165 credentials[1] = new String(Base64.getDecoder().decode(credentials[1].split(":")[1]));
167 CREDENTIALS = credentials[0] + ":" + credentials[1];
169 LOG.info("Properties applied.");
172 private void createContainers() {
173 final WriteTransaction t = dataBroker.newReadWriteTransaction();
175 FluentFuture<? extends @NonNull CommitInfo> checkedFuture = t.commit();
177 LOG.info("Create Containers succeeded!: ");
178 } catch (InterruptedException | ExecutionException e) {
179 LOG.error("Create Containers Failed: " + e);
180 LOG.error("context", e);
184 protected void initializeChild() {
189 public void close() throws Exception {
190 LOG.info("Closing provider for " + appName);
192 rpcRegistration.close();
193 LOG.info("Successfully closed provider for " + appName);
197 public void onDataTreeChanged(@Nonnull Collection changes) {
202 public ListenableFuture<RpcResult<BackupDataOutput>> backupData(BackupDataInput input) {
203 final String SVC_OPERATION = "backup-data";
204 LOG.info(appName + ":" + SVC_OPERATION + " called.");
207 String message = "Data sent to offsite location.";
212 LOG.info("Pod Name: " + POD_NAME);
213 Instant timestamp = Instant.now();
214 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HH").withZone(ZoneId.of("GMT"));
215 String timestampedArchive = DAEXIM_DIR + POD_NAME + '-' + formatter.format(timestamp) + "-" + BACKUP_ARCHIVE;
217 LOG.info("Creating archive...");
218 List<String> daeximFiles = Arrays.asList(DAEXIM_DIR + OPERATIONAL_JSON,DAEXIM_DIR + MODELS_JSON, DAEXIM_DIR + CONFIG_JSON);
219 createArchive(daeximFiles, timestampedArchive);
220 LOG.info("Archive created.");
221 } catch(IOException e) {
222 LOG.error("Error creating archive " + timestampedArchive);
223 LOG.error(e.getMessage());
225 message = "Archive creation failed.";
226 return buildBackupDataFuture(statusCode, message);
230 LOG.info("Sending archive to Nexus server: " + NEXUS_URL);
231 statusCode = Integer.toString(putArchive(timestampedArchive));
232 LOG.info("Archive sent to Nexus.");
233 } catch(IOException e) {
234 LOG.error("Nexus creation failed.", e);
236 message = "Nexus creation failed.";
239 File archive = new File(timestampedArchive);
240 if(archive.exists()) {
241 archive.delete(); // Save some space on the ODL, keep them from piling up
244 LOG.info("Sending Response statusCode=" + statusCode+ " message=" + message + " | " + SVC_OPERATION);
245 return buildBackupDataFuture(statusCode, message);
249 public ListenableFuture<RpcResult<RetrieveDataOutput>> retrieveData(RetrieveDataInput input) {
250 final String SVC_OPERATION = "retrieve-data";
251 LOG.info(appName + ":" + SVC_OPERATION + " called.");
253 String statusCode = "200";
254 String message = "Data retrieved from offsite location.";
259 LOG.info("Pod Name: " + POD_NAME);
260 String archiveIdentifier = POD_NAME + '-' + input.getTimestamp();
261 String timestampedArchive = DAEXIM_DIR + archiveIdentifier + "-" + BACKUP_ARCHIVE;
262 LOG.info("Trying to retrieve " + timestampedArchive);
264 statusCode = Integer.toString(getArchive(archiveIdentifier));
265 } catch(IOException e) {
266 LOG.error("Could not retrieve archive.", e);
268 message = "Could not retrieve archive.";
269 return retrieveDataOutputRpcResult(statusCode, message);
271 LOG.info("Retrieved archive.");
273 LOG.info("Extracting archive...");
275 extractArchive(DAEXIM_DIR + "-" + BACKUP_ARCHIVE);
276 } catch(IOException e) {
277 LOG.error("Could not extract archive.", e);
279 message = "Could not extract archive.";
280 return retrieveDataOutputRpcResult(statusCode, message);
282 LOG.info("Archive extracted.");
284 return retrieveDataOutputRpcResult(statusCode, message);
287 private boolean exportExists(List<String> daeximFiles) {
289 for(String f : daeximFiles) {
298 private void createArchive(List<String> daeximFiles, String timestampedArchive) throws IOException {
299 if(!exportExists(daeximFiles)) {
300 LOG.error("Daexim exports do not exist.");
301 throw new IOException();
303 LOG.info("Creating " + timestampedArchive);
304 FileOutputStream fileOutputStream = new FileOutputStream(timestampedArchive);
305 ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
307 FileInputStream fileInputStream;
311 for(String source : daeximFiles) {
312 LOG.info("Adding " + source + " to archive...");
313 targetZipFile = new File(source);
314 fileInputStream = new FileInputStream(targetZipFile);
315 zipEntry = new ZipEntry(targetZipFile.getName());
316 zipOutputStream.putNextEntry(zipEntry);
317 bytes = new byte[1024];
319 while((length = fileInputStream.read(bytes)) >= 0) {
320 zipOutputStream.write(bytes, 0, length);
322 fileInputStream.close();
325 zipOutputStream.close();
326 fileOutputStream.close();
329 private void extractArchive(String timestampedArchive) throws IOException {
330 byte[] bytes = new byte[1024];
331 ZipInputStream zis = new ZipInputStream(new FileInputStream(timestampedArchive));
332 ZipEntry zipEntry = zis.getNextEntry();
333 while(zipEntry != null){
334 String fileName = zipEntry.getName();
335 File newFile = new File(DAEXIM_DIR + fileName);
336 FileOutputStream fos = new FileOutputStream(newFile);
338 while ((len = zis.read(bytes)) > 0) {
339 fos.write(bytes, 0, len);
342 LOG.info(zipEntry.getName() + " extracted.");
343 zipEntry = zis.getNextEntry();
347 LOG.info(timestampedArchive + " extracted successfully.");
350 private int putArchive(String timestampedArchive) throws IOException {
351 File archive = new File(timestampedArchive);
352 HttpURLConnection connection = getNexusConnection(archive.getName());
353 connection.setRequestProperty("Content-Length", Long.toString(archive.length()));
354 connection.setRequestMethod("PUT");
355 connection.setDoOutput(true);
357 FileInputStream fileInputStream = new FileInputStream(archive);
358 OutputStream outputStream = connection.getOutputStream();
360 byte[] bytes = new byte[1024];
362 while((length = fileInputStream.read(bytes)) >= 0) {
363 outputStream.write(bytes, 0, length);
366 outputStream.flush();
367 outputStream.close();
368 fileInputStream.close();
369 connection.disconnect();
371 LOG.info("Status: " + connection.getResponseCode());
372 LOG.info("Message: " + connection.getResponseMessage());
373 return connection.getResponseCode();
376 private HttpURLConnection getNexusConnection(String archive) throws IOException {
377 URL url = new URL(NEXUS_URL + archive);
378 String auth = "Basic " + javax.xml.bind.DatatypeConverter.printBase64Binary(CREDENTIALS.getBytes());
379 HttpURLConnection connection = (HttpURLConnection) url.openConnection();
380 connection.addRequestProperty("Authorization", auth);
381 connection.setRequestProperty("Connection", "keep-alive");
382 connection.setRequestProperty("Proxy-Connection", "keep-alive");
386 private int getArchive(String archiveIdentifier) throws IOException {
387 File archive = new File(DAEXIM_DIR + "backup.zip");
388 if(archive.exists()) {
389 LOG.info("Recently retrieved archive found. Removing old archive...");
391 LOG.info("Archive removed.");
393 HttpURLConnection connection = getNexusConnection( archiveIdentifier + "-" + BACKUP_ARCHIVE);
394 connection.setRequestMethod("GET");
395 connection.setDoInput(true);
397 InputStream connectionInputStream = connection.getInputStream();
398 FileOutputStream fileOutputStream = new FileOutputStream(archive);
400 byte[] bytes = new byte[1024];
402 while((length = connectionInputStream.read(bytes)) >= 0) { // while connection has bytes
403 fileOutputStream.write(bytes, 0, length); // write to archive
405 connection.disconnect();
407 LOG.info("Status: " + connection.getResponseCode());
408 LOG.info("Message: " + connection.getResponseMessage());
409 LOG.info(archive.getName() + " successfully created.");
410 return connection.getResponseCode();
413 private ListenableFuture<RpcResult<BackupDataOutput>> buildBackupDataFuture(String statusCode, String message) {
414 BackupDataOutputBuilder outputBuilder = new BackupDataOutputBuilder();
415 outputBuilder.setStatus(statusCode);
416 outputBuilder.setMessage(message);
417 RpcResult<BackupDataOutput> rpcResult = RpcResultBuilder.<BackupDataOutput> status(true).withResult(outputBuilder.build()).build();
418 return Futures.immediateFuture(rpcResult);
421 private ListenableFuture<RpcResult<RetrieveDataOutput>> retrieveDataOutputRpcResult(String status, String message) {
422 RetrieveDataOutputBuilder outputBuilder = new RetrieveDataOutputBuilder();
423 outputBuilder.setStatus(status);
424 outputBuilder.setMessage(message);
425 RpcResult<RetrieveDataOutput> rpcResult = RpcResultBuilder.<RetrieveDataOutput> status(true).withResult(outputBuilder.build()).build();
426 return Futures.immediateFuture(rpcResult);