From def30df55e27fd1eccbcda7a19f6d7f1ef9004a8 Mon Sep 17 00:00:00 2001 From: ajay_dp001 Date: Thu, 21 Jan 2021 20:11:26 +0530 Subject: [PATCH] Fixed Config-Over-Netconf CSIT - Added Netconf-Pnp-Simulator - Fix, updated Config-Over-Netconf csit Issue-ID: INT-1835 Signed-off-by: ajay_dp001 Change-Id: Idb769fc027acfa24f82caaa27209d9f3f4084249 --- .../config-over-netconf/cds/cds_setup.sh | 55 --------- .../config-over-netconf/cds/docker-compose.yaml | 18 ++- .../cds/resources/application.properties | 97 +++++++++++++++ .../cds/resources/error-messages_en.properties | 77 ++++++++++++ .../cds/resources/importCerAndStartService.sh | 8 ++ .../netconf-pnp-simulator/docker-compose.yml | 12 ++ .../netconf-pnp-simulator/netconf-config/data.json | 10 ++ .../netconf-config/model.yang | 29 +++++ .../netconf-config/subscriber.py | 136 +++++++++++++++++++++ .../config-over-netconf/setup.sh | 131 ++++++++++---------- .../config-over-netconf/teardown.sh | 20 +-- .../config-over-netconf/test.properties | 14 +-- .../config-over-netconf/config_over_netconf.robot | 2 +- .../config-over-netconf/data/blueprint_archive.zip | Bin 10768 -> 17625 bytes 14 files changed, 460 insertions(+), 149 deletions(-) delete mode 100755 plans/usecases-config-over-netconf/config-over-netconf/cds/cds_setup.sh create mode 100755 plans/usecases-config-over-netconf/config-over-netconf/cds/resources/application.properties create mode 100755 plans/usecases-config-over-netconf/config-over-netconf/cds/resources/error-messages_en.properties create mode 100755 plans/usecases-config-over-netconf/config-over-netconf/cds/resources/importCerAndStartService.sh create mode 100755 plans/usecases-config-over-netconf/config-over-netconf/netconf-pnp-simulator/docker-compose.yml create mode 100644 plans/usecases-config-over-netconf/config-over-netconf/netconf-pnp-simulator/netconf-config/data.json create mode 100644 plans/usecases-config-over-netconf/config-over-netconf/netconf-pnp-simulator/netconf-config/model.yang create mode 100755 plans/usecases-config-over-netconf/config-over-netconf/netconf-pnp-simulator/netconf-config/subscriber.py diff --git a/plans/usecases-config-over-netconf/config-over-netconf/cds/cds_setup.sh b/plans/usecases-config-over-netconf/config-over-netconf/cds/cds_setup.sh deleted file mode 100755 index b595e6b1..00000000 --- a/plans/usecases-config-over-netconf/config-over-netconf/cds/cds_setup.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash -# -# ============LICENSE_START======================================================= -# Copyright (C) 2019 Nordix Foundation. -# ================================================================================ -# 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. -# -# SPDX-License-Identifier: Apache-2.0 -# ============LICENSE_END========================================================= - -# @author Rahul Tyagi (rahul.tyagi@est.tech) - -CDS_DATA_PATH=$WORKSPACE/plans/$PARENT/$SUB_PARENT/cds - -cd $CDS_DATA_PATH -export LOCAL_IP=$(ip -4 addr show docker0 | grep -Po 'inet \K[\d.]+') -unset http_proxy https_proxy - -#cd $WORKSPACE/archives/cds/ms/blueprintsprocessor/distribution/src/main/dc/ - -############# update ip of sdnc in docker-compose########### -SDNC_CONTAINER=$(docker ps -a -q --filter="name=sdnc_controller_container") -SDNC_CONTAINER_IP=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $SDNC_CONTAINER) -echo " " >> docker-compose.yaml -echo " extra_hosts:" >> docker-compose.yaml -echo " - 'sdnc:$LOCAL_IP'" >> docker-compose.yaml -############################################################# - -docker-compose up -d -sleep 10 -################# Check state of BP #################### -BP_CONTAINER=$(docker ps -a -q --filter="name=bp-rest") -CCSDK_MARIADB=$(docker ps -a -q --filter="name=ccsdk-mariadb") -for i in {1..10}; do -if [ $(docker inspect --format='{{ .State.Running }}' $BP_CONTAINER) ] && \ -[ $(docker inspect --format='{{ .State.Running }}' $CCSDK_MARIADB) ] -then - echo "Blueprint proc Service Running" - break -else - echo sleep $i - sleep $i -fi -done - diff --git a/plans/usecases-config-over-netconf/config-over-netconf/cds/docker-compose.yaml b/plans/usecases-config-over-netconf/config-over-netconf/cds/docker-compose.yaml index 4834f912..f914e48c 100755 --- a/plans/usecases-config-over-netconf/config-over-netconf/cds/docker-compose.yaml +++ b/plans/usecases-config-over-netconf/config-over-netconf/cds/docker-compose.yaml @@ -23,9 +23,17 @@ services: - "8000:8080" restart: always environment: - APPLICATIONNAME: BlueprintsProcessor - BUNDLEVERSION: 1.0.0 - APP_CONFIG_HOME: /opt/app/onap/config - STICKYSELECTORKEY: - ENVCONTEXT: dev + - APPLICATIONNAME=BlueprintsProcessor + - BUNDLEVERSION=1.0.0 + - APP_CONFIG_HOME=/opt/app/onap/config + - ENVCONTEXT=dev + volumes: + - /etc/localtime:/etc/localtime:ro + - ${WORKSPACE}/plans/usecases-config-over-netconf/config-over-netconf/cds/resources:/opt/app/onap/res + entrypoint: + - /bin/sh + - -c + - "/opt/app/onap/res/importCerAndStartService.sh" + extra_hosts: + - sdnc:${LOCAL_IP} diff --git a/plans/usecases-config-over-netconf/config-over-netconf/cds/resources/application.properties b/plans/usecases-config-over-netconf/config-over-netconf/cds/resources/application.properties new file mode 100755 index 00000000..34d572e5 --- /dev/null +++ b/plans/usecases-config-over-netconf/config-over-netconf/cds/resources/application.properties @@ -0,0 +1,97 @@ +# Web Server Configurations +# START -Controller Blueprints Properties & Load Resource Source Mappings +resourceSourceMappings=processor-db=source-db,input=source-input,default=source-default,sdnc=source-rest,aai-data=source-rest,capability=source-capability,rest=source-rest,vault-data=source-rest,script=source-capability + +# Controller Blueprints Core Configuration +blueprintsprocessor.blueprintDeployPath=/opt/app/onap/blueprints/deploy +blueprintsprocessor.blueprintArchivePath=/opt/app/onap/blueprints/archive +blueprintsprocessor.blueprintWorkingPath=/opt/app/onap/blueprints/working + +# Controller Blueprint Load Configurations +blueprintsprocessor.loadBluePrintPaths=/opt/app/onap/model-catalog/blueprint-model +blueprintsprocessor.loadModeTypePaths=/opt/app/onap/model-catalog/definition-type +blueprintsprocessor.loadResourceDictionaryPaths=/opt/app/onap/model-catalog/resource-dictionary + +# CBA file extension +controllerblueprints.loadCbaExtension=zip + +### END -Controller Blueprints Properties + +blueprintsprocessor.grpcEnable=true +blueprintsprocessor.httpPort=8080 +blueprintsprocessor.grpcPort=9111 + +# DB +blueprintsprocessor.db.url=jdbc:mysql://db:3306/sdnctl +blueprintsprocessor.db.username=sdnctl +blueprintsprocessor.db.password=sdnctl +blueprintsprocessor.db.driverClassName=org.mariadb.jdbc.Driver +blueprintsprocessor.db.hibernateHbm2ddlAuto=update +blueprintsprocessor.db.hibernateDDLAuto=update +blueprintsprocessor.db.hibernateNamingStrategy=org.hibernate.cfg.ImprovedNamingStrategy +blueprintsprocessor.db.hibernateDialect=org.hibernate.dialect.MySQL5InnoDBDialect + +# Processor-DB Endpoint +blueprintsprocessor.db.processor-db.type=maria-db +blueprintsprocessor.db.processor-db.url=jdbc:mysql://mariadb-galera:3306/sdnctl +blueprintsprocessor.db.processor-db.username=root +blueprintsprocessor.db.processor-db.password=secretpassword + +# Python Executor +blueprints.processor.functions.python.executor.executionPath=/opt/app/onap/scripts/jython/ccsdk_blueprints +blueprints.processor.functions.python.executor.modulePaths=/opt/app/onap/scripts/jython/ccsdk_blueprints,/opt/app/onap/scripts/jython/ccsdk_netconf,/opt/app/onap/scripts/jython/ccsdk_restconf + +security.user.password: {bcrypt}$2a$10$duaUzVUVW0YPQCSIbGEkQOXwafZGwQ/b32/Ys4R1iwSSawFgz7QNu +security.user.name: ccsdkapps + +# Used in Health Check +#endpoints.user.name=ccsdkapps +#endpoints.user.password=ccsdkapps + +# Executor Options +blueprintsprocessor.resourceResolution.enabled=true +blueprintsprocessor.netconfExecutor.enabled=true +blueprintsprocessor.restConfExecutor.enabled=true +blueprintsprocessor.cliExecutor.enabled=true +blueprintsprocessor.remoteScriptCommand.enabled=true + +# Command Executor +blueprintsprocessor.grpcclient.remote-python.type=token-auth +blueprintsprocessor.grpcclient.remote-python.host=localhost +blueprintsprocessor.grpcclient.remote-python.port=50051 +blueprintsprocessor.grpcclient.remote-python.token=Basic Y2NzZGthcHBzOmNjc2RrYXBwcw== + +# Python Executor +blueprintsprocessor.grpcclient.py-executor.type=tls-auth +blueprintsprocessor.grpcclient.py-executor.host=py-executor-default:50052 +blueprintsprocessor.grpcclient.py-executor.trustCertCollection=/opt/app/onap/config/certs/py-executor/py-executor-chain.pem + +# Config Data REST client settings +blueprintsprocessor.restconfEnabled=true +blueprintsprocessor.restclient.sdnc.type=basic-auth +blueprintsprocessor.restclient.sdnc.url=http://sdnc:8282 +blueprintsprocessor.restclient.sdnc.username=admin +blueprintsprocessor.restclient.sdnc.password=Kp8bJ4SXszM0WXlhak3eHlcse2gAw84vaoGGmJvUy2U +blueprintprocessor.remoteScriptCommand.enabled=true + +# Encrypted username and password for health check service +endpoints.user.name=eHbVUbJAj4AG2522cSbrOQ== +endpoints.user.password=eHbVUbJAj4AG2522cSbrOQ== + +# BaseUrls for health check blueprint processor services +blueprintprocessor.healthcheck.baseUrl=http://localhost:8080/ +blueprintprocessor.healthcheck.mapping-service-name-with-service-link=[Execution service,/api/v1/execution-service/health-check],[Resources service,/api/v1/resources/health-check],[Template service,/api/v1/template/health-check] + +# BaseUrls for health check Cds Listener services +cdslistener.healthcheck.baseUrl=http://cds-sdc-listener:8080/ +cdslistener.healthcheck.mapping-service-name-with-service-link=[SDC Listener service,/api/v1/sdclistener/healthcheck] + +# Actuator Properties +management.endpoints.web.exposure.include=* +management.endpoint.health.show-details=always +management.info.git.mode=full + +# Error Managements +error.catalog.applicationId=cds +error.catalog.type=properties +error.catalog.errorDefinitionDir=/opt/app/onap/config/ \ No newline at end of file diff --git a/plans/usecases-config-over-netconf/config-over-netconf/cds/resources/error-messages_en.properties b/plans/usecases-config-over-netconf/config-over-netconf/cds/resources/error-messages_en.properties new file mode 100755 index 00000000..3b731c70 --- /dev/null +++ b/plans/usecases-config-over-netconf/config-over-netconf/cds/resources/error-messages_en.properties @@ -0,0 +1,77 @@ +# Error Messages Configurations + +org.onap.ccsdk.cds.blueprintsprocessor.generic_failure=cause=Internal error in Blueprint Processor run time.,action=Contact CDS administrator team. +org.onap.ccsdk.cds.blueprintsprocessor.resource_path_missing=cause=Resource path missing or wrong.,action=Please reload your artifact in run time. +org.onap.ccsdk.cds.blueprintsprocessor.resource_writing_fail=cause=Fail to write resources files.,action=Please reload your files and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.io_file_interrupt=cause=IO file system interruption.,action=Please reload your file and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.unauthorized_request=cause=The request requires user authentication.,action=Please provide the right credentials. +org.onap.ccsdk.cds.blueprintsprocessor.request_not_found=cause=Request mapping doesn't exist.,action=Please verify your request. +org.onap.ccsdk.cds.blueprintsprocessor.conflict_adding_resource=cause=Duplicated entry while saving resource.,action=Please make the saving model doesn't exist. +org.onap.ccsdk.cds.blueprintsprocessor.duplicate_data=cause=Duplicated data - was expecting one result, got more than one.,action=Please provide single resource at a time. +org.onap.ccsdk.cds.blueprintsprocessor.resource_not_found=cause=No response was found for this request in the server.,action=Provide the ID to find the resource. +org.onap.ccsdk.cds.blueprintsprocessor.unsupported_media_type=cause=An invalid media was provided.,action=Please make sure your media or artifact is in the proper structure or format. + +# Self Service API +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.generic_failure=cause=Internal error in Self Service API.,action=Verify the request and try again. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.generic_process_failure=cause=Internal error while processing REST call to the Self Service API.,action=Verify the request and try again. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.invalid_file_extension=cause=Failed trying to upload a non ZIP file format.,action=Please reload your file and make sure it is in ZIP format. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.resource_path_missing=cause=Resource path missing or wrong.,action=Please reload your artifact in run time. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.resource_writing_fail=cause=Fail to write resources files.,action=Please reload your files and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.io_file_interrupt=cause=IO file system interruption.,action=Please reload your file and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.unauthorized_request=cause=The request requires user authentication.,action=Please provide the right credentials. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.request_not_found=cause=Request mapping doesn't exist.,action=Please verify your request. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.conflict_adding_resource=cause=Duplicated entry while saving resource.,action=Please make the saving model doesn't exist. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.duplicate_data=cause=Duplicated data - was expecting one result, got more than one.,action=Please provide single resource at a time. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.resource_not_found=cause=No response was found for this request in the server.,action=Provide the ID to find the resource. +org.onap.ccsdk.cds.blueprintsprocessor.selfservice.api.unsupported_media_type=cause=An invalid media was provided.,action=Please make sure your media or artifact is in the proper structure or format. + +# Designer API +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.generic_failure=cause=Internal error while processing REST call to the Designer API.,action=Verify the request and try again. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.invalid_file_extension=cause=Failed trying to upload a non ZIP file format.,action=Please reload your file and make sure it is in ZIP format. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.resource_path_missing=cause=Resource path missing or wrong.,action=Please reload your artifact in run time. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.resource_writing_fail=cause=Fail to write resources files.,action=Please reload your files and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.io_file_interrupt=cause=IO file system interruption.,action=Please reload your file and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.unauthorized_request=cause=The request requires user authentication.,action=Please provide the right credentials. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.request_not_found=cause=Request mapping doesn't exist.,action=Please verify your request. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.conflict_adding_resource=cause=Duplicated entry while saving resource.,action=Please make the saving model doesn't exist. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.duplicate_data=cause=Duplicated data - was expecting one result, got more than one.,action=Please provide single resource at a time. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.resource_not_found=cause=No response was found for this request in the server.,action=Provide the ID to find the resource. +org.onap.ccsdk.cds.blueprintsprocessor.designer.api.unsupported_media_type=cause=An invalid media was provided.,action=Please make sure your media or artifact is in the proper structure or format. + +# Resource API +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.generic_failure=cause=Internal error while processing REST call to the Resource API.,action=Verify the request and try again. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.invalid_file_extension=cause=Failed trying to upload a non ZIP file format.,action=Please reload your file and make sure it is in ZIP format. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.resource_path_missing=cause=Resource path missing or wrong.,action=Please reload your artifact in run time. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.resource_writing_fail=cause=Fail to write resources files.,action=Please reload your files and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.io_file_interrupt=cause=IO file system interruption.,action=Please reload your file and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.unauthorized_request=cause=The request requires user authentication.,action=Please provide the right credentials. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.request_not_found=cause=Request mapping doesn't exist.,action=Please verify your request. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.conflict_adding_resource=cause=Duplicated entry while saving resource.,action=Please make the saving model doesn't exist. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.duplicate_data=cause=Duplicated data - was expecting one result, got more than one.,action=Please provide single resource at a time. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.resource_not_found=cause=No response was found for this request in the server.,action=Provide the ID to find the resource. +org.onap.ccsdk.cds.blueprintsprocessor.resource.api.unsupported_media_type=cause=An invalid media was provided.,action=Please make sure your media or artifact is in the proper structure or format. + +# Configs API +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.generic_failure=cause=Internal error while processing REST call to the Configs API.,action=Verify the request and try again. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.resource_path_missing=cause=Resource path missing or wrong.,action=Please reload your artifact in run time. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.io_file_interrupt=cause=IO file system interruption.,action=Please reload your file and make sure it is in the right format. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.unauthorized_request=cause=The request requires user authentication.,action=Please provide the right credentials. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.request_not_found=cause=Request mapping doesn't exist.,action=Please verify your request. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.conflict_adding_resource=cause=Duplicated entry while saving resource.,action=Please make the saving model doesn't exist. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.duplicate_data=cause=Duplicated data - was expecting one result, got more than one.,action=Please provide single resource at a time. +org.onap.ccsdk.cds.blueprintsprocessor.configs.api.resource_not_found=cause=No response was found for this request in the server.,action=Provide the ID to find the resource. + +# Python Executor +org.onap.ccsdk.cds.blueprintsprocessor.functions.python.executor.generic_failure=cause=Internal error in Blueprint Processor run time.,action=Contact CDS administrator team. + +# Resource resolution +org.onap.ccsdk.cds.blueprintsprocessor.resource.resolution.invalid_request_format=cause=bad request provided.,action=Verify the request payload. +org.onap.ccsdk.cds.blueprintsprocessor.resource.resolution.resource_not_found=cause=No response was found for this resolution in CDS.,action=Verify definition of the resource in CBA. +org.onap.ccsdk.cds.blueprintsprocessor.resource.resolution.internal_error=cause=Internal error while processing Resource Resolution.,action=Verify the payload. + +org.onap.ccsdk.cds.sdclistener.generic_failure=cause=Internal error in SDC Listener.,action=Contact CDS administrator team. \ No newline at end of file diff --git a/plans/usecases-config-over-netconf/config-over-netconf/cds/resources/importCerAndStartService.sh b/plans/usecases-config-over-netconf/config-over-netconf/cds/resources/importCerAndStartService.sh new file mode 100755 index 00000000..b5816068 --- /dev/null +++ b/plans/usecases-config-over-netconf/config-over-netconf/cds/resources/importCerAndStartService.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +chmod -R 775 /opt/app/onap/res +cp -f /opt/app/onap/res/application.properties /opt/app/onap/config +cp -f /opt/app/onap/res/error-messages_en.properties /opt/app/onap/config + +echo "Starting Service..." +source /startService.sh \ No newline at end of file diff --git a/plans/usecases-config-over-netconf/config-over-netconf/netconf-pnp-simulator/docker-compose.yml b/plans/usecases-config-over-netconf/config-over-netconf/netconf-pnp-simulator/docker-compose.yml new file mode 100755 index 00000000..d8e723ba --- /dev/null +++ b/plans/usecases-config-over-netconf/config-over-netconf/netconf-pnp-simulator/docker-compose.yml @@ -0,0 +1,12 @@ +version: '3' + +services: + netconf-pnp-simulator: + image: nexus3.onap.org:10001/onap/integration/simulators/netconf-pnp-simulator:2.8.6 + container_name: netconf-simulator + restart: always + ports: + - "830:830" + - "6513:6513" + volumes: + - ${NETCONF_CONFIG_PATH}:/config/modules/mynetconf diff --git a/plans/usecases-config-over-netconf/config-over-netconf/netconf-pnp-simulator/netconf-config/data.json b/plans/usecases-config-over-netconf/config-over-netconf/netconf-pnp-simulator/netconf-config/data.json new file mode 100644 index 00000000..63872eef --- /dev/null +++ b/plans/usecases-config-over-netconf/config-over-netconf/netconf-pnp-simulator/netconf-config/data.json @@ -0,0 +1,10 @@ +{ + "mynetconf:netconflist": { + "netconf": [ + { + "netconf-id": 3, + "netconf-param": 3 + } + ] + } +} diff --git a/plans/usecases-config-over-netconf/config-over-netconf/netconf-pnp-simulator/netconf-config/model.yang b/plans/usecases-config-over-netconf/config-over-netconf/netconf-pnp-simulator/netconf-config/model.yang new file mode 100644 index 00000000..6c8c36ab --- /dev/null +++ b/plans/usecases-config-over-netconf/config-over-netconf/netconf-pnp-simulator/netconf-config/model.yang @@ -0,0 +1,29 @@ +module mynetconf { + yang-version 1.1; + namespace "urn:mynetconf:test"; + + prefix nft; + + organization + "mynetconf"; + contact + "my netconf address"; + description + "yang model for mynetconf"; + revision "2019-03-01" { + description + "initial version"; + } + + container netconflist { + list netconf { + key netconf-id; + leaf netconf-id { + type uint16; + } + leaf netconf-param { + type uint32; + } + } + } +} diff --git a/plans/usecases-config-over-netconf/config-over-netconf/netconf-pnp-simulator/netconf-config/subscriber.py b/plans/usecases-config-over-netconf/config-over-netconf/netconf-pnp-simulator/netconf-config/subscriber.py new file mode 100755 index 00000000..61272967 --- /dev/null +++ b/plans/usecases-config-over-netconf/config-over-netconf/netconf-pnp-simulator/netconf-config/subscriber.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 + +__author__ = "Mislav Novakovic " +__copyright__ = "Copyright 2018, Deutsche Telekom AG" +__license__ = "Apache 2.0" + +# 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. + +# This sample application demonstrates use of Python programming language bindings for sysrepo library. +# Original c application was rewritten in Python to show similarities and differences +# between the two. +# +# Most notable difference is in the very different nature of languages, c is weakly statically typed language +# while Python is strongly dynamically typed. Python code is much easier to read and logic easier to comprehend +# for smaller scripts. Memory safety is not an issue but lower performance can be expected. +# +# The original c implementation is also available in the source, so one can refer to it to evaluate trade-offs. + +import sysrepo as sr +import sys + + +# Helper function for printing changes given operation, old and new value. +def print_change(op, old_val, new_val): + if op == sr.SR_OP_CREATED: + print(f"CREATED: {new_val.to_string()}") + elif op == sr.SR_OP_DELETED: + print(f"DELETED: {old_val.to_string()}") + elif op == sr.SR_OP_MODIFIED: + print(f"MODIFIED: {old_val.to_string()} to {new_val.to_string()}") + elif op == sr.SR_OP_MOVED: + print(f"MOVED: {new_val.xpath()} after {old_val.xpath()}") + + +# Helper function for printing events. +def ev_to_str(ev): + if ev == sr.SR_EV_VERIFY: + return "verify" + elif ev == sr.SR_EV_APPLY: + return "apply" + elif ev == sr.SR_EV_ABORT: + return "abort" + else: + return "unknown" + + +# Function to print current configuration state. +# It does so by loading all the items of a session and printing them out. +def print_current_config(session, module_name): + select_xpath = f"/{module_name}:*//*" + + values = session.get_items(select_xpath) + + if values is not None: + print("========== BEGIN CONFIG ==========") + for i in range(values.val_cnt()): + print(values.val(i).to_string(), end='') + print("=========== END CONFIG ===========") + + +# Function to be called for subscribed client of given session whenever configuration changes. +def module_change_cb(sess, module_name, event, private_ctx): + try: + print("========== Notification " + ev_to_str(event) + " =============================================") + if event == sr.SR_EV_APPLY: + print_current_config(sess, module_name) + + print("========== CHANGES: =============================================") + + change_path = f"/{module_name}:*" + + it = sess.get_changes_iter(change_path) + + while True: + change = sess.get_change_next(it) + if change is None: + break + print_change(change.oper(), change.old_val(), change.new_val()) + + print("========== END OF CHANGES =======================================") + except Exception as e: + print(e) + + return sr.SR_ERR_OK + + +def main(): + # Notable difference between c implementation is using exception mechanism for open handling unexpected events. + # Here it is useful because `Connection`, `Session` and `Subscribe` could throw an exception. + try: + module_name = "ietf-interfaces" + if len(sys.argv) > 1: + module_name = sys.argv[1] + else: + print("\nYou can pass the module name to be subscribed as the first argument") + + print(f"Application will watch for changes in {module_name}") + + # connect to sysrepo + conn = sr.Connection(module_name) + + # start session + sess = sr.Session(conn) + + # subscribe for changes in running config */ + subscribe = sr.Subscribe(sess) + + subscribe.module_change_subscribe(module_name, module_change_cb) + + try: + print_current_config(sess, module_name) + except Exception as e: + print(e) + + print("========== STARTUP CONFIG APPLIED AS RUNNING ==========") + + sr.global_loop() + + print("Application exit requested, exiting.") + + except Exception as e: + print(e) + + +if __name__ == '__main__': + main() diff --git a/plans/usecases-config-over-netconf/config-over-netconf/setup.sh b/plans/usecases-config-over-netconf/config-over-netconf/setup.sh index fcedbecf..2e61da0a 100644 --- a/plans/usecases-config-over-netconf/config-over-netconf/setup.sh +++ b/plans/usecases-config-over-netconf/config-over-netconf/setup.sh @@ -20,101 +20,102 @@ # @author Rahul Tyagi (rahul.tyagi@est.tech) - -SCRIPTS="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -export PARENT=usecases-config-over-netconf -export SUB_PARENT=config-over-netconf -source ${WORKSPACE}/plans/$PARENT/$SUB_PARENT/test.properties +SCRIPTS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${WORKSPACE}"/plans/usecases-config-over-netconf/config-over-netconf/test.properties export MTU=$(/sbin/ifconfig | grep MTU | sed 's/.*MTU://' | sed 's/ .*//' | sort -n | head -1) if [ "$MTU" == "" ]; then - export MTU="1450" + export MTU="1450" fi -# clone integration branch for pnf-simulator -mkdir -m 755 -p $WORKSPACE/temp/integration -cd $WORKSPACE/temp -git clone -b dublin --single-branch --depth=1 http://gerrit.onap.org/r/integration.git integration - -HOST_IP_ADDR=localhost +export CONFIG_OVER_NETCONF=${CONFIG_OVER_NETCONF} -# setup sdnc +# Prepare enviroment +echo "Uninstall docker-py and reinstall docker." +pip uninstall -y docker-py +pip uninstall -y docker +pip install -U docker==2.7.0 -cd $SDNC_DOCKER_PATH +# Disable Proxy - for local run unset http_proxy https_proxy -docker pull $NETOPEER_DOCKER_REPO:$NETOPEER_IMAGE_TAG -docker tag $NETOPEER_DOCKER_REPO:$NETOPEER_IMAGE_TAG $NETOPEER_DOCKER_REPO:latest -#sed -i "s/DMAAP_TOPIC_ENV=.*/DMAAP_TOPIC_ENV="AUTO"/g" diocker-compose.yml -docker login -u $NEXUS_USERNAME -p $NEXUS_PASSWD $NEXUS_DOCKER_REPO +# Export default Networking bridge created on the host machine +export LOCAL_IP=$(ip -4 addr show docker0 | grep -Po 'inet \K[\d.]+') -docker pull $NEXUS_DOCKER_REPO/onap/sdnc-image:$SDNC_IMAGE_TAG -docker tag $NEXUS_DOCKER_REPO/onap/sdnc-image:$SDNC_IMAGE_TAG onap/sdnc-image:latest +###################### Netconf-PNP-Simulator Setup ###################### -docker pull $NEXUS_DOCKER_REPO/onap/sdnc-ansible-server-image:$ANSIBLE_IMAGE_TAG -docker tag $NEXUS_DOCKER_REPO/onap/sdnc-ansible-server-image:$ANSIBLE_IMAGE_TAG onap/sdnc-ansible-server-image:latest +# Export Netconf-Pnp Simulator conf path +export NETCONF_CONFIG_PATH -docker pull $NEXUS_DOCKER_REPO/onap/ccsdk-blueprintsprocessor:$BP_IMAGE_TAG -docker tag $NEXUS_DOCKER_REPO/onap/ccsdk-blueprintsprocessor:$BP_IMAGE_TAG onap/ccsdk-blueprintsprocessor:latest +# Start N etconf-Pnp-Simulator Container with docker-compose and configuration from docker-compose.yml +docker-compose -f "${CONFIG_OVER_NETCONF}"/netconf-pnp-simulator/docker-compose.yml up -d -export SDNC_CERT_PATH=${CERT_SUBPATH} -#sed -i 's/sdnc_controller_container/sdnc_controller_container\n volumes: \n - $SDNC_CERT_PATH:\/opt\/opendaylight\/current\/certs/' docker-compose.yaml -# start SDNC containers with docker compose and configuration from docker-compose.yml -docker-compose up -d +# Update default Networking bridge IP in mount.json file +sed -i "s/pnfaddr/${LOCAL_IP}/g" "${REQUEST_DATA_PATH}"/mount.xml -# start pnf simulator +############################## SDNC Setup ############################## -cd $INT_DOCKER_PATH +export SDNC_CERT_PATH="${CERT_SUBPATH}" -./simulator.sh start& +docker pull "${NEXUS_DOCKER_REPO}"/onap/sdnc-image:"${SDNC_IMAGE_TAG}" +docker tag "${NEXUS_DOCKER_REPO}"/onap/sdnc-image:"${SDNC_IMAGE_TAG}" onap/sdnc-image:latest -# WAIT 10 minutes maximum and test every 5 seconds if SDNC is up using HealthCheck API -TIME_OUT=1000 -INTERVAL=30 -TIME=0 -while [ "$TIME" -lt "$TIME_OUT" ]; do - response=$(curl --write-out '%{http_code}' --silent --output /dev/null -H "Authorization: Basic YWRtaW46S3A4Yko0U1hzek0wV1hsaGFrM2VIbGNzZTJnQXc4NHZhb0dHbUp2VXkyVQ==" -X POST -H "X-FromAppId: csit-sdnc" -H "X-TransactionId: csit-sdnc" -H "Accept: application/json" -H "Content-Type: application/json" http://localhost:8282/restconf/operations/SLI-API:healthcheck ); - echo $response +docker pull "${NEXUS_DOCKER_REPO}"/onap/sdnc-ansible-server-image:"${ANSIBLE_IMAGE_TAG}" +docker tag "${NEXUS_DOCKER_REPO}"/onap/sdnc-ansible-server-image:"${ANSIBLE_IMAGE_TAG}" onap/sdnc-ansible-server-image:latest - if [ "$response" == "200" ]; then - echo SDNC started in $TIME seconds - break; - fi +docker-compose -f "${CONFIG_OVER_NETCONF}"/sdn/docker-compose.yaml up -d - echo Sleep: $INTERVAL seconds before testing if SDNC is up. Total wait time up now is: $TIME seconds. Timeout is: $TIME_OUT seconds - sleep $INTERVAL - TIME=$(($TIME+$INTERVAL)) +# Check if SDNC Service is healthy and ready +for i in {1..10}; do + SDNC_IP=$(get-instance-ip.sh sdnc_controller_container) + RESP_CODE=$(curl --write-out '%{http_code}' --silent --output /dev/null -H "Authorization: Basic YWRtaW46S3A4Yko0U1hzek0wV1hsaGFrM2VIbGNzZTJnQXc4NHZhb0dHbUp2VXkyVQ==" -X POST -H "X-FromAppId: csit-sdnc" -H "X-TransactionId: csit-sdnc" -H "Accept: application/json" -H "Content-Type: application/json" http://localhost:8282/restconf/operations/SLI-API:healthcheck) + if [[ "${RESP_CODE}" == '200' ]]; then + echo "SDNC Service is Ready." + break + fi + echo "Waiting for SDNC Service to Start Up..." + sleep 2m done -export LOCAL_IP=$(ip -4 addr show docker0 | grep -Po 'inet \K[\d.]+') -sed -i "s/pnfaddr/$LOCAL_IP/g" $REQUEST_DATA_PATH/mount.xml +if [[ "${SDNC_IP}" == 'none' || "${SDNC_IP}" == '' || "${RESP_CODE}" != '200' ]]; then + echo "SDNC Service not started Could cause problems for testing activities...!" +fi +############################## CDS Setup ############################## -if [ "$TIME" -ge "$TIME_OUT" ]; then - echo TIME OUT: karaf session not started in $TIME_OUT seconds... Could cause problems for testing activities... -fi +docker pull "${NEXUS_DOCKER_REPO}"/onap/ccsdk-blueprintsprocessor:"${BP_IMAGE_TAG}" +docker tag "${NEXUS_DOCKER_REPO}"/onap/ccsdk-blueprintsprocessor:"${BP_IMAGE_TAG}" onap/ccsdk-blueprintsprocessor:latest -########################################## blueprintsprocessor setup ########################################################## -source $CDS_DOCKER_PATH/cds_setup.sh +docker-compose -f "${CONFIG_OVER_NETCONF}"/cds/docker-compose.yaml up -d -########## update pnf simulator ip in config deploy request ######## +echo "Sleeping 1 minute" +sleep 1m -NETOPEER_CONTAINER=$(docker ps -a -q --filter="name=netopeer") -NETOPEER_CONTAINER_IP=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $SDNC_CONTAINER) -RES_KEY=$(uuidgen -r) -sed -i "s/pnfaddr/$LOCAL_IP/g" $REQUEST_DATA_PATH/config-deploy.json -sed -i "s/pnfaddr/$LOCAL_IP/g" $REQUEST_DATA_PATH/config-assign.json +BP_CONTAINER=$(docker ps -a -q --filter="name=bp-rest") +CCSDK_MARIADB=$(docker ps -a -q --filter="name=ccsdk-mariadb") -sed -i "s/reskey/$RES_KEY/g" $REQUEST_DATA_PATH/config-deploy.json -sed -i "s/reskey/$RES_KEY/g" $REQUEST_DATA_PATH/config-assign.json +for i in {1..10}; do + if [ $(docker inspect --format='{{ .State.Running }}' "${BP_CONTAINER}") ] && + [ $(docker inspect --format='{{ .State.Running }}' "${CCSDK_MARIADB}") ]; then + echo "Blueprint Proc Service Running" + break + else + echo sleep "${i}" + sleep "${i}" + fi +done -#########################check if server is up gracefully ###################################### +############################ Update Setup ############################ -# Sleep additional 3 minutes (180 secs) to give application time to finish +RES_KEY=$(uuidgen -r) +sed -i "s/pnfaddr/$LOCAL_IP/g" "${REQUEST_DATA_PATH}"/config-deploy.json +sed -i "s/pnfaddr/$LOCAL_IP/g" "${REQUEST_DATA_PATH}"/config-assign.json -sleep 150 +sed -i "s/reskey/$RES_KEY/g" "${REQUEST_DATA_PATH}"/config-deploy.json +sed -i "s/reskey/$RES_KEY/g" "${REQUEST_DATA_PATH}"/config-assign.json # Pass any variables required by Robot test suites in ROBOT_VARIABLES - -ROBOT_VARIABLES="-v SCRIPTS:${SCRIPTS}" +REPO_IP='127.0.0.1' +ROBOT_VARIABLES+=" -v REPO_IP:${REPO_IP} " +ROBOT_VARIABLES+=" -v SCRIPTS:${SCRIPTS} " \ No newline at end of file diff --git a/plans/usecases-config-over-netconf/config-over-netconf/teardown.sh b/plans/usecases-config-over-netconf/config-over-netconf/teardown.sh index 9613e3ee..7257b366 100755 --- a/plans/usecases-config-over-netconf/config-over-netconf/teardown.sh +++ b/plans/usecases-config-over-netconf/config-over-netconf/teardown.sh @@ -1,18 +1,10 @@ #!/bin/bash -SDNC_DOCKER_COMPOSE_PATH=$SDNC_DOCKER_PATH/docker-compose.yaml -PNFSIM_DOCKER_COMPOSE_PATH=$INT_DOCKER_PATH/docker-compose.yml -CDS_DOCKER_COMPOSE_PATH=$CDS_DOCKER_PATH/docker-compose.yaml +echo 'Stop, Removing all running containers...' +docker stop $(docker ps -aq) && docker rm $(docker ps -aq) -echo "==========================blueprint-processor logs ==================================" -docker logs bp-rest +echo 'Removing Volumes...' +echo y | docker volume prune -echo "==========================sdnc-controller logs ======================================" -docker logs sdnc_controller_container - - -docker-compose -f $SDNC_DOCKER_COMPOSE_PATH down -docker-compose -f $PNFSIM_DOCKER_COMPOSE_PATH down -docker-compose -f $CDS_DOCKER_COMPOSE_PATH down - -rm -rf $WORKSPACE/temp +echo 'Removing Networks...' +echo y | docker network prune \ No newline at end of file diff --git a/plans/usecases-config-over-netconf/config-over-netconf/test.properties b/plans/usecases-config-over-netconf/config-over-netconf/test.properties index ee002461..4e4c99b4 100644 --- a/plans/usecases-config-over-netconf/config-over-netconf/test.properties +++ b/plans/usecases-config-over-netconf/config-over-netconf/test.properties @@ -1,14 +1,10 @@ NEXUS_DOCKER_REPO=nexus3.onap.org:10001 NEXUS_USERNAME=docker NEXUS_PASSWD=docker +BP_IMAGE_TAG=0.7.1 SDNC_IMAGE_TAG=1.7.6 ANSIBLE_IMAGE_TAG=1.7.6 -BP_IMAGE_TAG=0.6.4 -REQUEST_DATA_PATH=$WORKSPACE/tests/$PARENT/$SUB_PARENT/data -TC_PLANS_PATH=$WORKSPACE/plans/$PARENT/$SUB_PARENT -CDS_DOCKER_PATH=$TC_PLANS_PATH/cds -SDNC_DOCKER_PATH=$TC_PLANS_PATH/sdn -INT_DOCKER_PATH=$WORKSPACE/temp/integration/test/mocks/pnfsimulator -CERT_SUBPATH=$TC_PLANS_PATH/certs -NETOPEER_DOCKER_REPO=sysrepo/sysrepo-netopeer2 -NETOPEER_IMAGE_TAG=v0.7.7 +CERT_SUBPATH=${WORKSPACE}/plans/usecases-config-over-netconf/config-over-netconf/certs +CONFIG_OVER_NETCONF=${WORKSPACE}/plans/usecases-config-over-netconf/config-over-netconf +REQUEST_DATA_PATH=${WORKSPACE}/tests/usecases-config-over-netconf/config-over-netconf/data +NETCONF_CONFIG_PATH=${WORKSPACE}/plans/usecases-config-over-netconf/config-over-netconf/netconf-pnp-simulator/netconf-config \ No newline at end of file diff --git a/tests/usecases-config-over-netconf/config-over-netconf/config_over_netconf.robot b/tests/usecases-config-over-netconf/config-over-netconf/config_over_netconf.robot index 21c03c6f..e7d923f8 100644 --- a/tests/usecases-config-over-netconf/config-over-netconf/config_over_netconf.robot +++ b/tests/usecases-config-over-netconf/config-over-netconf/config_over_netconf.robot @@ -9,7 +9,7 @@ Library String ${SDNC_KEYSTORE_CONFIG_PATH} /config/netconf-keystore:keystore ${SDNC_MOUNT_PATH} /config/network-topology:network-topology/topology/topology-netconf/node/pnf-simulator ${PNFSIM_MOUNT_PATH} /config/network-topology:network-topology/topology/topology-netconf/node/pnf-simulator/yang-ext:mount/mynetconf:netconflist -${BP_UPLOAD_URL} /api/v1/execution-service/upload +${BP_UPLOAD_URL} /api/v1/blueprint-model/publish ${BP_PROCESS_URL} /api/v1/execution-service/process ${BP_ARCHIVE_PATH} ${CURDIR}/data/blueprint_archive.zip diff --git a/tests/usecases-config-over-netconf/config-over-netconf/data/blueprint_archive.zip b/tests/usecases-config-over-netconf/config-over-netconf/data/blueprint_archive.zip index ac346554b9e6769be63a2aa79e4744a9ec870925..642c85f56ca0bd3752c46fbcd23db698353c527b 100644 GIT binary patch literal 17625 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 literal 10768 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 NfCvEGB|HD?e*g&@i(&u( -- 2.16.6