[CCSDK-953]Create Daexim Offsite Backup 85/75885/2
authorHaddox, Anthony <ah0647@att.com>
Wed, 16 Jan 2019 16:08:35 +0000 (08:08 -0800)
committerHaddox, Anthony <ah0647@att.com>
Fri, 18 Jan 2019 14:23:03 +0000 (06:23 -0800)
Inital commit of ODL feature

Issue-ID: CCSDK-953
Change-Id: I7eb64fc9f414083db424700f2c901ba0a66cb0c4
Signed-off-by: Haddox, Anthony <ah0647@att.com>
27 files changed:
daexim-offsite-backup/.gitignore [new file with mode: 0755]
daexim-offsite-backup/README.md [new file with mode: 0755]
daexim-offsite-backup/features/ccsdk-daexim-offsite-backup/pom.xml [new file with mode: 0644]
daexim-offsite-backup/features/ccsdk-daexim-offsite-backup/src/main/feature/feature.xml [new file with mode: 0644]
daexim-offsite-backup/features/features-daexim-offsite-backup/pom.xml [new file with mode: 0644]
daexim-offsite-backup/features/pom.xml [new file with mode: 0755]
daexim-offsite-backup/installer/pom.xml [new file with mode: 0755]
daexim-offsite-backup/installer/src/assembly/assemble_installer_zip.xml [new file with mode: 0755]
daexim-offsite-backup/installer/src/assembly/assemble_mvnrepo_zip.xml [new file with mode: 0755]
daexim-offsite-backup/installer/src/main/resources/scripts/install-feature.sh [new file with mode: 0644]
daexim-offsite-backup/model/.gitignore [new file with mode: 0755]
daexim-offsite-backup/model/pom.xml [new file with mode: 0755]
daexim-offsite-backup/model/scripts/python/yang2props.py [new file with mode: 0755]
daexim-offsite-backup/model/src/main/yang/daexim-offsite-backup.yang [new file with mode: 0755]
daexim-offsite-backup/pom.xml [new file with mode: 0755]
daexim-offsite-backup/provider/.gitignore [new file with mode: 0755]
daexim-offsite-backup/provider/pom.xml [new file with mode: 0755]
daexim-offsite-backup/provider/src/main/java/org/onap/ccsdk/sli/northbound/daeximoffsitebackup/DaeximOffsiteBackupProvider.java [new file with mode: 0755]
daexim-offsite-backup/provider/src/main/java/org/onap/ccsdk/sli/northbound/daeximoffsitebackup/DaeximOffsiteBackupUtil.java [new file with mode: 0755]
daexim-offsite-backup/provider/src/main/resources/daexim-offsite-backup.properties [new file with mode: 0755]
daexim-offsite-backup/provider/src/main/resources/org/opendaylight/blueprint/DaeximOffsiteBackup.xml [new file with mode: 0755]
daexim-offsite-backup/provider/src/test/java/org/onap/ccsdk/sli/northbound/daeximoffsitebackup/DaeximOffsiteBackupProviderTest.java [new file with mode: 0644]
daexim-offsite-backup/provider/src/test/java/org/onap/ccsdk/sli/northbound/daeximoffsitebackup/DaeximOffsiteBackupUtilTest.java [new file with mode: 0644]
daexim-offsite-backup/provider/src/test/resources/daexim-offsite-backup.properties [new file with mode: 0755]
daexim-offsite-backup/provider/src/test/resources/fileToZip1 [new file with mode: 0644]
daexim-offsite-backup/provider/src/test/resources/fileToZip2 [new file with mode: 0644]
pom.xml

