3d32c7d747e045b1d13ab1796cf46ab3e2fc3230
[ccsdk/sli.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  * openECOMP : SDN-C
4  * ================================================================================
5  * Copyright (C) 2018 AT&T Intellectual Property. All rights
6  *                      reserved.
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
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
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=========================================================
20 */
21
22 package org.onap.ccsdk.sli.northbound.daeximoffsitebackup;
23
24 import com.google.common.util.concurrent.FluentFuture;
25 import com.google.common.util.concurrent.Futures;
26 import com.google.common.util.concurrent.ListenableFuture;
27 import java.io.File;
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;
34 import java.net.URL;
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;
69
70 public class DaeximOffsiteBackupProvider implements AutoCloseable, DaeximOffsiteBackupService, DataTreeChangeListener {
71     private static final Logger LOG = LoggerFactory.getLogger(DaeximOffsiteBackupProvider.class);
72
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";
81
82     private static final String BACKUP_ARCHIVE = "odl_backup.zip";
83     private static final String appName = "daexim-offsite-backup";
84
85     private final ExecutorService executor;
86     private Properties properties;
87     private DataBroker dataBroker;
88     private RpcProviderService rpcRegistry;
89     private ObjectRegistration<DaeximOffsiteBackupService> rpcRegistration;
90
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;
97         initialize();
98     }
99
100     public void initialize() {
101         LOG.info("Initializing provider for " + appName);
102         // Create the top level containers
103         createContainers();
104         try {
105             DaeximOffsiteBackupUtil.loadProperties();
106         } catch (Exception e) {
107             LOG.error("Caught Exception while trying to load properties file", e);
108         }
109         rpcRegistration = rpcRegistry.registerRpcImplementation(DaeximOffsiteBackupService.class, this);
110         LOG.info("Initialization complete for " + appName);
111     }
112
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");
127             return;
128         }
129         FileInputStream fileInputStream;
130         try {
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);
143         }
144     }
145
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");
151         }
152         DAEXIM_DIR =  properties.getProperty("daeximDirectory");
153         NEXUS_URL = properties.getProperty("nexusUrl");
154
155         OPERATIONAL_JSON = properties.getProperty("file.operational");
156         MODELS_JSON = properties.getProperty("file.models");
157         CONFIG_JSON = properties.getProperty("file.config");
158
159         if(!properties.getProperty("credentials").contains(":")) { //Entire thing is encoded
160             CREDENTIALS = new String(Base64.getDecoder().decode(properties.getProperty("credentials")));
161         }
162         else {
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]));
166             }
167             CREDENTIALS = credentials[0] + ":" + credentials[1];
168         }
169         LOG.info("Properties applied.");
170     }
171
172     private void createContainers() {
173         final WriteTransaction t = dataBroker.newReadWriteTransaction();
174         try {
175             FluentFuture<? extends @NonNull CommitInfo> checkedFuture = t.commit();
176             checkedFuture.get();
177             LOG.info("Create Containers succeeded!: ");
178         } catch (InterruptedException | ExecutionException e) {
179             LOG.error("Create Containers Failed: " + e);
180             LOG.error("context", e);
181         }
182     }
183
184     protected void initializeChild() {
185
186     }
187
188     @Override
189     public void close() throws Exception {
190         LOG.info("Closing provider for " + appName);
191         executor.shutdown();
192         rpcRegistration.close();
193         LOG.info("Successfully closed provider for " + appName);
194     }
195
196     @Override
197     public void onDataTreeChanged(@Nonnull Collection changes) {
198
199     }
200
201     @Override
202     public ListenableFuture<RpcResult<BackupDataOutput>> backupData(BackupDataInput input) {
203         final String SVC_OPERATION = "backup-data";
204         LOG.info(appName + ":" + SVC_OPERATION + " called.");
205
206         String statusCode;
207         String message = "Data sent to offsite location.";
208
209         loadProperties();
210         applyProperties();
211
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;
216         try {
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());
224             statusCode = "500";
225             message = "Archive creation failed.";
226             return buildBackupDataFuture(statusCode, message);
227         }
228
229         try{
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);
235             statusCode = "500";
236             message = "Nexus creation failed.";
237         }
238
239         File archive = new File(timestampedArchive);
240         if(archive.exists()) {
241             archive.delete(); // Save some space on the ODL, keep them from piling up
242         }
243
244         LOG.info("Sending Response statusCode=" + statusCode+ " message=" + message + " | " + SVC_OPERATION);
245         return buildBackupDataFuture(statusCode, message);
246     }
247
248     @Override
249     public ListenableFuture<RpcResult<RetrieveDataOutput>> retrieveData(RetrieveDataInput input) {
250         final String SVC_OPERATION = "retrieve-data";
251         LOG.info(appName + ":" + SVC_OPERATION + " called.");
252
253         String statusCode = "200";
254         String message = "Data retrieved from offsite location.";
255
256         loadProperties();
257         applyProperties();
258
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);
263         try {
264             statusCode = Integer.toString(getArchive(archiveIdentifier));
265         } catch(IOException e) {
266             LOG.error("Could not retrieve archive.", e);
267             statusCode = "500";
268             message = "Could not retrieve archive.";
269             return retrieveDataOutputRpcResult(statusCode, message);
270         }
271         LOG.info("Retrieved archive.");
272
273         LOG.info("Extracting archive...");
274         try {
275             extractArchive(DAEXIM_DIR + "-" + BACKUP_ARCHIVE);
276         } catch(IOException e) {
277             LOG.error("Could not extract archive.", e);
278             statusCode = "500";
279             message = "Could not extract archive.";
280             return retrieveDataOutputRpcResult(statusCode, message);
281         }
282         LOG.info("Archive extracted.");
283
284         return retrieveDataOutputRpcResult(statusCode, message);
285     }
286
287     private boolean exportExists(List<String> daeximFiles) {
288         File file;
289         for(String f : daeximFiles) {
290             file = new File(f);
291             if(!file.exists()) {
292                 return false;
293             }
294         }
295         return true;
296     }
297
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();
302         }
303         LOG.info("Creating " + timestampedArchive);
304         FileOutputStream fileOutputStream = new FileOutputStream(timestampedArchive);
305         ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
306         File targetZipFile;
307         FileInputStream fileInputStream;
308         ZipEntry zipEntry;
309         byte[] bytes;
310         int length;
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];
318
319             while((length = fileInputStream.read(bytes)) >= 0) {
320                 zipOutputStream.write(bytes, 0, length);
321             }
322             fileInputStream.close();
323         }
324
325         zipOutputStream.close();
326         fileOutputStream.close();
327     }
328
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);
337             int len;
338             while ((len = zis.read(bytes)) > 0) {
339                 fos.write(bytes, 0, len);
340             }
341             fos.close();
342             LOG.info(zipEntry.getName() + " extracted.");
343             zipEntry = zis.getNextEntry();
344         }
345         zis.closeEntry();
346         zis.close();
347         LOG.info(timestampedArchive + " extracted successfully.");
348     }
349
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);
356
357         FileInputStream fileInputStream = new FileInputStream(archive);
358         OutputStream outputStream = connection.getOutputStream();
359
360         byte[] bytes = new byte[1024];
361         int length;
362         while((length = fileInputStream.read(bytes)) >= 0) {
363             outputStream.write(bytes, 0, length);
364         }
365
366         outputStream.flush();
367         outputStream.close();
368         fileInputStream.close();
369         connection.disconnect();
370
371         LOG.info("Status: " + connection.getResponseCode());
372         LOG.info("Message: " + connection.getResponseMessage());
373         return connection.getResponseCode();
374     }
375
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");
383         return connection;
384     }
385
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...");
390             archive.delete();
391             LOG.info("Archive removed.");
392         }
393         HttpURLConnection connection = getNexusConnection( archiveIdentifier + "-" + BACKUP_ARCHIVE);
394         connection.setRequestMethod("GET");
395         connection.setDoInput(true);
396
397         InputStream connectionInputStream = connection.getInputStream();
398         FileOutputStream fileOutputStream = new FileOutputStream(archive);
399
400         byte[] bytes = new byte[1024];
401         int length;
402         while((length = connectionInputStream.read(bytes)) >= 0) { // while connection has bytes
403             fileOutputStream.write(bytes, 0, length); // write to archive
404         }
405         connection.disconnect();
406
407         LOG.info("Status: " + connection.getResponseCode());
408         LOG.info("Message: " + connection.getResponseMessage());
409         LOG.info(archive.getName() + " successfully created.");
410         return connection.getResponseCode();
411     }
412
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);
419     }
420
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);
427     }
428 }