RIFT OSM R1 Initial Submission
[osm/SO.git] / rwlaunchpad / test / mano_ut.py
diff --git a/rwlaunchpad/test/mano_ut.py b/rwlaunchpad/test/mano_ut.py
new file mode 100755 (executable)
index 0000000..69a0d40
--- /dev/null
@@ -0,0 +1,1198 @@
+#!/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 os
+import sys
+import unittest
+import uuid
+import xmlrunner
+import argparse
+import logging
+import time
+import types
+
+import gi
+gi.require_version('RwCloudYang', '1.0')
+gi.require_version('RwDts', '1.0')
+gi.require_version('RwNsmYang', '1.0')
+gi.require_version('RwLaunchpadYang', '1.0')
+gi.require_version('RwResourceMgrYang', '1.0')
+gi.require_version('RwcalYang', '1.0')
+gi.require_version('RwNsrYang', '1.0')
+gi.require_version('NsrYang', '1.0')
+gi.require_version('RwlogMgmtYang', '1.0')
+
+from gi.repository import (
+    RwCloudYang as rwcloudyang,
+    RwDts as rwdts,
+    RwLaunchpadYang as launchpadyang,
+    RwNsmYang as rwnsmyang,
+    RwNsrYang as rwnsryang,
+    NsrYang as nsryang,
+    RwResourceMgrYang as rmgryang,
+    RwcalYang as rwcalyang,
+    RwConfigAgentYang as rwcfg_agent,
+    RwlogMgmtYang
+)
+
+from gi.repository.RwTypes import RwStatus
+import rift.mano.examples.ping_pong_nsd as ping_pong_nsd
+import rift.tasklets
+import rift.test.dts
+import rw_peas
+
+
+openstack_info = {
+        'username': 'pluto',
+        'password': 'mypasswd',
+        'auth_url': 'http://10.66.4.27:5000/v3/',
+        'project_name': 'demo',
+        'mgmt_network': 'private',
+        }
+
+
+if sys.version_info < (3, 4, 4):
+    asyncio.ensure_future = asyncio.async
+
+
+class XPaths(object):
+    @staticmethod
+    def nsd(k=None):
+        return ("C,/nsd:nsd-catalog/nsd:nsd" +
+                ("[nsd:id='{}']".format(k) if k is not None else ""))
+
+    @staticmethod
+    def vld(k=None):
+        return ("C,/vld:vld-catalog/vld:vld" +
+                ("[vld:id='{}']".format(k) if k is not None else ""))
+
+    @staticmethod
+    def vnfd(k=None):
+        return ("C,/vnfd:vnfd-catalog/vnfd:vnfd" +
+                ("[vnfd:id='{}']".format(k) if k is not None else ""))
+
+    @staticmethod
+    def vnfr(k=None):
+        return ("D,/vnfr:vnfr-catalog/vnfr:vnfr" +
+                ("[vnfr:id='{}']".format(k) if k is not None else ""))
+
+    @staticmethod
+    def vlr(k=None):
+        return ("D,/vlr:vlr-catalog/vlr:vlr" +
+                ("[vlr:id='{}']".format(k) if k is not None else ""))
+
+    @staticmethod
+    def nsd_ref_count(k=None):
+        return ("D,/nsr:ns-instance-opdata/rw-nsr:nsd-ref-count" +
+                ("[rw-nsr:nsd-id-ref='{}']".format(k) if k is not None else ""))
+
+    @staticmethod
+    def vnfd_ref_count(k=None):
+        return ("D,/vnfr:vnfr-catalog/rw-vnfr:vnfd-ref-count" +
+                ("[rw-nsr:nsd-id-ref='{}']".format(k) if k is not None else ""))
+
+    @staticmethod
+    def nsr_config(k=None):
+        return ("C,/nsr:ns-instance-config/nsr:nsr" +
+                ("[nsr:id='{}']".format(k) if k is not None else ""))
+
+    @staticmethod
+    def nsr_opdata(k=None):
+        return ("D,/nsr:ns-instance-opdata/nsr:nsr" +
+                ("[nsr:ns-instance-config-ref='{}']".format(k) if k is not None else ""))
+
+    @staticmethod
+    def nsr_config_status(k=None):
+        return ("D,/nsr:ns-instance-opdata/nsr:nsr" +
+                ("[nsr:ns-instance-config-ref='{}']/config_status".format(k) if k is not None else ""))
+
+    @staticmethod
+    def cm_state(k=None):
+        if k is None:
+            return ("D,/rw-conman:cm-state/rw-conman:cm-nsr")
+        else:
+            return ("D,/rw-conman:cm-state/rw-conman:cm-nsr" +
+                    ("[rw-conman:id='{}']".format(k) if k is not None else ""))
+
+    @staticmethod
+    def nsr_scale_group_instance(nsr_id=None, group_name=None, index=None):
+        return (("D,/nsr:ns-instance-opdata/nsr:nsr") +
+                ("[nsr:ns-instance-config-ref='{}']".format(nsr_id) if nsr_id is not None else "") +
+                ("/nsr:scaling-group-record") +
+                ("[nsr:scaling-group-name-ref='{}']".format(group_name) if group_name is not None else "") +
+                ("/nsr:instance") +
+                ("[nsr:scaling-group-index-ref='{}']".format(index) if index is not None else ""))
+
+    @staticmethod
+    def nsr_scale_group_instance_config(nsr_id=None, group_name=None, index=None):
+        return (("C,/nsr:ns-instance-config/nsr:nsr") +
+                ("[nsr:id='{}']".format(nsr_id) if nsr_id is not None else "") +
+                ("/nsr:scaling-group") +
+                ("[nsr:scaling-group-name-ref='{}']".format(group_name) if group_name is not None else "") +
+                ("/nsr:instance") +
+                ("[nsr:index='{}']".format(index) if index is not None else ""))
+
+
+class ManoQuerier(object):
+    def __init__(self, log, dts):
+        self.log = log
+        self.dts = dts
+
+    @asyncio.coroutine
+    def _read_query(self, xpath, do_trace=False):
+        self.log.debug("Running XPATH read query: %s (trace: %s)", xpath, do_trace)
+        flags = rwdts.XactFlag.MERGE
+        flags += rwdts.XactFlag.TRACE if do_trace else 0
+        res_iter = yield from self.dts.query_read(
+                xpath, flags=flags
+                )
+
+        results = []
+        for i in res_iter:
+            result = yield from i
+            if result is not None:
+                results.append(result.result)
+
+        return results
+
+    @asyncio.coroutine
+    def get_cm_state(self, nsr_id=None):
+        return (yield from self._read_query(XPaths.cm_state(nsr_id), False))
+
+    @asyncio.coroutine
+    def get_nsr_opdatas(self, nsr_id=None):
+        return (yield from self._read_query(XPaths.nsr_opdata(nsr_id), False))
+
+    @asyncio.coroutine
+    def get_nsr_scale_group_instance_opdata(self, nsr_id=None, group_name=None, index=None):
+        return (yield from self._read_query(XPaths.nsr_scale_group_instance(nsr_id, group_name, index), False))
+        #return (yield from self._read_query(XPaths.nsr_scale_group_instance(nsr_id, group_name), True))
+
+    @asyncio.coroutine
+    def get_nsr_configs(self, nsr_id=None):
+        return (yield from self._read_query(XPaths.nsr_config(nsr_id)))
+
+    @asyncio.coroutine
+    def get_nsr_config_status(self, nsr_id=None):
+        return (yield from self._read_query(XPaths.nsr_config_status(nsr_id)))
+
+    @asyncio.coroutine
+    def get_vnfrs(self, vnfr_id=None):
+        return (yield from self._read_query(XPaths.vnfr(vnfr_id)))
+
+    @asyncio.coroutine
+    def get_vlrs(self, vlr_id=None):
+        return (yield from self._read_query(XPaths.vlr(vlr_id)))
+
+    @asyncio.coroutine
+    def get_nsd_ref_counts(self, nsd_id=None):
+        return (yield from self._read_query(XPaths.nsd_ref_count(nsd_id)))
+
+    @asyncio.coroutine
+    def get_vnfd_ref_counts(self, vnfd_id=None):
+        return (yield from self._read_query(XPaths.vnfd_ref_count(vnfd_id)))
+
+    @asyncio.coroutine
+    def delete_nsr(self, nsr_id):
+        with self.dts.transaction() as xact:
+            yield from self.dts.query_delete(
+                    XPaths.nsr_config(nsr_id),
+                    0
+                    #rwdts.XactFlag.TRACE,
+                    #rwdts.Flag.ADVISE,
+                    )
+
+    @asyncio.coroutine
+    def delete_nsd(self, nsd_id):
+        nsd_xpath = XPaths.nsd(nsd_id)
+        self.log.debug("Attempting to delete NSD with path = %s", nsd_xpath)
+        with self.dts.transaction() as xact:
+            yield from self.dts.query_delete(
+                    nsd_xpath,
+                    rwdts.XactFlag.ADVISE,
+                    )
+
+    @asyncio.coroutine
+    def delete_vnfd(self, vnfd_id):
+        vnfd_xpath = XPaths.vnfd(vnfd_id)
+        self.log.debug("Attempting to delete VNFD with path = %s", vnfd_xpath)
+        with self.dts.transaction() as xact:
+            yield from self.dts.query_delete(
+                    vnfd_xpath,
+                    rwdts.XactFlag.ADVISE,
+                    )
+
+    @asyncio.coroutine
+    def update_nsd(self, nsd_id, nsd_msg):
+        nsd_xpath = XPaths.nsd(nsd_id)
+        self.log.debug("Attempting to update NSD with path = %s", nsd_xpath)
+        with self.dts.transaction() as xact:
+            yield from self.dts.query_update(
+                    nsd_xpath,
+                    rwdts.XactFlag.ADVISE,
+                    nsd_msg,
+                    )
+
+    @asyncio.coroutine
+    def update_vnfd(self, vnfd_id, vnfd_msg):
+        vnfd_xpath = XPaths.vnfd(vnfd_id)
+        self.log.debug("Attempting to delete VNFD with path = %s", vnfd_xpath)
+        with self.dts.transaction() as xact:
+            yield from self.dts.query_update(
+                    vnfd_xpath,
+                    rwdts.XactFlag.ADVISE,
+                    vnfd_msg,
+                    )
+
+    @asyncio.coroutine
+    def update_nsr_config(self, nsr_id, nsr_msg):
+        nsr_xpath = XPaths.nsr_config(nsr_id)
+        self.log.debug("Attempting to update NSR with path = %s", nsr_xpath)
+        with self.dts.transaction() as xact:
+            yield from self.dts.query_update(
+                    nsr_xpath,
+                    rwdts.XactFlag.ADVISE|rwdts.XactFlag.REPLACE,
+                    nsr_msg,
+                    )
+
+
+class ManoTestCase(rift.test.dts.AbstractDTSTest):
+    @asyncio.coroutine
+    def verify_nsr_state(self, nsr_id, state):
+        nsrs = yield from self.querier.get_nsr_opdatas(nsr_id)
+        self.assertEqual(1, len(nsrs))
+        nsr = nsrs[0]
+
+        self.log.debug("Got nsr = %s", nsr)
+        self.assertEqual(state, nsr.operational_status)
+
+    @asyncio.coroutine
+    def verify_vlr_state(self, vlr_id, state):
+        vlrs = yield from self.querier.get_vlrs(vlr_id)
+        self.assertEqual(1, len(vlrs))
+        vlr = vlrs[0]
+
+        self.assertEqual(state, vlr.operational_status)
+
+    def verify_vdu_state(self, vdu, state):
+        self.assertEqual(state, vdu.operational_status)
+
+    @asyncio.coroutine
+    def verify_vnf_state(self, vnfr_id, state):
+        vnfrs = yield from self.querier.get_vnfrs(vnfr_id)
+        self.assertEqual(1, len(vnfrs))
+        vnfr = vnfrs[0]
+
+        self.assertEqual(state, vnfr.operational_status)
+
+    @asyncio.coroutine
+    def terminate_nsr(self, nsr_id):
+        self.log.debug("Terminating nsr id: %s", nsr_id)
+        yield from self.querier.delete_nsr(nsr_id)
+
+    @asyncio.coroutine
+    def verify_nsr_deleted(self, nsr_id):
+        nsr_opdatas = yield from self.querier.get_nsr_opdatas(nsr_id)
+        self.assertEqual(0, len(nsr_opdatas))
+
+        nsr_configs = yield from self.querier.get_nsr_configs(nsr_id)
+        self.assertEqual(0, len(nsr_configs))
+
+    @asyncio.coroutine
+    def verify_num_vlrs(self, num_vlrs):
+        vlrs = yield from self.querier.get_vlrs()
+        self.assertEqual(num_vlrs, len(vlrs))
+
+    @asyncio.coroutine
+    def get_nsr_vlrs(self, nsr_id):
+        nsrs = yield from self.querier.get_nsr_opdatas(nsr_id)
+        return [v.vlr_ref for v in nsrs[0].vlr]
+
+    @asyncio.coroutine
+    def get_nsr_vnfs(self, nsr_id):
+        nsrs = yield from self.querier.get_nsr_opdatas(nsr_id)
+        return nsrs[0].constituent_vnfr_ref
+
+    @asyncio.coroutine
+    def get_vnf_vlrs(self, vnfr_id):
+        vnfrs = yield from self.querier.get_vnfrs(vnfr_id)
+        return [i.vlr_ref for i in vnfrs[0].internal_vlr]
+
+    @asyncio.coroutine
+    def verify_num_nsr_vlrs(self, nsr_id, num_vlrs):
+        vlrs = yield from self.get_nsr_vlrs(nsr_id)
+        self.assertEqual(num_vlrs, len(vlrs))
+
+    @asyncio.coroutine
+    def verify_num_nsr_vnfrs(self, nsr_id, num_vnfs):
+        vnfs = yield from self.get_nsr_vnfs(nsr_id)
+        self.assertEqual(num_vnfs, len(vnfs))
+
+    @asyncio.coroutine
+    def verify_num_vnfr_vlrs(self, vnfr_id, num_vlrs):
+        vlrs = yield from self.get_vnf_vlrs(vnfr_id)
+        self.assertEqual(num_vlrs, len(vlrs))
+
+    @asyncio.coroutine
+    def get_vnf_vdus(self, vnfr_id):
+        vnfrs = yield from self.querier.get_vnfrs(vnfr_id)
+        return [i for i in vnfrs[0].vdur]
+
+    @asyncio.coroutine
+    def verify_num_vnfr_vdus(self, vnfr_id, num_vdus):
+        vdus = yield from self.get_vnf_vdus(vnfr_id)
+        self.assertEqual(num_vdus, len(vdus))
+
+    @asyncio.coroutine
+    def verify_num_vnfrs(self, num_vnfrs):
+        vnfrs = yield from self.querier.get_vnfrs()
+        self.assertEqual(num_vnfrs, len(vnfrs))
+
+    @asyncio.coroutine
+    def verify_nsd_ref_count(self, nsd_id, num_ref):
+        nsd_ref_counts = yield from self.querier.get_nsd_ref_counts(nsd_id)
+        self.assertEqual(num_ref, nsd_ref_counts[0].instance_ref_count)
+
+class DescriptorPublisher(object):
+    def __init__(self, log, loop, dts):
+        self.log = log
+        self.loop = loop
+        self.dts = dts
+
+        self._registrations = []
+
+    @asyncio.coroutine
+    def publish(self, w_path, path, desc):
+        ready_event = asyncio.Event(loop=self.loop)
+
+        @asyncio.coroutine
+        def on_ready(regh, status):
+            self.log.debug("Create element: %s, obj-type:%s obj:%s",
+                           path, type(desc), desc)
+            with self.dts.transaction() as xact:
+                regh.create_element(path, desc, xact.xact)
+            self.log.debug("Created element: %s, obj:%s", path, desc)
+            ready_event.set()
+
+        handler = rift.tasklets.DTS.RegistrationHandler(
+                on_ready=on_ready
+                )
+
+        self.log.debug("Registering path: %s, obj:%s", w_path, desc)
+        reg = yield from self.dts.register(
+                w_path,
+                handler,
+                flags=rwdts.Flag.PUBLISHER | rwdts.Flag.NO_PREP_READ
+                )
+        self._registrations.append(reg)
+        self.log.debug("Registered path : %s", w_path)
+        yield from ready_event.wait()
+
+        return reg
+
+    def unpublish_all(self):
+        self.log.debug("Deregistering all published descriptors")
+        for reg in self._registrations:
+            reg.deregister()
+
+
+class PingPongNsrConfigPublisher(object):
+    XPATH = "C,/nsr:ns-instance-config"
+
+    def __init__(self, log, loop, dts, ping_pong, cloud_account_name):
+        self.dts = dts
+        self.log = log
+        self.loop = loop
+        self.ref = None
+
+        self.querier = ManoQuerier(log, dts)
+
+        self.nsr_config = rwnsryang.YangData_Nsr_NsInstanceConfig()
+
+        nsr = rwnsryang.YangData_Nsr_NsInstanceConfig_Nsr()
+        nsr.id = str(uuid.uuid4())
+        nsr.name = "ns1.{}".format(nsr.id)
+        nsr.nsd = nsryang.YangData_Nsr_NsInstanceConfig_Nsr_Nsd()
+        nsr.nsd.from_dict(ping_pong.ping_pong_nsd.nsd.as_dict())
+        nsr.cloud_account = cloud_account_name
+
+        nsr.vnf_cloud_account_map.add().from_dict({
+            'member_vnf_index_ref': nsr.nsd.constituent_vnfd[0].member_vnf_index,
+            'config_agent_account': 'RiftCA',
+            #'cloud_account':'mock_account1'
+        })
+
+        inputs = nsryang.YangData_Nsr_NsInstanceConfig_Nsr_InputParameter()
+        inputs.xpath = "/nsd:nsd-catalog/nsd:nsd[nsd:id={}]/nsd:name".format(ping_pong.nsd_id)
+        inputs.value = "inigo montoya"
+
+        fast_cpu = {'metadata_key': 'FASTCPU', 'metadata_value': 'True'}
+        self.create_nsd_placement_group_map(nsr,
+                                            group_name      = 'Orcus',
+                                            cloud_type      = 'openstack',
+                                            construct_type  = 'host_aggregate',
+                                            construct_value = [fast_cpu])
+
+        fast_storage = {'metadata_key': 'FASTSSD', 'metadata_value': 'True'}
+        self.create_nsd_placement_group_map(nsr,
+                                            group_name      = 'Quaoar',
+                                            cloud_type      = 'openstack',
+                                            construct_type  = 'host_aggregate',
+                                            construct_value = [fast_storage])
+
+        fast_cpu = {'metadata_key': 'BLUE_HW', 'metadata_value': 'True'}
+        self.create_vnfd_placement_group_map(nsr,
+                                             group_name      = 'Eris',
+                                             vnfd_id         = ping_pong.ping_vnfd_id,
+                                             cloud_type      = 'openstack',
+                                             construct_type  = 'host_aggregate',
+                                             construct_value = [fast_cpu])
+
+        fast_storage = {'metadata_key': 'YELLOW_HW', 'metadata_value': 'True'}
+        self.create_vnfd_placement_group_map(nsr,
+                                             group_name      = 'Weywot',
+                                             vnfd_id         = ping_pong.pong_vnfd_id,
+                                             cloud_type      = 'openstack',
+                                             construct_type  = 'host_aggregate',
+                                             construct_value = [fast_storage])
+
+
+        nsr.input_parameter.append(inputs)
+
+        self._nsr = nsr
+        self.nsr_config.nsr.append(nsr)
+
+        self._ready_event = asyncio.Event(loop=self.loop)
+        asyncio.ensure_future(self.register(), loop=loop)
+
+    @asyncio.coroutine
+    def register(self):
+        @asyncio.coroutine
+        def on_ready(regh, status):
+            self._ready_event.set()
+
+        self.log.debug("Registering path: %s", PingPongNsrConfigPublisher.XPATH)
+        self.reg = yield from self.dts.register(
+                PingPongNsrConfigPublisher.XPATH,
+                flags=rwdts.Flag.PUBLISHER,
+                handler=rift.tasklets.DTS.RegistrationHandler(
+                    on_ready=on_ready,
+                    ),
+                )
+
+    @asyncio.coroutine
+    def publish(self):
+        self.log.debug("Publishing NSR: {}".format(self.nsr_config))
+        yield from self._ready_event.wait()
+        with self.dts.transaction() as xact:
+            self.reg.create_element(
+                    PingPongNsrConfigPublisher.XPATH,
+                    self.nsr_config,
+                    xact=xact.xact,
+                    )
+
+        return self._nsr.id
+
+    @asyncio.coroutine
+    def create_scale_group_instance(self, group_name, index):
+        index = 1
+        scaling_group = self.nsr_config.nsr[0].scaling_group.add()
+        scaling_group.from_dict({
+            "scaling_group_name_ref": group_name,
+            "instance": [{"index": index}],
+            })
+        with self.dts.transaction() as xact:
+            self.reg.update_element(
+                    PingPongNsrConfigPublisher.XPATH,
+                    self.nsr_config,
+                    xact=xact.xact,
+                    )
+
+        return index
+
+    def create_nsd_placement_group_map(self,
+                                       nsr,
+                                       group_name,
+                                       cloud_type,
+                                       construct_type,
+                                       construct_value):
+        placement_group  = nsr.nsd_placement_group_maps.add()
+        placement_group.from_dict({
+            "placement_group_ref" : group_name,
+            "cloud_type"          : cloud_type,
+            construct_type        : construct_value,
+            })
+        
+
+    def create_vnfd_placement_group_map(self,
+                                        nsr,
+                                        group_name,
+                                        vnfd_id,
+                                        cloud_type,
+                                        construct_type,
+                                        construct_value):
+        placement_group  = nsr.vnfd_placement_group_maps.add()
+        placement_group.from_dict({
+            "placement_group_ref"  : group_name,
+            "vnfd_id_ref"          : vnfd_id,
+            "cloud_type"           : cloud_type,
+            construct_type         : construct_value,
+            })
+        
+    
+    @asyncio.coroutine
+    def delete_scale_group_instance(self, group_name, index):
+        self.log.debug("Deleting scale group %s instance %s", group_name, index)
+        #del self.nsr_config.nsr[0].scaling_group[0].instance[0]
+        xpath = XPaths.nsr_scale_group_instance_config(self.nsr_config.nsr[0].id, group_name, index)
+        yield from self.dts.query_delete(xpath, flags=rwdts.XactFlag.ADVISE)
+        #with self.dts.transaction() as xact:
+        #    self.reg.update_element(
+        #            PingPongNsrConfigPublisher.XPATH,
+        #            self.nsr_config,
+        #            flags=rwdts.XactFlag.REPLACE,
+        #            xact=xact.xact,
+        #            )
+
+    def deregister(self):
+        if self.reg is not None:
+            self.reg.deregister()
+
+    def create_nsr_vl(self):
+        vld = self.nsr_config.nsr[0].nsd.vld.add()
+        vld.id = 'ping_pong_vld_2'
+        vld.name = 'ping_pong_vld_2'  # hard coded
+        vld.short_name = vld.name
+        vld.vendor = 'RIFT.io'
+        vld.description = 'Toy VL'
+        vld.version = '1.0'
+        vld.type_yang = 'ELAN'
+
+        # 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]
+
+        vld = self.nsr_config.nsr[0].vl_cloud_account_map.add()
+        vld.vld_id_ref = 'ping_pong_vld_2'
+        vld.cloud_accounts = ["mock_account"]
+
+    @asyncio.coroutine
+    def add_nsr_vl(self):
+        self.create_nsr_vl()
+        yield from self.querier.update_nsr_config(
+            self.nsr_config.nsr[0].id,
+            self.nsr_config.nsr[0],
+        )
+
+    @asyncio.coroutine
+    def del_nsr_vl(self):
+        for vld in self.nsr_config.nsr[0].nsd.vld:
+            if vld.id == 'ping_pong_vld_2':
+                self.nsr_config.nsr[0].nsd.vld.remove(vld)
+                break
+
+        yield from self.querier.update_nsr_config(
+            self.nsr_config.nsr[0].id,
+            self.nsr_config.nsr[0],
+        )
+
+    def update_vnf_cloud_map(self,vnf_cloud_map):
+        self.log.debug("Modifying NSR to add VNF cloud account map: {}".format(vnf_cloud_map))
+        for vnf_index,cloud_acct  in vnf_cloud_map.items():
+            vnf_maps = [vnf_map for vnf_map in self.nsr_config.nsr[0].vnf_cloud_account_map if vnf_index == vnf_map.member_vnf_index_ref]
+            if vnf_maps:
+                vnf_maps[0].cloud_account = cloud_acct
+            else: 
+                self.nsr_config.nsr[0].vnf_cloud_account_map.add().from_dict({
+                    'member_vnf_index_ref':vnf_index,
+                    'cloud_account':cloud_acct
+                    })
+
+
+class PingPongDescriptorPublisher(object):
+    def __init__(self, log, loop, dts, num_external_vlrs=1, num_internal_vlrs=1, num_ping_vms=1):
+        self.log = log
+        self.loop = loop
+        self.dts = dts
+
+        self.querier = ManoQuerier(self.log, self.dts)
+        self.publisher = DescriptorPublisher(self.log, self.loop, self.dts)
+        self.ping_vnfd, self.pong_vnfd, self.ping_pong_nsd = \
+                ping_pong_nsd.generate_ping_pong_descriptors(
+                        pingcount=1,
+                        external_vlr_count=num_external_vlrs,
+                        internal_vlr_count=num_internal_vlrs,
+                        num_vnf_vms=2,
+                        mano_ut=True,
+                        use_scale_group=True,
+                        use_mon_params=False,
+                        )
+
+        self.config_dir = os.path.join(os.getenv('RIFT_ARTIFACTS'),
+                                       "launchpad/libs",
+                                       self.ping_pong_nsd.id,
+                                       "config")
+
+    @property
+    def nsd_id(self):
+        return self.ping_pong_nsd.id
+
+    @property
+    def ping_vnfd_id(self):
+        return self.ping_vnfd.id
+
+    @property
+    def pong_vnfd_id(self):
+        return self.pong_vnfd.id
+
+    @asyncio.coroutine
+    def publish_desciptors(self):
+        # Publish ping_vnfd
+        xpath = XPaths.vnfd(self.ping_vnfd_id)
+        xpath_wild = XPaths.vnfd()
+        for obj in self.ping_vnfd.descriptor.vnfd:
+            self.log.debug("Publishing ping_vnfd path: %s - %s, type:%s, obj:%s",
+                           xpath, xpath_wild, type(obj), obj)
+            yield from self.publisher.publish(xpath_wild, xpath, obj)
+
+        # Publish pong_vnfd
+        xpath = XPaths.vnfd(self.pong_vnfd_id)
+        xpath_wild = XPaths.vnfd()
+        for obj in self.pong_vnfd.descriptor.vnfd:
+            self.log.debug("Publishing pong_vnfd path: %s, wild_path: %s, obj:%s",
+                           xpath, xpath_wild, obj)
+            yield from self.publisher.publish(xpath_wild, xpath, obj)
+
+        # Publish ping_pong_nsd
+        xpath = XPaths.nsd(self.nsd_id)
+        xpath_wild = XPaths.nsd()
+        for obj in self.ping_pong_nsd.descriptor.nsd:
+            self.log.debug("Publishing ping_pong nsd path: %s, wild_path: %s, obj:%s",
+                           xpath, xpath_wild, obj)
+            yield from self.publisher.publish(xpath_wild, xpath, obj)
+
+        self.log.debug("DONE - publish_desciptors")
+
+    def unpublish_descriptors(self):
+        self.publisher.unpublish_all()
+
+    @asyncio.coroutine
+    def delete_nsd(self):
+        yield from self.querier.delete_nsd(self.ping_pong_nsd.id)
+
+    @asyncio.coroutine
+    def delete_ping_vnfd(self):
+        yield from self.querier.delete_vnfd(self.ping_vnfd.id)
+
+    @asyncio.coroutine
+    def update_nsd(self):
+        yield from self.querier.update_nsd(
+                self.ping_pong_nsd.id,
+                self.ping_pong_nsd.descriptor.nsd[0]
+                )
+
+    @asyncio.coroutine
+    def update_ping_vnfd(self):
+        yield from self.querier.update_vnfd(
+                self.ping_vnfd.id,
+                self.ping_vnfd.descriptor.vnfd[0]
+                )
+
+
+
+
+class ManoTestCase(rift.test.dts.AbstractDTSTest):
+    """
+    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.
+    """
+
+    @classmethod
+    def configure_suite(cls, rwmain):
+        vns_dir = os.environ.get('VNS_DIR')
+        vnfm_dir = os.environ.get('VNFM_DIR')
+        nsm_dir = os.environ.get('NSM_DIR')
+        rm_dir = os.environ.get('RM_DIR')
+
+        rwmain.add_tasklet(vns_dir, 'rwvnstasklet')
+        rwmain.add_tasklet(vnfm_dir, 'rwvnfmtasklet')
+        rwmain.add_tasklet(nsm_dir, 'rwnsmtasklet')
+        rwmain.add_tasklet(rm_dir, 'rwresmgrtasklet')
+        rwmain.add_tasklet(rm_dir, 'rwconmantasklet')
+
+    @classmethod
+    def configure_schema(cls):
+        return rwnsmyang.get_schema()
+
+    @classmethod
+    def configure_timeout(cls):
+        return 240
+
+    @staticmethod
+    def get_cal_account(account_type, account_name):
+        """
+        Creates an object for class RwcalYang.Clo
+        """
+        account = rwcloudyang.CloudAccount()
+        if account_type == 'mock':
+            account.name          = account_name
+            account.account_type  = "mock"
+            account.mock.username = "mock_user"
+        elif ((account_type == 'openstack_static') or (account_type == 'openstack_dynamic')):
+            account.name = account_name
+            account.account_type = 'openstack'
+            account.openstack.key = openstack_info['username']
+            account.openstack.secret       = openstack_info['password']
+            account.openstack.auth_url     = openstack_info['auth_url']
+            account.openstack.tenant       = openstack_info['project_name']
+            account.openstack.mgmt_network = openstack_info['mgmt_network']
+        return account
+
+    @asyncio.coroutine
+    def configure_cloud_account(self, dts, cloud_type, cloud_name="cloud1"):
+        account = self.get_cal_account(cloud_type, cloud_name)
+        account_xpath = "C,/rw-cloud:cloud/rw-cloud:account[rw-cloud:name='{}']".format(cloud_name)
+        self.log.info("Configuring cloud-account: %s", account)
+        yield from dts.query_create(account_xpath,
+                                    rwdts.XactFlag.ADVISE,
+                                    account)
+
+    @asyncio.coroutine
+    def wait_tasklets(self):
+        yield from asyncio.sleep(5, loop=self.loop)
+
+    def configure_test(self, loop, test_id):
+        self.log.debug("STARTING - %s", self.id())
+        self.tinfo = self.new_tinfo(self.id())
+        self.dts = rift.tasklets.DTS(self.tinfo, self.schema, self.loop)
+        self.ping_pong = PingPongDescriptorPublisher(self.log, self.loop, self.dts)
+        self.querier = ManoQuerier(self.log, self.dts)
+        self.nsr_publisher = PingPongNsrConfigPublisher(
+                self.log,
+                loop,
+                self.dts,
+                self.ping_pong,
+                "mock_account",
+                )
+
+    def test_create_nsr_record(self):
+
+        @asyncio.coroutine
+        def verify_cm_state(termination=False, nsrid=None):
+            self.log.debug("Verifying cm_state path = %s", XPaths.cm_state(nsrid))
+            #print("###>>> Verifying cm_state path:", XPaths.cm_state(nsrid))
+
+            loop_count = 10
+            loop_sleep = 10
+            while loop_count:
+                yield from asyncio.sleep(loop_sleep, loop=self.loop)
+                loop_count -= 1
+                cm_nsr = None
+                cm_nsr_i = yield from self.querier.get_cm_state(nsr_id=nsrid)
+                if (cm_nsr_i is not None and len(cm_nsr_i) != 0):
+                    self.assertEqual(1, len(cm_nsr_i))
+                    cm_nsr = cm_nsr_i[0].as_dict()
+                    #print("###>>> cm_nsr=", cm_nsr)
+                if termination:
+                    if len(cm_nsr_i) == 0:
+                        print("\n###>>> cm-state NSR deleted OK <<<###\n")
+                        return
+                elif (cm_nsr is not None and
+                    'state' in cm_nsr and
+                    (cm_nsr['state'] == 'ready')):
+                    self.log.debug("Got cm_nsr record %s", cm_nsr)
+                    print("\n###>>> cm-state NSR 'ready' OK <<<###\n")
+                    return
+
+                # if (len(cm_nsr_i) == 1 and cm_nsr_i[0].state == 'ready'):
+                #     self.log.debug("Got cm_nsr record %s", cm_nsr)
+                # else:
+                #     yield from asyncio.sleep(10, loop=self.loop)
+
+            print("###>>> Failed cm-state, termination:", termination)
+            self.assertEqual(1, loop_count)
+
+        @asyncio.coroutine
+        def verify_nsr_opdata(termination=False):
+            self.log.debug("Verifying nsr opdata path = %s", XPaths.nsr_opdata())
+
+            while True:
+                nsrs = yield from self.querier.get_nsr_opdatas()
+                if termination:
+                    if len(nsrs) != 0:
+                        for i in range(10):
+                            nsrs = yield from self.querier.get_nsr_opdatas()
+                            if len(nsrs) == 0:
+                                self.log.debug("No active NSR records found. NSR termination successful")
+                                return
+                        else:
+                            self.assertEqual(0, len(nsrs))
+                            self.log.error("Active NSR records found. NSR termination failed")
+
+                    else:
+                        self.log.debug("No active NSR records found. NSR termination successful")
+                        self.assertEqual(0, len(nsrs))
+                        return
+
+                nsr = nsrs[0]
+                self.log.debug("Got nsr record %s", nsr)
+                if nsr.operational_status == 'running':
+                    self.log.debug("!!! Rcvd NSR with running status !!!")
+                    self.assertEqual("configuring", nsr.config_status)
+                    break
+
+                self.log.debug("Rcvd NSR with %s status", nsr.operational_status)
+                self.log.debug("Sleeping for 10 seconds")
+                yield from asyncio.sleep(10, loop=self.loop)
+
+        @asyncio.coroutine
+        def verify_nsr_config(termination=False):
+            self.log.debug("Verifying nsr config path = %s", XPaths.nsr_config())
+
+            nsr_configs = yield from self.querier.get_nsr_configs()
+            self.assertEqual(1, len(nsr_configs))
+
+            nsr_config = nsr_configs[0]
+            self.assertEqual(
+                    "/nsd:nsd-catalog/nsd:nsd[nsd:id={}]/nsd:name".format(self.ping_pong.nsd_id),
+                    nsr_config.input_parameter[0].xpath,
+                    )
+
+        @asyncio.coroutine
+        def verify_nsr_config_status(termination=False, nsrid=None):
+            if termination is False and nsrid is not None:
+                self.log.debug("Verifying nsr config status path = %s", XPaths.nsr_opdata(nsrid))
+
+                loop_count = 6
+                loop_sleep = 10
+                while loop_count:
+                    loop_count -= 1
+                    yield from asyncio.sleep(loop_sleep, loop=self.loop)
+                    nsr_opdata_l = yield from self.querier.get_nsr_opdatas(nsrid)
+                    self.assertEqual(1, len(nsr_opdata_l))
+                    nsr_opdata = nsr_opdata_l[0].as_dict()
+                    if ("configured" == nsr_opdata['config_status']):
+                        print("\n###>>> NSR Config Status 'configured' OK <<<###\n")
+                        return
+                self.assertEqual("configured", nsr_opdata['config_status'])
+
+        @asyncio.coroutine
+        def verify_vnfr_record(termination=False):
+            self.log.debug("Verifying vnfr record path = %s, Termination=%d",
+                           XPaths.vnfr(), termination)
+            if termination:
+                for i in range(10):
+                    vnfrs = yield from self.querier.get_vnfrs()
+                    if len(vnfrs) == 0:
+                        return True
+
+                    for vnfr in vnfrs:
+                        self.log.debug("VNFR still exists = %s", vnfr)
+
+                    yield from asyncio.sleep(.5, loop=self.loop)
+
+
+                assert len(vnfrs) == 0
+
+            while True:
+                vnfrs = yield from self.querier.get_vnfrs()
+                if len(vnfrs) != 0 and termination is False:
+                    vnfr = vnfrs[0]
+                    self.log.debug("Rcvd VNFR with %s status", vnfr.operational_status)
+                    if vnfr.operational_status == 'running':
+                        self.log.debug("!!! Rcvd VNFR with running status !!!")
+                        return True
+
+                    elif vnfr.operational_status == "failed":
+                        self.log.debug("!!! Rcvd VNFR with failed status !!!")
+                        return False
+
+                self.log.debug("Sleeping for 10 seconds")
+                yield from asyncio.sleep(10, loop=self.loop)
+
+
+        @asyncio.coroutine
+        def verify_vnfr_cloud_account(vnf_index, cloud_account):
+            self.log.debug("Verifying vnfr record Cloud account for vnf index = %d is %s", vnf_index,cloud_account)
+            vnfrs = yield from self.querier.get_vnfrs()
+            cloud_accounts = [vnfr.cloud_account for vnfr in vnfrs if vnfr.member_vnf_index_ref == vnf_index]
+            self.log.debug("VNFR cloud account for index %d is %s", vnf_index,cloud_accounts[0])
+            assert cloud_accounts[0] == cloud_account
+
+        @asyncio.coroutine
+        def verify_vlr_record(termination=False):
+            vlr_xpath = XPaths.vlr()
+            self.log.debug("Verifying vlr record path = %s, termination: %s",
+                           vlr_xpath, termination)
+            res_iter = yield from self.dts.query_read(vlr_xpath)
+
+            for i in res_iter:
+                result = yield from i
+                if termination:
+                    self.assertIsNone(result)
+
+                self.log.debug("Got vlr record %s", result)
+
+        @asyncio.coroutine
+        def verify_vlrs(nsr_id, count=0):
+            while True:
+                nsrs = yield from self.querier.get_nsr_opdatas()
+                nsr = nsrs[0]
+                self.log.debug("Got nsr record %s", nsr)
+                if nsr.operational_status == 'running':
+                    self.log.debug("!!! Rcvd NSR with running status !!!")
+                    # Check the VLR count
+                    if (len(nsr.vlr)) == count:
+                        self.log.debug("NSR %s has %d VLRs", nsr_id, count)
+                        break
+
+                self.log.debug("Rcvd NSR %s with %s status", nsr_id, nsr.operational_status)
+                self.log.debug("Sleeping for 10 seconds")
+                yield from asyncio.sleep(10, loop=self.loop)
+
+        @asyncio.coroutine
+        def verify_nsd_ref_count(termination):
+            self.log.debug("Verifying nsd ref count= %s", XPaths.nsd_ref_count())
+            res_iter = yield from self.dts.query_read(XPaths.nsd_ref_count())
+
+            for i in res_iter:
+                result = yield from i
+                self.log.debug("Got nsd ref count record %s", result)
+
+        @asyncio.coroutine
+        def verify_vnfd_ref_count(termination):
+            self.log.debug("Verifying vnfd ref count= %s", XPaths.vnfd_ref_count())
+            res_iter = yield from self.dts.query_read(XPaths.vnfd_ref_count())
+
+            for i in res_iter:
+                result = yield from i
+                self.log.debug("Got vnfd ref count record %s", result)
+
+        @asyncio.coroutine
+        def verify_scale_group_reaches_state(nsr_id, scale_group, index, state, timeout=1000):
+            start_time = time.time()
+            instance_state = None
+            while (time.time() - start_time) < timeout:
+                results = yield from self.querier.get_nsr_opdatas(nsr_id=nsr_id)
+                if len(results) == 1:
+                    result = results[0]
+                    if len(result.scaling_group_record) == 0:
+                        continue
+
+                    if len(result.scaling_group_record[0].instance) == 0:
+                        continue
+
+                    instance = result.scaling_group_record[0].instance[0]
+                    self.assertEqual(instance.scaling_group_index_ref, index)
+
+                    instance_state = instance.op_status
+                    if instance_state == state:
+                        self.log.debug("Scale group instance reached %s state", state)
+                        return
+
+                yield from asyncio.sleep(1, loop=self.loop)
+
+            self.assertEqual(state, instance_state)
+
+        @asyncio.coroutine
+        def verify_results(termination=False, nsrid=None):
+            yield from verify_vnfr_record(termination)
+            #yield from verify_vlr_record(termination)
+            yield from verify_nsr_opdata(termination)
+            yield from verify_nsr_config(termination)
+            yield from verify_nsd_ref_count(termination)
+            yield from verify_vnfd_ref_count(termination)
+
+            # Config Manager
+            yield from verify_cm_state(termination, nsrid)
+            yield from verify_nsr_config_status(termination, nsrid)
+
+        @asyncio.coroutine
+        def verify_scale_instance(index):
+            self.log.debug("Verifying scale record path = %s, Termination=%d",
+                           XPaths.vnfr(), termination)
+            if termination:
+                for i in range(5):
+                    vnfrs = yield from self.querier.get_vnfrs()
+                    if len(vnfrs) == 0:
+                        return True
+
+                    for vnfr in vnfrs:
+                        self.log.debug("VNFR still exists = %s", vnfr)
+
+
+                assert len(vnfrs) == 0
+
+            while True:
+                vnfrs = yield from self.querier.get_vnfrs()
+                if len(vnfrs) != 0 and termination is False:
+                    vnfr = vnfrs[0]
+                    self.log.debug("Rcvd VNFR with %s status", vnfr.operational_status)
+                    if vnfr.operational_status == 'running':
+                        self.log.debug("!!! Rcvd VNFR with running status !!!")
+                        return True
+
+                    elif vnfr.operational_status == "failed":
+                        self.log.debug("!!! Rcvd VNFR with failed status !!!")
+                        return False
+
+                self.log.debug("Sleeping for 10 seconds")
+                yield from asyncio.sleep(10, loop=self.loop)
+
+        @asyncio.coroutine
+        def terminate_ns(nsr_id):
+            xpath = XPaths.nsr_config(nsr_id)
+            self.log.debug("Terminating network service with path %s", xpath)
+            yield from self.dts.query_delete(xpath, flags=rwdts.XactFlag.ADVISE)
+            self.log.debug("Terminated network service with path %s", xpath)
+
+        @asyncio.coroutine
+        def run_test():
+            yield from self.wait_tasklets()
+
+
+            cloud_type = "mock"
+            yield from self.configure_cloud_account(self.dts, cloud_type, "mock_account")
+            yield from self.configure_cloud_account(self.dts, cloud_type, "mock_account1")
+
+            yield from self.ping_pong.publish_desciptors()
+
+            # Attempt deleting VNFD not in use
+            yield from self.ping_pong.update_ping_vnfd()
+
+            # Attempt updating NSD not in use
+            yield from self.ping_pong.update_nsd()
+
+            # Attempt deleting VNFD not in use
+            yield from self.ping_pong.delete_ping_vnfd()
+
+            # Attempt deleting NSD not in use
+            yield from self.ping_pong.delete_nsd()
+
+            yield from self.ping_pong.publish_desciptors()
+
+            nsr_id = yield from self.nsr_publisher.publish()
+
+            yield from verify_results(nsrid=nsr_id)
+
+            # yield from self.nsr_publisher.create_scale_group_instance("ping_group", 1)
+
+            # yield from verify_scale_group_reaches_state(nsr_id, "ping_group", 1, "running")
+
+            # yield from self.nsr_publisher.delete_scale_group_instance("ping_group", 1)
+
+            yield from asyncio.sleep(10, loop=self.loop)
+
+            # Attempt deleting VNFD in use
+            yield from self.ping_pong.delete_ping_vnfd()
+
+            # Attempt updating NSD in use
+            yield from self.ping_pong.update_nsd()
+
+            # Update NSD in use with new VL
+            yield from self.nsr_publisher.add_nsr_vl()
+
+            # Verify the new VL has been added
+            yield from verify_vlrs(nsr_id, count=2)
+
+            # Delete the added VL
+            yield from self.nsr_publisher.del_nsr_vl()
+
+            # Verify the new VL has been added
+            yield from verify_vlrs(nsr_id, count=1)
+
+            # Attempt deleting NSD in use
+            yield from self.ping_pong.delete_nsd()
+
+            yield from terminate_ns(nsr_id)
+
+            yield from asyncio.sleep(25, loop=self.loop)
+            self.log.debug("Verifying termination results")
+            yield from verify_results(termination=True, nsrid=nsr_id)
+            self.log.debug("Verified termination results")
+
+            # Multi site NS case
+            self.log.debug("Testing multi site NS")
+            self.nsr_publisher.update_vnf_cloud_map({1:"mock_account1",2:"mock_account"})
+            nsr_id = yield from self.nsr_publisher.publish()
+
+            yield from verify_results(nsrid=nsr_id)
+            yield from verify_vnfr_cloud_account(1,"mock_account1")
+            yield from verify_vnfr_cloud_account(2,"mock_account")
+            yield from verify_vlrs(nsr_id, count=2)
+
+            yield from terminate_ns(nsr_id)
+
+            yield from asyncio.sleep(25, loop=self.loop)
+            self.log.debug("Verifying termination results for multi site NS")
+            yield from verify_results(termination=True, nsrid=nsr_id)
+            self.log.debug("Verified termination results for multi site NS")
+
+            self.log.debug("Attempting to delete VNFD for real")
+            yield from self.ping_pong.delete_ping_vnfd()
+
+            self.log.debug("Attempting to delete NSD for real")
+            yield from self.ping_pong.delete_nsd()
+
+        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()
+
+
+def main():
+    plugin_dir = os.path.join(os.environ["RIFT_INSTALL"], "usr/lib/rift/plugins")
+    if 'VNS_DIR' not in os.environ:
+        os.environ['VNS_DIR'] = os.path.join(plugin_dir, 'rwvns')
+
+    if 'VNFM_DIR' not in os.environ:
+        os.environ['VNFM_DIR'] = os.path.join(plugin_dir, 'rwvnfm')
+
+    if 'NSM_DIR' not in os.environ:
+        os.environ['NSM_DIR'] = os.path.join(plugin_dir, 'rwnsm')
+
+    if 'RM_DIR' not in os.environ:
+        os.environ['RM_DIR'] = os.path.join(plugin_dir, 'rwresmgrtasklet')
+
+    runner = xmlrunner.XMLTestRunner(output=os.environ["RIFT_MODULE_TEST"])
+
+    parser = argparse.ArgumentParser()
+    parser.add_argument('-v', '--verbose', action='store_true')
+    parser.add_argument('-n', '--no-runner', action='store_true')
+    args, unittest_args = parser.parse_known_args()
+    if args.no_runner:
+        runner = None
+
+    ManoTestCase.log_level = logging.DEBUG if args.verbose else logging.WARN
+
+    unittest.main(testRunner=runner, argv=[sys.argv[0]] + unittest_args)
+
+if __name__ == '__main__':
+    main()
+
+# vim: sw=4