diff --git a/daexim-offsite-backup/.gitignore b/daexim-offsite-backup/.gitignore
new file mode 100755 (executable)
index 0000000..a01e90e
--- /dev/null
@@ -0,0 +1,38 @@
+tandard .git ignore entries#####
+
+## IDE Specific Files ##
+org.eclipse.core.resources.prefs
+.classpath
+.project
+.settings
+.idea
+.externalToolBuilders
+maven-eclipse.xml
+workspace
+
+## Compilation Files ##
+*.class
+**/target
+target
+target-ide
+MANIFEST.MF
+
+## Misc Ignores (OS specific etc) ##
+bin/
+dist
+*~
+*.ipr
+*.iml
+*.iws
+classes
+out/
+.DS_STORE
+.metadata
+
+## Folders which contain auto generated source code ##
+yang-gen-config
+yang-gen-sal
+
+#####Archetype specific .git ignore entries#######
+generate
+Archetype_Next_Steps.README
diff --git a/daexim-offsite-backup/README.md b/daexim-offsite-backup/README.md
new file mode 100755 (executable)
index 0000000..26d8a7e
--- /dev/null
@@ -0,0 +1,82 @@
+Introduction
+======================
+You have generated an MD-SAL module.
+
+* You should be able to successfully run ```mvn clean install``` on this project.
+
+Next Steps
+======================
+* run a ```mvn clean install``` if you haven't already. This will generate some code from the yang models.
+* Modify the model yang file under the model project.
+* Follow the comments in the generated provider class to wire your new provider into the generated 
+code.
+* Modify the generated provider model to respond to and handle the yang model. Depending on what
+you added to your model you may need to inherit additional interfaces or make other changes to
+the provider model.
+
+Generated Bundles
+======================
+* model
+    - Provides the yang model for your application. This is your primary northbound interface.
+* provider
+    - Provides a template implementation for a provider to respond to your yang model.
+* features
+    - Defines a karaf feature. If you add dependencies on third-party bundles then you will need to
+      modify the features.xml to list out the dependencies.
+* installer
+    - Bundles all of the jars and third party dependencies (minus ODL dependencies) into a single
+      .zip file.
+
+Usage
+======================
+## Purpose
+The purpose of this ODL feature is to support local and geo-redundancy by providing a way to
+back up and retrieve MD-SAL data exports to and from a Sonatype Nexus server. In order to function,
+this module requires the controller to have an installation of the ```data-export-import``` module and a valid export of MD-SAL data.
+
+## Backup
+MD-SAL Backup can be achieved using the ```daexim-offsite-backup:backup-data``` RPC either through the
+RESTConf portal or through a tool such as cURL or Postman. While no input is required for this RPC,
+the RPC does require the operational, models, and config .JSONs to be present in the daexim directory of the controller.
+```sh
+export USER=user
+export PASSWORD=password
+export ODL_HOST=https://yourhost.com:8181
+curl -X POST -u$USER:$PASSWORD ${ODL_HOST}/restconf/operations/daexim-offsite-backup:backup-data
+```
+Through this process a timestamped archive is created in the form of ```POD_NAME-yyyyMMdd_HH-odl_backup.zip``` 
+where ```POD_NAME``` is the name of the ODL, specified through the properties file or through an environment variable.
+
+## Retrieval
+MD-SAL Retrieval can be achieved by using the ```daexim-offsite-backup:retrieve-data``` RPC either through the
+RESTConf portal or through a tool such as cURL or Postman. This RPC requires timestamp information and may 
+be supplied with an optional podName.
+
+```sh
+export USER=user
+export PASSWORD=password
+export ODL_HOST=https://yourhost.com:8181
+export TARGET_ODL=targetOdlPodName
+export TIMESTAMP=yyyyMMdd_HH
+export DATA= '
+  {
+    "input": {
+      "pod-name": "'"$TARGET_ODL"'",
+      "timestamp": "'"$TIMESTAMP"'"
+    }
+  }'
+curl -X POST -u$USER:$PASSWORD --data $DATA ${ODL_HOST}/restconf/operations/daexim-offsite-backup:retrieve-data
+```
+
+Through this process an archive with the specified timestamp (and optional pod name) is downloaded from 
+the Nexus server and extracted into the controller's daexim directory. After this it is up to the user 
+to trigger an MD-SAL import.
+
+## Properties File
+Before each RPC execution this module pulls information from a user supplied properties file. The module expects to find:
+- daeximDirectory
+- credentials
+- nexusUrl
+- podName
+
+If the module cannot find the properties file it will default to generic values and attempt to move forward.
diff --git a/daexim-offsite-backup/features/ccsdk-daexim-offsite-backup/pom.xml b/daexim-offsite-backup/features/ccsdk-daexim-offsite-backup/pom.xml
new file mode 100644 (file)
index 0000000..13c0625
--- /dev/null
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onap.ccsdk.parent</groupId>
+        <artifactId>single-feature-parent</artifactId>
+        <version>1.2.1-SNAPSHOT</version>
+        <relativePath />
+    </parent>
+
+    <groupId>org.onap.ccsdk.sli.northbound</groupId>
+    <artifactId>ccsdk-daexim-offsite-backup</artifactId>
+    <version>0.4.1-SNAPSHOT</version>
+    <packaging>feature</packaging>
+
+    <name>ccsdk-sli-northbound :: daexim-offsite-backup :: ${project.artifactId}</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>odl-mdsal-broker</artifactId>
+            <type>xml</type>
+            <classifier>features</classifier>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.ccsdk.sli.core</groupId>
+            <artifactId>ccsdk-sli</artifactId>
+            <version>${ccsdk.sli.core.version}</version>
+            <type>xml</type>
+            <classifier>features</classifier>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>daexim-offsite-backup-model</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>daexim-offsite-backup-provider</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/daexim-offsite-backup/features/ccsdk-daexim-offsite-backup/src/main/feature/feature.xml b/daexim-offsite-backup/features/ccsdk-daexim-offsite-backup/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..3dcb900
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" name="ccsdk-daexim-offsite-backup">
+    <repository>mvn:org.onap.ccsdk.sli.core/ccsdk-sli/LATEST/xml/features</repository>
+    <feature name="ccsdk-daexim-offsite-backup">
+        <feature version="${ccsdk.sli.core.version}">ccsdk-sli</feature>
+    </feature>
+</features>
diff --git a/daexim-offsite-backup/features/features-daexim-offsite-backup/pom.xml b/daexim-offsite-backup/features/features-daexim-offsite-backup/pom.xml
new file mode 100644 (file)
index 0000000..d7f823c
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onap.ccsdk.parent</groupId>
+        <artifactId>feature-repo-parent</artifactId>
+        <version>1.2.1-SNAPSHOT</version>
+        <relativePath/>
+    </parent>
+
+    <groupId>org.onap.ccsdk.sli.northbound</groupId>
+    <artifactId>features-daexim-offsite-backup</artifactId>
+    <version>0.4.1-SNAPSHOT</version>
+    <packaging>feature</packaging>
+
+    <name>ccsdk-sli-northbound :: daexim-offsite-backup :: ${project.artifactId}</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>ccsdk-daexim-offsite-backup</artifactId>
+            <version>${project.version}</version>
+            <type>xml</type>
+            <classifier>features</classifier>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/daexim-offsite-backup/features/pom.xml b/daexim-offsite-backup/features/pom.xml
new file mode 100755 (executable)
index 0000000..8c4eaf5
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onap.ccsdk.parent</groupId>
+        <artifactId>odlparent-lite</artifactId>
+        <version>1.2.1-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.onap.ccsdk.sli.northbound</groupId>
+    <artifactId>daexim-offsite-backup-features</artifactId>
+    <version>0.4.1-SNAPSHOT</version>
+    <packaging>pom</packaging>
+    
+    <name>ccsdk-sli-northbound :: daexim-offsite-backup :: ${project.artifactId}</name>
+
+    <modules>
+        <module>ccsdk-daexim-offsite-backup</module>
+        <module>features-daexim-offsite-backup</module>
+    </modules>
+</project>
diff --git a/daexim-offsite-backup/installer/pom.xml b/daexim-offsite-backup/installer/pom.xml
new file mode 100755 (executable)
index 0000000..2cf83d7
--- /dev/null
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.onap.ccsdk.parent</groupId>
+        <artifactId>odlparent-lite</artifactId>
+        <version>1.2.1-SNAPSHOT</version>
+        <relativePath/>
+    </parent>
+
+    <name>ccsdk-sli-northbound :: daexim-offsite-backup :: ${project.artifactId}</name>
+    <groupId>org.onap.ccsdk.sli.northbound</groupId>
+    <artifactId>daexim-offsite-backup-installer</artifactId>
+    <version>0.4.1-SNAPSHOT</version>
+    <packaging>pom</packaging>
+    <properties>
+        <application.name>ccsdk-daexim-offsite-backup</application.name>
+        <features.boot>${application.name}</features.boot>
+        <features.repositories>mvn:org.onap.ccsdk.sli.northbound/${features.boot}/${project.version}/xml/features</features.repositories>
+        <include.transitive.dependencies>false</include.transitive.dependencies>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.onap.ccsdk.sli.northbound</groupId>
+            <artifactId>${application.name}</artifactId>
+            <version>${project.version}</version>
+            <type>xml</type>
+            <classifier>features</classifier>
+            <exclusions>
+                <exclusion>
+                    <groupId>*</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.ccsdk.sli.northbound</groupId>
+            <artifactId>daexim-offsite-backup-provider</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.ccsdk.sli.northbound</groupId>
+            <artifactId>daexim-offsite-backup-model</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>2.6</version>
+                <executions>
+                    <execution>
+                        <id>maven-repo-zip</id>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                        <phase>package</phase>
+                        <configuration>
+                            <attach>true</attach>
+                            <finalName>stage/${application.name}-${project.version}</finalName>
+                            <descriptors>
+                                <descriptor>src/assembly/assemble_mvnrepo_zip.xml</descriptor>
+                            </descriptors>
+                            <appendAssemblyId>true</appendAssemblyId>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>installer-zip</id>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                        <phase>package</phase>
+                        <configuration>
+                            <attach>true</attach>
+                            <finalName>${application.name}-${project.version}-installer</finalName>
+                            <descriptors>
+                                <descriptor>src/assembly/assemble_installer_zip.xml</descriptor>
+                            </descriptors>
+                            <appendAssemblyId>false</appendAssemblyId>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-dependencies</id>
+                        <goals>
+                            <goal>copy-dependencies</goal>
+                        </goals>
+                        <phase>prepare-package</phase>
+                        <configuration>
+                            <transitive>false</transitive>
+                            <outputDirectory>${project.build.directory}/assembly/system</outputDirectory>
+                            <overWriteReleases>false</overWriteReleases>
+                            <overWriteSnapshots>true</overWriteSnapshots>
+                            <overWriteIfNewer>true</overWriteIfNewer>
+                            <useRepositoryLayout>true</useRepositoryLayout>
+                            <addParentPoms>false</addParentPoms>
+                            <copyPom>false</copyPom>
+                            <includeGroupIds>org.onap.ccsdk.sli.northbound</includeGroupIds>
+                            <scope>provided</scope>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-resources-plugin</artifactId>
+                <version>2.6</version>
+                <executions>
+                    <execution>
+                        <id>copy-version</id>
+                        <goals>
+                            <goal>copy-resources</goal>
+                        </goals>
+                        <phase>validate</phase>
+                        <configuration>
+                            <outputDirectory>${basedir}/target/stage</outputDirectory>
+                            <resources>
+                                <resource>
+                                    <directory>src/main/resources/scripts</directory>
+                                    <includes>
+                                        <include>install-feature.sh</include>
+                                    </includes>
+                                    <filtering>true</filtering>
+                                </resource>
+                            </resources>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/daexim-offsite-backup/installer/src/assembly/assemble_installer_zip.xml b/daexim-offsite-backup/installer/src/assembly/assemble_installer_zip.xml
new file mode 100755 (executable)
index 0000000..41d23e8
--- /dev/null
@@ -0,0 +1,56 @@
+<!--
+  ============LICENSE_START=======================================================
+  openECOMP : SDN-C
+  ================================================================================
+  Copyright (C) 2018 AT&T Intellectual Property. All rights
+                       reserved.
+  ================================================================================
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+       http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ============LICENSE_END=========================================================
+  -->
+
+<!-- Defines how we build the .zip file which is our distribution. -->
+
+<assembly
+    xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+    <id>installer_zip</id>
+    <formats>
+        <format>zip</format>
+    </formats>
+
+    <!--  we want "system" and related files right at the root level
+          as this file is suppose to be unzip on top of a karaf
+          distro. -->
+    <includeBaseDirectory>false</includeBaseDirectory>
+
+    <fileSets>
+        <fileSet>
+            <directory>target/stage/</directory>
+            <outputDirectory>${application.name}</outputDirectory>
+            <fileMode>755</fileMode>
+            <includes>
+                <include>*.sh</include>
+            </includes>
+        </fileSet>
+        <fileSet>
+            <directory>target/stage/</directory>
+            <outputDirectory>${application.name}</outputDirectory>
+            <fileMode>644</fileMode>
+            <excludes>
+                <exclude>*.sh</exclude>
+            </excludes>
+        </fileSet>
+    </fileSets>
+</assembly>
diff --git a/daexim-offsite-backup/installer/src/assembly/assemble_mvnrepo_zip.xml b/daexim-offsite-backup/installer/src/assembly/assemble_mvnrepo_zip.xml
new file mode 100755 (executable)
index 0000000..2750609
--- /dev/null
@@ -0,0 +1,47 @@
+<!--
+  ============LICENSE_START=======================================================
+  openECOMP : SDN-C
+  ================================================================================
+  Copyright (C) 2018 AT&T Intellectual Property. All rights
+                       reserved.
+  ================================================================================
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+       http://www.apache.org/licenses/LICENSE-2.0
+  
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  ============LICENSE_END=========================================================
+  -->
+
+<!-- Defines how we build the .zip file which is our distribution. -->
+
+<assembly
+    xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+    <id>repo</id>
+    <formats>
+        <format>zip</format>
+    </formats>
+
+    <!--  we want "system" and related files right at the root level
+          as this file is suppose to be unzip on top of a karaf
+          distro. -->
+    <includeBaseDirectory>false</includeBaseDirectory>
+
+    <fileSets>
+        <fileSet>
+            <directory>target/assembly/</directory>
+            <outputDirectory>.</outputDirectory>
+            <excludes>
+            </excludes>
+        </fileSet>
+    </fileSets>
+
+</assembly>
diff --git a/daexim-offsite-backup/installer/src/main/resources/scripts/install-feature.sh b/daexim-offsite-backup/installer/src/main/resources/scripts/install-feature.sh
new file mode 100644 (file)
index 0000000..1d7be14
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+###
+# ============LICENSE_START=======================================================
+# openECOMP : SDN-C
+# ================================================================================
+# Copyright (C) 2018 AT&T Intellectual Property. All rights
+#                      reserved.
+# ================================================================================
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ============LICENSE_END=========================================================
+###
+
+ODL_HOME=${ODL_HOME:-/opt/opendaylight/current}
+ODL_KARAF_CLIENT=${ODL_KARAF_CLIENT:-${ODL_HOME}/bin/client}
+INSTALLERDIR=$(dirname $0)
+
+REPOZIP=${INSTALLERDIR}/${features.boot}-${project.version}-repo.zip
+
+if [ -f ${REPOZIP} ]
+then
+    unzip -d ${ODL_HOME} ${REPOZIP}
+else
+    echo "ERROR : repo zip ($REPOZIP) not found"
+    exit 1
+fi
+
+${ODL_KARAF_CLIENT} feature:repo-add ${features.repositories}
+${ODL_KARAF_CLIENT} feature:install ${features.boot}
diff --git a/daexim-offsite-backup/model/.gitignore b/daexim-offsite-backup/model/.gitignore
new file mode 100755 (executable)
index 0000000..eacf31a
--- /dev/null
@@ -0,0 +1 @@
+/target-ide/
diff --git a/daexim-offsite-backup/model/pom.xml b/daexim-offsite-backup/model/pom.xml
new file mode 100755 (executable)
index 0000000..a38f085
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.onap.ccsdk.parent</groupId>
+        <artifactId>binding-parent</artifactId>
+        <version>1.2.1-SNAPSHOT</version>
+        <relativePath/>
+    </parent>
+
+    <name>ccsdk-sli-northbound :: daexim-offsite-backup :: ${project.artifactId}</name>
+    <groupId>org.onap.ccsdk.sli.northbound</groupId>
+    <artifactId>daexim-offsite-backup-model</artifactId>
+    <version>0.4.1-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.opendaylight.mdsal.model</groupId>
+            <artifactId>ietf-inet-types-2013-07-15</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.mdsal.model</groupId>
+            <artifactId>ietf-yang-types-20130715</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/daexim-offsite-backup/model/scripts/python/yang2props.py b/daexim-offsite-backup/model/scripts/python/yang2props.py
new file mode 100755 (executable)
index 0000000..559d31b
--- /dev/null
@@ -0,0 +1,57 @@
+#!/usr/bin/python
+
+import re
+import sys
+
+
+# Convert word from foo-bar to FooBar
+# words begining with a digit will be converted to _digit
+def to_enum(s):
+    if s[0].isdigit():
+        s = "_" + s
+    else:
+        s = s[0].upper() + s[1:]
+    return re.sub(r'(?!^)-([a-zA-Z])', lambda m: m.group(1).upper(), s)
+
+leaf = ""
+val = ""
+li = []
+
+if len(sys.argv) < 3:
+     print 'yang2props.py <input yang> <output properties>'
+     sys.exit(2)
+
+with open(sys.argv[1], "r") as ins:
+    for line in ins:
+        # if we see a leaf save the name for later
+        if "leaf " in line:
+           match = re.search(r'leaf (\S+)', line)
+           if match:
+                 leaf = match.group(1)
+      
+        # if we see enum convert the value to enum format and see if it changed
+        # if the value is different write a property entry
+        if "enum " in line:
+           match = re.search(r'enum "(\S+)";', line)
+           if match:
+                val = match.group(1)
+               enum = to_enum(val)
+
+                # see if converting to enum changed the string
+               if val != enum:
+                    property = "yang."+leaf+"."+enum+"="+val
+                   if property not in li:
+                       li.append( property)
+
+
+# Open output file
+fo = open(sys.argv[2], "wb")
+fo.write("# yang conversion properties \n")
+fo.write("# used to convert Enum back to the original yang value \n")
+fo.write("\n".join(li))
+fo.write("\n")
+
+# Close opend file
+fo.close()
+
+   
diff --git a/daexim-offsite-backup/model/src/main/yang/daexim-offsite-backup.yang b/daexim-offsite-backup/model/src/main/yang/daexim-offsite-backup.yang
new file mode 100755 (executable)
index 0000000..363136c
--- /dev/null
@@ -0,0 +1,46 @@
+module daexim-offsite-backup{\r
+  namespace "org:onap:ccsdk:sli:northbound:daeximoffsitebackup";\r
+  prefix daexim-offsite-backup;\r
+\r
+  import ietf-inet-types {\r
+    prefix inet;\r
+  }\r
+  import ietf-yang-types {\r
+    prefix yang;\r
+  }\r
+  description\r
+    "This ODL feature is designed for transferring MD-SAL data\r
+     to an offsite location in the ECOMP-C containerized environments.";\r
+  revision "2018-09-26" {\r
+    description\r
+      "Release 19.02 draft";\r
+  }\r
+\r
+  rpc backup-data {\r
+    output {\r
+      leaf status { type string; }\r
+      leaf message { type string; }\r
+    }\r
+  }\r
+\r
+  rpc retrieve-data {\r
+    input {\r
+      leaf pod-name {\r
+        type string;\r
+        description\r
+          "Name of the desired MD-SAL backup's pod. If not supplied will\r
+          default to the name of this pod.";\r
+      }\r
+      leaf timestamp {\r
+        type string;\r
+        description\r
+          "Timestamp of the desired backup. Format: yyyyMMdd_HH";\r
+          mandatory true;\r
+      }\r
+    }\r
+    output {\r
+      leaf status { type string; }\r
+      leaf message { type string; }\r
+    }\r
+  }\r
+}////closes the module
\ No newline at end of file
diff --git a/daexim-offsite-backup/pom.xml b/daexim-offsite-backup/pom.xml
new file mode 100755 (executable)
index 0000000..2542043
--- /dev/null
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.onap.ccsdk.parent</groupId>
+        <artifactId>odlparent-lite</artifactId>
+        <version>1.2.1-SNAPSHOT</version>
+        <relativePath/>
+    </parent>
+
+    <groupId>org.onap.ccsdk.sli.northbound</groupId>
+    <artifactId>daexim-offsite-backup</artifactId>
+    <version>0.4.1-SNAPSHOT</version>
+    <packaging>pom</packaging>
+
+    <name>ccsdk-sli-northbound :: daexim-offsite-backup</name>
+    <description>ODL feature used for transferring MD-SAL data to an offsite location.</description>
+
+    <modules>
+        <module>model</module>
+        <module>provider</module>
+        <module>features</module>
+        <module>installer</module>
+    </modules>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.onap.ccsdk.sli.northbound</groupId>
+                <artifactId>daexim-offsite-backup-features</artifactId>
+                <classifier>features</classifier>
+                <type>xml</type>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.onap.ccsdk.sli.northbound</groupId>
+                <artifactId>daexim-offsite-backup-model</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.onap.ccsdk.sli.northbound</groupId>
+                <artifactId>daexim-offsite-backup-provider</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+</project>
diff --git a/daexim-offsite-backup/provider/.gitignore b/daexim-offsite-backup/provider/.gitignore
new file mode 100755 (executable)
index 0000000..527bb69
--- /dev/null
@@ -0,0 +1,3 @@
+/target-ide/
+fileToZip1
+fileToZip2
\ No newline at end of file
diff --git a/daexim-offsite-backup/provider/pom.xml b/daexim-offsite-backup/provider/pom.xml
new file mode 100755 (executable)
index 0000000..1dc8558
--- /dev/null
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.onap.ccsdk.parent</groupId>
+        <artifactId>binding-parent</artifactId>
+        <version>1.2.1-SNAPSHOT</version>
+        <relativePath/>
+    </parent>
+
+    <name>ccsdk-sli-northbound :: daexim-offsite-backup :: ${project.artifactId}</name>
+    <groupId>org.onap.ccsdk.sli.northbound</groupId>
+    <artifactId>daexim-offsite-backup-provider</artifactId>
+    <version>0.4.1-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>org.opendaylight.controller.config.yang.config.daexim-offsite-backup_provider</Export-Package>
+                        <Import-Package>*</Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.onap.ccsdk.sli.northbound</groupId>
+            <artifactId>daexim-offsite-backup-model</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>sal-binding-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>sal-common-util</artifactId>
+        </dependency>
+        <dependency>
+            <artifactId>sal-test-model</artifactId>
+            <groupId>org.opendaylight.controller</groupId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>sal-binding-broker-impl</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>sal-binding-broker-impl</artifactId>
+            <classifier>tests</classifier>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.ccsdk.sli.core</groupId>
+            <artifactId>sli-common</artifactId>
+            <version>${sdnctl.sli.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onap.ccsdk.sli.core</groupId>
+            <artifactId>sli-provider</artifactId>
+            <version>${sdnctl.sli.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.11</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>1.10.19</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/daexim-offsite-backup/provider/src/main/java/org/onap/ccsdk/sli/northbound/daeximoffsitebackup/DaeximOffsiteBackupProvider.java b/daexim-offsite-backup/provider/src/main/java/org/onap/ccsdk/sli/northbound/daeximoffsitebackup/DaeximOffsiteBackupProvider.java
new file mode 100755 (executable)
index 0000000..cc16bf7
--- /dev/null
@@ -0,0 +1,419 @@
+/*
+ * ============LICENSE_START=======================================================
+ * openECOMP : SDN-C
+ * ================================================================================
+ * Copyright (C) 2018 AT&T Intellectual Property. All rights
+ *                     reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+*/
+
+package org.onap.ccsdk.sli.northbound.daeximoffsitebackup;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+import javax.annotation.Nonnull;
+
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
+import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker;
+import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry;
+import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.BackupDataInput;
+import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.BackupDataOutput;
+import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.BackupDataOutputBuilder;
+import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.DaeximOffsiteBackupService;
+import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.RetrieveDataOutput;
+import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.RetrieveDataOutputBuilder;
+import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.RetrieveDataInput;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DaeximOffsiteBackupProvider implements AutoCloseable, DaeximOffsiteBackupService, DataTreeChangeListener {
+    private static final Logger LOG = LoggerFactory.getLogger(DaeximOffsiteBackupProvider.class);
+
+    private static String DAEXIM_DIR;
+    private static String CREDENTIALS;
+    private static String NEXUS_URL;
+    private static String POD_NAME;
+    private static String PROPERTIES_FILE = System.getenv("SDNC_CONFIG_DIR") + "/daexim-offsite-backup.properties";
+
+    private static final String OPERATIONAL_JSON = "odl_backup_operational.json";
+    private static final String MODELS_JSON = "odl_backup_models.json";
+    private static final String CONFIG_JSON = "odl_backup_config.json";
+    private static final String BACKUP_ARCHIVE = "odl_backup.zip";
+    private static final String appName = "daexim-offsite-backup";
+
+    private final ExecutorService executor;
+    private Properties properties;
+    private DataBroker dataBroker;
+    private RpcProviderRegistry rpcRegistry;
+    private BindingAwareBroker.RpcRegistration<DaeximOffsiteBackupService> rpcRegistration;
+
+    public DaeximOffsiteBackupProvider(DataBroker dataBroker,
+                                       RpcProviderRegistry rpcProviderRegistry) {
+        LOG.info("Creating provider for " + appName);
+        this.executor = Executors.newFixedThreadPool(1);
+        this.dataBroker = dataBroker;
+        this.rpcRegistry = rpcProviderRegistry;
+        initialize();
+    }
+
+    public void initialize() {
+        LOG.info("Initializing provider for " + appName);
+        // Create the top level containers
+        createContainers();
+        try {
+            DaeximOffsiteBackupUtil.loadProperties();
+        } catch (Exception e) {
+            LOG.error("Caught Exception while trying to load properties file", e);
+        }
+        rpcRegistration = rpcRegistry.addRpcImplementation(DaeximOffsiteBackupService.class, this);
+        LOG.info("Initialization complete for " + appName);
+    }
+
+    private void loadProperties() {
+        LOG.info("Loading properties from " + PROPERTIES_FILE);
+        if(properties == null)
+            properties = new Properties();
+        File propertiesFile = new File(PROPERTIES_FILE);
+        if(!propertiesFile.exists()) {
+            LOG.warn("Properties file (" + PROPERTIES_FILE + ") not found. Using default properties.");
+            properties.put("daeximDirectory", "/opt/opendaylight/current/daexim/");
+            properties.put("credentials", "admin:enc:YWRtaW4xMjM=");
+            properties.put("nexusUrl", "http://localhost:8081/nexus/content/repositories/");
+            properties.put("podName", "UNKNOWN_ODL");
+            return;
+        }
+        FileInputStream fileInputStream;
+        try {
+            fileInputStream = new FileInputStream(propertiesFile);
+            properties.load(fileInputStream);
+            fileInputStream.close();
+            LOG.info(properties.size() + " properties loaded.");
+            LOG.info("daeximDirectory: " + properties.getProperty("daeximDirectory"));
+            LOG.info("nexusUrl: " + properties.getProperty("nexusUrl"));
+            LOG.info("podName: " + properties.getProperty("podName"));
+        } catch(IOException e) {
+            LOG.error("Error loading properties.", e);
+        }
+    }
+
+    private void applyProperties() {
+        LOG.info("Applying properties...");
+        if(POD_NAME == null || POD_NAME.isEmpty()) {
+            LOG.warn("MY_POD_NAME environment variable not set. Using value from properties.");
+            POD_NAME = properties.getProperty("podName");
+        }
+        DAEXIM_DIR =  properties.getProperty("daeximDirectory");
+        NEXUS_URL = properties.getProperty("nexusUrl");
+
+        if(!properties.getProperty("credentials").contains(":")) { //Entire thing is encoded
+            CREDENTIALS = new String(Base64.getDecoder().decode(properties.getProperty("credentials")));
+        }
+        else {
+            String[] credentials = properties.getProperty("credentials").split(":", 2);
+            if(credentials[1].startsWith("enc:")) { // Password is encoded
+                credentials[1] = new String(Base64.getDecoder().decode(credentials[1].split(":")[1]));
+            }
+            CREDENTIALS = credentials[0] + ":" + credentials[1];
+        }
+        LOG.info("Properties applied.");
+    }
+
+    private void createContainers() {
+        final WriteTransaction t = dataBroker.newReadWriteTransaction();
+        try {
+            CheckedFuture<Void, TransactionCommitFailedException> checkedFuture = t.submit();
+            checkedFuture.get();
+            LOG.info("Create Containers succeeded!: ");
+        } catch (InterruptedException | ExecutionException e) {
+            LOG.error("Create Containers Failed: " + e);
+            LOG.error("context", e);
+        }
+    }
+
+    protected void initializeChild() {
+
+    }
+
+    @Override
+    public void close() throws Exception {
+        LOG.info("Closing provider for " + appName);
+        executor.shutdown();
+        rpcRegistration.close();
+        LOG.info("Successfully closed provider for " + appName);
+    }
+
+    @Override
+    public void onDataTreeChanged(@Nonnull Collection changes) {
+
+    }
+
+    @Override
+    public ListenableFuture<RpcResult<BackupDataOutput>> backupData(BackupDataInput input) {
+        final String SVC_OPERATION = "backup-data";
+        LOG.info(appName + ":" + SVC_OPERATION + " called.");
+
+        String statusCode;
+        String message = "Data sent to offsite location.";
+
+        loadProperties();
+        applyProperties();
+
+        LOG.info("Pod Name: " + POD_NAME);
+        Instant timestamp = Instant.now();
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HH").withZone(ZoneId.of("GMT"));
+        String timestampedArchive = DAEXIM_DIR + POD_NAME + '-' + formatter.format(timestamp) + "-" + BACKUP_ARCHIVE;
+        try {
+            LOG.info("Creating archive...");
+            List<String> daeximFiles = Arrays.asList(DAEXIM_DIR + OPERATIONAL_JSON,DAEXIM_DIR + MODELS_JSON, DAEXIM_DIR + CONFIG_JSON);
+            createArchive(daeximFiles, timestampedArchive);
+            LOG.info("Archive created.");
+        } catch(IOException e) {
+            LOG.error("Error creating archive " + timestampedArchive);
+            LOG.error(e.getMessage());
+            statusCode = "500";
+            message = "Archive creation failed.";
+            return buildBackupDataFuture(statusCode, message);
+        }
+
+        try{
+            LOG.info("Sending archive to Nexus server: " + NEXUS_URL);
+            statusCode = Integer.toString(putArchive(timestampedArchive));
+            LOG.info("Archive sent to Nexus.");
+        } catch(IOException e) {
+            LOG.error("Nexus creation failed.", e);
+            statusCode = "500";
+            message = "Nexus creation failed.";
+        }
+
+        File archive = new File(timestampedArchive);
+        if(archive.exists()) {
+            archive.delete(); // Save some space on the ODL, keep them from piling up
+        }
+
+        LOG.info("Sending Response statusCode=" + statusCode+ " message=" + message + " | " + SVC_OPERATION);
+        return buildBackupDataFuture(statusCode, message);
+    }
+
+    @Override
+    public ListenableFuture<RpcResult<RetrieveDataOutput>> retrieveData(RetrieveDataInput input) {
+        final String SVC_OPERATION = "retrieve-data";
+        LOG.info(appName + ":" + SVC_OPERATION + " called.");
+
+        String statusCode = "200";
+        String message = "Data retrieved from offsite location.";
+
+        loadProperties();
+        applyProperties();
+
+        LOG.info("Pod Name: " + POD_NAME);
+        String archiveIdentifier = POD_NAME  + '-' + input.getTimestamp();
+        String timestampedArchive = DAEXIM_DIR +  archiveIdentifier + "-" + BACKUP_ARCHIVE;
+        LOG.info("Trying to retrieve " + timestampedArchive);
+        try {
+            statusCode = Integer.toString(getArchive(archiveIdentifier));
+        } catch(IOException e) {
+            LOG.error("Could not retrieve archive.", e);
+            statusCode = "500";
+            message = "Could not retrieve archive.";
+            return retrieveDataOutputRpcResult(statusCode, message);
+        }
+        LOG.info("Retrieved archive.");
+
+        LOG.info("Extracting archive...");
+        try {
+            extractArchive(DAEXIM_DIR + "-" + BACKUP_ARCHIVE);
+        } catch(IOException e) {
+            LOG.error("Could not extract archive.", e);
+            statusCode = "500";
+            message = "Could not extract archive.";
+            return retrieveDataOutputRpcResult(statusCode, message);
+        }
+        LOG.info("Archive extracted.");
+
+        return retrieveDataOutputRpcResult(statusCode, message);
+    }
+
+    private boolean exportExists(List<String> daeximFiles) {
+        File file;
+        for(String f : daeximFiles) {
+            file = new File(f);
+            if(!file.exists()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void createArchive(List<String> daeximFiles, String timestampedArchive) throws IOException {
+        if(!exportExists(daeximFiles)) {
+            LOG.error("Daexim exports do not exist.");
+            throw new IOException();
+        }
+        LOG.info("Creating " + timestampedArchive);
+        FileOutputStream fileOutputStream = new FileOutputStream(timestampedArchive);
+        ZipOutputStream zipOutputStream = new ZipOutputStream(fileOutputStream);
+        File targetZipFile;
+        FileInputStream fileInputStream;
+        ZipEntry zipEntry;
+        byte[] bytes;
+        int length;
+        for(String source : daeximFiles) {
+            LOG.info("Adding " + source + " to archive...");
+            targetZipFile = new File(source);
+            fileInputStream = new FileInputStream(targetZipFile);
+            zipEntry = new ZipEntry(targetZipFile.getName());
+            zipOutputStream.putNextEntry(zipEntry);
+            bytes = new byte[1024];
+
+            while((length = fileInputStream.read(bytes)) >= 0) {
+                zipOutputStream.write(bytes, 0, length);
+            }
+            fileInputStream.close();
+        }
+
+        zipOutputStream.close();
+        fileOutputStream.close();
+    }
+
+    private void extractArchive(String timestampedArchive) throws IOException {
+        byte[] bytes = new byte[1024];
+        ZipInputStream zis = new ZipInputStream(new FileInputStream(timestampedArchive));
+        ZipEntry zipEntry = zis.getNextEntry();
+        while(zipEntry != null){
+            String fileName = zipEntry.getName();
+            File newFile = new File(DAEXIM_DIR + fileName);
+            FileOutputStream fos = new FileOutputStream(newFile);
+            int len;
+            while ((len = zis.read(bytes)) > 0) {
+                fos.write(bytes, 0, len);
+            }
+            fos.close();
+            LOG.info(zipEntry.getName() + " extracted.");
+            zipEntry = zis.getNextEntry();
+        }
+        zis.closeEntry();
+        zis.close();
+        LOG.info(timestampedArchive + " extracted successfully.");
+    }
+
+    private int putArchive(String timestampedArchive) throws IOException {
+        File archive = new File(timestampedArchive);
+        HttpURLConnection connection = getNexusConnection(archive.getName());
+        connection.setRequestProperty("Content-Length", Long.toString(archive.length()));
+        connection.setRequestMethod("PUT");
+        connection.setDoOutput(true);
+
+        FileInputStream fileInputStream = new FileInputStream(archive);
+        OutputStream outputStream = connection.getOutputStream();
+
+        byte[] bytes = new byte[1024];
+        int length;
+        while((length = fileInputStream.read(bytes)) >= 0) {
+            outputStream.write(bytes, 0, length);
+        }
+
+        outputStream.flush();
+        outputStream.close();
+        fileInputStream.close();
+        connection.disconnect();
+
+        LOG.info("Status: " + connection.getResponseCode());
+        LOG.info("Message: " + connection.getResponseMessage());
+        return connection.getResponseCode();
+    }
+
+    private HttpURLConnection getNexusConnection(String archive) throws IOException {
+        URL url = new URL(NEXUS_URL + archive);
+        String auth = "Basic " + javax.xml.bind.DatatypeConverter.printBase64Binary(CREDENTIALS.getBytes());
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        connection.addRequestProperty("Authorization", auth);
+        connection.setRequestProperty("Connection", "keep-alive");
+        connection.setRequestProperty("Proxy-Connection", "keep-alive");
+        return connection;
+    }
+
+    private int getArchive(String archiveIdentifier) throws IOException {
+        File archive = new File(DAEXIM_DIR + "backup.zip");
+        if(archive.exists()) {
+            LOG.info("Recently retrieved archive found. Removing old archive...");
+            archive.delete();
+            LOG.info("Archive removed.");
+        }
+        HttpURLConnection connection = getNexusConnection( archiveIdentifier + "-" + BACKUP_ARCHIVE);
+        connection.setRequestMethod("GET");
+        connection.setDoInput(true);
+
+        InputStream connectionInputStream = connection.getInputStream();
+        FileOutputStream fileOutputStream = new FileOutputStream(archive);
+
+        byte[] bytes = new byte[1024];
+        int length;
+        while((length = connectionInputStream.read(bytes)) >= 0) { // while connection has bytes
+            fileOutputStream.write(bytes, 0, length); // write to archive
+        }
+        connection.disconnect();
+
+        LOG.info("Status: " + connection.getResponseCode());
+        LOG.info("Message: " + connection.getResponseMessage());
+        LOG.info(archive.getName() + " successfully created.");
+        return connection.getResponseCode();
+    }
+
+    private ListenableFuture<RpcResult<BackupDataOutput>> buildBackupDataFuture(String statusCode, String message) {
+        BackupDataOutputBuilder outputBuilder = new BackupDataOutputBuilder();
+        outputBuilder.setStatus(statusCode);
+        outputBuilder.setMessage(message);
+        RpcResult<BackupDataOutput> rpcResult = RpcResultBuilder.<BackupDataOutput> status(true).withResult(outputBuilder.build()).build();
+        return Futures.immediateFuture(rpcResult);
+    }
+
+    private ListenableFuture<RpcResult<RetrieveDataOutput>> retrieveDataOutputRpcResult(String status, String message) {
+        RetrieveDataOutputBuilder outputBuilder = new RetrieveDataOutputBuilder();
+        outputBuilder.setStatus(status);
+        outputBuilder.setMessage(message);
+        RpcResult<RetrieveDataOutput> rpcResult = RpcResultBuilder.<RetrieveDataOutput> status(true).withResult(outputBuilder.build()).build();
+        return Futures.immediateFuture(rpcResult);
+    }
+}
diff --git a/daexim-offsite-backup/provider/src/main/java/org/onap/ccsdk/sli/northbound/daeximoffsitebackup/DaeximOffsiteBackupUtil.java b/daexim-offsite-backup/provider/src/main/java/org/onap/ccsdk/sli/northbound/daeximoffsitebackup/DaeximOffsiteBackupUtil.java
new file mode 100755 (executable)
index 0000000..7adb2fa
--- /dev/null
@@ -0,0 +1,78 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * openECOMP : SDN-C
+ * ================================================================================
+ * Copyright (C) 2018 AT&T Intellectual Property. All rights
+ *                     reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.ccsdk.sli.northbound.daeximoffsitebackup;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.BackupDataOutputBuilder;
+
+import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.RetrieveDataInputBuilder;
+import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.RetrieveDataOutputBuilder;
+
+import org.onap.ccsdk.sli.core.sli.provider.MdsalHelper;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DaeximOffsiteBackupUtil extends MdsalHelper {
+    private static final Logger LOG = LoggerFactory.getLogger(DaeximOffsiteBackupUtil.class);
+    private static String PROPERTIES_FILE;
+
+    public static void loadProperties() {
+        File file = new File(PROPERTIES_FILE);
+        Properties properties = new Properties();
+        InputStream input = null;
+        if(file.isFile() && file.canRead()) {
+            try {
+                input = new FileInputStream(file);
+                properties.load(input);
+                LOG.info("Loaded properties from " + PROPERTIES_FILE);
+                setProperties(properties);
+            } catch (Exception e) {
+                LOG.error("Failed to load properties " + PROPERTIES_FILE + "\n", e);
+            } finally {
+                if(input != null) {
+                    try {
+                        input.close();
+                    } catch (IOException e) {
+                        LOG.error("Failed to close properties file " + PROPERTIES_FILE + "\n", e);
+                    }
+                }
+            }
+        }
+    }
+
+    static {
+        // Trick class loader into loading builders. Some of
+        // these will be needed later by Reflection classes, but need
+        // to explicitly "new" them here to get class loader to load them.
+
+        BackupDataOutputBuilder b1 = new BackupDataOutputBuilder();
+
+        RetrieveDataOutputBuilder b2 = new RetrieveDataOutputBuilder();
+        RetrieveDataInputBuilder b3 = new RetrieveDataInputBuilder();
+    }
+}
diff --git a/daexim-offsite-backup/provider/src/main/resources/daexim-offsite-backup.properties b/daexim-offsite-backup/provider/src/main/resources/daexim-offsite-backup.properties
new file mode 100755 (executable)
index 0000000..bdfa815
--- /dev/null
@@ -0,0 +1,6 @@
+# Example properties file
+
+daeximDirectory=/opt/opendaylight/current/daexim/
+credentials=admin:enc:YWRtaW4xMjM=
+nexusUrl=http://localhost:8081/nexus/content/repositories/
+podName=UNKNOWN_ODL
\ No newline at end of file
diff --git a/daexim-offsite-backup/provider/src/main/resources/org/opendaylight/blueprint/DaeximOffsiteBackup.xml b/daexim-offsite-backup/provider/src/main/resources/org/opendaylight/blueprint/DaeximOffsiteBackup.xml
new file mode 100755 (executable)
index 0000000..8e2101a
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+           xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"
+           odl:use-default-for-reference-types="true">
+
+    <reference id="dataBroker"
+               interface="org.opendaylight.controller.md.sal.binding.api.DataBroker"
+               odl:type="default" />
+
+    <reference id="rpcRegistry"
+               interface="org.opendaylight.controller.sal.binding.api.RpcProviderRegistry"
+               odl:type="default" />
+
+    <bean id="provider" class="org.onap.ccsdk.sli.northbound.daeximoffsitebackup.DaeximOffsiteBackupProvider">
+        <argument ref="dataBroker" />
+        <argument ref="rpcRegistry" />
+    </bean>
+
+    <odl:rpc-implementation ref="provider"/>
+</blueprint>
diff --git a/daexim-offsite-backup/provider/src/test/java/org/onap/ccsdk/sli/northbound/daeximoffsitebackup/DaeximOffsiteBackupProviderTest.java b/daexim-offsite-backup/provider/src/test/java/org/onap/ccsdk/sli/northbound/daeximoffsitebackup/DaeximOffsiteBackupProviderTest.java
new file mode 100644 (file)
index 0000000..65d0dca
--- /dev/null
@@ -0,0 +1,223 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * openECOMP : SDN-C
+ * ================================================================================
+ * Copyright (C) 2018 AT&T Intellectual Property. All rights
+ *                     reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.ccsdk.sli.northbound.daeximoffsitebackup;
+
+import org.eclipse.jdt.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.opendaylight.controller.md.sal.binding.api.*;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry;
+
+import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.DaeximOffsiteBackupService;
+
+import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.northbound.daeximoffsitebackup.rev180926.RetrieveDataInput;
+
+import org.opendaylight.yangtools.yang.binding.Augmentation;
+import org.opendaylight.yangtools.yang.binding.DataContainer;
+
+import com.google.common.util.concurrent.CheckedFuture;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.ExecutionException;
+
+import static org.mockito.Mockito.*;
+import static org.junit.Assert.*;
+
+public class DaeximOffsiteBackupProviderTest {
+    public DataBroker dataBroker;
+    public ReadWriteTransaction writeTransaction;
+    public CheckedFuture<Void, TransactionCommitFailedException> checkedFuture;
+    public RpcProviderRegistry rpcRegistry;
+    public DaeximOffsiteBackupProvider provider;
+    public Properties resProps;
+
+    @Before
+    public void setup() {
+        resProps = new Properties();
+        resProps.put("error-code", "200");
+        resProps.put("error-message", "Success");
+        dataBroker = mock(DataBroker.class);
+        writeTransaction = mock(ReadWriteTransaction.class);
+        checkedFuture = mock(CheckedFuture.class);
+        rpcRegistry = mock(RpcProviderRegistry.class);
+        when(rpcRegistry.addRoutedRpcImplementation(any(), any(DaeximOffsiteBackupService.class))).thenReturn(null);
+        try {
+            when(checkedFuture.get()).thenReturn(null);
+        }
+        catch(InterruptedException | ExecutionException e) {
+            e.printStackTrace();
+        }
+        when(writeTransaction.submit()).thenReturn(checkedFuture);
+        when(dataBroker.newReadWriteTransaction()).thenReturn(writeTransaction);
+
+        provider = new DaeximOffsiteBackupProvider(dataBroker, rpcRegistry);
+    }
+
+    @Test
+    public void initializeTest() {
+        provider.initialize();
+    }
+
+    @Test
+    public void closeTest() {
+        try {
+            provider.close();
+        }
+        catch(Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Test
+    public void onDataTreeChangedTest() {
+        provider.onDataTreeChanged(null);
+        // onDataTreeChanged is an empty stub
+    }
+
+    @Test
+    public void backupDataTest() {
+        try {
+            assertNotNull(provider.backupData(null));
+        }
+        catch(Exception e) {
+            fail();
+        }
+        try {
+            assertNotNull(provider.backupData(null));
+        }
+        catch(Exception e) {
+            fail();
+        }
+        try {
+            assertNotNull(provider.backupData(null));
+        }
+        catch(Exception e) {
+            fail();
+        }
+        try {
+            assertNotNull(provider.backupData(null));
+        }
+        catch(Exception e) {
+            fail();
+        }
+        try {
+            assertNotNull(provider.backupData(null));
+        }
+        catch(Exception e) {
+            fail();
+        }
+    }
+
+    @Test
+    public void retrieveDataTest() {
+        RetrieveDataInput input = new RetrieveDataInput() {
+            @Override
+            public <E extends Augmentation<RetrieveDataInput>> @Nullable E augmentation(Class<E> augmentationType) {
+                return null;
+            }
+
+            @Override
+            public String getPodName() {
+                return "Some Pod";
+            }
+
+            @Override
+            public String getTimestamp() {
+                return "Some Timestamp";
+            }
+
+            @Override
+            public Class<? extends DataContainer> getImplementedInterface() {
+                return null;
+            }
+        };
+        try {
+            assertNotNull(provider.retrieveData(input));
+        }
+        catch(Exception e) {
+            fail();
+        }
+        try {
+            assertNotNull(provider.retrieveData(input));
+        }
+        catch(Exception e) {
+            fail();
+        }
+        try {
+            assertNotNull(provider.retrieveData(input));
+        }
+        catch(Exception e) {
+            fail();
+        }
+        try {
+            assertNotNull(provider.retrieveData(input));
+        }
+        catch(Exception e) {
+            fail();
+        }
+        try {
+            assertNotNull(provider.retrieveData(input));
+        }
+        catch(Exception e) {
+            fail();
+        }
+    }
+
+    @Test
+    public void archiveOperationsTest() {
+        List<String> files = Arrays.asList("src/test/resources/fileToZip1", "src/test/resources/fileToZip2");
+        String archive = "src/test/resources/zippedArchive.zip";
+        try {
+            Method method = provider.getClass().getDeclaredMethod("createArchive", List.class, String.class);
+            method.setAccessible(true);
+            method.invoke(provider, files, archive);
+
+        }
+        catch(NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+            fail();
+        }
+
+        try {
+            Field field = provider.getClass().getDeclaredField("DAEXIM_DIR");
+            field.setAccessible(true);
+            field.set(provider, "");
+            Method method = provider.getClass().getDeclaredMethod("extractArchive", String.class);
+            method.setAccessible(true);
+            method.invoke(provider, archive);
+        }
+        catch(NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) {
+            fail();
+        }
+        finally {
+            File zip = new File(archive);
+            zip.delete();
+        }
+    }
+}
diff --git a/daexim-offsite-backup/provider/src/test/java/org/onap/ccsdk/sli/northbound/daeximoffsitebackup/DaeximOffsiteBackupUtilTest.java b/daexim-offsite-backup/provider/src/test/java/org/onap/ccsdk/sli/northbound/daeximoffsitebackup/DaeximOffsiteBackupUtilTest.java
new file mode 100644 (file)
index 0000000..16ce681
--- /dev/null
@@ -0,0 +1,40 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * openECOMP : SDN-C
+ * ================================================================================
+ * Copyright (C) 2018 AT&T Intellectual Property. All rights
+ *                     reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.ccsdk.sli.northbound.daeximoffsitebackup;
+
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+
+public class DaeximOffsiteBackupUtilTest {
+    @Test
+    public void loadProperties() {
+        try {
+            Field field = DaeximOffsiteBackupUtil.class.getDeclaredField("PROPERTIES_FILE");
+            field.setAccessible(true);
+            field.set(new DaeximOffsiteBackupUtil(), "src/test/resources/daexim-offsite-backup.properties");
+            DaeximOffsiteBackupUtil.loadProperties();
+        } catch(Exception e) {
+            // Files don't exist on build server
+        }
+    }
+}
diff --git a/daexim-offsite-backup/provider/src/test/resources/daexim-offsite-backup.properties b/daexim-offsite-backup/provider/src/test/resources/daexim-offsite-backup.properties
new file mode 100755 (executable)
index 0000000..49dd133
--- /dev/null
@@ -0,0 +1,4 @@
+daeximDirectory=/opt/opendaylight/current/daexim/
+credentials=admin:admin123
+nexusUrl=http://localhost:8081/nexus/content/repositories/
+podName=UNKNOWN_ODL
\ No newline at end of file
diff --git a/daexim-offsite-backup/provider/src/test/resources/fileToZip1 b/daexim-offsite-backup/provider/src/test/resources/fileToZip1
new file mode 100644 (file)
index 0000000..c7ef426
--- /dev/null
@@ -0,0 +1 @@
+Lorem ipsum.
\ No newline at end of file
diff --git a/daexim-offsite-backup/provider/src/test/resources/fileToZip2 b/daexim-offsite-backup/provider/src/test/resources/fileToZip2
new file mode 100644 (file)
index 0000000..c7ef426
--- /dev/null
@@ -0,0 +1 @@
+Lorem ipsum.
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index e170498..031d405 100644 (file)
--- a/pom.xml
+++ b/pom.xml
         <module>lcm</module>
         <module>dmaap-listener</module>
         <module>ueb-listener</module>
+        <module>daexim-offsite-backup</module>
         <module>features</module>
         <module>artifacts</module>
     </modules>