RIFT OSM R1 Initial Submission

Signed-off-by: Jeremy Mordkoff <jeremy.mordkoff@riftio.com>
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
new file mode 100644
index 0000000..ff329dd
--- /dev/null
+++ b/examples/CMakeLists.txt
@@ -0,0 +1,35 @@
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
+# Author(s): Anil Gunturu
+# Creation Date: 03/26/2014
+# 
+
+cmake_minimum_required(VERSION 2.8)
+
+set(PKG_NAME rwmano_examples)
+set(PKG_VERSION 1.0)
+set(PKG_RELEASE 1)
+set(PKG_LONG_NAME ${PKG_NAME}-${PKG_VERSION})
+
+
+##
+# Include the subdirs
+##
+set(subdirs
+  ping_pong_ns
+  )
+
+rift_add_subdirs(SUBDIR_LIST ${subdirs})
diff --git a/examples/Makefile b/examples/Makefile
new file mode 100644
index 0000000..2b691a8
--- /dev/null
+++ b/examples/Makefile
@@ -0,0 +1,36 @@
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
+# Author(s): Tim Mortsolf
+# Creation Date: 11/25/2013
+# 
+
+##
+# Define a Makefile function: find_upwards(filename)
+#
+# Searches for a file of the given name in the directory ., .., ../.., ../../.., etc.,
+# until the file is found or the root directory is reached
+##
+find_upward = $(word 1, $(shell while [ `pwd` != / ] ; do find `pwd` -maxdepth 1 -name $1 ; cd .. ; done))
+
+##
+# Call find_upward("Makefile.top") to find the nearest upwards adjacent Makefile.top
+##
+makefile.top := $(call find_upward, "Makefile.top")
+
+##
+# If Makefile.top was found, then include it
+##
+include $(makefile.top)
diff --git a/examples/ping_pong_ns/CMakeLists.txt b/examples/ping_pong_ns/CMakeLists.txt
new file mode 100644
index 0000000..9667465
--- /dev/null
+++ b/examples/ping_pong_ns/CMakeLists.txt
@@ -0,0 +1,80 @@
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
+# Author(s): Anil Gunturu
+# Creation Date: 03/26/2014
+# 
+
+cmake_minimum_required(VERSION 2.8)
+
+configure_file(
+  ${CMAKE_CURRENT_SOURCE_DIR}/generate_packages.sh.in
+  ${CMAKE_CURRENT_BINARY_DIR}/generate_packages.sh
+  ESCAPE_QUOTES @ONLY
+  )
+
+set(PACKAGE_OUTPUT
+  ${CMAKE_CURRENT_BINARY_DIR}/ping_pong_nsd.tar.gz
+  ${CMAKE_CURRENT_BINARY_DIR}/ping_vnfd.tar.gz
+  ${CMAKE_CURRENT_BINARY_DIR}/pong_vnfd.tar.gz
+  ${CMAKE_CURRENT_BINARY_DIR}/ping_pong_nsd_aws.tar.gz
+  ${CMAKE_CURRENT_BINARY_DIR}/ping_vnfd_aws.tar.gz
+  ${CMAKE_CURRENT_BINARY_DIR}/pong_vnfd_aws.tar.gz
+  ${CMAKE_CURRENT_BINARY_DIR}/ping_pong_nsd_with_epa.tar.gz
+  ${CMAKE_CURRENT_BINARY_DIR}/ping_vnfd_with_epa.tar.gz
+  ${CMAKE_CURRENT_BINARY_DIR}/pong_vnfd_with_epa.tar.gz)
+
+add_custom_command(
+    OUTPUT ${PACKAGE_OUTPUT}
+    COMMAND ${CMAKE_CURRENT_BINARY_DIR}/generate_packages.sh
+    DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ping_pong_nsd.py
+  )
+
+add_custom_target(ping_pong_pkg_gen ALL
+    DEPENDS mano_yang ${PACKAGE_OUTPUT}
+  )
+
+install(
+    FILES ${PACKAGE_OUTPUT}
+    DESTINATION
+      usr/rift/mano/examples/ping_pong_ns
+    COMPONENT ${PKG_LONG_NAME}
+    )
+
+install(
+    FILES
+      ${CMAKE_CURRENT_BINARY_DIR}/ping_vnfd_with_image.tar.gz
+      ${CMAKE_CURRENT_BINARY_DIR}/pong_vnfd_with_image.tar.gz
+    DESTINATION
+      usr/rift/mano/examples/ping_pong_ns
+    COMPONENT ${PKG_LONG_NAME}
+    OPTIONAL
+    )
+
+rift_python_install_tree(
+  COMPONENT ${PKG_LONG_NAME}
+  FILES
+    rift/mano/examples/ping_pong_nsd.py
+    rift/mano/examples/start_traffic.py
+  )
+
+install(
+  PROGRAMS
+    rift/mano/examples/ping_config.py
+    stand_up_ping_pong
+  DESTINATION usr/bin
+  COMPONENT ${PKG_LONG_NAME}
+  )
+
diff --git a/examples/ping_pong_ns/Makefile b/examples/ping_pong_ns/Makefile
new file mode 100644
index 0000000..2b691a8
--- /dev/null
+++ b/examples/ping_pong_ns/Makefile
@@ -0,0 +1,36 @@
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
+# Author(s): Tim Mortsolf
+# Creation Date: 11/25/2013
+# 
+
+##
+# Define a Makefile function: find_upwards(filename)
+#
+# Searches for a file of the given name in the directory ., .., ../.., ../../.., etc.,
+# until the file is found or the root directory is reached
+##
+find_upward = $(word 1, $(shell while [ `pwd` != / ] ; do find `pwd` -maxdepth 1 -name $1 ; cd .. ; done))
+
+##
+# Call find_upward("Makefile.top") to find the nearest upwards adjacent Makefile.top
+##
+makefile.top := $(call find_upward, "Makefile.top")
+
+##
+# If Makefile.top was found, then include it
+##
+include $(makefile.top)
diff --git a/examples/ping_pong_ns/config_desc.py b/examples/ping_pong_ns/config_desc.py
new file mode 100755
index 0000000..fcd1400
--- /dev/null
+++ b/examples/ping_pong_ns/config_desc.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python3
+
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
+
+import argparse
+import logging
+import rift.auto.proxy
+import rift.vcs
+import sys
+
+import gi
+gi.require_version('RwYang', '1.0')
+
+from gi.repository import NsdYang, VldYang, VnfdYang, RwYang
+
+logging.basicConfig(level=logging.DEBUG)
+logger = logging.getLogger(__name__)
+
+model = RwYang.Model.create_libncx()
+model.load_schema_ypbc(VldYang.get_schema())
+model.load_schema_ypbc(NsdYang.get_schema())
+model.load_schema_ypbc(VnfdYang.get_schema())
+
+
+def configure_vld(proxy, vld_xml_hdl):
+    vld_xml = vld_xml_hdl.read()
+    logger.debug("Attempting to deserialize XML into VLD protobuf: %s", vld_xml)
+    vld = VldYang.YangData_Vld_VldCatalog_Vld()
+    vld.from_xml_v2(model, vld_xml)
+
+    logger.debug("Sending VLD to netconf: %s", vld)
+    proxy.merge_config(vld.to_xml_v2(model))
+
+
+def configure_vnfd(proxy, vnfd_xml_hdl):
+    vnfd_xml = vnfd_xml_hdl.read()
+    logger.debug("Attempting to deserialize XML into VNFD protobuf: %s", vnfd_xml)
+    vnfd = VnfdYang.YangData_Vnfd_VnfdCatalog_Vnfd()
+    vnfd.from_xml_v2(model, vnfd_xml)
+
+    logger.debug("Sending VNFD to netconf: %s", vnfd)
+    proxy.merge_config(vnfd.to_xml_v2(model))
+
+
+def configure_nsd(proxy, nsd_xml_hdl):
+    nsd_xml = nsd_xml_hdl.read()
+    logger.debug("Attempting to deserialize XML into NSD protobuf: %s", nsd_xml)
+    nsd = NsdYang.YangData_Nsd_NsdCatalog_Nsd()
+    nsd.from_xml_v2(model, nsd_xml)
+
+    logger.debug("Sending NSD to netconf: %s", nsd)
+    proxy.merge_config(nsd.to_xml_v2(model))
+
+
+def parse_args(argv=sys.argv[1:]):
+    """Create a parser which includes all generic demo arguments and parse args
+
+    Arguments:
+        argv - arguments to be parsed
+
+    Returns: List of parsed arguments
+    """
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+            '--confd-host',
+            default="127.0.0.1",
+            help="Hostname or IP where the confd netconf server is running.")
+
+    parser.add_argument(
+            '--vld-xml-file',
+            action="append",
+            default=[],
+            type=argparse.FileType(),
+            help="VLD XML File Path",
+            )
+
+    parser.add_argument(
+            '--vnfd-xml-file',
+            action="append",
+            default=[],
+            type=argparse.FileType(),
+            help="VNFD XML File Path",
+            )
+
+    parser.add_argument(
+            '--nsd-xml-file',
+            action="append",
+            default=[],
+            type=argparse.FileType(),
+            help="VNFD XML File Path",
+            )
+
+    parser.add_argument(
+            '-v', '--verbose',
+            action='store_true',
+            help="Logging is normally set to an INFO level. When this flag "
+                 "is used logging is set to DEBUG. ")
+
+    args = parser.parse_args(argv)
+
+    return args
+
+
+def connect(args):
+    # Initialize Netconf Management Proxy
+    mgmt_proxy = rift.auto.proxy.NetconfProxy(args.confd_host)
+    mgmt_proxy.connect()
+
+    # Ensure system started
+    vcs_component_info = rift.vcs.mgmt.VcsComponentInfo(mgmt_proxy)
+    vcs_component_info.wait_until_system_started()
+
+    return mgmt_proxy
+
+
+def main():
+    args = parse_args()
+    proxy = connect(args)
+    for xml_file in args.vnfd_xml_file:
+        configure_vnfd(proxy, xml_file)
+
+    for xml_file in args.vld_xml_file:
+        configure_vld(proxy, xml_file)
+
+    for xml_file in args.nsd_xml_file:
+        configure_nsd(proxy, xml_file)
+
+
+if __name__ == "__main__":
+    main()
+
diff --git a/examples/ping_pong_ns/generate_packages.sh.in b/examples/ping_pong_ns/generate_packages.sh.in
new file mode 100755
index 0000000..ae54052
--- /dev/null
+++ b/examples/ping_pong_ns/generate_packages.sh.in
@@ -0,0 +1,140 @@
+#! /bin/bash
+
+set -e
+set -x
+
+SOURCE_DIR=@CMAKE_CURRENT_SOURCE_DIR@
+BINARY_DIR=@CMAKE_CURRENT_BINARY_DIR@
+PROJECT_TOP_DIR=@PROJECT_TOP_DIR@
+QCOW_IMAGE=${RIFT_ROOT}/images/Fedora-x86_64-20-20131211.1-sda.qcow2
+RIFT_QCOW_IMAGE=${RIFT_ROOT}/images/Fedora-x86_64-20-20131211.1-sda.qcow2
+PONG_QCOW_IMAGE=${RIFT_ROOT}/images/Fedora-x86_64-20-20131211.1-sda-pong.qcow2
+PING_QCOW_IMAGE=${RIFT_ROOT}/images/Fedora-x86_64-20-20131211.1-sda-ping.qcow2
+
+# These paths are needed for finding the overrides and so files
+PYTHONPATH=${PYTHONPATH}:@RIFT_SUBMODULE_SOURCE_ROOT@/rwvcs/ra:@RIFT_SUBMODULE_BINARY_ROOT@/models/plugins/yang
+PYTHON3PATH=${PYTHON3PATH}:@RIFT_SUBMODULE_SOURCE_ROOT@/rwvcs/ra:@RIFT_SUBMODULE_BINARY_ROOT@/models/plugins/yang
+LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:@RIFT_SUBMODULE_BINARY_ROOT@/models/plugins/yang
+
+#Rift Logos
+PING_VNFD_LOGO=${SOURCE_DIR}/rift_logo.png
+PONG_VNFD_LOGO=${SOURCE_DIR}/rift_logo.png
+PING_PONG_NSD_LOGO=${SOURCE_DIR}/rift_logo.png
+
+# Remove any old directories
+rm -rf ${BINARY_DIR}/ping_vnfd
+rm -rf ${BINARY_DIR}/pong_vnfd
+rm -rf ${BINARY_DIR}/ping_pong_nsd
+
+rm -rf ${BINARY_DIR}/ping_vnfd_with_image
+rm -rf ${BINARY_DIR}/pong_vnfd_with_image
+
+
+rm -rf ${BINARY_DIR}/ping_vnfd_aws
+rm -rf ${BINARY_DIR}/pong_vnfd_aws
+rm -rf ${BINARY_DIR}/ping_pong_nsd_aws
+
+rm -rf ${BINARY_DIR}/ping_vnfd_with_epa
+rm -rf ${BINARY_DIR}/pong_vnfd_with_epa
+rm -rf ${BINARY_DIR}/ping_pong_nsd_with_epa
+
+
+# Generate image md5sum
+ping_md5sum="$(md5sum ${PING_QCOW_IMAGE} | cut -f1 -d" ")"
+pong_md5sum="$(md5sum ${PONG_QCOW_IMAGE} | cut -f1 -d" ")"
+
+# Generate the descriptors (in various formats)
+${SOURCE_DIR}/ping_pong_nsd.py --outdir=${BINARY_DIR} --format=yaml --ping-image-md5=${ping_md5sum} --pong-image-md5=${pong_md5sum} --pong-cloud-init=pong_cloud_init.cfg --ping-cloud-init=ping_cloud_init.cfg
+
+
+# create directories for packages with images
+cp -r ${BINARY_DIR}/ping_vnfd ${BINARY_DIR}/ping_vnfd_with_image
+cp -r ${BINARY_DIR}/pong_vnfd ${BINARY_DIR}/pong_vnfd_with_image
+mkdir -p ${BINARY_DIR}/ping_vnfd_with_image/images
+mkdir -p ${BINARY_DIR}/pong_vnfd_with_image/images
+
+### Generate descriptors for AWS
+${SOURCE_DIR}/ping_pong_nsd.py --outdir=${BINARY_DIR}/aws --format=json --aws
+
+### Move the generated artifacts to appropriate directories
+mv ${BINARY_DIR}/aws/ping_vnfd ${BINARY_DIR}/ping_vnfd_aws
+mv ${BINARY_DIR}/aws/pong_vnfd ${BINARY_DIR}/pong_vnfd_aws
+mv ${BINARY_DIR}/aws/ping_pong_nsd ${BINARY_DIR}/ping_pong_nsd_aws
+
+### ReMove the original directories
+rm -rf ${BINARY_DIR}/aws
+
+### Generate descriptors with EPA
+${SOURCE_DIR}/ping_pong_nsd.py --outdir=${BINARY_DIR}/with_epa --format=json --epa --ping-image-md5=${ping_md5sum} --pong-image-md5=${pong_md5sum}
+
+### Move the generated artifacts to appropriate directories
+mv ${BINARY_DIR}/with_epa/ping_vnfd ${BINARY_DIR}/ping_vnfd_with_epa
+mv ${BINARY_DIR}/with_epa/pong_vnfd ${BINARY_DIR}/pong_vnfd_with_epa
+mv ${BINARY_DIR}/with_epa/ping_pong_nsd ${BINARY_DIR}/ping_pong_nsd_with_epa
+
+### ReMove the original directories
+rm -rf ${BINARY_DIR}/with_epa
+
+# copy a dummy image for now
+if [ -e ${PING_QCOW_IMAGE} ]; then
+# Add RIFT Logos
+    mkdir -p ${BINARY_DIR}/ping_vnfd_with_image/icons
+    cp ${PING_VNFD_LOGO} ${BINARY_DIR}/ping_vnfd_with_image/icons/
+
+    cp ${PING_QCOW_IMAGE} ${BINARY_DIR}/ping_vnfd_with_image/images/
+    ${RIFT_INSTALL}/usr/rift/toolchain/cmake/bin/generate_descriptor_pkg.sh ${BINARY_DIR} ping_vnfd_with_image
+else
+    echo >&2 "Warn: Skipped creating ping_vnfd_with_image due to missing image: ${PING_QCOW_IMAGE}"
+fi
+
+if [ -e ${PONG_QCOW_IMAGE} ]; then
+# Add RIFT Logos
+    mkdir -p ${BINARY_DIR}/pong_vnfd_with_image/icons
+    cp ${PONG_VNFD_LOGO} ${BINARY_DIR}/pong_vnfd_with_image/icons/
+
+    cp ${PONG_QCOW_IMAGE} ${BINARY_DIR}/pong_vnfd_with_image/images/
+    ${RIFT_INSTALL}/usr/rift/toolchain/cmake/bin/generate_descriptor_pkg.sh ${BINARY_DIR} pong_vnfd_with_image
+else
+    echo >&2 "Warn: Skipped creating pong_vnfd_with_image due to missing image: ${PONG_QCOW_IMAGE}"
+fi
+
+# Add RIFT Logos
+mkdir -p ${BINARY_DIR}/ping_vnfd/icons
+mkdir -p ${BINARY_DIR}/pong_vnfd/icons
+mkdir -p ${BINARY_DIR}/ping_pong_nsd/icons
+
+cp ${PING_VNFD_LOGO}      ${BINARY_DIR}/ping_vnfd/icons/
+cp ${PONG_VNFD_LOGO}      ${BINARY_DIR}/pong_vnfd/icons/
+cp ${PING_PONG_NSD_LOGO}  ${BINARY_DIR}/ping_pong_nsd/icons/
+
+# Generate the tar files
+${RIFT_INSTALL}/usr/rift/toolchain/cmake/bin/generate_descriptor_pkg.sh ${BINARY_DIR} ping_vnfd
+${RIFT_INSTALL}/usr/rift/toolchain/cmake/bin/generate_descriptor_pkg.sh ${BINARY_DIR} pong_vnfd
+${RIFT_INSTALL}/usr/rift/toolchain/cmake/bin/generate_descriptor_pkg.sh ${BINARY_DIR} ping_pong_nsd
+
+
+# Add RIFT Logos
+mkdir -p ${BINARY_DIR}/ping_vnfd_aws/icons
+mkdir -p ${BINARY_DIR}/pong_vnfd_aws/icons
+mkdir -p ${BINARY_DIR}/ping_pong_nsd_aws/icons
+
+cp ${PING_VNFD_LOGO}      ${BINARY_DIR}/ping_vnfd_aws/icons/
+cp ${PONG_VNFD_LOGO}      ${BINARY_DIR}/pong_vnfd_aws/icons/
+cp ${PING_PONG_NSD_LOGO}  ${BINARY_DIR}/ping_pong_nsd_aws/icons/
+
+${RIFT_INSTALL}/usr/rift/toolchain/cmake/bin/generate_descriptor_pkg.sh ${BINARY_DIR} ping_vnfd_aws
+${RIFT_INSTALL}/usr/rift/toolchain/cmake/bin/generate_descriptor_pkg.sh ${BINARY_DIR} pong_vnfd_aws
+${RIFT_INSTALL}/usr/rift/toolchain/cmake/bin/generate_descriptor_pkg.sh ${BINARY_DIR} ping_pong_nsd_aws
+
+# Add RIFT Logos
+mkdir -p ${BINARY_DIR}/ping_vnfd_with_epa/icons
+mkdir -p ${BINARY_DIR}/pong_vnfd_with_epa/icons
+mkdir -p ${BINARY_DIR}/ping_pong_nsd_with_epa/icons
+
+cp ${PING_VNFD_LOGO}      ${BINARY_DIR}/ping_vnfd_with_epa/icons/
+cp ${PONG_VNFD_LOGO}      ${BINARY_DIR}/pong_vnfd_with_epa/icons/
+cp ${PING_PONG_NSD_LOGO}  ${BINARY_DIR}/ping_pong_nsd_with_epa/icons/
+
+${RIFT_INSTALL}/usr/rift/toolchain/cmake/bin/generate_descriptor_pkg.sh ${BINARY_DIR} ping_vnfd_with_epa
+${RIFT_INSTALL}/usr/rift/toolchain/cmake/bin/generate_descriptor_pkg.sh ${BINARY_DIR} pong_vnfd_with_epa
+${RIFT_INSTALL}/usr/rift/toolchain/cmake/bin/generate_descriptor_pkg.sh ${BINARY_DIR} ping_pong_nsd_with_epa
diff --git a/examples/ping_pong_ns/ping_pong_ns/__init__.py b/examples/ping_pong_ns/ping_pong_ns/__init__.py
new file mode 100644
index 0000000..966870a
--- /dev/null
+++ b/examples/ping_pong_ns/ping_pong_ns/__init__.py
@@ -0,0 +1,15 @@
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
diff --git a/examples/ping_pong_ns/ping_pong_ns/ping.py b/examples/ping_pong_ns/ping_pong_ns/ping.py
new file mode 100644
index 0000000..00da96c
--- /dev/null
+++ b/examples/ping_pong_ns/ping_pong_ns/ping.py
@@ -0,0 +1,312 @@
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
+
+from datetime import date
+import logging
+import json
+import socket
+import threading
+import time
+
+import tornado.web
+
+from util.util import get_url_target
+ 
+class Ping(object):
+    def __init__(self):
+        self._log = logging.getLogger("ping")
+        self._log.setLevel(logging.DEBUG)
+
+        self._ping_count = 0;
+        self._request_count = 0;
+        self._response_count = 0;
+
+        self._pong_ip = None
+        self._pong_port = None
+
+        self._send_rate = 1 # per second
+
+        self._close_lock = threading.Lock()
+
+        self._enabled = False
+        self._socket = None
+
+    @property
+    def rate(self):
+        return self._send_rate
+
+    @rate.setter
+    def rate(self, value):
+        self._log.debug("new rate: %s" % value)
+        self._send_rate = value
+
+    @property
+    def pong_port(self):
+        return self._pong_port
+
+    @pong_port.setter
+    def pong_port(self, value):
+        self._log.debug("new pong port: %s" % value)
+        self._pong_port = value
+
+    @property
+    def pong_ip(self):
+        return self._pong_ip
+
+    @pong_ip.setter
+    def pong_ip(self, value):
+
+        self._log.debug("new pong ip: %s" % value)
+        self._pong_ip = value
+
+    @property
+    def enabled(self):
+        return self._enabled
+
+    @property
+    def request_count(self):
+        return self._request_count
+
+    @property
+    def response_count(self):
+        return self._response_count
+
+    def start(self):
+        self._log.debug("starting")
+        self._enabled = True
+        # self.open_socket()
+        self.send_thread = threading.Thread(target=self.send_ping)
+        self.recv_thread = threading.Thread(target=self.recv_resp)
+        self.send_thread.start()
+        self.recv_thread.start()
+
+    def stop(self):
+        self._log.debug("stopping")
+        self._enabled = False
+        self.close_socket("stopping")
+
+    def close_socket(self, msg):
+        self._close_lock.acquire()
+        if self._socket != None:
+            self._socket.close()
+            self._socket = None
+            self._log.info("Closed socket with msg={}".format(msg))
+        self._close_lock.release()
+
+    def open_socket(self):
+        try:
+            self._log.debug("construct socket")
+            self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            self._socket.settimeout(1)
+        except socket.error as msg:
+            self._log.error("error constructing socket %s" % msg)
+            self._socket = None
+
+        while self._enabled:
+            try:
+                self._log.info("Trying to connect....")
+                self._socket.connect((self.pong_ip, self.pong_port))
+                self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+                self._log.info("Socket connected")
+                break
+            except socket.error as msg:
+                time.sleep(1)
+                
+
+    def send_ping(self):
+        self.open_socket()
+
+        while self._enabled:
+            if self._socket != None:
+                req = "rwpingpong-{}".format(self._ping_count)
+                try:
+                    self._log.info("sending: %s" %req)
+                    self._socket.sendall(req)
+                    self._ping_count += 1
+                    self._request_count += 1
+                except socket.error as msg:
+                    self._log.error("Error({}) sending data".format(msg))
+                    self.close_socket(msg)
+                    return
+        
+            time.sleep(1.0/self._send_rate)
+
+        self._log.info("Stopping send_ping")
+
+    def recv_resp(self):
+        while self._enabled:
+            respb = None
+            if self._socket != None:
+                try:
+                    respb = self._socket.recv(1024)
+                except socket.timeout:
+                    continue
+                except socket.error as msg:
+                    self._log.error("Error({}) receiving data".format(msg))
+                    time.sleep(1)
+                    continue
+                    # self.close_socket(msg)
+                    # return
+
+            if not respb:
+                continue
+
+            resp = respb.decode('UTF-8')
+            self._response_count += 1
+            self._log.info("receive: %s" % resp)
+
+        self._log.info("Stopping recv_resp")
+
+class PingServerHandler(tornado.web.RequestHandler):
+    def initialize(self, ping_instance):
+        self._ping_instance = ping_instance
+
+    def get(self, args):
+        response = {'ip': self._ping_instance.pong_ip,
+                    'port': self._ping_instance.pong_port}
+
+        self.write(response)
+
+    def post(self, args):
+        target = get_url_target(self.request.uri)
+        body = self.request.body.decode("utf-8")
+        body_header = self.request.headers.get("Content-Type")
+
+        if "json" not in body_header:
+            self.write("Content-Type must be some kind of json 2")
+            self.set_status(405)
+            return
+
+        try:
+            json_dicts = json.loads(body)
+        except:
+            self.write("Content-Type must be some kind of json 1")
+            self.set_status(405)
+            return
+
+        if target == "server":
+            if type(json_dicts['port']) is not int:
+                self.set_status(405)
+                return
+
+            if type(json_dicts['ip']) not in (str, unicode):
+                self.set_status(405)
+                return
+
+            self._ping_instance.pong_ip = json_dicts['ip']
+            self._ping_instance.pong_port = json_dicts['port']
+
+        else:
+            self.set_status(404)
+            return
+
+        self.set_status(200)
+
+class PingAdminStatusHandler(tornado.web.RequestHandler):
+    def initialize(self, ping_instance):
+        self._ping_instance = ping_instance
+
+    def get(self, args):
+        target = get_url_target(self.request.uri)
+        if target == "state":
+            value = "enabled" if self._ping_instance.enabled else "disabled"
+
+            response = { 'adminstatus': value }
+        else:
+            self.set_status(404)
+            return
+
+        self.write(response)
+
+    def post(self, args):
+        target = get_url_target(self.request.uri)
+        body = self.request.body.decode("utf-8")
+        body_header = self.request.headers.get("Content-Type")
+
+        if "json" not in body_header:
+            self.write("Content-Type must be some kind of json 2")
+            self.set_status(405)            
+            return
+            
+        try:
+            json_dicts = json.loads(body)
+        except:
+            self.write("Content-Type must be some kind of json 1")
+            self.set_status(405)            
+            return
+
+        if target == "state":
+            if type(json_dicts['enable']) is not bool:
+                self.set_status(405)            
+                return
+
+            if json_dicts['enable']:
+                if not self._ping_instance.enabled:
+                    self._ping_instance.start()
+            else:
+                self._ping_instance.stop()            
+
+        else:
+            self.set_status(404)
+            return
+
+        self.set_status(200)
+
+class PingStatsHandler(tornado.web.RequestHandler):
+    def initialize(self, ping_instance):
+        self._ping_instance = ping_instance
+
+    def get(self):
+        response = {'ping-request-tx-count': self._ping_instance.request_count,
+                    'ping-response-rx-count': self._ping_instance.response_count}
+
+        self.write(response)
+
+class PingRateHandler(tornado.web.RequestHandler):
+    def initialize(self, ping_instance):
+        self._ping_instance = ping_instance
+
+    def get(self, args):
+        response = { 'rate': self._ping_instance.rate }
+
+        self.write(response)
+
+    def post(self, args):
+        target = get_url_target(self.request.uri)
+        body = self.request.body.decode("utf-8")
+        body_header = self.request.headers.get("Content-Type")
+
+        if "json" not in body_header:
+            self.set_status(405)
+            return
+
+        try:
+            json_dicts = json.loads(body)
+        except:
+            self.set_status(405)
+            return
+
+        if target == "rate":
+            if type(json_dicts['rate']) is not int:
+                self.set_status(405)
+                return
+
+            self._ping_instance.rate = json_dicts['rate']
+        else:
+            self.set_status(404)
+            return
+
+        self.set_status(200)
diff --git a/examples/ping_pong_ns/ping_pong_ns/ping.service b/examples/ping_pong_ns/ping_pong_ns/ping.service
new file mode 100644
index 0000000..cd0ac65
--- /dev/null
+++ b/examples/ping_pong_ns/ping_pong_ns/ping.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=Ping Client
+After=syslog.target network.target
+
+[Service]
+Type=simple
+ExecStart=/opt/rift/ping_pong_ns/start_ping
+
+[Install]
+WantedBy=multi-user.target
+
+
diff --git a/examples/ping_pong_ns/ping_pong_ns/pong.py b/examples/ping_pong_ns/ping_pong_ns/pong.py
new file mode 100644
index 0000000..ee5c2d2
--- /dev/null
+++ b/examples/ping_pong_ns/ping_pong_ns/pong.py
@@ -0,0 +1,334 @@
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
+
+from datetime import date
+from Queue import Queue
+import logging
+import json
+import socket
+import threading
+import time
+
+import tornado.web
+
+from util.util import get_url_target
+ 
+class Stats(object):
+    def __init__(self):
+        self._request_count = 0
+        self._response_count = 0
+
+        self._lock = threading.Lock()
+
+    @property
+    def request_count(self):
+        with self._lock:
+            return self._request_count
+
+    @request_count.setter
+    def request_count(self, value):
+        with self._lock:
+            self._request_count = value
+
+    @property
+    def response_count(self):
+        with self._lock:
+            return self._response_count
+
+    @response_count.setter
+    def response_count(self, value):
+        with self._lock:
+            self._response_count = value
+        
+class Worker(threading.Thread):
+    def __init__(self, log, connections, stats):
+        super(Worker, self).__init__()
+        self._log = log
+        self._connections = connections
+        self._stats = stats
+
+        self._running = True
+
+        self._lock = threading.Lock()
+        
+    @property
+    def running(self):
+        return self._running
+
+    @running.setter
+    def running(self, value):
+        self._running = value
+
+    def run(self):
+        while self.running:
+            try:
+                connection = self._connections.get_nowait()
+            except:
+                continue
+            
+            try:
+                req = connection.recv(1024)
+            except socket.error as msg:
+                self._log.error("error with connection read: " % msg)
+                self._connections.put(connection)
+                continue
+
+            if not req:
+                self._connections.put(connection)
+                continue
+
+            resp = req.decode('UTF-8')
+            self._log.debug("got: %s", resp)
+
+            self._stats.request_count += 1
+
+            try:
+                connection.sendall(resp)
+                self._stats.response_count += 1
+            except socket.error as msg:
+                self._log.error("error with connection read: " % msg)
+                self._connections.put(connection)
+                continue
+
+            self._connections.put(connection)        
+
+class Pong(object):
+    def __init__(self, worker_count=5):
+        self._log = logging.getLogger("pong")
+        self._log.setLevel(logging.DEBUG)
+
+        self.listen_ip = None
+        self.listen_port = None
+
+        self._lock = threading.Lock()
+
+        self._connections = Queue()
+        
+        self._stats = Stats()
+
+        self._workers = list()
+
+        self._enabled = False
+
+        for _ in range(worker_count):
+            self._workers.append(Worker(self._log, self._connections, self._stats))
+
+    @property
+    def listen_port(self):
+        return self._listen_port
+
+    @listen_port.setter
+    def listen_port(self, value):
+        self._log.debug("new listen port: %s" % value)
+        self._listen_port = value
+
+    @property
+    def listen_ip(self):
+        return self._listen_ip
+
+    @listen_ip.setter
+    def listen_ip(self, value):
+        self._log.debug("listen pong ip: %s" % value)
+        self._listen_ip = value
+
+
+    @property
+    def enabled(self):
+        with self._lock:
+            return self._enabled
+
+    @property
+    def request_count(self):
+        return self._stats.request_count
+
+    @property
+    def response_count(self):
+        return self._stats.response_count
+
+    def start(self):
+        self._log.debug("starting")
+        self._enabled = True
+        self.listener_thread = threading.Thread(target=self._listen)
+        self.listener_thread.start()
+        for worker in self._workers:
+            worker.start()
+
+    def stop(self):
+        with self._lock:
+            self._enabled = False
+
+            self._log.debug("stopping workers")
+            for worker in self._workers:
+                worker.running = False
+
+            self._log.debug("joining on workers")
+            for worker in self._workers:
+                if worker.is_alive():
+                    worker.join()
+
+            while self._connections.full():
+                try:
+                    connection = self._connections.get_nowait()
+                    connection.close()
+                except:
+                    pass
+
+    def close_socket(self, msg):
+        with self._lock:
+            if self._socket != None:
+                self._socket.shutdown(socket.SHUT_RD)
+                self._socket.close()
+                self._socket = None
+                self._log.info("Closed socket with msg={}".format(msg))
+
+    def _listen(self):
+        if self._listen_ip is None or self.listen_port is None:
+            self._log.error("address not properly configured to listen")
+            return
+
+        self._log.info("listen for incomming connections")
+        try:
+            self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+            self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+            # self._socket.bind((self.listen_ip, self.listen_port))
+            self._socket.bind(("0.0.0.0", self.listen_port))
+            self._socket.settimeout(1)
+
+            while self.enabled:
+                
+                try:
+                    self._socket.listen(1)
+                    connection, address = self._socket.accept()
+                except socket.timeout:
+                    continue
+                self._log.info("Accepted connection from {}".format(address))
+
+                self._connections.put(connection)
+            else:
+                self.stop()
+        except socket.error as msg:
+            self.close_socket(msg)
+
+class PongStatsHandler(tornado.web.RequestHandler):
+    def initialize(self, pong_instance):
+        self._pong_instance = pong_instance
+
+    def get(self):
+        response = {'ping-request-rx-count': self._pong_instance.request_count,
+                    'ping-response-tx-count': self._pong_instance.response_count}
+
+        self.write(response)
+
+
+class PongServerHandler(tornado.web.RequestHandler):
+    def initialize(self, pong_instance):
+        self._pong_instance = pong_instance
+
+    def get(self, args):
+        response = {'ip': self._pong_instance.listen_ip,
+                    'port': self._pong_instance.listen_port}
+
+        self.write(response)
+
+    def post(self, args):
+        target = get_url_target(self.request.uri)
+        body = self.request.body.decode("utf-8")
+        body_header = self.request.headers.get("Content-Type")
+
+        if "json" not in body_header:
+            self.write("Content-Type must be some kind of json")
+            self.set_status(405)
+            return
+
+        try:
+            json_dicts = json.loads(body)
+        except:
+            self.write("Content-Type must be some kind of json")
+            self.set_status(405)
+            return
+
+        if target == "server":
+
+            if type(json_dicts['port']) is not int:
+                self.set_status(405)
+                return
+
+            if type(json_dicts['ip']) not in (str, unicode):
+                self.set_status(405)
+                return
+
+            self._pong_instance.listen_ip = json_dicts['ip']
+            self._pong_instance.listen_port = json_dicts['port']
+
+        else:
+            self.set_status(404)
+            return
+
+        self.set_status(200)
+
+class PongAdminStatusHandler(tornado.web.RequestHandler):
+    def initialize(self, pong_instance):
+        self._pong_instance = pong_instance
+
+    def get(self, args):
+        target = get_url_target(self.request.uri)
+        
+        if target == "state":
+            value = "enabled" if self._pong_instance.enabled else "disabled"
+
+            response = { 'adminstatus': value }
+        else:
+            self.set_status(404)
+            return
+
+        self.write(response)
+
+    def post(self, args):
+        target = get_url_target(self.request.uri)
+        body = self.request.body.decode("utf-8")
+        body_header = self.request.headers.get("Content-Type")
+
+        if "json" not in body_header:
+            self.write("Content-Type must be some kind of json")
+            self.set_status(405)            
+            return
+            
+        try:
+            json_dicts = json.loads(body)
+        except:
+            self.write("Content-Type must be some kind of json")
+            self.set_status(405)            
+            return
+
+        if target == "state":
+            if type(json_dicts['enable']) is not bool:
+                self.set_status(405)            
+                return
+
+            if json_dicts['enable']:
+                if not self._pong_instance.enabled:
+                    self._pong_instance.start()
+            else:
+                self._pong_instance.stop()
+
+        else:
+            self.set_status(404)
+            return
+
+        self.set_status(200)
+
+
diff --git a/examples/ping_pong_ns/ping_pong_ns/pong.service b/examples/ping_pong_ns/ping_pong_ns/pong.service
new file mode 100644
index 0000000..7d94836
--- /dev/null
+++ b/examples/ping_pong_ns/ping_pong_ns/pong.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=Ping Client
+After=syslog.target network.target
+
+[Service]
+Type=simple
+ExecStart=/opt/rift/ping_pong_ns/start_pong
+
+[Install]
+WantedBy=multi-user.target
+
+
diff --git a/examples/ping_pong_ns/ping_pong_ns/prepare_ping_pong_qcow.sh b/examples/ping_pong_ns/ping_pong_ns/prepare_ping_pong_qcow.sh
new file mode 100755
index 0000000..e73144a
--- /dev/null
+++ b/examples/ping_pong_ns/ping_pong_ns/prepare_ping_pong_qcow.sh
@@ -0,0 +1,137 @@
+#! /bin/bash
+
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
+# Author(s): Anil Gunturu
+# Creation Date: 07/24/2014
+# 
+
+##
+# This script is used to copy the riftware software into the qcow image
+# This script must be run on the grunt machine as root
+##
+
+set -x
+set -e
+
+if ! [ $# -eq 1 ]; then
+    echo "Usage: $0 <ping-pong-ns-dir>"
+    echo "       Example:"
+    echo "       $0 /net/boson/home1/agunturu/lepton/atg/modules/core/mc/examples/ping_pong_ns"
+    exit 1
+fi
+
+# Currently returning 0 on error as this script fails in Bangalore
+# systems and causes the jenkins spot_debug to fail
+function cleanup {
+  if [ "$(ls -A $MOUNT_PT)" ]; then
+    guestunmount $MOUNT_PT
+  fi
+  exit 0
+}
+trap cleanup EXIT
+
+MOUNT_PT=ping_pong/mnt$$
+
+if  [ -d $MOUNT_PT ]; then
+  echo "ping_pong_mnt directory exists - deleting..!!"
+  guestunmount $MOUNT_PT || true
+  rm -rf ping_pong
+fi
+
+mkdir -p $MOUNT_PT
+FC20QCOW=Fedora-x86_64-20-20131211.1-sda.qcow2
+PINGQCOW=Fedora-x86_64-20-20131211.1-sda-ping.qcow2
+PONGQCOW=Fedora-x86_64-20-20131211.1-sda-pong.qcow2
+
+if [ ! -e ${RIFT_ROOT}/images/${FC20QCOW} ]; then
+    echo >&2 "Warn: Cannot prepare ping_pong qcow due to missing FC20 image: ${RIFT_ROOT}/images/${FC20QCOW}"
+    exit 0
+fi
+
+echo "Copying $FC20QCOW"
+cp ${RIFT_ROOT}/images/${FC20QCOW} ping_pong/${PINGQCOW}
+chmod +w ping_pong/${PINGQCOW}
+cp ${RIFT_ROOT}/images/${FC20QCOW} ping_pong/${PONGQCOW}
+chmod +w ping_pong/${PONGQCOW}
+
+CURRENT_DIR=$PWD
+echo "Mounting guestfs for $PINGQCOW"
+guestmount -a ping_pong/$PINGQCOW -m /dev/sda1 $MOUNT_PT
+
+echo "Setting up resolv.conf"
+# removed RIFT.io lab-centric setup in RIFT-11991
+#echo "search lab.riftio.com eng.riftio.com riftio.com" >  $MOUNT_PT/etc/resolv.conf
+#echo "nameserver 10.64.1.3" >>  $MOUNT_PT/etc/resolv.conf
+#echo "PEERDNS=no" >> $MOUNT_PT/etc/sysconfig/network-scripts/ifcfg-eth0
+
+# add a valid DNS server just in case
+echo "nameserver 8.8.8.8" >  $MOUNT_PT/etc/resolv.conf
+echo "DEFROUTE=yes" >> $MOUNT_PT/etc/sysconfig/network-scripts/ifcfg-eth0
+
+for i in 1 2
+do
+    cat <<EOF >> $MOUNT_PT/etc/sysconfig/network-scripts/ifcfg-eth$i
+DEVICE="eth$i"
+BOOTPROTO="dhcp"
+ONBOOT="no"
+TYPE="Ethernet"
+DEFROUTE=no
+PEERDNS=no
+EOF
+done
+
+
+echo "Copying ping/pong ns..."
+cd $MOUNT_PT/opt
+mkdir rift
+cd rift
+cp -r $1 .
+cd $CURRENT_DIR
+mv $MOUNT_PT/opt/rift/ping_pong_ns/ping.service $MOUNT_PT/etc/systemd/system
+cp -ar /usr/lib/python2.7/site-packages/tornado $MOUNT_PT/usr/lib/python2.7/site-packages/
+guestunmount $MOUNT_PT
+
+echo "Mounting guestfs for $PINGQCOW"
+guestmount -a ping_pong/$PONGQCOW -m /dev/sda1 $MOUNT_PT
+
+echo "Setting up resolv.conf"
+echo "search lab.riftio.com eng.riftio.com riftio.com" >  $MOUNT_PT/etc/resolv.conf
+echo "nameserver 10.64.1.3" >>  $MOUNT_PT/etc/resolv.conf
+echo "PEERDNS=no" >> $MOUNT_PT/etc/sysconfig/network-scripts/ifcfg-eth0
+echo "DEFROUTE=yes" >> $MOUNT_PT/etc/sysconfig/network-scripts/ifcfg-eth0
+
+for i in 1 2
+do
+    cat <<EOF >> $MOUNT_PT/etc/sysconfig/network-scripts/ifcfg-eth$i
+DEVICE="eth$i"
+BOOTPROTO="dhcp"
+ONBOOT="no"
+DEFROUTE=no
+TYPE="Ethernet"
+PEERDNS=no
+EOF
+done
+
+echo "Copying ping/pong ns..."
+cd $MOUNT_PT/opt
+mkdir rift
+cd rift
+cp -r $1 .
+cd $CURRENT_DIR
+cp -ar /usr/lib/python2.7/site-packages/tornado $MOUNT_PT/usr/lib/python2.7/site-packages/
+mv $MOUNT_PT/opt/rift/ping_pong_ns/pong.service $MOUNT_PT/etc/systemd/system
+guestunmount $MOUNT_PT
diff --git a/examples/ping_pong_ns/ping_pong_ns/start_ping b/examples/ping_pong_ns/ping_pong_ns/start_ping
new file mode 100755
index 0000000..fb29422
--- /dev/null
+++ b/examples/ping_pong_ns/ping_pong_ns/start_ping
@@ -0,0 +1,5 @@
+#!/bin/bash
+ulimit -c 0 
+#yum install -y python-tornado
+python /opt/rift/ping_pong_ns/start_ping.py 2>&1 | logger
+
diff --git a/examples/ping_pong_ns/ping_pong_ns/start_ping.py b/examples/ping_pong_ns/ping_pong_ns/start_ping.py
new file mode 100644
index 0000000..ace5981
--- /dev/null
+++ b/examples/ping_pong_ns/ping_pong_ns/start_ping.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
+
+import argparse
+import signal
+import logging
+
+import tornado
+import tornado.httpserver
+
+from ping import (
+    Ping,
+    PingAdminStatusHandler,
+    PingServerHandler,
+    PingRateHandler,
+    PingStatsHandler,
+)
+from util.util import (
+    VersionHandler,    
+)
+
+logging.basicConfig(level=logging.DEBUG,
+                    format='(%(threadName)-10s) %(name)-8s :: %(message)s',
+)
+
+def main():
+    log = logging.getLogger("main")
+
+    # parse arguments
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        "--ping-manager-port",
+        required=False,
+        default="18888",
+        help="port number for ping")
+
+    arguments = parser.parse_args()
+
+    # setup application
+    log.debug("setup application")
+    ping_instance = Ping()
+    ping_application_arguments = {'ping_instance': ping_instance}
+    ping_application = tornado.web.Application([
+        (r"/api/v1/ping/stats", PingStatsHandler, ping_application_arguments),
+        (r"/api/v1/ping/adminstatus/([a-z]+)", PingAdminStatusHandler, ping_application_arguments),
+        (r"/api/v1/ping/server/?([0-9a-z\.]*)", PingServerHandler, ping_application_arguments),
+        (r"/api/v1/ping/rate/?([0-9]*)", PingRateHandler, ping_application_arguments),
+        (r"/version", VersionHandler, ping_application_arguments)
+    ])
+    ping_server = tornado.httpserver.HTTPServer(
+        ping_application)
+
+    # setup SIGINT handler
+    log.debug("setup SIGINT handler")
+    def signal_handler(signal, frame):
+        print("") # print newline to clear user input
+        log.info("Exiting")
+        ping_instance.stop()
+        ping_server.stop()
+        log.info("Sayonara!")
+        quit()
+
+    signal.signal(signal.SIGINT, signal_handler)
+    
+    # start
+    log.debug("start")
+    try:
+        ping_server.listen(arguments.ping_manager_port)
+    except OSError:
+        print("port %s is already is use, exiting" % arguments.ping_manager_port)
+        return
+
+    tornado.ioloop.IOLoop.instance().start()
+    
+if __name__ == "__main__":
+    main()
+
+
diff --git a/examples/ping_pong_ns/ping_pong_ns/start_pong b/examples/ping_pong_ns/ping_pong_ns/start_pong
new file mode 100755
index 0000000..af46646
--- /dev/null
+++ b/examples/ping_pong_ns/ping_pong_ns/start_pong
@@ -0,0 +1,5 @@
+#!/bin/bash
+ulimit -c 0 
+#yum install -y python-tornado
+python /opt/rift/ping_pong_ns/start_pong.py 2>&1 | logger
+
diff --git a/examples/ping_pong_ns/ping_pong_ns/start_pong.py b/examples/ping_pong_ns/ping_pong_ns/start_pong.py
new file mode 100644
index 0000000..235efb2
--- /dev/null
+++ b/examples/ping_pong_ns/ping_pong_ns/start_pong.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
+
+import argparse
+import signal
+import logging
+
+import tornado
+import tornado.httpserver
+
+from pong import (
+    Pong,
+    PongAdminStatusHandler,
+    PongServerHandler,
+    PongStatsHandler,
+)
+from util.util import (
+    VersionHandler,    
+)
+
+logging.basicConfig(level=logging.DEBUG,
+                    format='(%(threadName)-10s) %(name)-8s :: %(message)s',
+)
+
+def main():
+    log = logging.getLogger("main")
+
+    # parse arguments
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        "--pong-manager-port",
+        required=False,
+        default="18889",
+        help="port number for pong")
+    parser.add_argument(
+        "--worker-count",
+        required=False,
+        default=5,
+        help="ip address of pong")
+
+    arguments = parser.parse_args()
+
+    # setup application
+    log.debug("setup application")
+    pong_instance = Pong(arguments.worker_count)
+    pong_application_arguments = {'pong_instance': pong_instance}
+    pong_application = tornado.web.Application([
+        (r"/version", VersionHandler, pong_application_arguments),
+        (r"/api/v1/pong/stats", PongStatsHandler, pong_application_arguments),
+        (r"/api/v1/pong/server/?([0-9a-z\.]*)", PongServerHandler, pong_application_arguments),
+        (r"/api/v1/pong/adminstatus/([a-z]+)", PongAdminStatusHandler, pong_application_arguments)
+    ])
+    pong_server = tornado.httpserver.HTTPServer(
+        pong_application)
+
+    # setup SIGINT handler
+    log.debug("setup SIGINT handler")
+    def signal_handler(signal, frame):
+        print("") # print newline to clear user input
+        log.info("Exiting")
+        pong_instance.stop()
+        pong_server.stop()
+        log.info("Sayonara!")
+        quit()
+
+    signal.signal(signal.SIGINT, signal_handler)
+    
+    # start
+    log.debug("pong application listening on %s" % arguments.pong_manager_port)
+    try:
+        pong_server.listen(arguments.pong_manager_port)
+    except OSError:
+        print("port %s is already is use, exiting" % arguments.ping_manager_port)
+        return
+    tornado.ioloop.IOLoop.instance().start()
+    
+if __name__ == "__main__":
+    main()
+
+
diff --git a/examples/ping_pong_ns/ping_pong_ns/test/test.sh b/examples/ping_pong_ns/ping_pong_ns/test/test.sh
new file mode 100644
index 0000000..c05a7d5
--- /dev/null
+++ b/examples/ping_pong_ns/ping_pong_ns/test/test.sh
@@ -0,0 +1,150 @@
+#!/bin/bash
+
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
+
+
+pong_ip='14.0.0.2'
+pong_port=18889
+
+ping_ip='14.0.0.3'
+
+ping_port=18888
+
+if [ "$1" == "pong" ];
+then
+    if [ "$2" == "enable" ];
+    then
+	echo "enable pong"
+
+	curl -D /dev/stdout \
+	     -H "Accept: application/vnd.yang.data+xml" \
+	     -H "Content-Type: application/vnd.yang.data+json" \
+	     -X POST \
+	     -d "{\"enable\":true}" \
+	     http://${pong_ip}:${pong_port}/api/v1/pong/adminstatus/state
+    fi
+    if [ "$2" == "disable" ];
+    then
+    	echo "disable pong"
+	
+	curl -D /dev/stdout \
+	     -H "Accept: application/vnd.yang.data+xml" \
+	     -H "Content-Type: application/vnd.yang.data+json" \
+	     -X POST \
+	     -d "{\"enable\":false}" \
+	     http://${pong_ip}:${pong_port}/api/v1/pong/adminstatus/state
+    fi
+
+    if [ "$2" == "server" ];
+    then
+	echo "set server"
+	
+	curl -D /dev/stdout \
+	     -H "Accept: application/vnd.yang.data+xml" \
+	     -H "Content-Type: application/vnd.yang.data+json" \
+	     -X POST \
+	     -d "{\"ip\":\"$3\", \"port\":$4}" \
+	     http://${pong_ip}:${pong_port}/api/v1/pong/server
+    fi
+
+    echo ""
+fi
+
+if [ "$1" == "ping" ];
+then
+    if [ "$2" == "enable" ];
+    then
+	echo "enable ping"
+
+	curl -D /dev/stdout \
+	     -H "Accept: application/vnd.yang.data+xml" \
+	     -H "Content-Type: application/vnd.yang.data+json" \
+	     -X POST \
+	     -d "{\"enable\":true}" \
+	     http://${ping_ip}:${ping_port}/api/v1/ping/adminstatus/state
+    fi
+    if [ "$2" == "disable" ];
+    then
+	echo "disable ping"
+	
+	curl -D /dev/stdout \
+	     -H "Accept: application/vnd.yang.data+xml" \
+	     -H "Content-Type: application/vnd.yang.data+json" \
+	     -X POST \
+	     -d "{\"enable\":false}" \
+	     http://${ping_ip}:${ping_port}/api/v1/ping/adminstatus/state
+    fi
+    echo ""
+
+    if [ "$2" == "rate" ];
+    then
+	echo "disable ping"
+	
+	curl -D /dev/stdout \
+	     -H "Accept: application/vnd.yang.data+xml" \
+	     -H "Content-Type: application/vnd.yang.data+json" \
+	     -X POST \
+	     -d "{\"rate\":$3}" \
+	     http://${ping_ip}:${ping_port}/api/v1/ping/rate
+    fi
+    echo ""
+
+    if [ "$2" == "server" ];
+    then
+	echo "set server"
+	
+	curl -D /dev/stdout \
+	     -H "Accept: application/vnd.yang.data+xml" \
+	     -H "Content-Type: application/vnd.yang.data+json" \
+	     -X POST \
+	     -d "{\"ip\":\"$3\", \"port\":$4}" \
+	     http://${ping_ip}:${ping_port}/api/v1/ping/server
+    fi
+    echo ""
+
+    
+fi
+
+if [ "$1" == "stats" ];
+then
+    echo "ping stats:"
+    curl http://${ping_ip}:${ping_port}/api/v1/ping/stats
+    echo ""
+
+    echo "pong stats:"
+    curl http://${pong_ip}:${pong_port}/api/v1/pong/stats
+    echo ""
+fi
+
+if [ "$1" == "config" ];
+then
+    echo "ping server:"
+    curl http://${ping_ip}:${ping_port}/api/v1/ping/server
+    echo ""
+    echo "ping rate:"
+    curl http://${ping_ip}:${ping_port}/api/v1/ping/rate
+    echo ""
+    echo "ping admin status:"
+    curl http://${ping_ip}:${ping_port}/api/v1/ping/adminstatus/state
+    echo ""
+    echo "pong server:"
+    curl http://${pong_ip}:${pong_port}/api/v1/pong/server
+    echo ""
+    echo "pong admin status:"
+    curl http://${pong_ip}:${pong_port}/api/v1/pong/adminstatus/state
+    echo ""
+fi
diff --git a/examples/ping_pong_ns/ping_pong_ns/user-data b/examples/ping_pong_ns/ping_pong_ns/user-data
new file mode 100644
index 0000000..9bf1d5b
--- /dev/null
+++ b/examples/ping_pong_ns/ping_pong_ns/user-data
@@ -0,0 +1,8 @@
+#cloud-config
+password: fedora
+chpasswd: { expire: False }
+ssh_pwauth: True
+runcmd:
+  - [ systemctl, daemon-reload ]
+  - [ systemctl, enable, ping.service ]
+  - [ systemctl, start, --no-block, ping.service ]
diff --git a/examples/ping_pong_ns/ping_pong_ns/util/__init__.py b/examples/ping_pong_ns/ping_pong_ns/util/__init__.py
new file mode 100644
index 0000000..966870a
--- /dev/null
+++ b/examples/ping_pong_ns/ping_pong_ns/util/__init__.py
@@ -0,0 +1,15 @@
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
diff --git a/examples/ping_pong_ns/ping_pong_ns/util/util.py b/examples/ping_pong_ns/ping_pong_ns/util/util.py
new file mode 100644
index 0000000..66c11fc
--- /dev/null
+++ b/examples/ping_pong_ns/ping_pong_ns/util/util.py
@@ -0,0 +1,38 @@
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
+
+from datetime import date
+import urlparse
+
+import tornado.web
+
+class VersionHandler(tornado.web.RequestHandler):
+    def initialize(self, instance):
+        self._instance = instance
+
+    def get(self):
+        response = { 'version': '3.5.1',
+                     'last_build':  date.today().isoformat() }
+        self.write(response)
+ 
+def get_url_target(url):
+    is_operation = False
+    url_parts = urlparse.urlsplit(url)
+    whole_url = url_parts[2]
+
+    url_pieces = whole_url.split("/")
+    
+    return url_pieces[-1]
diff --git a/examples/ping_pong_ns/ping_pong_nsd.py b/examples/ping_pong_ns/ping_pong_nsd.py
new file mode 120000
index 0000000..3147ac8
--- /dev/null
+++ b/examples/ping_pong_ns/ping_pong_nsd.py
@@ -0,0 +1 @@
+rift/mano/examples/ping_pong_nsd.py
\ No newline at end of file
diff --git a/examples/ping_pong_ns/rift/mano/__init__.py b/examples/ping_pong_ns/rift/mano/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/ping_pong_ns/rift/mano/__init__.py
diff --git a/examples/ping_pong_ns/rift/mano/examples/__init__.py b/examples/ping_pong_ns/rift/mano/examples/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/ping_pong_ns/rift/mano/examples/__init__.py
diff --git a/examples/ping_pong_ns/rift/mano/examples/ping_config.py b/examples/ping_pong_ns/rift/mano/examples/ping_config.py
new file mode 100755
index 0000000..4e5fd35
--- /dev/null
+++ b/examples/ping_pong_ns/rift/mano/examples/ping_config.py
@@ -0,0 +1,195 @@
+#!/usr/bin/env python3
+
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
+
+import argparse
+import logging
+import os
+import stat
+import subprocess
+import sys
+import time
+import yaml
+
+def ping_config(run_dir, mgmt_ip, mgmt_port, pong_cp, logger, dry_run):
+    sh_file = "{}/ping_config-{}.sh".format(run_dir, time.strftime("%Y%m%d%H%M%S"))
+    logger.debug("Creating script file %s" % sh_file)
+    f = open(sh_file, "w")
+    f.write(r'''
+#!/bin/bash
+
+# Rest API config
+ping_mgmt_ip='{}'
+ping_mgmt_port={}
+
+# VNF specific configuration
+pong_server_ip='{}'
+ping_rate=5
+server_port=5555
+'''.format(mgmt_ip, mgmt_port, pong_cp))
+
+    f.write(r'''
+# Check if the port is open
+DELAY=1
+MAX_TRIES=60
+COUNT=0
+while true; do
+    COUNT=$(expr $COUNT + 1)
+    timeout 1 bash -c "cat < /dev/null > /dev/tcp/${ping_mgmt_ip}/${ping_mgmt_port}"
+    rc=$?
+    if [ $rc -ne 0 ]
+    then
+        echo "Failed to connect to server ${ping_mgmt_ip}:${ping_mgmt_port} for ping with $rc!"
+        if [ ${COUNT} -gt ${MAX_TRIES} ]; then
+            exit $rc
+        fi
+        sleep ${DELAY}
+    else
+        break
+    fi
+done
+
+# Make rest API calls to configure VNF
+curl -D /dev/stdout \
+    -H "Accept: application/vnd.yang.data+xml" \
+    -H "Content-Type: application/vnd.yang.data+json" \
+    -X POST \
+    -d "{\"ip\":\"$pong_server_ip\", \"port\":$server_port}" \
+    http://${ping_mgmt_ip}:${ping_mgmt_port}/api/v1/ping/server
+rc=$?
+if [ $rc -ne 0 ]
+then
+    echo "Failed to set server info for ping!"
+    exit $rc
+fi
+
+curl -D /dev/stdout \
+    -H "Accept: application/vnd.yang.data+xml" \
+    -H "Content-Type: application/vnd.yang.data+json" \
+    -X POST \
+    -d "{\"rate\":$ping_rate}" \
+    http://${ping_mgmt_ip}:${ping_mgmt_port}/api/v1/ping/rate
+rc=$?
+if [ $rc -ne 0 ]
+then
+    echo "Failed to set ping rate!"
+    exit $rc
+fi
+
+output=$(curl -D /dev/stdout \
+    -H "Accept: application/vnd.yang.data+xml" \
+    -H "Content-Type: application/vnd.yang.data+json" \
+    -X POST \
+    -d "{\"enable\":true}" \
+    http://${ping_mgmt_ip}:${ping_mgmt_port}/api/v1/ping/adminstatus/state)
+if [[ $output == *"Internal Server Error"* ]]
+then
+    echo $output
+    exit 3
+else
+    echo $output
+fi
+
+exit 0
+''')
+    f.close()
+    os.chmod(sh_file, stat.S_IRWXU)
+    if not dry_run:
+        rc = subprocess.call(sh_file, shell=True)
+        if rc:
+            logger.error("Config failed: {}".format(rc))
+            return False
+    return True
+
+
+
+def main(argv=sys.argv[1:]):
+    try:
+        parser = argparse.ArgumentParser()
+        parser.add_argument("yaml_cfg_file", type=argparse.FileType('r'))
+        parser.add_argument("--dry-run", action="store_true")
+        parser.add_argument("--quiet", "-q", dest="verbose", action="store_false")
+        args = parser.parse_args()
+
+        run_dir = os.path.join(os.environ['RIFT_INSTALL'], "var/run/rift")
+        if not os.path.exists(run_dir):
+            os.makedirs(run_dir)
+        log_file = "{}/rift_ping_config-{}.log".format(run_dir, time.strftime("%Y%m%d%H%M%S"))
+        logging.basicConfig(filename=log_file, level=logging.DEBUG)
+        logger = logging.getLogger()
+
+        ch = logging.StreamHandler()
+        if args.verbose:
+            ch.setLevel(logging.DEBUG)
+        else:
+            ch.setLevel(logging.INFO)
+
+        # create formatter and add it to the handlers
+        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+        ch.setFormatter(formatter)
+        logger.addHandler(ch)
+
+    except Exception as e:
+        print("Got exception:{}".format(e))
+        raise e
+
+    try:
+        dry_run = args.dry_run
+
+        yaml_str = args.yaml_cfg_file.read()
+        logger.debug("Input YAML file: {}".format(yaml_str))
+        yaml_cfg = yaml.load(yaml_str)
+        logger.debug("Input YAML: {}".format(yaml_cfg))
+
+        # Check if this is post scale out trigger
+        if yaml_cfg['trigger'] != 'post_scale_out':
+            logger.error("Unexpected trigger {}".
+                         format(yaml_cfg['trigger']))
+            raise
+
+        pong_cp = ""
+        for vnfr in yaml_cfg['vnfrs_others']:
+            # Find the pong VNFR, assuming vnfr name will
+            # have pong_vnfd as a substring
+            if 'pong_vnfd' in vnfr['name']:
+                for cp in vnfr['connection_points']:
+                    logger.debug("Connection point {}".format(cp))
+                    if 'cp0' in cp['name']:
+                        pong_cp = cp['ip_address']
+                        break
+        if not len(pong_cp):
+            logger.error("Did not get Pong cp0 IP")
+            raise
+
+        for vnfr in yaml_cfg['vnfrs_in_group']:
+            mgmt_ip = vnfr['rw_mgmt_ip']
+            mgmt_port = vnfr['rw_mgmt_port']
+            if ping_config(run_dir, mgmt_ip, mgmt_port, pong_cp, logger, dry_run):
+                logger.info("Successfully configured Ping {} at {}".
+                            format(vnfr['name'], mgmt_ip))
+            else:
+                logger.error("Config of ping {} with {} failed".
+                             format(vnfr['name'], mgmt_ip))
+                raise
+
+    except Exception as e:
+        logger.error("Got exception {}".format(e))
+        logger.exception(e)
+        raise e
+
+if __name__ == "__main__":
+    main()
diff --git a/examples/ping_pong_ns/rift/mano/examples/ping_config_ut.sh b/examples/ping_pong_ns/rift/mano/examples/ping_config_ut.sh
new file mode 100755
index 0000000..67f3f19
--- /dev/null
+++ b/examples/ping_pong_ns/rift/mano/examples/ping_config_ut.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
+
+
+echo "Executed ping config!"
diff --git a/examples/ping_pong_ns/rift/mano/examples/ping_pong_nsd.py b/examples/ping_pong_ns/rift/mano/examples/ping_pong_nsd.py
new file mode 100755
index 0000000..bb834ed
--- /dev/null
+++ b/examples/ping_pong_ns/rift/mano/examples/ping_pong_nsd.py
@@ -0,0 +1,1075 @@
+#!/usr/bin/env python3
+
+# 
+#   Copyright 2016 RIFT.IO Inc
+#
+#   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.
+#
+
+
+import argparse
+import os
+import shutil
+import sys
+import uuid
+
+import gi
+gi.require_version('RwYang', '1.0')
+gi.require_version('RwVnfdYang', '1.0')
+gi.require_version('VnfdYang', '1.0')
+gi.require_version('RwNsdYang', '1.0')
+gi.require_version('NsdYang', '1.0')
+
+
+from gi.repository import (
+    RwNsdYang,
+    NsdYang,
+    RwVnfdYang,
+    VnfdYang,
+    RwYang,
+    )
+
+
+try:
+    import rift.mano.config_data.config as config_data
+except ImportError:
+    # Load modules from common which are not yet installed
+    path = os.path.abspath(os.path.dirname(os.path.abspath(__file__)) + "../../../common/python/rift/mano")
+    sys.path.append(path)
+
+    import config_data.config as config_data
+
+
+NUM_PING_INSTANCES = 1
+MAX_VNF_INSTANCES_PER_NS = 10
+use_epa = False
+aws = False
+pingcount = NUM_PING_INSTANCES
+use_ping_cloud_init_file = ""
+use_pong_cloud_init_file = ""
+
+PING_USERDATA_FILE = '''#cloud-config
+password: fedora
+chpasswd: { expire: False }
+ssh_pwauth: True
+runcmd:
+  - [ systemctl, daemon-reload ]
+  - [ systemctl, enable, ping.service ]
+  - [ systemctl, start, --no-block, ping.service ]
+  - [ ifup, eth1 ]
+'''
+
+PONG_USERDATA_FILE = '''#cloud-config
+password: fedora
+chpasswd: { expire: False }
+ssh_pwauth: True
+runcmd:
+  - [ systemctl, daemon-reload ]
+  - [ systemctl, enable, pong.service ]
+  - [ systemctl, start, --no-block, pong.service ]
+  - [ ifup, eth1 ]
+'''
+
+
+class UnknownVNFError(Exception):
+    pass
+
+
+class ManoDescriptor(object):
+    def __init__(self, name):
+        self.name = name
+        self.descriptor = None
+
+    def write_to_file(self, module_list, outdir, output_format):
+        model = RwYang.Model.create_libncx()
+        for module in module_list:
+            model.load_module(module)
+
+        if output_format == 'json':
+            with open('%s/%s.json' % (outdir, self.name), "w") as fh:
+                fh.write(self.descriptor.to_json(model))
+        elif output_format.strip() == 'xml':
+            with open('%s/%s.xml' % (outdir, self.name), "w") as fh:
+                fh.write(self.descriptor.to_xml_v2(model))
+        elif output_format.strip() == 'yaml':
+            with open('%s/%s.yaml' % (outdir, self.name), "w") as fh:
+                fh.write(self.descriptor.to_yaml(model))
+        else:
+            raise Exception("Invalid output format for the descriptor")
+
+    def get_json(self, module_list):
+        model = RwYang.Model.create_libncx()
+        for module in module_list:
+            model.load_module(module)
+        print(self.descriptor.to_json(model))
+
+
+class VirtualNetworkFunction(ManoDescriptor):
+    def __init__(self, name, instance_count=1):
+        self.vnfd_catalog = None
+        self.vnfd = None
+        self.instance_count = instance_count
+        self._placement_groups = []
+        super(VirtualNetworkFunction, self).__init__(name)
+
+    def add_placement_group(self, group):
+        self._placement_groups.append(group)
+
+    def compose(self, image_name, cloud_init="", cloud_init_file="", endpoint=None, mon_params=[],
+                mon_port=8888, mgmt_port=8888, num_vlr_count=1, num_ivlr_count=1,
+                num_vms=1, image_md5sum=None, mano_ut=False):
+        self.descriptor = RwVnfdYang.YangData_Vnfd_VnfdCatalog()
+        self.id = str(uuid.uuid1())
+        vnfd = self.descriptor.vnfd.add()
+        vnfd.id = self.id
+        vnfd.name = self.name
+        vnfd.short_name = self.name
+        vnfd.vendor = 'RIFT.io'
+        vnfd.logo = 'rift_logo.png'
+        vnfd.description = 'This is an example RIFT.ware VNF'
+        vnfd.version = '1.0'
+
+        self.vnfd = vnfd
+
+        if mano_ut is True:
+            internal_vlds = []
+            for i in range(num_ivlr_count):
+                internal_vld = vnfd.internal_vld.add()
+                internal_vld.id = 'ivld%s' % i
+                internal_vld.name = 'fabric%s' % i
+                internal_vld.short_name = 'fabric%s' % i
+                internal_vld.description = 'Virtual link for internal fabric%s' % i
+                internal_vld.type_yang = 'ELAN'
+                internal_vlds.append(internal_vld)
+
+        for i in range(num_vlr_count):
+            cp = vnfd.connection_point.add()
+            cp.type_yang = 'VPORT'
+            cp.name = '%s/cp%d' % (self.name, i)
+
+        if endpoint is not None:
+            endp = VnfdYang.YangData_Vnfd_VnfdCatalog_Vnfd_HttpEndpoint(
+                    path=endpoint, port=mon_port, polling_interval_secs=2
+                    )
+            vnfd.http_endpoint.append(endp)
+
+        # Monitoring params
+        for monp_dict in mon_params:
+            monp = VnfdYang.YangData_Vnfd_VnfdCatalog_Vnfd_MonitoringParam.from_dict(monp_dict)
+            monp.http_endpoint_ref = endpoint
+            vnfd.monitoring_param.append(monp)
+
+
+        for i in range(num_vms):
+            # VDU Specification
+            vdu = vnfd.vdu.add()
+            vdu.id = 'iovdu_%s' % i
+            vdu.name = 'iovdu_%s' % i
+            vdu.count = 1
+            # vdu.mgmt_vpci = '0000:00:20.0'
+
+            # specify the VM flavor
+            if use_epa:
+                vdu.vm_flavor.vcpu_count = 4
+                vdu.vm_flavor.memory_mb = 1024
+                vdu.vm_flavor.storage_gb = 4
+            else:
+                vdu.vm_flavor.vcpu_count = 1
+                vdu.vm_flavor.memory_mb = 512
+                vdu.vm_flavor.storage_gb = 4
+
+            # Management interface
+            mgmt_intf = vnfd.mgmt_interface
+            mgmt_intf.vdu_id = vdu.id
+            mgmt_intf.port = mgmt_port
+            mgmt_intf.dashboard_params.path = endpoint
+            mgmt_intf.dashboard_params.port = mgmt_port
+
+            if cloud_init_file and len(cloud_init_file):
+                vdu.cloud_init_file = cloud_init_file
+            else:
+                vdu.cloud_init = cloud_init
+                if aws:
+                    vdu.cloud_init += "  - [ systemctl, restart, --no-block, elastic-network-interfaces.service ]\n"
+
+            # sepcify the guest EPA
+            if use_epa:
+                vdu.guest_epa.trusted_execution = False
+                vdu.guest_epa.mempage_size = 'LARGE'
+                vdu.guest_epa.cpu_pinning_policy = 'DEDICATED'
+                vdu.guest_epa.cpu_thread_pinning_policy = 'PREFER'
+                vdu.guest_epa.numa_node_policy.node_cnt = 2
+                vdu.guest_epa.numa_node_policy.mem_policy = 'STRICT'
+
+                node = vdu.guest_epa.numa_node_policy.node.add()
+                node.id = 0
+                node.memory_mb = 512
+                node.vcpu = [0, 1]
+
+                node = vdu.guest_epa.numa_node_policy.node.add()
+                node.id = 1
+                node.memory_mb = 512
+                node.vcpu = [2, 3]
+
+                # specify the vswitch EPA
+                vdu.vswitch_epa.ovs_acceleration = 'DISABLED'
+                vdu.vswitch_epa.ovs_offload = 'DISABLED'
+
+                # Specify the hypervisor EPA
+                vdu.hypervisor_epa.type_yang = 'PREFER_KVM'
+
+                # Specify the host EPA
+                # vdu.host_epa.cpu_model = 'PREFER_SANDYBRIDGE'
+                # vdu.host_epa.cpu_arch = 'PREFER_X86_64'
+                # vdu.host_epa.cpu_vendor = 'PREFER_INTEL'
+                # vdu.host_epa.cpu_socket_count = 2
+                # vdu.host_epa.cpu_core_count = 8
+                # vdu.host_epa.cpu_core_thread_count = 2
+                # vdu.host_epa.cpu_feature = ['PREFER_AES', 'REQUIRE_VME', 'PREFER_MMX','REQUIRE_SSE2']
+
+            if aws:
+                vdu.image = 'rift-ping-pong'
+            else:
+                vdu.image = image_name
+                if image_md5sum is not None:
+                    vdu.image_checksum = image_md5sum
+
+            if mano_ut is True:
+                for i in range(num_ivlr_count):
+                    internal_cp = vdu.internal_connection_point.add()
+                    if vnfd.name.find("ping") >= 0:
+                        cp_name = "ping"
+                    else:
+                        cp_name = "pong"
+                    internal_cp.name = cp_name + "/icp{}".format(i)
+                    internal_cp.id = cp_name + "/icp{}".format(i)
+                    internal_cp.type_yang = 'VPORT'
+                    internal_vlds[i].internal_connection_point_ref.append(internal_cp.id)
+
+                    internal_interface = vdu.internal_interface.add()
+                    internal_interface.name = 'fab%d' % i
+                    internal_interface.vdu_internal_connection_point_ref = internal_cp.id
+                    internal_interface.virtual_interface.type_yang = 'VIRTIO'
+
+                    # internal_interface.virtual_interface.vpci = '0000:00:1%d.0'%i
+
+            for i in range(num_vlr_count):
+                external_interface = vdu.external_interface.add()
+                external_interface.name = 'eth%d' % i
+                external_interface.vnfd_connection_point_ref = '%s/cp%d' % (self.name, i)
+                if use_epa:
+                    external_interface.virtual_interface.type_yang = 'VIRTIO'
+                else:
+                    external_interface.virtual_interface.type_yang = 'VIRTIO'
+                # external_interface.virtual_interface.vpci = '0000:00:2%d.0'%i
+
+        for group in self._placement_groups:
+            placement_group = vnfd.placement_groups.add()
+            placement_group.name = group.name
+            placement_group.requirement = group.requirement
+            placement_group.strategy = group.strategy
+            if group.vdu_list:
+                ### Add specific VDUs to placement group
+                for vdu in group.vdu_list:
+                    member_vdu = placement_group.member_vdus.add()
+                    member_vdu.member_vdu_ref = vdu.id
+            else:
+                ### Add all VDUs to placement group
+                for vdu in vnfd.vdu:
+                    member_vdu = placement_group.member_vdus.add()
+                    member_vdu.member_vdu_ref = vdu.id
+
+
+    def write_to_file(self, outdir, output_format):
+        dirpath = "%s/%s" % (outdir, self.name)
+        if not os.path.exists(dirpath):
+            os.makedirs(dirpath)
+        super(VirtualNetworkFunction, self).write_to_file(['vnfd', 'rw-vnfd'],
+                                                          dirpath,
+                                                          output_format)
+        self.add_scripts(outdir)
+
+    def add_scripts(self, outdir):
+        script_dir = os.path.join(outdir, self.name, 'cloud_init')
+        try:
+            os.makedirs(script_dir)
+        except OSError:
+            if not os.path.isdir(script_dir):
+                raise
+
+        if 'ping' in self.name:
+            script_file = os.path.join(script_dir, 'ping_cloud_init.cfg')
+            cfg = PING_USERDATA_FILE
+        else:
+            script_file = os.path.join(script_dir, 'pong_cloud_init.cfg')
+            cfg = PONG_USERDATA_FILE
+
+        with open(script_file, "w") as f:
+            f.write("{}".format(cfg))
+
+
+class NetworkService(ManoDescriptor):
+    def __init__(self, name):
+        super(NetworkService, self).__init__(name)
+        self._scale_groups = []
+        self.vnfd_config = {}
+        self._placement_groups = []
+
+    def ping_config(self, mano_ut, use_ns_init_conf):
+        suffix = ''
+        if mano_ut:
+            ping_cfg = r'''
+#!/bin/bash
+
+echo "!!!!!!!! Executed ping Configuration !!!!!!!!!"
+            '''
+        else:
+            ping_cfg = r'''
+#!/bin/bash
+
+# Rest API config
+ping_mgmt_ip='<rw_mgmt_ip>'
+ping_mgmt_port=18888
+
+# VNF specific configuration
+pong_server_ip='<rw_connection_point_name pong_vnfd%s/cp0>'
+ping_rate=5
+server_port=5555
+
+# Make rest API calls to configure VNF
+curl -D /dev/stdout \
+    -H "Accept: application/vnd.yang.data+xml" \
+    -H "Content-Type: application/vnd.yang.data+json" \
+    -X POST \
+    -d "{\"ip\":\"$pong_server_ip\", \"port\":$server_port}" \
+    http://${ping_mgmt_ip}:${ping_mgmt_port}/api/v1/ping/server
+rc=$?
+if [ $rc -ne 0 ]
+then
+    echo "Failed to set server info for ping!"
+    exit $rc
+fi
+
+curl -D /dev/stdout \
+    -H "Accept: application/vnd.yang.data+xml" \
+    -H "Content-Type: application/vnd.yang.data+json" \
+    -X POST \
+    -d "{\"rate\":$ping_rate}" \
+    http://${ping_mgmt_ip}:${ping_mgmt_port}/api/v1/ping/rate
+rc=$?
+if [ $rc -ne 0 ]
+then
+    echo "Failed to set ping rate!"
+    exit $rc
+fi
+
+''' % suffix
+            if use_ns_init_conf:
+                ping_cfg += "exit 0\n"
+            else:
+                ping_cfg +='''
+output=$(curl -D /dev/stdout \
+    -H "Accept: application/vnd.yang.data+xml" \
+    -H "Content-Type: application/vnd.yang.data+json" \
+    -X POST \
+    -d "{\"enable\":true}" \
+    http://${ping_mgmt_ip}:${ping_mgmt_port}/api/v1/ping/adminstatus/state)
+if [[ $output == *"Internal Server Error"* ]]
+then
+    echo $output
+    exit 3
+else
+    echo $output
+fi
+
+exit 0
+'''
+        return ping_cfg
+
+    def pong_config(self, mano_ut, use_ns_init_conf):
+        suffix = ''
+        if mano_ut:
+            pong_cfg = r'''
+#!/bin/bash
+
+echo "!!!!!!!! Executed pong Configuration !!!!!!!!!"
+            '''
+        else:
+            pong_cfg = r'''
+#!/bin/bash
+
+# Rest API configuration
+pong_mgmt_ip='<rw_mgmt_ip>'
+pong_mgmt_port=18889
+# username=<rw_username>
+# password=<rw_password>
+
+# VNF specific configuration
+pong_server_ip='<rw_connection_point_name pong_vnfd%s/cp0>'
+server_port=5555
+
+# Make Rest API calls to configure VNF
+curl -D /dev/stdout \
+    -H "Accept: application/vnd.yang.data+xml" \
+    -H "Content-Type: application/vnd.yang.data+json" \
+    -X POST \
+    -d "{\"ip\":\"$pong_server_ip\", \"port\":$server_port}" \
+    http://${pong_mgmt_ip}:${pong_mgmt_port}/api/v1/pong/server
+rc=$?
+if [ $rc -ne 0 ]
+then
+    echo "Failed to set server(own) info for pong!"
+    exit $rc
+fi
+
+''' % suffix
+
+            if use_ns_init_conf:
+                pong_cfg += "exit 0\n"
+            else:
+                pong_cfg +='''
+curl -D /dev/stdout \
+    -H "Accept: application/vnd.yang.data+xml" \
+    -H "Content-Type: application/vnd.yang.data+json" \
+    -X POST \
+    -d "{\"enable\":true}" \
+    http://${pong_mgmt_ip}:${pong_mgmt_port}/api/v1/pong/adminstatus/state
+rc=$?
+if [ $rc -ne 0 ]
+then
+    echo "Failed to enable pong service!"
+    exit $rc
+fi
+
+exit 0
+'''
+        return pong_cfg
+
+    def pong_fake_juju_config(self, vnf_config):
+
+        if vnf_config:
+            # Select "script" configuration
+            vnf_config.juju.charm = 'clearwater-aio-proxy'
+
+            # Set the initital-config
+            vnf_config.create_initial_config_primitive()
+            init_config = VnfdYang.InitialConfigPrimitive.from_dict({
+                "seq": 1,
+                "name": "config",
+                "parameter": [
+                    {"name": "proxied_ip", "value": "<rw_mgmt_ip>"},
+                ]
+            })
+            vnf_config.initial_config_primitive.append(init_config)
+
+            init_config_action = VnfdYang.InitialConfigPrimitive.from_dict({
+                "seq": 2,
+                "name": "action1",
+                "parameter": [
+                    {"name": "Pong Connection Point", "value": "pong_vnfd/cp0"},
+                ]
+            })
+            vnf_config.initial_config_primitive.append(init_config_action)
+            init_config_action = VnfdYang.InitialConfigPrimitive.from_dict({
+                "seq": 3,
+                "name": "action2",
+                "parameter": [
+                    {"name": "Ping Connection Point", "value": "ping_vnfd/cp0"},
+                ]
+            })
+            vnf_config.initial_config_primitive.append(init_config_action)
+
+            # Config parameters can be taken from config.yaml and
+            # actions from actions.yaml in the charm
+            # Config to set the home domain
+            vnf_config.create_service_primitive()
+            config = VnfdYang.ServicePrimitive.from_dict({
+                "name": "config",
+                "parameter": [
+                    {"name": "home_domain", "data_type": "STRING"},
+                    {"name": "base_number", "data_type": "STRING"},
+                    {"name": "number_count", "data_type": "INTEGER"},
+                    {"name": "password", "data_type": "STRING"},
+                ]
+            })
+            vnf_config.service_primitive.append(config)
+
+            config = VnfdYang.ServicePrimitive.from_dict({
+                "name": "create-update-user",
+                # "user-defined-script":"/tmp/test.py",
+                "parameter": [
+                    {"name": "number", "data_type": "STRING", "mandatory": True},
+                    {"name": "password", "data_type": "STRING", "mandatory": True},
+                ]
+            })
+            vnf_config.service_primitive.append(config)
+
+            config = VnfdYang.ServicePrimitive.from_dict({
+                "name": "delete-user",
+                "parameter": [
+                    {"name": "number", "data_type": "STRING", "mandatory": True},
+                ]
+            })
+            vnf_config.service_primitive.append(config)
+
+    def default_config(self, const_vnfd, vnfd, mano_ut, use_ns_init_conf):
+        vnf_config = vnfd.vnfd.vnf_configuration
+
+        vnf_config.config_attributes.config_priority = 0
+        vnf_config.config_attributes.config_delay = 0
+
+        # Select "script" configuration
+        vnf_config.script.script_type = 'bash'
+
+        if vnfd.name == 'pong_vnfd' or vnfd.name == 'pong_vnfd_with_epa' or vnfd.name == 'pong_vnfd_aws':
+            vnf_config.config_attributes.config_priority = 1
+            vnf_config.config_template = self.pong_config(mano_ut, use_ns_init_conf)
+            # First priority config delay will delay the entire NS config delay
+            if mano_ut is False:
+                vnf_config.config_attributes.config_delay = 60
+            else:
+                # This is PONG and inside mano_ut
+                # This is test only
+                vnf_config.config_attributes.config_delay = 10
+                # vnf_config.config_template = self.pong_config(vnf_config, use_ns_init_conf)
+
+        if vnfd.name == 'ping_vnfd' or vnfd.name == 'ping_vnfd_with_epa' or vnfd.name == 'ping_vnfd_aws':
+            vnf_config.config_attributes.config_priority = 2
+            vnf_config.config_template = self.ping_config(mano_ut, use_ns_init_conf)
+
+    def ns_config(self, nsd, vnfd_list, mano_ut):
+        # Used by scale group
+        if mano_ut:
+            nsd.service_primitive.add().from_dict(
+                {
+                    "name": "ping config",
+                    "user_defined_script": "{}".format(os.path.join(
+                        os.environ['RIFT_ROOT'],
+                        'modules/core/mano',
+                        'examples/ping_pong_ns/rift/mano/examples',
+                        'ping_config_ut.sh'))
+                })
+        else:
+            nsd.service_primitive.add().from_dict(
+                {
+                    "name": "ping config",
+                    "user_defined_script": "ping_config.py"
+                })
+
+    def ns_initial_config(self, nsd):
+        nsd.initial_config_primitive.add().from_dict(
+            {
+                "seq": 1,
+                "name": "start traffic",
+                "user_defined_script": "start_traffic.py",
+                "parameter": [
+                    {
+                        'name': 'userid',
+                        'value': 'rift',
+                    },
+                ],
+            }
+        )
+
+    def add_scale_group(self, scale_group):
+        self._scale_groups.append(scale_group)
+
+    def add_placement_group(self, placement_group):
+        self._placement_groups.append(placement_group)
+
+    def create_mon_params(self, vnfds):
+        NsdMonParam = NsdYang.YangData_Nsd_NsdCatalog_Nsd_MonitoringParam
+        param_id = 1
+        for vnfd_obj in vnfds:
+            for mon_param in vnfd_obj.vnfd.monitoring_param:
+                nsd_monp = NsdMonParam.from_dict({
+                        'id': str(param_id),
+                        'name': mon_param.name,
+                        'aggregation_type': "AVERAGE",
+                        'value_type': mon_param.value_type,
+                        'vnfd_monitoring_param': [
+                                {'vnfd_id_ref': vnfd_obj.vnfd.id,
+                                'vnfd_monitoring_param_ref': mon_param.id}]
+                        })
+
+                self.nsd.monitoring_param.append(nsd_monp)
+                param_id += 1
+
+
+
+
+    def compose(self, vnfd_list, cpgroup_list, mano_ut, use_ns_init_conf=True):
+
+        if mano_ut:
+            # Disable NS initial config primitive
+            use_ns_init_conf=False
+
+        self.descriptor = RwNsdYang.YangData_Nsd_NsdCatalog()
+        self.id = str(uuid.uuid1())
+        nsd = self.descriptor.nsd.add()
+        self.nsd = nsd
+        nsd.id = self.id
+        nsd.name = self.name
+        nsd.short_name = self.name
+        nsd.vendor = 'RIFT.io'
+        nsd.logo = 'rift_logo.png'
+        nsd.description = 'Toy NS'
+        nsd.version = '1.0'
+        nsd.input_parameter_xpath.append(
+                NsdYang.YangData_Nsd_NsdCatalog_Nsd_InputParameterXpath(
+                    xpath="/nsd:nsd-catalog/nsd:nsd/nsd:vendor",
+                    )
+                )
+
+        ip_profile = nsd.ip_profiles.add()
+        ip_profile.name = "InterVNFLink"
+        ip_profile.description  = "Inter VNF Link"
+        ip_profile.ip_profile_params.ip_version = "ipv4"
+        ip_profile.ip_profile_params.subnet_address = "31.31.31.0/24"
+        ip_profile.ip_profile_params.gateway_address = "31.31.31.210"
+        
+        vld_id = 1
+        for cpgroup in cpgroup_list:
+            vld = nsd.vld.add()
+            vld.id = 'ping_pong_vld%s' % vld_id
+            vld_id += 1
+            vld.name = 'ping_pong_vld'  # hard coded
+            vld.short_name = vld.name
+            vld.vendor = 'RIFT.io'
+            vld.description = 'Toy VL'
+            vld.version = '1.0'
+            vld.type_yang = 'ELAN'
+            vld.ip_profile_ref = 'InterVNFLink'
+            for cp in cpgroup:
+                cpref = vld.vnfd_connection_point_ref.add()
+                cpref.member_vnf_index_ref = cp[0]
+                cpref.vnfd_id_ref = cp[1]
+                cpref.vnfd_connection_point_ref = cp[2]
+
+        vnfd_index_map = {}
+        member_vnf_index = 1
+        for vnfd in vnfd_list:
+            for i in range(vnfd.instance_count):
+                constituent_vnfd = nsd.constituent_vnfd.add()
+                constituent_vnfd.member_vnf_index = member_vnf_index
+                vnfd_index_map[vnfd] = member_vnf_index
+
+                # Set the start by default to false  for ping vnfd,
+                # if scaling is enabled
+                if (len(self._scale_groups) and
+                    vnfd.descriptor.vnfd[0].name == 'ping_vnfd'):
+                    constituent_vnfd.start_by_default = False
+
+                constituent_vnfd.vnfd_id_ref = vnfd.descriptor.vnfd[0].id
+                self.default_config(constituent_vnfd, vnfd, mano_ut,
+                                    use_ns_init_conf,)
+                member_vnf_index += 1
+
+        # Enable config primitives if either mano_ut or
+        # scale groups are enabled
+        if mano_ut or len(self._scale_groups):
+            self.ns_config(nsd, vnfd_list, mano_ut)
+
+        # Add NS initial config to start traffic
+        if use_ns_init_conf:
+            self.ns_initial_config(nsd)
+
+        for scale_group in self._scale_groups:
+            group_desc = nsd.scaling_group_descriptor.add()
+            group_desc.name = scale_group.name
+            group_desc.max_instance_count = scale_group.max_count
+            group_desc.min_instance_count = scale_group.min_count
+            for vnfd, count in scale_group.vnfd_count_map.items():
+                member = group_desc.vnfd_member.add()
+                member.member_vnf_index_ref = vnfd_index_map[vnfd]
+                member.count = count
+
+            for trigger in scale_group.config_action:
+                config_action = group_desc.scaling_config_action.add()
+                config_action.trigger = trigger
+                config = scale_group.config_action[trigger]
+                config_action.ns_config_primitive_name_ref = config['ns-config-primitive-name-ref']
+
+        for placement_group in self._placement_groups:
+            group = nsd.placement_groups.add()
+            group.name = placement_group.name
+            group.strategy = placement_group.strategy
+            group.requirement = placement_group.requirement
+            for member_vnfd in placement_group.vnfd_list:
+                member = group.member_vnfd.add()
+                member.vnfd_id_ref = member_vnfd.descriptor.vnfd[0].id
+                member.member_vnf_index_ref = vnfd_index_map[member_vnfd]
+
+        # self.create_mon_params(vnfd_list)
+
+    def write_config(self, outdir, vnfds):
+
+        converter = config_data.ConfigPrimitiveConvertor()
+        yaml_data = converter.extract_nsd_config(self.nsd)
+
+        ns_config_dir = os.path.join(outdir, self.name, "ns_config")
+        os.makedirs(ns_config_dir, exist_ok=True)
+        vnf_config_dir = os.path.join(outdir, self.name, "vnf_config")
+        os.makedirs(vnf_config_dir, exist_ok=True)
+
+        if len(yaml_data):
+            with open('%s/%s.yaml' % (ns_config_dir, self.id), "w") as fh:
+                fh.write(yaml_data)
+
+        for i, vnfd in enumerate(vnfds, start=1):
+            yaml_data = converter.extract_vnfd_config(vnfd)
+
+            if len(yaml_data):
+                with open('%s/%s__%s.yaml' % (vnf_config_dir, vnfd.id, i), "w") as fh:
+                    fh.write(yaml_data)
+
+    def write_initial_config_script(self, outdir):
+        script_name = 'start_traffic.py'
+
+        src_path = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
+        script_src = os.path.join(src_path, script_name)
+        if not os.path.exists(script_src):
+            src_path = os.path.join(os.environ['RIFT_ROOT'],
+            'modules/core/mano/examples/ping_pong_ns/rift/mano/examples')
+            script_src = os.path.join(src_path, script_name)
+
+        dest_path = os.path.join(outdir, 'scripts')
+        os.makedirs(dest_path, exist_ok=True)
+
+        shutil.copy2(script_src, dest_path)
+
+    def write_to_file(self, outdir, output_format):
+        dirpath = os.path.join(outdir, self.name)
+        if not os.path.exists(dirpath):
+            os.makedirs(dirpath)
+
+        super(NetworkService, self).write_to_file(["nsd", "rw-nsd"],
+                                                  dirpath,
+                                                  output_format)
+
+        # Write the initial config script
+        self.write_initial_config_script(dirpath)
+
+
+def get_ping_mon_params(path):
+    return [
+            {
+                'id': '1',
+                'name': 'ping-request-tx-count',
+                'http_endpoint_ref': path,
+                'json_query_method': "NAMEKEY",
+                'value_type': "INT",
+                'description': 'no of ping requests',
+                'group_tag': 'Group1',
+                'widget_type': 'COUNTER',
+                'units': 'packets'
+                },
+
+            {
+                'id': '2',
+                'name': 'ping-response-rx-count',
+                'http_endpoint_ref': path,
+                'json_query_method': "NAMEKEY",
+                'value_type': "INT",
+                'description': 'no of ping responses',
+                'group_tag': 'Group1',
+                'widget_type': 'COUNTER',
+                'units': 'packets'
+                },
+            ]
+
+
+def get_pong_mon_params(path):
+    return [
+            {
+                'id': '1',
+                'name': 'ping-request-rx-count',
+                'http_endpoint_ref': path,
+                'json_query_method': "NAMEKEY",
+                'value_type': "INT",
+                'description': 'no of ping requests',
+                'group_tag': 'Group1',
+                'widget_type': 'COUNTER',
+                'units': 'packets'
+                },
+
+            {
+                'id': '2',
+                'name': 'ping-response-tx-count',
+                'http_endpoint_ref': path,
+                'json_query_method': "NAMEKEY",
+                'value_type': "INT",
+                'description': 'no of ping responses',
+                'group_tag': 'Group1',
+                'widget_type': 'COUNTER',
+                'units': 'packets'
+                },
+            ]
+
+
+class ScaleGroup(object):
+    def __init__(self, name, min_count=1, max_count=1):
+        self.name = name
+        self.min_count = min_count
+        self.max_count = max_count
+        self.vnfd_count_map = {}
+        self.config_action = {}
+
+    def add_vnfd(self, vnfd, vnfd_count):
+        self.vnfd_count_map[vnfd] = vnfd_count
+
+    def add_config(self):
+        self.config_action['post_scale_out']= {'ns-config-primitive-name-ref':
+                                               'ping config'}
+
+class PlacementGroup(object):
+    def __init__(self, name):
+        self.name = name
+        self.strategy = ''
+        self.requirement = ''
+
+    def add_strategy(self, strategy):
+        self.strategy = strategy
+
+    def add_requirement(self, requirement):
+        self.requirement = requirement
+
+class NsdPlacementGroup(PlacementGroup):
+    def __init__(self, name):
+        self.vnfd_list = []
+        super(NsdPlacementGroup, self).__init__(name)
+
+    def add_member(self, vnfd):
+        self.vnfd_list.append(vnfd)
+
+
+class VnfdPlacementGroup(PlacementGroup):
+    def __init__(self, name):
+        self.vdu_list = []
+        super(VnfdPlacementGroup, self).__init__(name)
+
+    def add_member(self, vdu):
+        self.vdu_list.append(vdu)
+
+
+
+
+def generate_ping_pong_descriptors(fmt="json",
+                                   write_to_file=False,
+                                   out_dir="./",
+                                   pingcount=NUM_PING_INSTANCES,
+                                   external_vlr_count=1,
+                                   internal_vlr_count=1,
+                                   num_vnf_vms=1,
+                                   ping_md5sum=None,
+                                   pong_md5sum=None,
+                                   mano_ut=False,
+                                   use_scale_group=False,
+                                   ping_fmt=None,
+                                   pong_fmt=None,
+                                   nsd_fmt=None,
+                                   use_mon_params=True,
+                                   ping_userdata=None,
+                                   pong_userdata=None,
+                                   ex_ping_userdata=None,
+                                   ex_pong_userdata=None,
+                                   use_placement_group=True,
+                                   use_ns_init_conf=True,
+                                   ):
+    # List of connection point groups
+    # Each connection point group refers to a virtual link
+    # the CP group consists of tuples of connection points
+    cpgroup_list = []
+    for i in range(external_vlr_count):
+        cpgroup_list.append([])
+
+    suffix = ''
+    ping = VirtualNetworkFunction("ping_vnfd%s" % (suffix), pingcount)
+
+    if use_placement_group:
+        ### Add group name Eris
+        group = VnfdPlacementGroup('Eris')
+        group.add_strategy('COLOCATION')
+        group.add_requirement('''Place this VM on the Kuiper belt object Eris''')
+        ping.add_placement_group(group)
+
+    # ping = VirtualNetworkFunction("ping_vnfd", pingcount)
+    if not ping_userdata:
+        ping_userdata = PING_USERDATA_FILE
+
+    if ex_ping_userdata:
+        ping_userdata = '''\
+{ping_userdata}
+{ex_ping_userdata}
+        '''.format(
+            ping_userdata=ping_userdata,
+            ex_ping_userdata=ex_ping_userdata
+        )
+
+    ping.compose(
+            "Fedora-x86_64-20-20131211.1-sda-ping.qcow2",
+            ping_userdata,
+            use_ping_cloud_init_file,
+            "api/v1/ping/stats",
+            get_ping_mon_params("api/v1/ping/stats") if use_mon_params else [],
+            mon_port=18888,
+            mgmt_port=18888,
+            num_vlr_count=external_vlr_count,
+            num_ivlr_count=internal_vlr_count,
+            num_vms=num_vnf_vms,
+            image_md5sum=ping_md5sum,
+            mano_ut=mano_ut,
+            )
+
+    pong = VirtualNetworkFunction("pong_vnfd%s" % (suffix))
+
+    if use_placement_group:
+        ### Add group name Weywot
+        group = VnfdPlacementGroup('Weywot')
+        group.add_strategy('COLOCATION')
+        group.add_requirement('''Place this VM on the Kuiper belt object Weywot''')
+        pong.add_placement_group(group)
+
+
+    # pong = VirtualNetworkFunction("pong_vnfd")
+
+    if not pong_userdata:
+        pong_userdata = PONG_USERDATA_FILE
+
+    if ex_pong_userdata:
+        pong_userdata = '''\
+{pong_userdata}
+{ex_pong_userdata}
+        '''.format(
+            pong_userdata=pong_userdata,
+            ex_pong_userdata=ex_pong_userdata
+        )
+
+
+    pong.compose(
+            "Fedora-x86_64-20-20131211.1-sda-pong.qcow2",
+            pong_userdata,
+            use_pong_cloud_init_file,
+            "api/v1/pong/stats",
+            get_pong_mon_params("api/v1/pong/stats") if use_mon_params else [],
+            mon_port=18889,
+            mgmt_port=18889,
+            num_vlr_count=external_vlr_count,
+            num_ivlr_count=internal_vlr_count,
+            num_vms=num_vnf_vms,
+            image_md5sum=pong_md5sum,
+            mano_ut=mano_ut,
+            )
+
+    # Initialize the member VNF index
+    member_vnf_index = 1
+
+    # define the connection point groups
+    for index, cp_group in enumerate(cpgroup_list):
+        desc_id = ping.descriptor.vnfd[0].id
+        filename = 'ping_vnfd{}/cp{}'.format(suffix, index)
+
+        for idx in range(pingcount):
+            cp_group.append((
+                member_vnf_index,
+                desc_id,
+                filename,
+                ))
+
+            member_vnf_index += 1
+
+        desc_id = pong.descriptor.vnfd[0].id
+        filename = 'pong_vnfd{}/cp{}'.format(suffix, index)
+
+        cp_group.append((
+            member_vnf_index,
+            desc_id,
+            filename,
+            ))
+
+        member_vnf_index += 1
+
+    vnfd_list = [ping, pong]
+
+    nsd_catalog = NetworkService("ping_pong_nsd%s" % (suffix))
+
+    if use_scale_group:
+        group = ScaleGroup("ping_group", max_count=10)
+        group.add_vnfd(ping, 1)
+        group.add_config()
+        nsd_catalog.add_scale_group(group)
+
+    if use_placement_group:
+        ### Add group name Orcus
+        group = NsdPlacementGroup('Orcus')
+        group.add_strategy('COLOCATION')
+        group.add_requirement('''Place this VM on the Kuiper belt object Orcus''')
+
+        for member_vnfd in vnfd_list:
+            group.add_member(member_vnfd)
+
+        nsd_catalog.add_placement_group(group)
+
+        ### Add group name Quaoar
+        group = NsdPlacementGroup('Quaoar')
+        group.add_strategy('COLOCATION')
+        group.add_requirement('''Place this VM on the Kuiper belt object Quaoar''')
+
+        for member_vnfd in vnfd_list:
+            group.add_member(member_vnfd)
+
+        nsd_catalog.add_placement_group(group)
+
+
+    nsd_catalog.compose(vnfd_list,
+                        cpgroup_list,
+                        mano_ut,
+                        use_ns_init_conf=use_ns_init_conf,)
+
+    if write_to_file:
+        ping.write_to_file(out_dir, ping_fmt if ping_fmt is not None else fmt)
+        pong.write_to_file(out_dir, pong_fmt if ping_fmt is not None else fmt)
+        nsd_catalog.write_config(out_dir, vnfd_list)
+        nsd_catalog.write_to_file(out_dir, ping_fmt if nsd_fmt is not None else fmt)
+
+    return (ping, pong, nsd_catalog)
+
+
+def main(argv=sys.argv[1:]):
+    global outdir, output_format, use_epa, aws, use_ping_cloud_init_file, use_pong_cloud_init_file
+    parser = argparse.ArgumentParser()
+    parser.add_argument('-o', '--outdir', default='.')
+    parser.add_argument('-f', '--format', default='json')
+    parser.add_argument('-e', '--epa', action="store_true", default=False)
+    parser.add_argument('-a', '--aws', action="store_true", default=False)
+    parser.add_argument('-n', '--pingcount', default=NUM_PING_INSTANCES)
+    parser.add_argument('--ping-image-md5')
+    parser.add_argument('--pong-image-md5')
+    parser.add_argument('--ping-cloud-init', default=None)
+    parser.add_argument('--pong-cloud-init', default=None)
+    args = parser.parse_args()
+    outdir = args.outdir
+    output_format = args.format
+    use_epa = args.epa
+    aws = args.aws
+    pingcount = args.pingcount
+    use_ping_cloud_init_file = args.ping_cloud_init
+    use_pong_cloud_init_file = args.pong_cloud_init
+
+    generate_ping_pong_descriptors(args.format, True, args.outdir, pingcount,
+                                   ping_md5sum=args.ping_image_md5, pong_md5sum=args.pong_image_md5,
+                                   mano_ut=False,
+                                   use_scale_group=False,)
+
+if __name__ == "__main__":
+    main()
diff --git a/examples/ping_pong_ns/rift/mano/examples/start_traffic.py b/examples/ping_pong_ns/rift/mano/examples/start_traffic.py
new file mode 100755
index 0000000..af6f62f
--- /dev/null
+++ b/examples/ping_pong_ns/rift/mano/examples/start_traffic.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python3
+
+############################################################################
+# Copyright 2016 RIFT.IO Inc                                               #
+#                                                                          #
+# 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.                                           #
+############################################################################
+
+
+import argparse
+import logging
+import os
+import subprocess
+import sys
+import time
+
+import yaml
+
+
+def start_traffic(yaml_cfg, logger):
+    '''Use curl and set admin status to enable on pong and ping vnfs'''
+
+    def enable_service(mgmt_ip, port, vnf_type):
+        curl_cmd = 'curl -D /dev/stdout -H "Accept: application/vnd.yang.data' \
+                   '+xml" -H "Content-Type: application/vnd.yang.data+json" ' \
+                   '-X POST -d "{{\\"enable\\":true}}" http://{mgmt_ip}:' \
+                   '{mgmt_port}/api/v1/{vnf_type}/adminstatus/state'. \
+                   format(
+                       mgmt_ip=mgmt_ip,
+                       mgmt_port=port,
+                       vnf_type=vnf_type)
+
+        logger.debug("Executing cmd: %s", curl_cmd)
+        subprocess.check_call(curl_cmd, shell=True)
+
+    # Enable pong service first
+    for index, vnfr in yaml_cfg['vnfr'].items():
+        logger.debug("VNFR {}: {}".format(index, vnfr))
+
+        # Check if it is pong vnf
+        if 'pong_vnfd' in vnfr['name']:
+            vnf_type = 'pong'
+            port = 18889
+            enable_service(vnfr['mgmt_ip_address'], port, vnf_type)
+            break
+
+    # Add a delay to provide pong port to come up
+    time.sleep(0.1)
+
+    # Enable ping service next
+    for index, vnfr in yaml_cfg['vnfr'].items():
+        logger.debug("VNFR {}: {}".format(index, vnfr))
+
+        # Check if it is pong vnf
+        if 'ping_vnfd' in vnfr['name']:
+            vnf_type = 'ping'
+            port = 18888
+            enable_service(vnfr['mgmt_ip_address'], port, vnf_type)
+            break
+
+def main(argv=sys.argv[1:]):
+    try:
+        parser = argparse.ArgumentParser()
+        parser.add_argument("yaml_cfg_file", type=argparse.FileType('r'))
+        parser.add_argument("-q", "--quiet", dest="verbose", action="store_false")
+        args = parser.parse_args()
+
+        run_dir = os.path.join(os.environ['RIFT_INSTALL'], "var/run/rift")
+        if not os.path.exists(run_dir):
+            os.makedirs(run_dir)
+        log_file = "{}/ping_pong_start_traffic-{}.log".format(run_dir, time.strftime("%Y%m%d%H%M%S"))
+        logging.basicConfig(filename=log_file, level=logging.DEBUG)
+        logger = logging.getLogger()
+
+    except Exception as e:
+        print("Exception in {}: {}".format(__file__, e))
+        sys.exit(1)
+
+    try:
+        ch = logging.StreamHandler()
+        if args.verbose:
+            ch.setLevel(logging.DEBUG)
+        else:
+            ch.setLevel(logging.INFO)
+
+        # create formatter and add it to the handlers
+        formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+        ch.setFormatter(formatter)
+        logger.addHandler(ch)
+
+    except Exception as e:
+        logger.exception(e)
+        raise e
+
+    try:
+        yaml_str = args.yaml_cfg_file.read()
+        # logger.debug("Input YAML file:\n{}".format(yaml_str))
+        yaml_cfg = yaml.load(yaml_str)
+        logger.debug("Input YAML: {}".format(yaml_cfg))
+
+        start_traffic(yaml_cfg, logger)
+
+    except Exception as e:
+        logger.exception(e)
+        raise e
+
+if __name__ == "__main__":
+    main()
diff --git a/examples/ping_pong_ns/rift_logo.png b/examples/ping_pong_ns/rift_logo.png
new file mode 100644
index 0000000..09b47c7
--- /dev/null
+++ b/examples/ping_pong_ns/rift_logo.png
Binary files differ
diff --git a/examples/ping_pong_ns/stand_up_ping_pong b/examples/ping_pong_ns/stand_up_ping_pong
new file mode 100644
index 0000000..a8e5e55
--- /dev/null
+++ b/examples/ping_pong_ns/stand_up_ping_pong
@@ -0,0 +1,232 @@
+#!/bin/bash
+
+# arguments
+if [ -z $1 ] 
+then 
+    echo "must supply ip for launchpad"
+    exit -1
+else
+    lp_ip=${1}
+fi
+
+username=$(whoami)
+
+# make sure we're in a rift shell
+if [ -z $RIFT_ROOT ] 
+then 
+    echo "must be in a rift-shell"
+    exit -1
+fi
+
+# make sure the system is up
+system_is_up() {
+    response=$(curl --silent --insecure \
+		    -o /dev/null \
+		    --write-out "%{http_code}"\
+		    --user admin:admin \
+		    https://${lp_ip}:8008/api/config/launchpad-config \
+		    --request GET \
+	    )
+
+   if [ ${response} -eq 200 ]
+   then
+       return 0
+   else
+       if [ ${response} -eq 404 ]
+       then
+	   # not running a launchpad!
+	   echo "riftware is running on ${lp_ip} but there is no launchpad-config"
+	   exit -1
+       fi
+       return 1
+   fi
+}
+
+echo -n "wait for system"
+while ! system_is_up
+do
+    echo -n "."
+    sleep 5s
+done
+
+echo "system is up"
+
+# setup the openstack account
+echo -n "adding account"
+
+post_json_to_rwrest() {
+   if [ -z "$1" ]
+   then
+     echo "must supply url"
+     exit -1
+   else
+       url=$1
+   fi
+
+   if [ -z "$2" ]
+   then
+     echo "must supply payload"
+     exit -1
+   else
+       payload="${2}"
+   fi
+
+   response=$(curl --silent --insecure \
+		   --header "content-type:application/vnd.yang.data+json" \
+		   --header "Accept:application/vnd.yang.data+json" \
+		   --user admin:admin \
+		   https://${lp_ip}:8008${url} \
+		   --request POST --data "${payload}" \
+	   )
+
+    added_account=$(echo "${response}" | grep -e \"success|ok\" | wc -l)
+    already_exists=$(echo "${response}" | grep \"data-exists\" | wc -l)
+    success=$((added_account + already_exists))
+}
+
+account_payload=" {
+  \"account\": [
+    {
+      \"name\": \"OS\",
+      \"account-type\": \"openstack\",
+      \"openstack\": {
+        \"auth_url\": \"http://engstack.eng.riftio.com:5000/v3/\",
+        \"secret\": \"mypasswd\",
+        \"key\": \"${username}_automation\",
+        \"tenant\": \"${username}_automation\",
+        \"mgmt-network\": \"private\"
+      }
+    }
+  ]
+}"
+
+post_json_to_rwrest "/api/config/cloud/account" "${account_payload}"
+
+if [ ${success} -ne 1 ];
+then
+    echo -en "\r" # clear pending line
+    echo "failed to add cloud account:"
+    echo ${response}
+    exit 0
+else
+    echo " success"
+fi
+
+# onboard descriptors
+cd $RIFT_BUILD/modules/core/mano/src/core_mano-build/examples/ping_pong_ns
+
+wait_for_package() {
+   if [ -z "$1" ]
+   then
+     echo "must supply transaction id to wait for"
+     exit -1
+   fi
+
+   response=$(curl --silent --insecure https://${lp_ip}:4567/api/upload/${transaction_id}/state)
+   transaction_state=$(echo ${response} | awk -F "status" '{print $2}' | awk '{print $2}')
+   transaction_state=${transaction_state:1:-2}
+   
+   if [ ${transaction_state} == "pending" ];
+   then
+       return 0
+   else
+       return 1
+   fi
+}
+
+upload_package() {
+   if [ -z "$1" ]
+   then
+     echo "must supply package to upload"
+     exit -1
+   else
+       package=$1
+   fi
+
+   echo -n "upload ${package} package"
+
+   response=$(curl --silent --insecure -F "descriptor=@${package}" https://${lp_ip}:4567/api/upload)
+   transaction_id=$(echo ${response} | awk '{print $2}')
+   transaction_id=${transaction_id:1:-2}
+
+   while wait_for_package transaction_id
+   do
+       echo -n "."
+       sleep 1s
+   done
+   if [ ${transaction_state} == "failure" ];
+   then
+       echo "failed"
+       status=1
+   else
+       echo "success"
+       status=0
+   fi
+
+}
+
+upload_package "ping_vnfd.tar.gz"
+ping_status=${status}
+upload_package "pong_vnfd.tar.gz"
+pong_status=${status}
+
+success=$((ping_status + pong_status))
+
+if [ ${success} -ne 0 ];
+then
+    echo -en "\r" # clear pending line
+    echo "cannot on-board nsd when a vnfd fails"
+    exit -1
+fi
+
+upload_package "ping_pong_nsd.tar.gz"
+if [ ${status} -ne 0 ];
+then
+    echo -en "\r" # clear pending line
+    echo "failed to on-board nsd"
+    exit -1
+fi
+
+# instantiate ping_pong_nsd
+echo "instantiate ping pong nsd"
+
+tag=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 4 | head -n 1)
+
+tmpdir="/tmp/${tag}"
+mkdir ${tmpdir}
+
+tar -xf ping_pong_nsd.tar.gz -C ${tmpdir}
+
+nsdfile="${tmpdir}/ping_pong_nsd/ping_pong_nsd.yaml"
+
+nsd_id=$(cat ${nsdfile} | grep "nsd:id" | head -n1 | awk '{print $2}')
+
+rm -r ${tmpdir}
+
+nsr_id=$(cat /proc/sys/kernel/random/uuid)
+nsd_payload="{
+    \"nsr\":[
+        {
+            \"id\":\"${nsr_id}\",
+            \"nsd-ref\":\"${nsd_id}\",
+            \"name\":\"${username}-${tag}-ping-pong-nsd\",
+            \"short-name\":\"pingpong\",
+            \"description\":\"ping pong nsd instantiated by ${username} with tag ${tag}\",
+            \"admin-status\":\"ENABLED\",
+            \"cloud-account\":\"OS\"
+        }
+    ]
+}"
+
+post_json_to_rwrest "/api/config/ns-instance-config" "${nsd_payload}"
+
+if [ ${success} -ne 1 ];
+then
+    echo -en "\r" # clear pending line
+    echo "failed to instantiate nsd:"
+    echo ${response}
+    exit -1
+else
+    echo " success"
+fi
+