From 4fc631ccf42b2eccd506a94c955cfed5aae40e7f Mon Sep 17 00:00:00 2001 From: "Haddox, Anthony" Date: Wed, 23 Jan 2019 06:10:06 -0800 Subject: [PATCH] [CCSDK-987]Create GR Toolkit Initial commit of ODL feature Issue-ID: CCSDK-987 Change-Id: I6b10c4c00af09bf7f31820ba3b54e53a4fbe2160 Signed-off-by: Haddox, Anthony --- grToolkit/.gitignore | 38 + grToolkit/README.md | 316 +++++++ grToolkit/features/.gitignore | 2 + grToolkit/features/ccsdk-gr-toolkit/pom.xml | 45 + .../ccsdk-gr-toolkit/src/main/feature/feature.xml | 7 + grToolkit/features/features-gr-toolkit/pom.xml | 29 + grToolkit/features/pom.xml | 21 + grToolkit/installer/pom.xml | 137 +++ .../src/assembly/assemble_installer_zip.xml | 56 ++ .../src/assembly/assemble_mvnrepo_zip.xml | 45 + .../src/main/resources/scripts/install-feature.sh | 39 + grToolkit/model/.gitignore | 1 + grToolkit/model/pom.xml | 26 + grToolkit/model/scripts/python/yang2props.py | 78 ++ grToolkit/model/src/main/yang/gr-toolkit.yang | 183 ++++ grToolkit/pom.xml | 48 ++ grToolkit/provider/.gitignore | 1 + grToolkit/provider/pom.xml | 102 +++ .../onap/ccsdk/sli/plugins/GrToolkitProvider.java | 937 +++++++++++++++++++++ .../org/onap/ccsdk/sli/plugins/GrToolkitUtil.java | 91 ++ .../onap/ccsdk/sli/plugins/data/ClusterActor.java | 212 +++++ .../onap/ccsdk/sli/plugins/data/MemberBuilder.java | 83 ++ .../src/main/resources/gr-toolkit.properties | 15 + .../org/opendaylight/blueprint/GrToolkit.xml | 33 + pom.xml | 1 + 25 files changed, 2546 insertions(+) create mode 100755 grToolkit/.gitignore create mode 100755 grToolkit/README.md create mode 100755 grToolkit/features/.gitignore create mode 100755 grToolkit/features/ccsdk-gr-toolkit/pom.xml create mode 100755 grToolkit/features/ccsdk-gr-toolkit/src/main/feature/feature.xml create mode 100755 grToolkit/features/features-gr-toolkit/pom.xml create mode 100755 grToolkit/features/pom.xml create mode 100755 grToolkit/installer/pom.xml create mode 100755 grToolkit/installer/src/assembly/assemble_installer_zip.xml create mode 100755 grToolkit/installer/src/assembly/assemble_mvnrepo_zip.xml create mode 100755 grToolkit/installer/src/main/resources/scripts/install-feature.sh create mode 100755 grToolkit/model/.gitignore create mode 100755 grToolkit/model/pom.xml create mode 100755 grToolkit/model/scripts/python/yang2props.py create mode 100755 grToolkit/model/src/main/yang/gr-toolkit.yang create mode 100755 grToolkit/pom.xml create mode 100755 grToolkit/provider/.gitignore create mode 100755 grToolkit/provider/pom.xml create mode 100755 grToolkit/provider/src/main/java/org/onap/ccsdk/sli/plugins/GrToolkitProvider.java create mode 100755 grToolkit/provider/src/main/java/org/onap/ccsdk/sli/plugins/GrToolkitUtil.java create mode 100755 grToolkit/provider/src/main/java/org/onap/ccsdk/sli/plugins/data/ClusterActor.java create mode 100755 grToolkit/provider/src/main/java/org/onap/ccsdk/sli/plugins/data/MemberBuilder.java create mode 100755 grToolkit/provider/src/main/resources/gr-toolkit.properties create mode 100755 grToolkit/provider/src/main/resources/org/opendaylight/blueprint/GrToolkit.xml diff --git a/grToolkit/.gitignore b/grToolkit/.gitignore new file mode 100755 index 00000000..a01e90ef --- /dev/null +++ b/grToolkit/.gitignore @@ -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/grToolkit/README.md b/grToolkit/README.md new file mode 100755 index 00000000..84594496 --- /dev/null +++ b/grToolkit/README.md @@ -0,0 +1,316 @@ +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 geo-redundancy through a series of ODL integrated health checks and tools. + +## Properties File +On initialization gr-toolkit expects to find a file named ```gr-toolkit.properties``` located in the ```SDNC_CONFIG``` directory. The properties file should contain: +- ```akka.conf.location``` + - The path to the akka.conf configuration file. +- ```adm.useSsl``` + - true/false; Determines whether or not to use http or https when making requests to the Admin Portal. +- ```adm.fqdn``` + - The FQDN or url of the site's Admin Portal. +- ```adm.healthcheck``` + - The url path of the Admin Portal's health check page. +- ```adm.port.http``` + - The HTTP port for the Admin Portal. +- ```adm.port.ssl``` + - The HTTPS port for the Admin Portal. +- ```controller.credentials``` + - username:password; The credentials used to make requests to the ODL controller. +- ```controller.useSsl``` + - true/false; Determines whether or not to use http or https when making requests to the controller. +- ```controller.port.http``` + - The HTTP port for the ODL Controller. +- ```controller.port.ssl``` + - The HTTPS port for the ODL Controller. +- ```controller.port.akka``` + - The port used for Akka communications on the ODL Controller. +- ```mbean.cluster``` + - The Jolokia path for the Akka Cluster MBean. +- ```mbean.shardManager``` + - The Jolokia path for the Akka ShardManager MBean. +- ```mbean.shard.config``` + - The Jolokia path for the Akka Shard MBean. This should be templated to look like ```/jolokia/read/org.opendaylight.controller:Category=Shards,name=%s,type=DistributedConfigDatastore```. GR Toolkit will use this template with information pulled from the Akka ShardManager MBean. +- ```site.identifier``` + - A unique identifier for the site the ODL Controller resides on. + +## Site Identifier +Returns a unique site identifier of the site the ODL resides on. + +> ### Input: None +> +> ### Output +> ```json +> { +> "output": { +> "id": "UNIQUE_IDENTIFIER_HERE", +> "status": "200" +> } +> } +> ``` + +## Admin Health +Returns HEALTHY/FAULTY based on whether or not a 200 response is received from the Admin Portal's health check page. + +> ### Input: None +> +> ### Output +> ```json +> { +> "output": { +> "status": "200", +> "health": "HEALTHY" +> } +> } +> ``` + +## Database Health +Returns HEALTHY/FAULTY based on if DbLib can obtain a writeable connection from its pool. + +> ### Input: None +> +> ### Output +> ```json +> { +> "output": { +> "status": "200", +> "health": "HEALTHY" +> } +> } +> ``` + +## Cluster Health +Uses Jolokia queries to determine shard health and voting status. In a 3 ODL node configuration, 2 FAULTY nodes constitutes a FAULTY site. In a 6 node configuration it is assumed that there are 2 sites consiting of 3 nodes each. + +> ### Input: None +> +> ### Output +> ```json +> { +> "output": { +> "site1-health": "HEALTHY", +> "members": [ +> { +> "address": "member-3.node", +> "role": "member-3", +> "unreachable": false, +> "voting": true, +> "up": true, +> "replicas": [ +> { +> "shard": "member-3-shard-default-config" +> } +> ] +> }, +> { +> "address": "member-1.node", +> "role": "member-1", +> "unreachable": false, +> "voting": true, +> "up": true, +> "replicas": [ +> { +> "shard": "member-1-shard-default-config" +> } +> ] +> }, +> { +> "address": "member-5.node", +> "role": "member-5", +> "unreachable": false, +> "voting": false, +> "up": true, +> "replicas": [ +> { +> "shard": "member-5-shard-default-config" +> } +> ] +> }, +> { +> "address": "member-2.node", +> "role": "member-2", +> "unreachable": false, +> "leader": [ +> { +> "shard": "member-2-shard-default-config" +> } +> ], +> "commit-status": [ +> { +> "shard": "member-5-shard-default-config", +> "delta": 148727 +> }, +> { +> "shard": "member-4-shard-default-config", +> "delta": 148869 +> } +> ], +> "voting": true, +> "up": true, +> "replicas": [ +> { +> "shard": "member-2-shard-default-config" +> } +> ] +> }, +> { +> "address": "member-4.node", +> "role": "member-4", +> "unreachable": false, +> "voting": false, +> "up": true, +> "replicas": [ +> { +> "shard": "member-4-shard-default-config" +> } +> ] +> }, +> { +> "address": "member-6.node", +> "role": "member-6", +> "unreachable": false, +> "voting": false, +> "up": true, +> "replicas": [ +> { +> "shard": "member-6-shard-default-config" +> } +> ] +> } +> ], +> "status": "200", +> "site2-health": "HEALTHY" +> } +> } +> ``` + +## Site Health +Aggregates data from Admin Health, Database Health, and Cluster Health and returns a simplified payload containing the health of a site. A FAULTY Admin Portal or Database health status will constitute a FAULTY site; in a 3 ODL node configuration, 2 FAULTY nodes constitutes a FAULTY site. If any portion of the health check registers as FAULTY, the entire site will be designated as FAULTY. In a 6 node configuration these health checks are performed cross site as well. + +> ### Input: None +> +> ### Output +> ```json +> { +> "output": { +> "sites": [ +> { +> "id": "SITE_1", +> "role": "ACTIVE", +> "health": "HEALTHY" +> }, +> { +> "id": "SITE_2", +> "role": "STANDBY", +> "health": "FAULTY" +> } +> ], +> "status": "200" +> } +> } +> ``` + +## Halt Akka Traffic +Places rules in IP Tables to block Akka traffic to/from a specific node on a specified port. + +> ### Input: +> ```json +> { +> "input": { +> "node-info": [ +> { +> "node": "your.odl.node", +> "port": "2550" +> } +> ] +> } +> } +> ``` +> +> ### Output +> ```json +> { +> "output": { +> "status": "200" +> } +> } +> ``` + +## Resume Akka Traffic +Removes rules in IP Tables to allow Akka traffic to/from a specifc node on a specified port. + +> ### Input: +> ```json +> { +> "input": { +> "node-info": [ +> { +> "node": "your.odl.node", +> "port": "2550" +> } +> ] +> } +> } +> ``` +> +> ### Output +> ```json +> { +> "output": { +> "status": "200" +> } +> } +> ``` + +## Failover +Only usable in a 6 ODL node configuration. Determines which site is active/standby, switches voting to the standby site, and isolates the old active site. If backupData=true an MD-SAL export will be scheduled and backed up to a Nexus server (requires ccsdk.sli.northbound.daexim-offsite-backup feature). + +> ### Input: +> ```json +> { +> "input": { +> "backupData": "true" +> } +> } +> ``` +> +> ### Output +> ```json +> { +> "output": { +> "status": "200", +> "message": "Failover complete." +> } +> } +> ``` \ No newline at end of file diff --git a/grToolkit/features/.gitignore b/grToolkit/features/.gitignore new file mode 100755 index 00000000..05a0d25f --- /dev/null +++ b/grToolkit/features/.gitignore @@ -0,0 +1,2 @@ +/target-ide/ +/bin/ diff --git a/grToolkit/features/ccsdk-gr-toolkit/pom.xml b/grToolkit/features/ccsdk-gr-toolkit/pom.xml new file mode 100755 index 00000000..f4865a5b --- /dev/null +++ b/grToolkit/features/ccsdk-gr-toolkit/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + + org.onap.ccsdk.parent + single-feature-parent + 1.2.1-SNAPSHOT + + + + org.onap.ccsdk.sli.plugins + ccsdk-gr-toolkit + 0.4.1-SNAPSHOT + feature + + ccsdk-sli-plugins :: gr-toolkit :: ${project.artifactId} + + + + org.opendaylight.controller + odl-mdsal-broker + xml + features + + + org.onap.ccsdk.sli.core + ccsdk-sli + ${ccsdk.sli.core.version} + xml + features + + + ${project.groupId} + gr-toolkit-model + ${project.version} + + + ${project.groupId} + gr-toolkit-provider + ${project.version} + + + diff --git a/grToolkit/features/ccsdk-gr-toolkit/src/main/feature/feature.xml b/grToolkit/features/ccsdk-gr-toolkit/src/main/feature/feature.xml new file mode 100755 index 00000000..9f8278a0 --- /dev/null +++ b/grToolkit/features/ccsdk-gr-toolkit/src/main/feature/feature.xml @@ -0,0 +1,7 @@ + + + mvn:org.onap.ccsdk.sli.core/ccsdk-sli/LATEST/xml/features + + ccsdk-sli + + diff --git a/grToolkit/features/features-gr-toolkit/pom.xml b/grToolkit/features/features-gr-toolkit/pom.xml new file mode 100755 index 00000000..d74cbb63 --- /dev/null +++ b/grToolkit/features/features-gr-toolkit/pom.xml @@ -0,0 +1,29 @@ + + + 4.0.0 + + + org.onap.ccsdk.parent + feature-repo-parent + 1.2.1-SNAPSHOT + + + + org.onap.ccsdk.sli.plugins + features-gr-toolkit + 0.4.1-SNAPSHOT + feature + + ccsdk-sli-plugins :: gr-toolkit :: ${project.artifactId} + + + + ${project.groupId} + ccsdk-gr-toolkit + ${project.version} + xml + features + + + + diff --git a/grToolkit/features/pom.xml b/grToolkit/features/pom.xml new file mode 100755 index 00000000..3f6ab618 --- /dev/null +++ b/grToolkit/features/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + org.onap.ccsdk.parent + odlparent-lite + 1.2.1-SNAPSHOT + + + + ccsdk-sli-plugins :: gr-toolkit :: ${project.artifactId} + gr-toolkit-features + org.onap.ccsdk.sli.plugins + 0.4.1-SNAPSHOT + pom + + + ccsdk-gr-toolkit + features-gr-toolkit + + diff --git a/grToolkit/installer/pom.xml b/grToolkit/installer/pom.xml new file mode 100755 index 00000000..b15c877f --- /dev/null +++ b/grToolkit/installer/pom.xml @@ -0,0 +1,137 @@ + + + 4.0.0 + + org.onap.ccsdk.parent + odlparent-lite + 1.2.1-SNAPSHOT + + + + ccsdk-sli-plugins :: gr-toolkit :: ${project.artifactId} + org.onap.ccsdk.sli.plugins + gr-toolkit-installer + 0.4.1-SNAPSHOT + pom + + ccsdk-gr-toolkit + ${application.name} + mvn:org.onap.ccsdk.sli.plugins/${features.boot}/${project.version}/xml/features + false + + + + org.onap.ccsdk.sli.plugins + ${application.name} + ${project.version} + xml + features + + + * + * + + + + + org.onap.ccsdk.sli.plugins + gr-toolkit-provider + ${project.version} + + + org.onap.ccsdk.sli.plugins + gr-toolkit-model + ${project.version} + + + + + + maven-assembly-plugin + 2.6 + + + maven-repo-zip + + single + + package + + true + stage/${application.name}-${project.version} + + src/assembly/assemble_mvnrepo_zip.xml + + true + + + + installer-zip + + single + + package + + true + ${application.name}-${project.version}-installer + + src/assembly/assemble_installer_zip.xml + + false + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-dependencies + + copy-dependencies + + prepare-package + + false + ${project.build.directory}/assembly/system + false + true + true + true + false + false + org.onap.ccsdk.sli.plugins + provided + + + + + + maven-resources-plugin + 2.6 + + + copy-version + + copy-resources + + validate + + ${basedir}/target/stage + + + src/main/resources/scripts + + install-feature.sh + + true + + + + + + + + + diff --git a/grToolkit/installer/src/assembly/assemble_installer_zip.xml b/grToolkit/installer/src/assembly/assemble_installer_zip.xml new file mode 100755 index 00000000..41d23e88 --- /dev/null +++ b/grToolkit/installer/src/assembly/assemble_installer_zip.xml @@ -0,0 +1,56 @@ + + + + + + installer_zip + + zip + + + + false + + + + target/stage/ + ${application.name} + 755 + + *.sh + + + + target/stage/ + ${application.name} + 644 + + *.sh + + + + diff --git a/grToolkit/installer/src/assembly/assemble_mvnrepo_zip.xml b/grToolkit/installer/src/assembly/assemble_mvnrepo_zip.xml new file mode 100755 index 00000000..fe7a90ce --- /dev/null +++ b/grToolkit/installer/src/assembly/assemble_mvnrepo_zip.xml @@ -0,0 +1,45 @@ + + + + + + repo + + zip + + + + false + + + + target/assembly/ + . + + + + diff --git a/grToolkit/installer/src/main/resources/scripts/install-feature.sh b/grToolkit/installer/src/main/resources/scripts/install-feature.sh new file mode 100755 index 00000000..1d7be149 --- /dev/null +++ b/grToolkit/installer/src/main/resources/scripts/install-feature.sh @@ -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/grToolkit/model/.gitignore b/grToolkit/model/.gitignore new file mode 100755 index 00000000..eacf31a6 --- /dev/null +++ b/grToolkit/model/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/grToolkit/model/pom.xml b/grToolkit/model/pom.xml new file mode 100755 index 00000000..348b3478 --- /dev/null +++ b/grToolkit/model/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + + org.onap.ccsdk.parent + binding-parent + 1.2.1-SNAPSHOT + + + ccsdk-sli-plugins :: gr-toolkit :: ${project.artifactId} + org.onap.ccsdk.sli.plugins + gr-toolkit-model + 0.4.1-SNAPSHOT + bundle + + + + org.opendaylight.mdsal.model + ietf-inet-types-2013-07-15 + + + org.opendaylight.mdsal.model + ietf-yang-types-20130715 + + + diff --git a/grToolkit/model/scripts/python/yang2props.py b/grToolkit/model/scripts/python/yang2props.py new file mode 100755 index 00000000..85daccfb --- /dev/null +++ b/grToolkit/model/scripts/python/yang2props.py @@ -0,0 +1,78 @@ +#!/usr/bin/python + +### +# ============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========================================================= +### + +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 ' + 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/grToolkit/model/src/main/yang/gr-toolkit.yang b/grToolkit/model/src/main/yang/gr-toolkit.yang new file mode 100755 index 00000000..951201c5 --- /dev/null +++ b/grToolkit/model/src/main/yang/gr-toolkit.yang @@ -0,0 +1,183 @@ +module gr-toolkit{ + namespace "org:onap:ccsdk:sli:plugins:gr-toolkit"; + prefix gr-toolkit; + + import ietf-inet-types { + prefix inet; + } + import ietf-yang-types { + prefix yang; + } + description + "This ODL feature is designed to gauge the health of all cluster members."; + revision "2018-09-26" { + description + "Release 19.02 draft"; + } + + grouping member { + leaf address { + type string; + mandatory true; + } + leaf role { + type string; + mandatory true; + } + leaf up { + type boolean; + mandatory true; + } + leaf unreachable { + type boolean; + mandatory true; + } + leaf voting { + type boolean; + mandatory true; + } + list leader { + leaf shard { + type string; + mandatory true; + } + } + list replicas { + leaf shard { + type string; + mandatory true; + } + } + list commit-status { + leaf shard { + type string; + mandatory true; + } + leaf delta { + type int32; + mandatory true; + } + } + } + + grouping site { + leaf id { + type string; + mandatory true; + } + leaf role { + type string; + mandatory true; + } + leaf health { + type string; + mandatory true; + } + } + + grouping node { + leaf node { + type string; + mandatory true; + } + leaf port { + type string; + mandatory true; + } + } + + rpc cluster-health { + description + "Parses akka.conf for seed nodes and queries Jolokia for the health + of each node."; + output { + leaf status { type string; } + leaf message { type string; } + leaf site1-health { type string; } + leaf site2-health { type string; } + list members { + uses member; + } + } + } + + rpc database-health { + description + "Uses DbLibService connection info to determine if the database is reachable."; + output { + leaf status { type string; } + leaf health { type string; } + } + } + + rpc admin-health { + description + "Pings the admin portal health check to determine if the admin portal is reachable."; + output { + leaf status { type string; } + leaf health { type string; } + } + } + + rpc site-health { + description + "Gathers health information on the ODL cluster, database, and admin portal + to determine if the entire site is in a healthy state."; + output { + leaf status { type string; } + list sites { + uses site; + } + } + } + + rpc site-identifier { + description + "Returns the unique site identifier."; + output { + leaf status { type string; } + leaf id { type string; mandatory true; } + } + } + + rpc halt-akka-traffic { + description + "Invokes a utility script to halt traffic to the akka port."; + input { + list node-info { + uses node; + } + } + output { + leaf status { type string; } + } + } + + rpc resume-akka-traffic { + description + "Invokes a utility script to resume traffic to the akka port."; + input { + list node-info { + uses node; + } + } + output { + leaf status { type string; } + } + } + + rpc failover { + description + "Performs a failover from primary site to standby site."; + input { + leaf backupData { + type string; + default "false"; + } + } + output { + leaf status { type string; } + leaf message { type string; } + } + } +} diff --git a/grToolkit/pom.xml b/grToolkit/pom.xml new file mode 100755 index 00000000..c36e2f16 --- /dev/null +++ b/grToolkit/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + + org.onap.ccsdk.parent + odlparent-lite + 1.2.1-SNAPSHOT + + + + org.onap.ccsdk.sli.plugins + gr-toolkit + 0.4.1-SNAPSHOT + pom + + ccsdk-sli-plugins :: gr-toolkit + ODL bundle used to judge health of SDN-C application. + + + model + features + installer + provider + + + + + + org.onap.ccsdk.sli.plugins + gr-toolkit-features + features + xml + ${project.version} + + + org.onap.ccsdk.sli.plugins + gr-toolkit-model + ${project.version} + + + org.onap.ccsdk.sli.plugins + gr-toolkit-provider + ${project.version} + + + + diff --git a/grToolkit/provider/.gitignore b/grToolkit/provider/.gitignore new file mode 100755 index 00000000..eacf31a6 --- /dev/null +++ b/grToolkit/provider/.gitignore @@ -0,0 +1 @@ +/target-ide/ diff --git a/grToolkit/provider/pom.xml b/grToolkit/provider/pom.xml new file mode 100755 index 00000000..75b6d431 --- /dev/null +++ b/grToolkit/provider/pom.xml @@ -0,0 +1,102 @@ + + + 4.0.0 + + org.onap.ccsdk.parent + binding-parent + 1.2.1-SNAPSHOT + + + ccsdk-sli-plugins :: gr-toolkit :: ${project.artifactId} + org.onap.ccsdk.sli.plugins + gr-toolkit-provider + 0.4.1-SNAPSHOT + bundle + + + + + org.apache.felix + maven-bundle-plugin + true + + + org.opendaylight.controller.config.yang.config.gr-toolkit_provider + * + + + + + + + + + org.onap.ccsdk.sli.plugins + gr-toolkit-model + ${project.version} + + + org.opendaylight.controller + sal-binding-api + + + org.onap.ccsdk.sli.core + dblib-provider + ${ccsdk.sli.core.version} + + + org.onap.ccsdk.sli.core + sli-common + ${ccsdk.sli.core.version} + + + org.onap.ccsdk.sli.core + sli-provider + ${ccsdk.sli.core.version} + + + sal-test-model + org.opendaylight.controller + test + + + org.opendaylight.controller + sal-binding-broker-impl + test + + + org.opendaylight.controller + sal-binding-broker-impl + tests + test-jar + test + + + junit + junit + 4.11 + test + + + org.mockito + mockito-core + 1.10.19 + test + + + org.json + json + + + org.opendaylight.controller + sal-distributed-datastore + provided + + + com.typesafe.akka + akka-cluster_2.12 + ${akka.version} + provided + + + diff --git a/grToolkit/provider/src/main/java/org/onap/ccsdk/sli/plugins/GrToolkitProvider.java b/grToolkit/provider/src/main/java/org/onap/ccsdk/sli/plugins/GrToolkitProvider.java new file mode 100755 index 00000000..249640af --- /dev/null +++ b/grToolkit/provider/src/main/java/org/onap/ccsdk/sli/plugins/GrToolkitProvider.java @@ -0,0 +1,937 @@ +/*- + * ============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.plugins; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Properties; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +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.onap.ccsdk.sli.core.dblib.DbLibService; +import org.onap.ccsdk.sli.plugins.data.ClusterActor; +import org.onap.ccsdk.sli.plugins.data.MemberBuilder; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import org.opendaylight.controller.cluster.datastore.DistributedDataStoreInterface; +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.NotificationPublishService; +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.plugins.gr.toolkit.rev180926.AdminHealthInput; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.AdminHealthOutput; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.AdminHealthOutputBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.ClusterHealthInput; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.ClusterHealthOutput; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.ClusterHealthOutputBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.DatabaseHealthInput; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.DatabaseHealthOutput; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.DatabaseHealthOutputBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.FailoverInput; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.FailoverOutput; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.FailoverOutputBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.GrToolkitService; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.HaltAkkaTrafficInput; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.HaltAkkaTrafficOutput; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.HaltAkkaTrafficOutputBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.Member; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.ResumeAkkaTrafficInput; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.ResumeAkkaTrafficOutput; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.ResumeAkkaTrafficOutputBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.Site; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.SiteHealthInput; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.SiteHealthOutput; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.SiteHealthOutputBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.SiteIdentifierInput; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.SiteIdentifierOutput; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.SiteIdentifierOutputBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.site.health.output.SitesBuilder; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.common.RpcResultBuilder; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class GrToolkitProvider implements AutoCloseable, GrToolkitService, DataTreeChangeListener { + private static final String PROPERTIES_FILE = System.getenv("SDNC_CONFIG_DIR") + "/gr-toolkit.properties"; + private static final String HEALTHY = "HEALTHY"; + private static final String FAULTY = "FAULTY"; + private static String AKKA_CONFIG; + private static String JOLOKIA_CLUSTER_PATH; + private static String SHARD_MANAGER_PATH; + private static String SHARD_PATH_TEMPLATE; + private static String CREDENTIALS; + private static String HTTP_PROTOCOL; + private static String SITE_IDENTIFIER = System.getenv("SITE_NAME"); + private final Logger log = LoggerFactory.getLogger(GrToolkitProvider.class); + private final String appName = "gr-toolkit"; + private final ExecutorService executor; + protected DataBroker dataBroker; + protected NotificationPublishService notificationService; + protected RpcProviderRegistry rpcRegistry; + protected BindingAwareBroker.RpcRegistration rpcRegistration; + protected DbLibService dbLib; + private String member; + private ClusterActor self; + private HashMap members; + private SiteConfiguration siteConfiguration; + private Properties properties; + private DistributedDataStoreInterface configDatastore; + public GrToolkitProvider(DataBroker dataBroker, + NotificationPublishService notificationProviderService, + RpcProviderRegistry rpcProviderRegistry, + DistributedDataStoreInterface configDatastore, + DbLibService dbLibService) { + this.log.info("Creating provider for " + appName); + this.executor = Executors.newFixedThreadPool(1); + this.dataBroker = dataBroker; + this.notificationService = notificationProviderService; + this.rpcRegistry = rpcProviderRegistry; + this.configDatastore = configDatastore; + this.dbLib = dbLibService; + initialize(); + } + + public void initialize() { + log.info("Initializing provider for " + appName); + // Create the top level containers + createContainers(); + try { + GrToolkitUtil.loadProperties(); + } catch (Exception e) { + log.error("Caught Exception while trying to load properties file.", e); + } + + setProperties(); + defineMembers(); + + rpcRegistration = rpcRegistry.addRpcImplementation(GrToolkitService.class, this); + log.info("Initialization complete for " + appName); + } + + private void setProperties() { + log.info("Loading properties from " + PROPERTIES_FILE); + properties = new Properties(); + File propertiesFile = new File(PROPERTIES_FILE); + if(!propertiesFile.exists()) { + log.warn("Properties file not found."); + return; + } + try(FileInputStream fileInputStream = new FileInputStream(propertiesFile)) { + properties.load(fileInputStream); + if(!properties.containsKey("site.identifier")) { + properties.put("site.identifier", "Unknown Site"); + } + String port = "true".equals(properties.getProperty("controller.useSsl").trim()) ? properties.getProperty("controller.port.ssl").trim() : properties.getProperty("controller.port.http").trim(); + HTTP_PROTOCOL = "true".equals(properties.getProperty("controller.useSsl").trim()) ? "https://" : "http://"; + AKKA_CONFIG = properties.getProperty("akka.conf.location").trim(); + JOLOKIA_CLUSTER_PATH = ":" + port + properties.getProperty("mbean.cluster").trim(); + SHARD_MANAGER_PATH = ":" + port + properties.getProperty("mbean.shardManager").trim(); + SHARD_PATH_TEMPLATE = ":" + port + properties.getProperty("mbean.shard.config").trim(); + if(SITE_IDENTIFIER == null || SITE_IDENTIFIER.isEmpty()) { + SITE_IDENTIFIER = properties.getProperty("site.identifier").trim(); + } + CREDENTIALS = properties.getProperty("controller.credentials").trim(); + log.info("Loaded properties."); + } catch(IOException e) { + log.error("Error loading properties.", e); + } + } + + private void defineMembers() { + member = configDatastore.getActorContext().getCurrentMemberName().getName(); + log.info("Cluster member: " + member); + + log.info("Parsing akka.conf for cluster members..."); + try { + File akkaConfig = new File(AKKA_CONFIG); + FileReader fileReader = new FileReader(akkaConfig); + BufferedReader bufferedReader = new BufferedReader(fileReader); + String line; + while((line = bufferedReader.readLine()) != null) { + if(line.contains("seed-nodes =")) { + parseSeedNodes(line); + break; + } + } + bufferedReader.close(); + fileReader.close(); + } catch(IOException e) { + log.error("Couldn't load akka", e); + } + log.info("self:\n{}", self.toString()); + } + + private void createContainers() { + final WriteTransaction t = dataBroker.newReadWriteTransaction(); + try { + CheckedFuturecheckedFuture = 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 if you have custom initialization intelligence + } + + @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) { + log.info("onDataTreeChanged() called. but there is no change here"); + } + + @Override + public ListenableFuture> clusterHealth(ClusterHealthInput input) { + log.info(appName + ":cluster-health invoked."); + getControllerHealth(); + return buildClusterHealthOutput("200"); + } + + @Override + public ListenableFuture> siteHealth(SiteHealthInput input) { + log.info(appName + ":site-health invoked."); + getControllerHealth(); + return buildSiteHealthOutput("200", getAdminHealth(), getDatabaseHealth()); + } + + @Override + public ListenableFuture> databaseHealth(DatabaseHealthInput input) { + log.info(appName + ":database-health invoked."); + DatabaseHealthOutputBuilder outputBuilder = new DatabaseHealthOutputBuilder(); + outputBuilder.setStatus("200"); + outputBuilder.setHealth(getDatabaseHealth()); + + return Futures.immediateFuture(RpcResultBuilder.status(true).withResult(outputBuilder.build()).build()); + } + + @Override + public ListenableFuture> adminHealth(AdminHealthInput input) { + log.info(appName + ":admin-health invoked."); + AdminHealthOutputBuilder outputBuilder = new AdminHealthOutputBuilder(); + outputBuilder.setStatus("200"); + outputBuilder.setHealth(getAdminHealth()); + + return Futures.immediateFuture(RpcResultBuilder.status(true).withResult(outputBuilder.build()).build()); + } + + @Override + public ListenableFuture> haltAkkaTraffic(HaltAkkaTrafficInput input) { + log.info(appName + ":halt-akka-traffic invoked."); + HaltAkkaTrafficOutputBuilder outputBuilder = new HaltAkkaTrafficOutputBuilder(); + outputBuilder.setStatus("200"); + modifyIpTables(IpTables.Add, input.getNodeInfo().toArray()); + + return Futures.immediateFuture(RpcResultBuilder.status(true).withResult(outputBuilder.build()).build()); + } + + @Override + public ListenableFuture> resumeAkkaTraffic(ResumeAkkaTrafficInput input) { + log.info(appName + ":resume-akka-traffic invoked."); + ResumeAkkaTrafficOutputBuilder outputBuilder = new ResumeAkkaTrafficOutputBuilder(); + outputBuilder.setStatus("200"); + modifyIpTables(IpTables.Delete, input.getNodeInfo().toArray()); + + return Futures.immediateFuture(RpcResultBuilder.status(true).withResult(outputBuilder.build()).build()); + } + + @Override + public ListenableFuture> siteIdentifier(SiteIdentifierInput input) { + log.info(appName + ":site-identifier invoked."); + SiteIdentifierOutputBuilder outputBuilder = new SiteIdentifierOutputBuilder(); + outputBuilder.setStatus("200"); + outputBuilder.setId(SITE_IDENTIFIER); + + return Futures.immediateFuture(RpcResultBuilder.status(true).withResult(outputBuilder.build()).build()); + } + + @Override + public ListenableFuture> failover(FailoverInput input) { + log.info(appName + ":failover invoked."); + FailoverOutputBuilder outputBuilder = new FailoverOutputBuilder(); + if(siteConfiguration != SiteConfiguration.Geo) { + log.info("Cannot failover non-Geo site."); + outputBuilder.setMessage("Failover aborted. This is not a Geo configuration."); + outputBuilder.setStatus("400"); + return Futures.immediateFuture(RpcResultBuilder.status(true).withResult(outputBuilder.build()).build()); + } + ArrayList activeSite = new ArrayList<>(); + ArrayList standbySite = new ArrayList<>(); + + log.info("Performing preliminary cluster health check..."); + // Necessary to populate all member info. Health is not used for judgement calls. + getControllerHealth(); + + log.info("Determining active site..."); + for(String key : members.keySet()) { + if(members.get(key).isVoting()) { + activeSite.add(members.get(key)); + log.debug("Active Site member: " + key); + } + else { + standbySite.add(members.get(key)); + log.debug("Standby Site member: " + key); + } + } + + String port = "true".equals(properties.getProperty("controller.useSsl")) ? properties.getProperty("controller.port.ssl") : properties.getProperty("controller.port.http"); + + if(Boolean.parseBoolean(input.getBackupData())) { + log.info("Backing up data..."); + for(ClusterActor actor : activeSite) { + try { + // Schedule backup + log.info("Scheduling backup for: " + actor.getNode()); + getRequestContent(HTTP_PROTOCOL + actor.getNode() + ":" + port + "/restconf/operations/data-export-import:schedule-export", HttpMethod.Post, ""); + try { + // Move data offsite + log.info("Backing up data for: " + actor.getNode()); + getRequestContent(HTTP_PROTOCOL + actor.getNode() + ":" + port + "/restconf/operations/daexim-offsite-backup:backup-data", HttpMethod.Post); + } catch(IOException e) { + log.error("Error backing up data.", e); + throw e; + } + } + catch(IOException e) { + log.error("Error exporting MD-SAL data.", e); + } + } + } + + log.info("Changing voting for all shards to standby site..."); + try { + JSONObject votingInput = new JSONObject(); + JSONObject inputBlock = new JSONObject(); + JSONArray votingStateArray = new JSONArray(); + JSONObject memberVotingState; + for(ClusterActor actor : activeSite) { + memberVotingState = new JSONObject(); + memberVotingState.put("member-name", actor.getMember()); + memberVotingState.put("voting", false); + votingStateArray.put(memberVotingState); + } + for(ClusterActor actor : standbySite) { + memberVotingState = new JSONObject(); + memberVotingState.put("member-name", actor.getMember()); + memberVotingState.put("voting", true); + votingStateArray.put(memberVotingState); + } + inputBlock.put("member-voting-state", votingStateArray); + votingInput.put("input", inputBlock); + log.debug(votingInput.toString(2)); + // Change voting all shards + getRequestContent(HTTP_PROTOCOL + self.getNode() + ":" + port + "/restconf/operations/cluster-admin:change-member-voting-states-for-all-shards", HttpMethod.Post, votingInput.toString()); + } catch(IOException e) { + log.error("Changing voting", e); + outputBuilder.setMessage("Failover aborted. Failed to change voting."); + outputBuilder.setStatus("500"); + return Futures.immediateFuture(RpcResultBuilder.status(true).withResult(outputBuilder.build()).build()); + } + + // Halt akka traffic + log.info("Halting Akka traffic..."); + for(ClusterActor actor : standbySite) { + try { + log.info("Halting Akka traffic for: " + actor.getNode()); + // Build JSON with activeSite actor.getNode() and actor.getAkkaPort(); + JSONObject akkaInput = new JSONObject(); + JSONObject inputBlock = new JSONObject(); + JSONArray votingStateArray = new JSONArray(); + JSONObject nodeInfo; + for(ClusterActor node : activeSite) { + nodeInfo = new JSONObject(); + nodeInfo.put("node", node.getNode()); + nodeInfo.put("port", node.getAkkaPort()); + votingStateArray.put(nodeInfo); + } + inputBlock.put("node-info", votingStateArray); + akkaInput.put("input", inputBlock); + getRequestContent(HTTP_PROTOCOL + actor.getNode() + ":" + port + "/restconf/operations/gr-toolkit:halt-akka-traffic", HttpMethod.Post, akkaInput.toString()); + } catch(IOException e) { + log.error("Could not halt Akka traffic for: " + actor.getNode(), e); + } + } + + // Set unreachable + log.info("Setting site unreachable..."); + JSONObject jolokiaInput = new JSONObject(); + jolokiaInput.put("type", "EXEC"); + jolokiaInput.put("mbean", "akka:type=Cluster"); + jolokiaInput.put("operation", "down"); + JSONArray arguments = new JSONArray(); + for(ClusterActor actor : activeSite) { + // Build Jolokia input + //TODO: May need to change from akka port to actor.getAkkaPort() + arguments.put("akka.tcp://opendaylight-cluster-data@" + actor.getNode() + ":" + properties.getProperty("controller.port.akka")); + } + jolokiaInput.put("arguments", arguments); + log.debug(jolokiaInput.toString(2)); + try { + log.info("Setting nodes unreachable"); + getRequestContent(HTTP_PROTOCOL + standbySite.get(0).getNode() + ":" + port + "/jolokia", HttpMethod.Post, jolokiaInput.toString()); + } catch(IOException e) { + log.error("Error setting nodes unreachable", e); + } + + log.info(appName + ":failover complete."); + + outputBuilder.setMessage("Failover complete."); + outputBuilder.setStatus("200"); + return Futures.immediateFuture(RpcResultBuilder.status(true).withResult(outputBuilder.build()).build()); + } + + private ListenableFuture> buildClusterHealthOutput(String statusCode) { + ClusterHealthOutputBuilder outputBuilder = new ClusterHealthOutputBuilder(); + outputBuilder.setStatus(statusCode); + outputBuilder.setMembers((List) new ArrayList()); + int site1Health = 0; + int site2Health = 0; + + for(String key : members.keySet()) { + if(members.get(key).isUp() && !members.get(key).isUnreachable()) { + if(ClusterActor.SITE_1.equals(members.get(key).getSite())) + site1Health++; + else if(ClusterActor.SITE_2.equals(members.get(key).getSite())) + site2Health++; + } + outputBuilder.getMembers().add(new MemberBuilder(members.get(key)).build()); + } + if(siteConfiguration == SiteConfiguration.Solo) { + outputBuilder.setSite1Health(HEALTHY); + } + else { + if(site1Health > 1) { + outputBuilder.setSite1Health(HEALTHY); + } + else { + outputBuilder.setSite1Health(FAULTY); + } + } + if(siteConfiguration == SiteConfiguration.Geo) { + if(site2Health > 1) { + outputBuilder.setSite2Health(HEALTHY); + } + else { + outputBuilder.setSite2Health(FAULTY); + } + } + + RpcResult rpcResult = RpcResultBuilder.status(true).withResult(outputBuilder.build()).build(); + return Futures.immediateFuture(rpcResult); + } + + private ListenableFuture> buildSiteHealthOutput(String statusCode, String adminHealth, String databaseHealth) { + SiteHealthOutputBuilder outputBuilder = new SiteHealthOutputBuilder(); + outputBuilder.setStatus(statusCode); + outputBuilder.setSites((List) new ArrayList()); + + if(siteConfiguration != SiteConfiguration.Geo) { + int healthyODLs = 0; + SitesBuilder builder = new SitesBuilder(); + for(String key : members.keySet()) { + if(members.get(key).isUp() && !members.get(key).isUnreachable()) { + healthyODLs++; + } + } + if(siteConfiguration != SiteConfiguration.Solo) { + builder.setHealth(HEALTHY); + builder.setRole("ACTIVE"); + builder.setId(SITE_IDENTIFIER); + } + else { + builder = getSitesBuilder(healthyODLs, true, HEALTHY.equals(adminHealth), HEALTHY.equals(databaseHealth), SITE_IDENTIFIER); + } + outputBuilder.getSites().add(builder.build()); + } + else { + int site1HealthyODLs = 0; + int site2HealthyODLs = 0; + boolean site1Voting = false; + boolean site2Voting = false; + boolean performedCrossSiteHealthCheck = false; + boolean crossSiteAdminHealthy = false; + boolean crossSiteDbHealthy = false; + String crossSiteIdentifier = "UNKNOWN_SITE"; + String port = "true".equals(properties.getProperty("controller.useSsl")) ? properties.getProperty("controller.port.ssl") : properties.getProperty("controller.port.http"); + if(isSite1()) { + // Make calls over to site 2 healthchecks + for(String key : members.keySet()) { + if(members.get(key).isUp() && !members.get(key).isUnreachable()) { + if(ClusterActor.SITE_1.equals(members.get(key).getSite())) { + site1HealthyODLs++; + if(members.get(key).isVoting()) { + site1Voting = true; + } + } + else { + site2HealthyODLs++; + if(members.get(key).isVoting()) { + site2Voting = true; + } + if(!performedCrossSiteHealthCheck) { + try { + String content = getRequestContent(HTTP_PROTOCOL + members.get(key).getNode() + ":" + port + "/restconf/operations/gr-toolkit:site-identifier", HttpMethod.Post); + crossSiteIdentifier = new JSONObject(content).getJSONObject("output").getString("id"); + crossSiteDbHealthy = crossSiteHealthRequest(HTTP_PROTOCOL + members.get(key).getNode() + ":" + port + "/restconf/operations/gr-toolkit:database-health"); + crossSiteAdminHealthy = crossSiteHealthRequest(HTTP_PROTOCOL + members.get(key).getNode() + ":" + port + "/restconf/operations/gr-toolkit:admin-health"); + performedCrossSiteHealthCheck = true; + } catch(Exception e) { + log.error("Cannot get site identifier from " + members.get(key).getNode(), e); + } + } + } + } + } + SitesBuilder builder = getSitesBuilder(site1HealthyODLs, site1Voting, HEALTHY.equals(adminHealth), HEALTHY.equals(databaseHealth), SITE_IDENTIFIER); + outputBuilder.getSites().add(builder.build()); + builder = getSitesBuilder(site2HealthyODLs, site2Voting, crossSiteAdminHealthy, crossSiteDbHealthy, crossSiteIdentifier); + outputBuilder.getSites().add(builder.build()); + } + else { + // Make calls over to site 1 healthchecks + for(String key : members.keySet()) { + if(members.get(key).isUp() && !members.get(key).isUnreachable()) { + if(ClusterActor.SITE_1.equals(members.get(key).getSite())) { + site1HealthyODLs++; + if(members.get(key).isVoting()) { + site1Voting = true; + } + if(!performedCrossSiteHealthCheck) { + try { + String content = getRequestContent(HTTP_PROTOCOL + members.get(key).getNode() + ":" + port + "/restconf/operations/gr-toolkit:site-identifier", HttpMethod.Post); + crossSiteIdentifier = new JSONObject(content).getJSONObject("output").getString("id"); + crossSiteDbHealthy = crossSiteHealthRequest(HTTP_PROTOCOL + members.get(key).getNode() + ":" + port + "/restconf/operations/gr-toolkit:database-health"); + crossSiteAdminHealthy = crossSiteHealthRequest(HTTP_PROTOCOL + members.get(key).getNode() + ":" + port + "/restconf/operations/gr-toolkit:admin-health"); + performedCrossSiteHealthCheck = true; + } catch(Exception e) { + log.error("Cannot get site identifier from " + members.get(key).getNode(), e); + } + } + } + else { + site2HealthyODLs++; + if(members.get(key).isVoting()) { + site2Voting = true; + } + } + } + } + // Build Output + SitesBuilder builder = getSitesBuilder(site1HealthyODLs, site1Voting, crossSiteAdminHealthy, crossSiteDbHealthy, crossSiteIdentifier); + outputBuilder.getSites().add(builder.build()); + builder = getSitesBuilder(site2HealthyODLs, site2Voting, HEALTHY.equals(adminHealth), HEALTHY.equals(databaseHealth), SITE_IDENTIFIER); + outputBuilder.getSites().add(builder.build()); + } + } + + RpcResult rpcResult = RpcResultBuilder.status(true).withResult(outputBuilder.build()).build(); + return Futures.immediateFuture(rpcResult); + } + + private SitesBuilder getSitesBuilder(int siteHealthyODLs, boolean siteVoting, boolean adminHealthy, boolean dbHealthy, String siteIdentifier) { + SitesBuilder builder = new SitesBuilder(); + if(siteHealthyODLs > 1) { + builder.setHealth(HEALTHY); + } + else { + log.warn(siteIdentifier + " Healthy ODLs: " + siteHealthyODLs); + builder.setHealth(FAULTY); + } + if(!adminHealthy) { + log.warn(siteIdentifier + " Admin Health: " + FAULTY); + builder.setHealth(FAULTY); + } + if(!dbHealthy) { + log.warn(siteIdentifier + " Database Health: " + FAULTY); + builder.setHealth(FAULTY); + } + if(siteVoting) { + builder.setRole("ACTIVE"); + } + else { + builder.setRole("STANDBY"); + } + builder.setId(siteIdentifier); + return builder; + } + + private boolean isSite1() { + int memberNumber = Integer.parseInt(member.split("-")[1]); + boolean isSite1 = memberNumber < 4; + log.info("isSite1(): " + isSite1); + return isSite1; + } + + private void parseSeedNodes(String line) { + members = new HashMap<>(); + line = line.substring(line.indexOf("[\""), line.indexOf("]")); + String[] splits = line.split(","); + + for(int ndx = 0; ndx < splits.length; ndx++) { + String nodeName = splits[ndx]; + int delimLocation = nodeName.indexOf("@"); + String port = nodeName.substring(splits[ndx].indexOf(":", delimLocation) + 1, splits[ndx].indexOf("\"", splits[ndx].indexOf(":"))); + splits[ndx] = nodeName.substring(delimLocation + 1, splits[ndx].indexOf(":", delimLocation)); + log.info("Adding node: " + splits[ndx] + ":" + port); + ClusterActor clusterActor = new ClusterActor(); + clusterActor.setNode(splits[ndx]); + clusterActor.setAkkaPort(port); + clusterActor.setMember("member-" + (ndx + 1)); + if(ndx < 3) { + clusterActor.setSite(ClusterActor.SITE_1); + } + else { + clusterActor.setSite(ClusterActor.SITE_2); + } + + if(member.equals(clusterActor.getMember())) { + self = clusterActor; + } + members.put(clusterActor.getNode(), clusterActor); + log.info(clusterActor.toString()); + } + + if(members.size() == 1) { + log.info("1 member found. This is a solo environment."); + siteConfiguration = SiteConfiguration.Solo; + } + else if(members.size() == 3) { + log.info("This is a single site."); + siteConfiguration = SiteConfiguration.Single; + } + else if(members.size() == 6) { + log.info("This is a georedundant site."); + siteConfiguration = SiteConfiguration.Geo; + } + } + + private void getMemberStatus(ClusterActor clusterActor) throws IOException { + log.info("Getting member status for " + clusterActor.getNode()); + String content = getRequestContent(HTTP_PROTOCOL + clusterActor.getNode() + JOLOKIA_CLUSTER_PATH, HttpMethod.Get); + try { + JSONObject responseJson = new JSONObject(content); + JSONObject responseValue = responseJson.getJSONObject("value"); + clusterActor.setUp("Up".equals(responseValue.getString("MemberStatus"))); + clusterActor.setUnreachable(false); + } catch(JSONException e) { + log.error("Error parsing response from " + clusterActor.getNode(), e); + clusterActor.setUp(false); + clusterActor.setUnreachable(true); + } + } + + private void getShardStatus(ClusterActor clusterActor) throws IOException { + log.info("Getting shard status for " + clusterActor.getNode()); + String content = getRequestContent(HTTP_PROTOCOL + clusterActor.getNode() + SHARD_MANAGER_PATH, HttpMethod.Get); + try { + JSONObject responseValue = new JSONObject(content).getJSONObject("value"); + JSONArray shardList = responseValue.getJSONArray("LocalShards"); + + String pattern = "-config$"; + Pattern r = Pattern.compile(pattern); + Matcher m; + for(int ndx = 0; ndx < shardList.length(); ndx++) { + String configShardName = shardList.getString(ndx); + m = r.matcher(configShardName); + String operationalShardName = m.replaceFirst("-operational"); + String shardConfigPath = String.format(SHARD_PATH_TEMPLATE, configShardName); + String shardOperationalPath = String.format(SHARD_PATH_TEMPLATE, operationalShardName).replace("Config", "Operational"); + extractShardInfo(clusterActor, configShardName, shardConfigPath); + extractShardInfo(clusterActor, operationalShardName, shardOperationalPath); + } + } catch(JSONException e) { + log.error("Error parsing response from " + clusterActor.getNode(), e); + } + } + + private void extractShardInfo(ClusterActor clusterActor, String shardName, String shardPath) throws IOException { + log.info("Extracting shard info for " + shardName); + log.debug("Pulling config info for " + shardName + " from: " + shardPath); + String content = getRequestContent(HTTP_PROTOCOL + clusterActor.getNode() + shardPath, HttpMethod.Get); + log.debug("Response: " + content); + + try { + JSONObject shardValue = new JSONObject(content).getJSONObject("value"); + clusterActor.setVoting(shardValue.getBoolean("Voting")); + if(shardValue.getString("PeerAddresses").length() > 0) { + clusterActor.getReplicaShards().add(shardName); + if(shardValue.getString("Leader").startsWith(clusterActor.getMember())) { + clusterActor.getShardLeader().add(shardName); + } + } + else { + clusterActor.getNonReplicaShards().add(shardName); + } + JSONArray followerInfo = shardValue.getJSONArray("FollowerInfo"); + for(int followerNdx = 0; followerNdx < followerInfo.length(); followerNdx++) { + int commitIndex = shardValue.getInt("CommitIndex"); + int matchIndex = followerInfo.getJSONObject(followerNdx).getInt("matchIndex"); + if(commitIndex != -1 && matchIndex != -1) { + int commitsBehind = commitIndex - matchIndex; + clusterActor.getCommits().put(followerInfo.getJSONObject(followerNdx).getString("id"), commitsBehind); + } + } + } catch(JSONException e) { + log.error("Error parsing response from " + clusterActor.getNode(), e); + } + } + + private void getControllerHealth() { + ClusterActor clusterActor; + for(String key : members.keySet()) { + try { + clusterActor = members.get(key); + // First flush out the old values + clusterActor.flush(); + log.info("Gathering info for " + clusterActor.getNode()); + getMemberStatus(clusterActor); + getShardStatus(clusterActor); + log.info("MemberInfo:\n" + clusterActor.toString()); + } catch(IOException e) { + log.error("Connection Error", e); + members.get(key).setUnreachable(true); + members.get(key).setUp(false); + log.info("MemberInfo:\n" + members.get(key).toString()); + } + } + } + + private void modifyIpTables(IpTables task, Object[] nodeInfo) { + log.info("Modifying IPTables rules..."); + switch(task) + { + case Add: + for(Object node : nodeInfo) { + org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.halt.akka.traffic.input.NodeInfo n = + (org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.halt.akka.traffic.input.NodeInfo) node; + log.info("Isolating " + n.getNode()); + executeCommand(String.format("sudo /sbin/iptables -A INPUT -p tcp --destination-port %s -j DROP -s %s", properties.get("controller.port.akka"), n.getNode())); + executeCommand(String.format("sudo /sbin/iptables -A OUTPUT -p tcp --destination-port %s -j DROP -s %s", n.getPort(), n.getNode())); + } + break; + case Delete: + for(Object node : nodeInfo) { + org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.resume.akka.traffic.input.NodeInfo n = + (org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.resume.akka.traffic.input.NodeInfo) node; + log.info("De-isolating " + n.getNode()); + executeCommand(String.format("sudo /sbin/iptables -D INPUT -p tcp --destination-port %s -j DROP -s %s", properties.get("controller.port.akka"), n.getNode())); + executeCommand(String.format("sudo /sbin/iptables -D OUTPUT -p tcp --destination-port %s -j DROP -s %s", n.getPort(), n.getNode())); + } + break; + } + executeCommand("sudo /sbin/iptables -L"); + } + + private void executeCommand(String command) { + log.info("Executing command: " + command); + String[] cmd = command.split(" "); + try { + Process p = Runtime.getRuntime().exec(cmd); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(p.getInputStream())); + String inputLine; + StringBuffer content = new StringBuffer(); + while((inputLine = bufferedReader.readLine()) != null) { + content.append(inputLine); + } + bufferedReader.close(); + log.info(content.toString()); + } catch(IOException e) { + log.error("Error executing command", e); + } + } + + private boolean crossSiteHealthRequest(String path) throws IOException { + String content = getRequestContent(path, HttpMethod.Post); + try { + JSONObject responseJson = new JSONObject(content); + JSONObject responseValue = responseJson.getJSONObject("value"); + return HEALTHY.equals(responseValue.getString("health")); + } catch(JSONException e) { + log.error("Error parsing JSON", e); + throw new IOException(); + } + } + + private String getAdminHealth() { + String protocol = "true".equals(properties.getProperty("adm.useSsl")) ? "https://" : "http://"; + String port = "true".equals(properties.getProperty("adm.useSsl")) ? properties.getProperty("adm.port.ssl") : properties.getProperty("adm.port.http"); + String path = protocol + properties.getProperty("adm.fqdn") + ":" + port + properties.getProperty("adm.healthcheck"); + log.info("Requesting healthcheck from " + path); + try { + int response = getRequestStatus(path, HttpMethod.Get); + log.info("Response: " + response); + if(response == 200) + return HEALTHY; + return FAULTY; + } catch(IOException e) { + log.error("Problem getting ADM health.", e); + return FAULTY; + } + } + + private String getDatabaseHealth() { + log.info("Determining database health..."); + try { + log.info("DBLib isActive(): " + dbLib.isActive()); + log.info("DBLib isReadOnly(): " + dbLib.getConnection().isReadOnly()); + log.info("DBLib isClosed(): " + dbLib.getConnection().isClosed()); + if(!dbLib.isActive() || dbLib.getConnection().isClosed() || dbLib.getConnection().isReadOnly()) { + log.warn("Database is FAULTY"); + return FAULTY; + } + log.info("Database is HEALTHY"); + } catch(SQLException e) { + log.error("Database is FAULTY"); + log.error("Error", e); + return FAULTY; + } + + return HEALTHY; + } + + private String getRequestContent(String path, HttpMethod method) throws IOException { + return getRequestContent(path, method, null); + } + + private String getRequestContent(String path, HttpMethod method, String input) throws IOException { + HttpURLConnection connection = getConnection(path); + connection.setRequestMethod(method.getMethod()); + connection.setDoInput(true); + + if(input != null) { + sendPayload(input, connection); + } + + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String inputLine; + StringBuffer content = new StringBuffer(); + while((inputLine = bufferedReader.readLine()) != null) { + content.append(inputLine); + } + bufferedReader.close(); + connection.disconnect(); + return content.toString(); + } + + private int getRequestStatus(String path, HttpMethod method) throws IOException { + return getRequestStatus(path, method, null); + } + + private int getRequestStatus(String path, HttpMethod method, String input) throws IOException { + HttpURLConnection connection = getConnection(path); + connection.setRequestMethod(method.getMethod()); + connection.setDoInput(true); + + if(input != null) { + sendPayload(input, connection); + } + int response = connection.getResponseCode(); + log.info("Received " + response + " response code from " + path); + connection.disconnect(); + return response; + } + + private void sendPayload(String input, HttpURLConnection connection) throws IOException { + byte[] out = input.getBytes(StandardCharsets.UTF_8); + int length = out.length; + + connection.setFixedLengthStreamingMode(length); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setDoOutput(true); + connection.connect(); + try(OutputStream os = connection.getOutputStream()) { + os.write(out); + } + } + + private HttpURLConnection getConnection(String host) throws IOException { + log.info("Getting connection to: " + host); + URL url = new URL(host); + 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 enum IpTables { + Add, + Delete + } + + private enum SiteConfiguration { + Solo, + Single, + Geo + } + + private enum HttpMethod { + Get("GET"), + Post("POST"); + + private String method; + HttpMethod(String method) { + this.method = method; + } + public String getMethod() { + return method; + } + } +} \ No newline at end of file diff --git a/grToolkit/provider/src/main/java/org/onap/ccsdk/sli/plugins/GrToolkitUtil.java b/grToolkit/provider/src/main/java/org/onap/ccsdk/sli/plugins/GrToolkitUtil.java new file mode 100755 index 00000000..7f91467a --- /dev/null +++ b/grToolkit/provider/src/main/java/org/onap/ccsdk/sli/plugins/GrToolkitUtil.java @@ -0,0 +1,91 @@ +/*- + * ============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.plugins; + +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.plugins.gr.toolkit.rev180926.ClusterHealthOutputBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.SiteHealthOutputBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.site.health.output.SitesBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.DatabaseHealthOutputBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.AdminHealthOutputBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.HaltAkkaTrafficOutputBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.ResumeAkkaTrafficOutputBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.SiteIdentifierOutputBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.FailoverOutputBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.FailoverInputBuilder; + + +import org.onap.ccsdk.sli.core.sli.provider.MdsalHelper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class GrToolkitUtil extends MdsalHelper { + private static final Logger LOG = LoggerFactory.getLogger(GrToolkitUtil.class); + public static String PROPERTIES_FILE = "/opt/opendaylight/current/controller/configuration/gr-toolkit.properties"; + + 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. + + ClusterHealthOutputBuilder b1 = new ClusterHealthOutputBuilder(); + SiteHealthOutputBuilder b2 = new SiteHealthOutputBuilder(); + SitesBuilder b3 = new SitesBuilder(); + DatabaseHealthOutputBuilder b4 = new DatabaseHealthOutputBuilder(); + AdminHealthOutputBuilder b5 = new AdminHealthOutputBuilder(); + HaltAkkaTrafficOutputBuilder b6 = new HaltAkkaTrafficOutputBuilder(); + ResumeAkkaTrafficOutputBuilder b7 = new ResumeAkkaTrafficOutputBuilder(); + SiteIdentifierOutputBuilder b8 = new SiteIdentifierOutputBuilder(); + FailoverOutputBuilder b9 = new FailoverOutputBuilder(); + FailoverInputBuilder b10 = new FailoverInputBuilder(); + } +} diff --git a/grToolkit/provider/src/main/java/org/onap/ccsdk/sli/plugins/data/ClusterActor.java b/grToolkit/provider/src/main/java/org/onap/ccsdk/sli/plugins/data/ClusterActor.java new file mode 100755 index 00000000..34a51192 --- /dev/null +++ b/grToolkit/provider/src/main/java/org/onap/ccsdk/sli/plugins/data/ClusterActor.java @@ -0,0 +1,212 @@ +/*- + * ============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.plugins.data; + +import java.util.ArrayList; +import java.util.HashMap; + +public class ClusterActor { + private String node; + private String member; + private String site; + private String akkaPort; + private boolean voting; + private boolean up; + private boolean unreachable; + private ArrayList shardLeader; + private ArrayList replicaShards; + private ArrayList nonReplicaShards; + private HashMap commits; + + public static final String SITE_1 = "Site 1"; + public static final String SITE_2 = "Site 2"; + + public ClusterActor() { + node = ""; + member = ""; + site = ""; + voting = false; + up = false; + unreachable = false; + shardLeader = new ArrayList<>(); + replicaShards = new ArrayList<>(); + nonReplicaShards = new ArrayList<>(); + commits = new HashMap<>(); + } + + public String getNode() { + return node; + } + + public void setNode(String node) { + this.node = node; + } + + public String getMember() { + return member; + } + + public void setMember(String member) { + this.member = member; + } + + public String getSite() { + return site; + } + + public void setSite(String site) { + this.site = site; + } + + public String getAkkaPort() { + return akkaPort; + } + + public void setAkkaPort(String akkaPort) { + this.akkaPort = akkaPort; + } + + public boolean isVoting() { + return voting; + } + + public void setVoting(boolean voting) { + this.voting = voting; + } + + public boolean isUp() { + return up; + } + + public void setUp(boolean up) { + this.up = up; + } + + public boolean isUnreachable() { + return unreachable; + } + + public void setUnreachable(boolean unreachable) { + this.unreachable = unreachable; + } + + public ArrayList getShardLeader() { + return shardLeader; + } + + public void setShardLeader(ArrayList shardLeader) { + this.shardLeader = shardLeader; + } + + public ArrayList getReplicaShards() { + return replicaShards; + } + + public void setReplicaShards(ArrayList replicaShards) { + this.replicaShards = replicaShards; + } + + public ArrayList getNonReplicaShards() { + return nonReplicaShards; + } + + public void setNonReplicaShards(ArrayList nonReplicaShards) { + this.nonReplicaShards = nonReplicaShards; + } + + public HashMap getCommits() { + return commits; + } + + public void setCommits(HashMap commits) { + this.commits = commits; + } + + public void flush() { + shardLeader.clear(); + replicaShards.clear(); + nonReplicaShards.clear(); + commits.clear(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("[ "); + builder.append(this.member); + builder.append(" ] "); + + builder.append(this.node); + builder.append(":"); + builder.append(this.akkaPort); + builder.append(" is"); + if(up) + builder.append(" Up"); + else + builder.append(" Down"); + if(unreachable) + builder.append(" [ UNREACHABLE ]"); + + if(voting) + builder.append(" (Voting)"); + + builder.append("\n"); + + for(String l : this.shardLeader) { + builder.append("\tLeader: "); + builder.append(l); + builder.append("\n"); + } + + for(String r : this.replicaShards) { + builder.append("\tReplicating: "); + builder.append(r); + builder.append("\n"); + } + + for(String n : this.nonReplicaShards) { + builder.append("\tNot replicating: "); + builder.append(n); + builder.append("\n"); + } + + for(String key : commits.keySet()) { + int value = commits.get(key); + if(value > 0) { + builder.append("\t"); + builder.append(value); + builder.append(" commits ahead of "); + builder.append(key); + builder.append("\n"); + } + else if(value < 0) { + builder.append("\t"); + builder.append(value); + builder.append(" commits behind "); + builder.append(key); + builder.append("\n"); + } + } + + return builder.toString(); + } +} diff --git a/grToolkit/provider/src/main/java/org/onap/ccsdk/sli/plugins/data/MemberBuilder.java b/grToolkit/provider/src/main/java/org/onap/ccsdk/sli/plugins/data/MemberBuilder.java new file mode 100755 index 00000000..1eb0e75a --- /dev/null +++ b/grToolkit/provider/src/main/java/org/onap/ccsdk/sli/plugins/data/MemberBuilder.java @@ -0,0 +1,83 @@ +/*- + * ============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.plugins.data; + +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.cluster.health.output.MembersBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.member.CommitStatusBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.member.CommitStatus; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.member.ReplicasBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.member.Replicas; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.member.LeaderBuilder; +import org.opendaylight.yang.gen.v1.org.onap.ccsdk.sli.plugins.gr.toolkit.rev180926.member.Leader; + + +import java.util.HashMap; +import java.util.ArrayList; +import java.util.List; + +public class MemberBuilder extends MembersBuilder { + public MemberBuilder(ClusterActor actor) { + super(); + this.setAddress(actor.getNode()); + this.setRole(actor.getMember()); + this.setVoting(actor.isVoting()); + this.setUp(actor.isUp()); + this.setUnreachable(actor.isUnreachable()); + populateReplicas(actor.getReplicaShards()); + populateCommits(actor.getCommits()); + populateLeader(actor.getShardLeader()); + //actor.getNonReplicaShards(); + } + + private void populateLeader(ArrayList shardLeader) { + LeaderBuilder builder; + this.setLeader((List) new ArrayList()); + for(String leader : shardLeader) { + builder = new LeaderBuilder(); + builder.setShard(leader); + this.getLeader().add(builder.build()); + } + } + + private void populateCommits(HashMap commits) { + CommitStatusBuilder builder; + this.setCommitStatus((List) new ArrayList()); + for(String key : commits.keySet()) { + if(commits.get(key) != 0) { + builder = new CommitStatusBuilder(); + builder.setShard(key); + builder.setDelta(commits.get(key)); + this.getCommitStatus().add(builder.build()); + } + } + } + + private void populateReplicas(ArrayList replicaShards) { + ReplicasBuilder builder; + this.setReplicas((List) new ArrayList()); + for(String shard : replicaShards) { + builder = new ReplicasBuilder(); + builder.setShard(shard); + this.getReplicas().add(builder.build()); + } + } +} diff --git a/grToolkit/provider/src/main/resources/gr-toolkit.properties b/grToolkit/provider/src/main/resources/gr-toolkit.properties new file mode 100755 index 00000000..2ddaa9a4 --- /dev/null +++ b/grToolkit/provider/src/main/resources/gr-toolkit.properties @@ -0,0 +1,15 @@ +akka.conf.location=/opt/opendaylight/current/controller/configuration/initial/akka.conf +adm.useSsl=true +adm.fqdn=sdnmlcadm-conexus-it.ecomp.cci.att.com +adm.healthcheck=/healthcheck +adm.port.http=8181 +adm.port.ssl=8443 +controller.credentials=admin:admin +controller.useSsl=true +controller.port.http=8181 +controller.port.ssl=8443 +controller.port.akka=2550 +mbean.cluster=/jolokia/read/akka:type=Cluster +mbean.shardManager=/jolokia/read/org.opendaylight.controller:Category=ShardManager,name=shard-manager-config,type=DistributedConfigDatastore +mbean.shard.config=/jolokia/read/org.opendaylight.controller:Category=Shards,name=%s,type=DistributedConfigDatastore +site.identifier=UniqueSiteNamehere diff --git a/grToolkit/provider/src/main/resources/org/opendaylight/blueprint/GrToolkit.xml b/grToolkit/provider/src/main/resources/org/opendaylight/blueprint/GrToolkit.xml new file mode 100755 index 00000000..606ce277 --- /dev/null +++ b/grToolkit/provider/src/main/resources/org/opendaylight/blueprint/GrToolkit.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 201a59b3..7528a9b7 100755 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,7 @@ sshapi-call-node restconf-client template-node + grToolkit features artifacts -- 2.16.6