RIFT OSM R1 Initial Submission

Signed-off-by: Jeremy Mordkoff <jeremy.mordkoff@riftio.com>
diff --git a/rwcm/test/CMakeLists.txt b/rwcm/test/CMakeLists.txt
new file mode 100644
index 0000000..ead05af
--- /dev/null
+++ b/rwcm/test/CMakeLists.txt
@@ -0,0 +1,39 @@
+# 
+#   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): Manish Patel
+# Creation Date: 10/28/2015
+# 
+
+cmake_minimum_required(VERSION 2.8)
+
+set(CONMAN_INSTALL "demos/conman")
+
+install(
+  FILES
+    start_cm_system.py
+    README.start_cm
+  DESTINATION ${CONMAN_INSTALL}
+  COMPONENT ${PKG_LONG_NAME})
+  
+# set(NS_NAME ping_pong_nsd)
+# install(
+#   FILES
+#     ${NS_NAME}/configuration_input_params.yml
+#     ${NS_NAME}/ping_vnfd_1_scriptconf_template.cfg
+#     ${NS_NAME}/pong_vnfd_11_scriptconf_template.cfg
+#   DESTINATION ${CONMAN_INSTALL}/${NS_NAME}
+#   COMPONENT ${PKG_LONG_NAME})
+
diff --git a/rwcm/test/README.start_cm b/rwcm/test/README.start_cm
new file mode 100644
index 0000000..7a8098b
--- /dev/null
+++ b/rwcm/test/README.start_cm
@@ -0,0 +1,4 @@
+# Following example command line to launch the system in collapse mode.
+# Please tailor for expanded mode or any other requirements
+
+./start_cm_system.py -m ethsim -c --skip-prepare-vm
diff --git a/rwcm/test/cwims_juju_nsd/configuration_input_params.yml b/rwcm/test/cwims_juju_nsd/configuration_input_params.yml
new file mode 100644
index 0000000..bbbe5bc
--- /dev/null
+++ b/rwcm/test/cwims_juju_nsd/configuration_input_params.yml
@@ -0,0 +1,20 @@
+
+# This is input parameters file for Network Service configuration.
+# This file is formatted as below:
+
+# configuration_delay : 120           # Number of seconds to wait before applying configuration after NS is up
+# number_of_vnfs_to_be_configured : 1 # Total number of VNFs in this NS to be configured by Service Orchestrator
+# 1 :                                 # Configuration Priority, order in which each VNF will be configured
+#   name : vnfd_name                  # Name of the VNF
+#   member_vnf_index : 11             # member index of the VNF that makes it unique (in case of multiple instances of same VNF)
+#   configuration_type : scriptconf   # Type of configuration (Currently supported values : scriptconf, netconf)
+#
+# Repeat VNF block for as many VNFs
+
+configuration_delay : 30
+number_of_vnfs_to_be_configured : 1
+1 :
+  name : cwims_vnfd
+  member_vnf_index : 1
+  configuration_type : jujuconf
+
diff --git a/rwcm/test/cwims_juju_nsd/cwaio_vnfd_1_juju_template.cfg b/rwcm/test/cwims_juju_nsd/cwaio_vnfd_1_juju_template.cfg
new file mode 100644
index 0000000..d32efe3
--- /dev/null
+++ b/rwcm/test/cwims_juju_nsd/cwaio_vnfd_1_juju_template.cfg
@@ -0,0 +1,23 @@
+ims-a:
+  deploy:
+    store: local
+    directory: /usr/rift/charms/cw-aio-proxy/trusty/
+    series: trusty
+    to: "lxc:0"
+
+  # Data under config passed as such during deployment
+  config:
+      proxied_ip: <rw_mgmt_ip>
+      home_domain: "ims.riftio.local"
+      base_number: "1234567000"
+      number_count: 1000
+
+  units:
+    - unit:
+        # Wait for each command to complete
+        wait: true
+        # Bail on failure
+        bail: true
+        actions:
+          - create-user: { number: "1234567001", password: "secret"}
+          - create-user: { number: "1234567002", password: "secret"}
diff --git a/rwcm/test/ping_pong_nsd/configuration_input_params.yml b/rwcm/test/ping_pong_nsd/configuration_input_params.yml
new file mode 100644
index 0000000..47c4fc3
--- /dev/null
+++ b/rwcm/test/ping_pong_nsd/configuration_input_params.yml
@@ -0,0 +1,23 @@
+
+# This is input parameters file for Network Service configuration.
+# This file is formatted as below:
+
+# configuration_delay : 120           # Number of seconds to wait before applying configuration after NS is up
+# number_of_vnfs_to_be_configured : 1 # Total number of VNFs in this NS to be configured by Service Orchestrator
+# 1 :                                 # Configuration Priority, order in which each VNF will be configured
+#   name : vnfd_name                  # Name of the VNF
+#   member_vnf_index : 11             # member index of the VNF that makes it unique (in case of multiple instances of same VNF)
+#   configuration_type : scriptconf   # Type of configuration (Currently supported values : scriptconf, netconf)
+#
+# Repeat VNF block for as many VNFs
+
+configuration_delay : 30
+number_of_vnfs_to_be_configured : 2
+1 :
+  name : pong_vnfd
+  member_vnf_index : 2
+  configuration_type : scriptconf
+2 :
+  name : ping_vnfd
+  member_vnf_index : 1
+  configuration_type : scriptconf
diff --git a/rwcm/test/ping_pong_nsd/ping_vnfd_1_scriptconf_template.cfg b/rwcm/test/ping_pong_nsd/ping_vnfd_1_scriptconf_template.cfg
new file mode 100755
index 0000000..e6e9889
--- /dev/null
+++ b/rwcm/test/ping_pong_nsd/ping_vnfd_1_scriptconf_template.cfg
@@ -0,0 +1,54 @@
+#!/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/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
+
+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
diff --git a/rwcm/test/ping_pong_nsd/pong_vnfd_11_scriptconf_template.cfg b/rwcm/test/ping_pong_nsd/pong_vnfd_11_scriptconf_template.cfg
new file mode 100755
index 0000000..28b01df
--- /dev/null
+++ b/rwcm/test/ping_pong_nsd/pong_vnfd_11_scriptconf_template.cfg
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+# Rest API configuration
+pong_mgmt_ip='<rw_mgmt_ip>'
+pong_mgmt_port=18889
+
+# Test
+# username=<rw_username>
+# password=<rw_password>
+
+# VNF specific configuration
+pong_server_ip='<rw_connection_point_name pong_vnfd/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
+
+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
diff --git a/rwcm/test/rwso_test.py b/rwcm/test/rwso_test.py
new file mode 100755
index 0000000..e0c5011
--- /dev/null
+++ b/rwcm/test/rwso_test.py
@@ -0,0 +1,349 @@
+#!/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 asyncio
+import logging
+import os
+import sys
+import types
+import unittest
+import uuid
+
+import xmlrunner
+
+import gi.repository.CF as cf
+import gi.repository.RwDts as rwdts
+import gi.repository.RwMain as rwmain
+import gi.repository.RwManifestYang as rwmanifest
+import gi.repository.RwConmanYang as conmanY
+import gi.repository.RwLaunchpadYang as launchpadyang
+
+import rift.tasklets
+
+if sys.version_info < (3, 4, 4):
+    asyncio.ensure_future = asyncio.async
+
+
+class RWSOTestCase(unittest.TestCase):
+    """
+    DTS GI interface unittests
+
+    Note:  Each tests uses a list of asyncio.Events for staging through the
+    test.  These are required here because we are bring up each coroutine
+    ("tasklet") at the same time and are not implementing any re-try
+    mechanisms.  For instance, this is used in numerous tests to make sure that
+    a publisher is up and ready before the subscriber sends queries.  Such
+    event lists should not be used in production software.
+    """
+    rwmain = None
+    tinfo = None
+    schema = None
+    id_cnt = 0
+
+    @classmethod
+    def setUpClass(cls):
+        msgbroker_dir = os.environ.get('MESSAGE_BROKER_DIR')
+        router_dir = os.environ.get('ROUTER_DIR')
+        cm_dir = os.environ.get('SO_DIR')
+
+        manifest = rwmanifest.Manifest()
+        manifest.init_phase.settings.rwdtsrouter.single_dtsrouter.enable = True
+
+        cls.rwmain = rwmain.Gi.new(manifest)
+        cls.tinfo = cls.rwmain.get_tasklet_info()
+
+        # Run router in mainq.  Eliminates some ill-diagnosed bootstrap races.
+        os.environ['RWDTS_ROUTER_MAINQ']='1'
+        cls.rwmain.add_tasklet(msgbroker_dir, 'rwmsgbroker-c')
+        cls.rwmain.add_tasklet(router_dir, 'rwdtsrouter-c')
+        cls.rwmain.add_tasklet(cm_dir, 'rwconmantasklet')
+
+        cls.log = rift.tasklets.logger_from_tasklet_info(cls.tinfo)
+        cls.log.setLevel(logging.DEBUG)
+
+        stderr_handler = logging.StreamHandler(stream=sys.stderr)
+        fmt = logging.Formatter(
+                '%(asctime)-23s %(levelname)-5s  (%(name)s@%(process)d:%(filename)s:%(lineno)d) - %(message)s')
+        stderr_handler.setFormatter(fmt)
+        cls.log.addHandler(stderr_handler)
+        cls.schema = conmanY.get_schema()
+
+    def setUp(self):
+        def scheduler_tick(self, *args):
+            self.call_soon(self.stop)
+            self.run_forever()
+
+        self.loop = asyncio.new_event_loop()
+        self.loop.scheduler_tick = types.MethodType(scheduler_tick, self.loop)
+        self.loop.set_debug(True)
+        os.environ["PYTHONASYNCIODEBUG"] = "1"
+        asyncio_logger = logging.getLogger("asyncio")
+        asyncio_logger.setLevel(logging.DEBUG)
+
+        self.asyncio_timer = None
+        self.stop_timer = None
+        self.id_cnt += 1
+
+    @asyncio.coroutine
+    def wait_tasklets(self):
+        yield from asyncio.sleep(1, loop=self.loop)
+
+    def run_until(self, test_done, timeout=30):
+        """
+        Attach the current asyncio event loop to rwsched and then run the
+        scheduler until the test_done function returns True or timeout seconds
+        pass.
+
+        @param test_done  - function which should return True once the test is
+                            complete and the scheduler no longer needs to run.
+        @param timeout    - maximum number of seconds to run the test.
+        """
+        def shutdown(*args):
+            if args:
+                self.log.debug('Shutting down loop due to timeout')
+
+            if self.asyncio_timer is not None:
+                self.tinfo.rwsched_tasklet.CFRunLoopTimerRelease(self.asyncio_timer)
+                self.asyncio_timer = None
+
+            if self.stop_timer is not None:
+                self.tinfo.rwsched_tasklet.CFRunLoopTimerRelease(self.stop_timer)
+                self.stop_timer = None
+
+            self.tinfo.rwsched_instance.CFRunLoopStop()
+
+        def tick(*args):
+            self.loop.call_later(0.1, self.loop.stop)
+            self.loop.run_forever()
+            if test_done():
+                shutdown()
+
+        self.asyncio_timer = self.tinfo.rwsched_tasklet.CFRunLoopTimer(
+            cf.CFAbsoluteTimeGetCurrent(),
+            0.1,
+            tick,
+            None)
+
+        self.stop_timer = self.tinfo.rwsched_tasklet.CFRunLoopTimer(
+            cf.CFAbsoluteTimeGetCurrent() + timeout,
+            0,
+            shutdown,
+            None)
+
+        self.tinfo.rwsched_tasklet.CFRunLoopAddTimer(
+            self.tinfo.rwsched_tasklet.CFRunLoopGetCurrent(),
+            self.stop_timer,
+            self.tinfo.rwsched_instance.CFRunLoopGetMainMode())
+
+        self.tinfo.rwsched_tasklet.CFRunLoopAddTimer(
+            self.tinfo.rwsched_tasklet.CFRunLoopGetCurrent(),
+            self.asyncio_timer,
+            self.tinfo.rwsched_instance.CFRunLoopGetMainMode())
+
+        self.tinfo.rwsched_instance.CFRunLoopRun()
+
+        self.assertTrue(test_done())
+
+    def new_tinfo(self, name):
+        """
+        Create a new tasklet info instance with a unique instance_id per test.
+        It is up to each test to use unique names if more that one tasklet info
+        instance is needed.
+
+        @param name - name of the "tasklet"
+        @return     - new tasklet info instance
+        """
+        ret = self.rwmain.new_tasklet_info(name, RWSOTestCase.id_cnt)
+
+        log = rift.tasklets.logger_from_tasklet_info(ret)
+        log.setLevel(logging.DEBUG)
+
+        stderr_handler = logging.StreamHandler(stream=sys.stderr)
+        fmt = logging.Formatter(
+                '%(asctime)-23s %(levelname)-5s  (%(name)s@%(process)d:%(filename)s:%(lineno)d) - %(message)s')
+        stderr_handler.setFormatter(fmt)
+        log.addHandler(stderr_handler)
+
+        return ret
+
+    def get_cloud_account_msg(self):
+        cloud_account = launchpadyang.CloudAccount()
+        cloud_account.name = "cloudy"
+        cloud_account.account_type = "mock"
+        cloud_account.mock.username = "rainy"
+        return cloud_account
+
+    def get_compute_pool_msg(self, name, pool_type):
+        pool_config = rmgryang.ResourcePools()
+        pool = pool_config.pools.add()
+        pool.name = name
+        pool.resource_type = "compute"
+        if pool_type == "static":
+            # Need to query CAL for resource
+            pass
+        else:
+            pool.max_size = 10
+        return pool_config
+
+    def get_network_pool_msg(self, name, pool_type):
+        pool_config = rmgryang.ResourcePools()
+        pool = pool_config.pools.add()
+        pool.name = name
+        pool.resource_type = "network"
+        if pool_type == "static":
+            # Need to query CAL for resource
+            pass
+        else:
+            pool.max_size = 4
+        return pool_config
+
+
+    def get_network_reserve_msg(self, xpath):
+        event_id = str(uuid.uuid4())
+        msg = rmgryang.VirtualLinkEventData()
+        msg.event_id = event_id
+        msg.request_info.name = "mynet"
+        msg.request_info.subnet = "1.1.1.0/24"
+        return msg, xpath.format(event_id)
+
+    def get_compute_reserve_msg(self,xpath):
+        event_id = str(uuid.uuid4())
+        msg = rmgryang.VDUEventData()
+        msg.event_id = event_id
+        msg.request_info.name = "mynet"
+        msg.request_info.image_id  = "This is a image_id"
+        msg.request_info.vm_flavor.vcpu_count = 4
+        msg.request_info.vm_flavor.memory_mb = 8192*2
+        msg.request_info.vm_flavor.storage_gb = 40
+        c1 = msg.request_info.connection_points.add()
+        c1.name = "myport1"
+        c1.virtual_link_id = "This is a network_id"
+        return msg, xpath.format(event_id)
+
+    def test_create_resource_pools(self):
+        self.log.debug("STARTING - test_create_resource_pools")
+        tinfo = self.new_tinfo('poolconfig')
+        dts = rift.tasklets.DTS(tinfo, self.schema, self.loop)
+        pool_xpath = "C,/rw-resource-mgr:resource-mgr-config/rw-resource-mgr:resource-pools"
+        pool_records_xpath = "D,/rw-resource-mgr:resource-pool-records"
+        account_xpath = "C,/rw-launchpad:cloud-account"
+        compute_xpath = "D,/rw-resource-mgr:resource-mgmt/vdu-event/vdu-event-data[event-id='{}']"
+        network_xpath = "D,/rw-resource-mgr:resource-mgmt/vlink-event/vlink-event-data[event-id='{}']"
+
+        @asyncio.coroutine
+        def configure_cloud_account():
+            msg = self.get_cloud_account_msg()
+            self.log.info("Configuring cloud-account: %s",msg)
+            yield from dts.query_create(account_xpath,
+                                        rwdts.XactFlag.ADVISE,
+                                        msg)
+            yield from asyncio.sleep(3, loop=self.loop)
+
+        @asyncio.coroutine
+        def configure_compute_resource_pools():
+            msg = self.get_compute_pool_msg("virtual-compute", "dynamic")
+            self.log.info("Configuring compute-resource-pool: %s",msg)
+            yield from dts.query_create(pool_xpath,
+                                        rwdts.XactFlag.ADVISE,
+                                        msg)
+            yield from asyncio.sleep(3, loop=self.loop)
+
+
+        @asyncio.coroutine
+        def configure_network_resource_pools():
+            msg = self.get_network_pool_msg("virtual-network", "dynamic")
+            self.log.info("Configuring network-resource-pool: %s",msg)
+            yield from dts.query_create(pool_xpath,
+                                        rwdts.XactFlag.ADVISE,
+                                        msg)
+            yield from asyncio.sleep(3, loop=self.loop)
+
+
+        @asyncio.coroutine
+        def verify_resource_pools():
+            self.log.debug("Verifying test_create_resource_pools results")
+            res_iter = yield from dts.query_read(pool_records_xpath,)
+            for result in res_iter:
+                response = yield from result
+                records = response.result.records
+                #self.assertEqual(len(records), 2)
+                #names = [i.name for i in records]
+                #self.assertTrue('virtual-compute' in names)
+                #self.assertTrue('virtual-network' in names)
+                for record in records:
+                    self.log.debug("Received Pool Record, Name: %s, Resource Type: %s, Pool Status: %s, Pool Size: %d, Busy Resources: %d",
+                                   record.name,
+                                   record.resource_type,
+                                   record.pool_status,
+                                   record.max_size,
+                                   record.busy_resources)
+        @asyncio.coroutine
+        def reserve_network_resources():
+            msg,xpath = self.get_network_reserve_msg(network_xpath)
+            self.log.debug("Sending create event to network-event xpath %s with msg: %s" % (xpath, msg))
+            yield from dts.query_create(xpath, rwdts.XactFlag.TRACE, msg)
+            yield from asyncio.sleep(3, loop=self.loop)
+            yield from dts.query_delete(xpath, rwdts.XactFlag.TRACE)
+
+        @asyncio.coroutine
+        def reserve_compute_resources():
+            msg,xpath = self.get_compute_reserve_msg(compute_xpath)
+            self.log.debug("Sending create event to compute-event xpath %s with msg: %s" % (xpath, msg))
+            yield from dts.query_create(xpath, rwdts.XactFlag.TRACE, msg)
+            yield from asyncio.sleep(3, loop=self.loop)
+            yield from dts.query_delete(xpath, rwdts.XactFlag.TRACE)
+
+        @asyncio.coroutine
+        def run_test():
+            yield from self.wait_tasklets()
+            yield from configure_cloud_account()
+            yield from configure_compute_resource_pools()
+            yield from configure_network_resource_pools()
+            yield from verify_resource_pools()
+            yield from reserve_network_resources()
+            yield from reserve_compute_resources()
+
+        future = asyncio.ensure_future(run_test(), loop=self.loop)
+        self.run_until(future.done)
+        if future.exception() is not None:
+            self.log.error("Caught exception during test")
+            raise future.exception()
+
+        self.log.debug("DONE - test_create_resource_pools")
+
+
+def main():
+    plugin_dir = os.path.join(os.environ["RIFT_INSTALL"], "usr/lib/rift/plugins")
+
+    if 'MESSAGE_BROKER_DIR' not in os.environ:
+        os.environ['MESSAGE_BROKER_DIR'] = os.path.join(plugin_dir, 'rwmsgbroker-c')
+
+    if 'ROUTER_DIR' not in os.environ:
+        os.environ['ROUTER_DIR'] = os.path.join(plugin_dir, 'rwdtsrouter-c')
+
+    if 'SO_DIR' not in os.environ:
+        os.environ['SO_DIR'] = os.path.join(plugin_dir, 'rwconmantasklet')
+
+    runner = xmlrunner.XMLTestRunner(output=os.environ["RIFT_MODULE_TEST"])
+    unittest.main(testRunner=runner)
+
+if __name__ == '__main__':
+    main()
+
+# vim: sw=4
diff --git a/rwcm/test/start_cm_system.py b/rwcm/test/start_cm_system.py
new file mode 100755
index 0000000..1975a0a
--- /dev/null
+++ b/rwcm/test/start_cm_system.py
@@ -0,0 +1,131 @@
+#!/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 logging
+import os
+import sys
+
+import rift.vcs
+import rift.vcs.demo
+import rift.vcs.vms
+
+from rift.vcs.ext import ClassProperty
+
+logger = logging.getLogger(__name__)
+
+
+class ConfigManagerTasklet(rift.vcs.core.Tasklet):
+    """
+    This class represents SO tasklet.
+    """
+
+    def __init__(self, name='rwcmtasklet', uid=None):
+        """
+        Creates a PingTasklet object.
+
+        Arguments:
+            name  - the name of the tasklet
+            uid   - a unique identifier
+        """
+        super(ConfigManagerTasklet, self).__init__(name=name, uid=uid)
+
+    plugin_directory = ClassProperty('./usr/lib/rift/plugins/rwconmantasklet')
+    plugin_name = ClassProperty('rwconmantasklet')
+
+
+# Construct the system. This system consists of 1 cluster in 1
+# colony. The master cluster houses CLI and management VMs
+sysinfo = rift.vcs.SystemInfo(
+        colonies=[
+            rift.vcs.Colony(
+                clusters=[
+                    rift.vcs.Cluster(
+                        name='master',
+                        virtual_machines=[
+                            rift.vcs.VirtualMachine(
+                                name='vm-so',
+                                ip='127.0.0.1',
+                                tasklets=[
+                                    rift.vcs.uAgentTasklet(),
+                                    ],
+                                procs=[
+                                    rift.vcs.CliTasklet(),
+                                    rift.vcs.DtsRouterTasklet(),
+                                    rift.vcs.MsgBrokerTasklet(),
+                                    rift.vcs.RestconfTasklet(),
+                                    ConfigManagerTasklet()
+                                    ],
+                                ),
+                            ]
+                        )
+                    ]
+                )
+            ]
+        )
+
+
+# Define the generic portmap.
+port_map = {}
+
+
+# Define a mapping from the placeholder logical names to the real
+# port names for each of the different modes supported by this demo.
+port_names = {
+    'ethsim': {
+    },
+    'pci': {
+    }
+}
+
+
+# Define the connectivity between logical port names.
+port_groups = {}
+
+def main(argv=sys.argv[1:]):
+    logging.basicConfig(format='%(asctime)-15s %(levelname)s %(message)s')
+
+    # Create a parser which includes all generic demo arguments
+    parser = rift.vcs.demo.DemoArgParser()
+
+    args = parser.parse_args(argv)
+
+    #load demo info and create Demo object
+    demo = rift.vcs.demo.Demo(sysinfo=sysinfo,
+                              port_map=port_map,
+                              port_names=port_names,
+                              port_groups=port_groups)
+
+    # Create the prepared system from the demo
+    system = rift.vcs.demo.prepared_system_from_demo_and_args(demo, args, netconf_trace_override=True)
+
+    # Start the prepared system
+    system.start()
+
+
+if __name__ == "__main__":
+    try:
+        main()
+    except rift.vcs.demo.ReservationError:
+        print("ERROR: unable to retrieve a list of IP addresses from the reservation system")
+        sys.exit(1)
+    except rift.vcs.demo.MissingModeError:
+        print("ERROR: you need to provide a mode to run the script")
+        sys.exit(1)
+    finally:
+        os.system("stty sane")
diff --git a/rwcm/test/tg_vrouter_ts_nsd/configuration_input_params.yml b/rwcm/test/tg_vrouter_ts_nsd/configuration_input_params.yml
new file mode 100644
index 0000000..b5a126f
--- /dev/null
+++ b/rwcm/test/tg_vrouter_ts_nsd/configuration_input_params.yml
@@ -0,0 +1,23 @@
+
+# This is input parameters file for Network Service configuration.
+# This file is formatted as below:
+
+# configuration_delay : 120           # Number of seconds to wait before applying configuration after NS is up
+# number_of_vnfs_to_be_configured : 1 # Total number of VNFs in this NS to be configured by Service Orchestrator
+# 1 :                                 # Configuration Priority, order in which each VNF will be configured
+#   name : vnfd_name                  # Name of the VNF
+#   member_vnf_index : 11             # member index of the VNF that makes it unique (in case of multiple instances of same VNF)
+#   configuration_type : scriptconf   # Type of configuration (Currently supported values : scriptconf, netconf)
+#
+# Repeat VNF block for as many VNFs
+
+configuration_delay : 120
+number_of_vnfs_to_be_configured : 2
+1 :
+  name : trafsink_vnfd
+  member_vnf_index : 3
+  configuration_type : netconf
+2 :
+  name : trafgen_vnfd
+  member_vnf_index : 1
+  configuration_type : netconf
diff --git a/rwcm/test/tg_vrouter_ts_nsd/trafgen_vnfd_1_netconf_template.cfg b/rwcm/test/tg_vrouter_ts_nsd/trafgen_vnfd_1_netconf_template.cfg
new file mode 100644
index 0000000..02dfc85
--- /dev/null
+++ b/rwcm/test/tg_vrouter_ts_nsd/trafgen_vnfd_1_netconf_template.cfg
@@ -0,0 +1,79 @@
+    <vnf-config xmlns="http://riftio.com/ns/riftware-1.0/mano-base">
+      <vnf>
+        <name>trafgen</name>
+        <instance>0</instance>
+        <network-context xmlns="http://riftio.com/ns/riftware-1.0/rw-vnf-base-config">
+          <name>trafgen-lb</name>
+          <interface>
+            <name>N1TenGi-1</name>
+            <bind>
+              <port>trafgen_vnfd/cp0</port>
+            </bind>
+          </interface>
+        </network-context>
+        <port xmlns="http://riftio.com/ns/riftware-1.0/rw-vnf-base-config">
+          <name>trafgen_vnfd/cp0</name>
+          <open/>
+          <application>
+            <rx>rw_trafgen</rx>
+            <tx>rw_trafgen</tx>
+          </application>
+          <receive-q-length>2</receive-q-length>
+          <port-identity>
+          <ip-address><rw_connection_point_name trafgen_vnfd/cp0></ip-address>
+          <port-mode>direct</port-mode>
+          </port-identity>
+          <trafgen xmlns="http://riftio.com/ns/riftware-1.0/rw-trafgen">
+            <transmit-params>
+              <transmit-mode>
+                <range/>
+              </transmit-mode>
+            </transmit-params>
+            <range-template>
+              <destination-mac>
+                <dynamic>
+                  <gateway><rw_connection_point_name vrouter_vnfd/cp0></gateway>
+                </dynamic>
+              </destination-mac>
+              <source-ip>
+                <start><rw_connection_point_name trafgen_vnfd/cp0></start>
+                <minimum><rw_connection_point_name trafgen_vnfd/cp0></minimum>
+                <maximum><rw_connection_point_name trafgen_vnfd/cp0></maximum>
+                <increment>1</increment>
+              </source-ip>
+              <destination-ip>
+                <start><rw_connection_point_name trafsink_vnfd/cp0></start>
+                <minimum><rw_connection_point_name trafsink_vnfd/cp0></minimum>
+                <maximum><rw_connection_point_name trafsink_vnfd/cp0></maximum>
+                <increment>1</increment>
+              </destination-ip>
+              <source-port>
+                <start>10000</start>
+                <minimum>10000</minimum>
+                <maximum>10128</maximum>
+                <increment>1</increment>
+              </source-port>
+              <destination-port>
+                <start>5678</start>
+                <minimum>5678</minimum>
+                <maximum>5678</maximum>
+                <increment>1</increment>
+              </destination-port>
+              <packet-size>
+                <start>512</start>
+                <minimum>512</minimum>
+                <maximum>512</maximum>
+                <increment>1</increment>
+              </packet-size>
+            </range-template>
+          </trafgen>
+        </port>
+      </vnf>
+    </vnf-config>
+    <logging xmlns="http://riftio.com/ns/riftware-1.0/rwlog-mgmt">
+      <sink>
+        <name>syslog</name>
+        <server-address><rw_mgmt_ip></server-address>
+        <port>514</port>
+      </sink>
+    </logging>
diff --git a/rwcm/test/tg_vrouter_ts_nsd/trafsink_vnfd_3_netconf_template.cfg b/rwcm/test/tg_vrouter_ts_nsd/trafsink_vnfd_3_netconf_template.cfg
new file mode 100644
index 0000000..6402201
--- /dev/null
+++ b/rwcm/test/tg_vrouter_ts_nsd/trafsink_vnfd_3_netconf_template.cfg
@@ -0,0 +1,42 @@
+    <vnf-config xmlns="http://riftio.com/ns/riftware-1.0/mano-base">
+      <vnf>
+        <name>trafsink</name>
+        <instance>0</instance>
+        <network-context xmlns="http://riftio.com/ns/riftware-1.0/rw-vnf-base-config">
+          <name>lb-trafsink</name>
+          <interface>
+            <name>N3TenGigi-1</name>
+            <bind>
+              <port>trafsink_vnfd/cp0</port>
+            </bind>
+          </interface>
+        </network-context>
+        <port xmlns="http://riftio.com/ns/riftware-1.0/rw-vnf-base-config">
+          <name>trafsink_vnfd/cp0</name>
+          <open/>
+          <application>
+            <rx>rw_trafgen</rx>
+            <tx>rw_trafgen</tx>
+          </application>
+          <receive-q-length>2</receive-q-length>
+          <port-identity>
+          <ip-address><rw_connection_point_name trafsink_vnfd/cp0></ip-address>
+          <port-mode>direct</port-mode>
+          </port-identity>
+          <trafgen xmlns="http://riftio.com/ns/riftware-1.0/rw-trafgen">
+            <receive-param>
+              <receive-echo>
+                <on/>
+              </receive-echo>
+            </receive-param>
+          </trafgen>
+        </port>
+      </vnf>
+    </vnf-config>
+    <logging xmlns="http://riftio.com/ns/riftware-1.0/rwlog-mgmt">
+      <sink>
+        <name>syslog</name>
+        <server-address><rw_mgmt_ip></server-address>
+        <port>514</port>
+      </sink>
+    </logging>