Merge "Provide API for allowing full stack emulation"
authorpeusterm <manuel.peuster@uni-paderborn.de>
Mon, 18 Mar 2019 13:47:19 +0000 (14:47 +0100)
committerGerrit Code Review <root@osm.etsi.org>
Mon, 18 Mar 2019 13:47:19 +0000 (14:47 +0100)
18 files changed:
examples/full_stack_emulation_complex.py [new file with mode: 0755]
examples/full_stack_emulation_multiple_osm.py [new file with mode: 0755]
examples/full_stack_emulation_simple.py [new file with mode: 0755]
src/emuvim/api/osm/__init__.py [new file with mode: 0644]
src/emuvim/api/osm/kafka.py [new file with mode: 0644]
src/emuvim/api/osm/lcm.py [new file with mode: 0644]
src/emuvim/api/osm/mongo.py [new file with mode: 0644]
src/emuvim/api/osm/mysql.py [new file with mode: 0644]
src/emuvim/api/osm/nbi.py [new file with mode: 0644]
src/emuvim/api/osm/osm.py [new file with mode: 0644]
src/emuvim/api/osm/osm_component_base.py [new file with mode: 0644]
src/emuvim/api/osm/pre_configured_osm.py [new file with mode: 0644]
src/emuvim/api/osm/ro.py [new file with mode: 0644]
src/emuvim/api/osm/zookeeper.py [new file with mode: 0644]
src/emuvim/api/util/__init__.py [new file with mode: 0644]
src/emuvim/api/util/docker_utils.py [new file with mode: 0644]
src/emuvim/api/util/path_utils.py [new file with mode: 0644]
src/emuvim/api/util/process_utils.py [new file with mode: 0644]

diff --git a/examples/full_stack_emulation_complex.py b/examples/full_stack_emulation_complex.py
new file mode 100755 (executable)
index 0000000..011c1a4
--- /dev/null
@@ -0,0 +1,100 @@
+#!/usr/bin/env python2
+# Copyright (c) 2019 Erik Schilling
+# ALL RIGHTS RESERVED.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from emuvim.api.openstack.openstack_api_endpoint import OpenstackApiEndpoint
+from emuvim.api.osm.kafka import Kafka
+from emuvim.api.osm.lcm import LCM
+from emuvim.api.osm.mongo import Mongo
+from emuvim.api.osm.mysql import Mysql
+from emuvim.api.osm.nbi import NBI
+from emuvim.api.osm.ro import RO
+from emuvim.api.osm.zookeeper import Zookeeper
+from emuvim.dcemulator.net import DCNetwork
+
+
+from mininet.log import setLogLevel
+setLogLevel('debug')
+
+net = DCNetwork(monitor=False, enable_learning=True)
+api = None
+try:
+    dc1 = net.addDatacenter("dc1")
+    api = OpenstackApiEndpoint("0.0.0.0", 6001)
+    api.connect_datacenter(dc1)
+    api.connect_dc_network(net)
+
+    s1 = net.addSwitch('s1')
+
+    zookeeper_ip = '10.0.0.96'
+    kafka_ip = '10.0.0.97'
+    mongo_ip = '10.0.0.98'
+    nbi_ip = '10.0.0.99'
+    ro_db_ip = '10.0.0.100'
+    ro_ip = '10.0.0.101'
+    lcm_ip = '10.0.0.102'
+
+    d1 = net.addDocker('d1', dimage='ubuntu:trusty')
+
+    VERSION = 'releasefive-daily'
+
+    zookeeper = Zookeeper(net, zookeeper_ip)
+    kafka = Kafka(net, kafka_ip, zookeeper_ip)
+    mongo = Mongo(net, mongo_ip)
+    nbi = NBI(net, nbi_ip, mongo_ip, kafka_ip)
+    ro_db = Mysql(net, ro_db_ip)
+    ro = RO(net, ro_ip, ro_db_ip, version=VERSION)
+    lcm = LCM(net, lcm_ip, ro_ip, mongo_ip, kafka_ip)
+
+    net.addLink(d1, s1)
+    net.addLink(zookeeper.instance, s1)
+    net.addLink(kafka.instance, s1)
+    net.addLink(mongo.instance, s1)
+    net.addLink(nbi.instance, s1)
+    net.addLink(ro_db.instance, s1)
+    net.addLink(ro.instance, s1)
+    net.addLink(lcm.instance, s1)
+
+    net.start()
+    api.start()
+
+    zookeeper.start()
+    kafka.start()
+    mongo.start()
+    nbi.start()
+    ro_db.start()
+    ro.start()
+    lcm.start()
+    vim_id = nbi.register_emulated_api('emu-vim1', api)
+
+    net.ping([d1, zookeeper.instance])
+    net.ping([d1, kafka.instance])
+    net.ping([d1, mongo.instance])
+    net.ping([d1, nbi.instance])
+    net.ping([d1, ro.instance])
+    net.ping([d1, ro_db.instance])
+    net.ping([d1, lcm.instance])
+
+    nbi.onboard_vnfd('vnfs/ping_vnf')
+    nbi.onboard_vnfd('vnfs/pong_vnf')
+    nsd_id = nbi.onboard_nsd('services/pingpong_ns')
+    ns_id = nbi.ns_create('pingpong-test', nsd_id=nsd_id, vim_id=vim_id)
+
+    nbi.ns_wait_until_all_in_status('running')
+    nbi.ns_delete(ns_id)
+    nbi.ns_wait_until_all_in_status('terminated')
+finally:
+    net.stop()
+    api.stop()
diff --git a/examples/full_stack_emulation_multiple_osm.py b/examples/full_stack_emulation_multiple_osm.py
new file mode 100755 (executable)
index 0000000..bd3927d
--- /dev/null
@@ -0,0 +1,66 @@
+#!/usr/bin/env python2
+# Copyright (c) 2019 Erik Schilling
+# ALL RIGHTS RESERVED.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from emuvim.api.openstack.openstack_api_endpoint import OpenstackApiEndpoint
+from emuvim.api.osm.osm import OSM
+from emuvim.dcemulator.net import DCNetwork
+
+net = DCNetwork(monitor=False, enable_learning=True)
+dc1 = net.addDatacenter("dc1")
+api = OpenstackApiEndpoint("0.0.0.0", 6001)
+api.connect_datacenter(dc1)
+api.connect_dc_network(net)
+
+try:
+    s1 = net.addSwitch('s1')
+    s2 = net.addSwitch('s2')
+    osm1 = OSM(net, s1, name='1')
+    osm2 = OSM(net, s2, name='2')
+
+    net.start()
+    api.start()
+    osm1.start()
+    print('osm1 up!')
+    osm2.start()
+    print('osm2 up!')
+
+    vim_id1 = osm1.register_emulated_api('vim', api)
+    osm1.onboard_vnfd('vnfs/ping_vnf')
+    osm1.onboard_vnfd('vnfs/pong_vnf')
+    nsd_id1 = osm1.onboard_nsd('services/pingpong_ns')
+    ns_id1 = osm1.ns_create('pingpong-test1', nsd_id1, vim_id1)
+
+    vim_id2 = osm2.register_emulated_api('vim', api)
+    osm2.onboard_vnfd('vnfs/ping_vnf')
+    osm2.onboard_vnfd('vnfs/pong_vnf')
+    nsd_id2 = osm2.onboard_nsd('services/pingpong_ns')
+    ns_id2 = osm2.ns_create('pingpong-test2', nsd_id2, vim_id2)
+
+    osm1.ns_wait_until_all_in_status('running')
+    osm2.ns_wait_until_all_in_status('running')
+    print('all ready!')
+
+    osm1.ns_delete(ns_id1)
+    osm2.ns_delete(ns_id2)
+
+    osm1.ns_wait_until_all_in_status('terminated')
+    osm2.ns_wait_until_all_in_status('terminated')
+    print('all deleted!')
+
+finally:
+    api.stop()
+    net.stop()
diff --git a/examples/full_stack_emulation_simple.py b/examples/full_stack_emulation_simple.py
new file mode 100755 (executable)
index 0000000..d477a57
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/env python2
+# Copyright (c) 2019 Erik Schilling
+# ALL RIGHTS RESERVED.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from emuvim.api.osm.pre_configured_osm import PreConfiguredOSM
+
+from mininet.log import setLogLevel
+setLogLevel('debug')
+
+with PreConfiguredOSM() as osm:
+    osm.onboard_vnfd('vnfs/ping_vnf')
+    osm.onboard_vnfd('vnfs/pong_vnf')
+    nsd_id = osm.onboard_nsd('services/pingpong_ns')
+    ns_id = osm.ns_create('pingpong-test', nsd_id)
+
+    osm.ns_wait_until_all_in_status('running')
+    osm.ns_delete(ns_id)
+    osm.ns_wait_until_all_in_status('terminated')
diff --git a/src/emuvim/api/osm/__init__.py b/src/emuvim/api/osm/__init__.py
new file mode 100644 (file)
index 0000000..97bc953
--- /dev/null
@@ -0,0 +1,15 @@
+#!/usr/bin/env python2
+# Copyright (c) 2019 Erik Schilling
+# ALL RIGHTS RESERVED.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/src/emuvim/api/osm/kafka.py b/src/emuvim/api/osm/kafka.py
new file mode 100644 (file)
index 0000000..4443621
--- /dev/null
@@ -0,0 +1,34 @@
+#!/usr/bin/env python2
+# Copyright (c) 2019 Erik Schilling
+# ALL RIGHTS RESERVED.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from emuvim.api.osm.osm_component_base import OSMComponentBase
+from emuvim.api.util.process_utils import wait_until
+
+
+class Kafka(OSMComponentBase):
+    def __init__(self, net, ip, zookeeper_ip, version='latest', name_prefix=''):
+        OSMComponentBase.__init__(self)
+        self.instance = net.addDocker(
+            '{}kafka'.format(name_prefix), ip=ip, dimage='wurstmeister/kafka:%s' % version,
+            environment={'KAFKA_ADVERTISED_HOST_NAME': ip,
+                         'KAFKA_ADVERTISED_PORT': '9092',
+                         'KAFKA_ZOOKEEPER_CONNECT': '%s:2181' % zookeeper_ip,
+                         'KAFKA_CREATE_TOPICS': 'admin:1:1,ns:1:1,vim_account:1:1,wim_account:1:1,sdn:1:1,nsi:1:1'
+                         })
+
+    def start(self):
+        OSMComponentBase.start(self)
+        wait_until('nc -z %s 9092' % self.instance.dcinfo['NetworkSettings']['IPAddress'])
diff --git a/src/emuvim/api/osm/lcm.py b/src/emuvim/api/osm/lcm.py
new file mode 100644 (file)
index 0000000..dd5f6e5
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/env python2
+# Copyright (c) 2019 Erik Schilling
+# ALL RIGHTS RESERVED.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+
+import time
+
+from emuvim.api.osm.osm_component_base import OSMComponentBase
+from emuvim.api.util.docker_utils import wrap_debian_like
+
+
+class LCM(OSMComponentBase):
+    def __init__(self, net, ip, ro_ip, mongo_ip, kafka_ip,
+                 vca_host=os.environ.get('VCA_HOST'),
+                 vca_secret=os.environ.get('VCA_SECRET'),
+                 version='latest',
+                 name_prefix=''):
+        OSMComponentBase.__init__(self)
+        self.instance = net.addDocker(
+            '{}lcm'.format(name_prefix), ip=ip, dimage=wrap_debian_like('opensourcemano/lcm:%s' % version),
+            volumes=['osm_packages:/app/storage'],
+            environment={
+                'OSMLCM_RO_HOST': ro_ip,
+                'OSMLCM_VCA_HOST': vca_host,
+                'OSMLCM_VCA_SECRET': vca_secret,
+                'OSMLCM_DATABASE_URI': 'mongodb://%s:27017' % mongo_ip,
+                'OSMLCM_MESSAGE_HOST': kafka_ip,
+            })
+
+    def start(self):
+        OSMComponentBase.start(self)
+        time.sleep(3)
diff --git a/src/emuvim/api/osm/mongo.py b/src/emuvim/api/osm/mongo.py
new file mode 100644 (file)
index 0000000..7e10683
--- /dev/null
@@ -0,0 +1,24 @@
+#!/usr/bin/env python2
+# Copyright (c) 2019 Erik Schilling
+# ALL RIGHTS RESERVED.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from emuvim.api.osm.osm_component_base import OSMComponentBase
+from emuvim.api.util.docker_utils import wrap_debian_like
+
+
+class Mongo(OSMComponentBase):
+    def __init__(self, net, ip, version='latest', name_prefix=''):
+        OSMComponentBase.__init__(self)
+        self.instance = net.addDocker('{}mongo'.format(name_prefix), ip=ip, dimage=wrap_debian_like('mongo:%s' % version))
diff --git a/src/emuvim/api/osm/mysql.py b/src/emuvim/api/osm/mysql.py
new file mode 100644 (file)
index 0000000..80f187a
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/env python2
+# Copyright (c) 2019 Erik Schilling
+# ALL RIGHTS RESERVED.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from emuvim.api.osm.osm_component_base import OSMComponentBase
+from emuvim.api.util.docker_utils import wrap_debian_like
+
+
+class Mysql(OSMComponentBase):
+    def __init__(self, net, ip, version='5', name_prefix=''):
+        OSMComponentBase.__init__(self)
+        self.instance = net.addDocker(
+            '{}ro-db'.format(name_prefix), ip=ip, dimage=wrap_debian_like('mysql:%s' % version),
+            environment={'MYSQL_ROOT_PASSWORD': 'TEST'})
diff --git a/src/emuvim/api/osm/nbi.py b/src/emuvim/api/osm/nbi.py
new file mode 100644 (file)
index 0000000..0c5c914
--- /dev/null
@@ -0,0 +1,189 @@
+#!/usr/bin/env python2
+# Copyright (c) 2019 Erik Schilling
+# ALL RIGHTS RESERVED.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import logging
+import os
+import subprocess
+
+import requests
+import time
+from pipes import quote
+from tempfile import NamedTemporaryFile
+
+import urllib3
+import yaml
+
+from emuvim.api.osm.osm_component_base import OSMComponentBase
+from emuvim.api.util.docker_utils import wrap_debian_like, DOCKER_HOST_IP
+from emuvim.api.util.path_utils import get_absolute_path
+from emuvim.api.util.process_utils import wait_until
+from mininet.log import debug
+
+# disables warnings about verify=False for TLS
+# since NBI runs with self-signed certificates this otherwise spams the log
+urllib3.disable_warnings()
+
+LOG = logging.getLogger(__name__)
+
+
+class NBI(OSMComponentBase):
+
+    def __init__(self, net, ip, mongo_ip, kafka_ip, version='latest', name_prefix=''):
+        OSMComponentBase.__init__(self)
+        self.instance = net.addDocker(
+            '{}nbi'.format(name_prefix), ip=ip, dimage=wrap_debian_like('opensourcemano/nbi:%s' % version),
+            volumes=['osm_packages:/app/storage'],
+            environment={'OSMNBI_DATABASE_URI': 'mongodb://%s:27017' % mongo_ip,
+                         'OSMNBI_MESSAGE_HOST': kafka_ip})
+        self._ip = self.instance.dcinfo['NetworkSettings']['IPAddress']
+
+    def start(self):
+        OSMComponentBase.start(self)
+        wait_until('nc -z %s 9999' % self._ip)
+
+    def _osm(self, command):
+        prefixed_command = 'osm %s' % command
+        debug('executing command: %s\n' % prefixed_command)
+        output = subprocess.check_output(prefixed_command, env={'OSM_HOSTNAME': self._ip}, shell=True)
+        debug('output: \n%s\n' % output)
+        return output
+
+    @staticmethod
+    def _create_archive(folder, tmp_file):
+        folder = get_absolute_path(folder)
+        parent_folder = os.path.dirname(folder)
+        basename = os.path.basename(folder)
+        subprocess.call('tar czf %s %s' % (quote(tmp_file.name), quote(basename)), cwd=parent_folder, shell=True)
+
+    def onboard_vnfd(self, folder):
+        try:
+            with NamedTemporaryFile() as tmp_archive:
+                self._create_archive(folder, tmp_archive)
+                return self._osm('vnfd-create %s' % quote(tmp_archive.name)).strip()
+        except subprocess.CalledProcessError as e:
+            raise RuntimeError('creating vnfd failed: %s' % e.output)
+
+    def onboard_nsd(self, folder):
+        try:
+            with NamedTemporaryFile() as tmp_archive:
+                self._create_archive(folder, tmp_archive)
+                return self._osm('nsd-create %s' % quote(tmp_archive.name)).strip()
+        except subprocess.CalledProcessError as e:
+            raise RuntimeError('creating nsd failed: %s' % e.output)
+
+    def _get_api_token(self):
+        token_request = requests.post('https://%s:9999/osm/admin/v1/tokens' % self._ip,
+                                      data={'username': 'admin', 'password': 'admin'}, verify=False)
+        if not token_request.ok:
+            raise RuntimeError('getting token failed with: %s' % token_request.text)
+        token = yaml.safe_load(token_request.text)
+        return token['id']
+
+    def _api_request_args(self):
+        return {'headers': {'Authorization': 'Bearer %s' % self._get_api_token()}, 'verify': False}
+
+    def _api_post_request(self, endpoint, data):
+        r = requests.post('https://%s:9999/%s' % (self._ip, endpoint),
+                          json=data,
+                          **self._api_request_args())
+        if not r.ok:
+            raise RuntimeError('POST request failed with: %s' % r.text)
+        result = yaml.safe_load(r.text)
+        return result
+
+    def _api_get_request(self, endpoint):
+        r = requests.get('https://%s:9999/%s' % (self._ip, endpoint),
+                         **self._api_request_args())
+        if not r.ok:
+            raise RuntimeError('GET request failed with: %s' % r.text)
+        result = yaml.safe_load(r.text)
+        return result
+
+    def _api_delete_request(self, endpoint):
+        r = requests.delete('https://%s:9999/%s' % (self._ip, endpoint),
+                            **self._api_request_args())
+        if not r.ok:
+            raise RuntimeError('DELETE request failed with: %s' % r.text)
+
+    def register_emulated_api(self, name, api):
+        output = self._osm('vim-create --name %s '
+                           '--user username '
+                           '--password password '
+                           '--auth_url http://%s:%d/v2.0 '
+                           '--tenant tenantName '
+                           '--account_type openstack' % (
+                               quote(name),
+                               quote(DOCKER_HOST_IP),
+                               api.port
+                           ))
+        vim_id = output.strip()
+        while self._api_get_request('osm/admin/v1/vim_accounts/%s' % vim_id)['_admin']['detailed-status'] != 'Done':
+            time.sleep(1)
+        return vim_id
+
+    def ns_create(self, ns_name, nsd_id, vim_id):
+        result = self._api_post_request('osm/nslcm/v1/ns_instances_content', {
+            'nsdId': nsd_id,
+            'nsName': ns_name,
+            # dummy text since this cannot be empty
+            'nsDescription': 'created by vim-emu',
+            'vimAccountId': vim_id,
+        })
+        return result['id']
+
+    def ns_get(self, ns_id):
+        return self._api_get_request('osm/nslcm/v1/ns_instances_content/%s' % ns_id)
+
+    def ns_action(self, ns_id, vnf_member_index, action, args=None):
+        if args is None:
+            args = {}
+        result = self._api_post_request('osm/nslcm/v1/ns_instances/%s/action' % ns_id, {
+            'vnf_member_index': str(vnf_member_index),
+            'primitive': action,
+            'primitive_params': args,
+        })
+        return result['id']
+
+    def ns_delete(self, ns_id):
+        self._api_delete_request('osm/nslcm/v1/ns_instances_content/%s' % ns_id)
+
+    def ns_list(self):
+        return self._api_get_request('osm/nslcm/v1/ns_instances_content')
+
+    @staticmethod
+    def _count_all_in_operational_status(ns_list, status):
+        return len(filter(lambda x: x['operational-status'] == status, ns_list))
+
+    def ns_wait_until_all_in_status(self, *statuses):
+        """Waits for all NSs to be in one of the specified stati.
+        Returns a tuple with the counts of the NSs in the invididual
+        stati in the same order as the specified stati"""
+
+        LOG.debug('Waiting for all NS to be in status {}'.format(statuses))
+        while True:
+            ns_list = self.ns_list()
+            state = ()
+            for status in statuses:
+                state += (self._count_all_in_operational_status(ns_list, status),)
+            number_correct = sum(state)
+            missing = len(ns_list) - number_correct
+            if missing == 0:
+                break
+            logging.debug('waiting for the status of %s services to change to %s' % (missing, statuses))
+            time.sleep(1)
+
+        logging.debug('all %d NSs in status %s' % (len(ns_list), statuses))
+        return state
diff --git a/src/emuvim/api/osm/osm.py b/src/emuvim/api/osm/osm.py
new file mode 100644 (file)
index 0000000..f4de8b9
--- /dev/null
@@ -0,0 +1,99 @@
+#!/usr/bin/env python2
+# Copyright (c) 2019 Erik Schilling
+# ALL RIGHTS RESERVED.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+
+from emuvim.api.openstack.resources.net import Net
+from emuvim.api.osm.kafka import Kafka
+from emuvim.api.osm.lcm import LCM
+from emuvim.api.osm.mongo import Mongo
+from emuvim.api.osm.mysql import Mysql
+from emuvim.api.osm.nbi import NBI
+from emuvim.api.osm.ro import RO
+from emuvim.api.osm.zookeeper import Zookeeper
+
+
+class OSM:
+    def __init__(self, net,
+                 switch,
+                 name='osm',
+                 vca_host=os.environ.get('VCA_HOST'),
+                 vca_secret=os.environ.get('VCA_SECRET'),
+                 osm_version='releasefive-daily',
+                 ip_start='10.0.0.100'):
+        ip_int = Net.ip_2_int(ip_start)
+        zookeeper_ip = ip_start
+        kafka_ip = Net.int_2_ip(ip_int + 1)
+        mongo_ip = Net.int_2_ip(ip_int + 2)
+        nbi_ip = Net.int_2_ip(ip_int + 3)
+        ro_db_ip = Net.int_2_ip(ip_int + 4)
+        ro_ip = Net.int_2_ip(ip_int + 5)
+        lcm_ip = Net.int_2_ip(ip_int + 6)
+
+        name_prefix = '%s-' % name
+        self.zookeeper = Zookeeper(net, '%s/16' % zookeeper_ip, name_prefix=name_prefix)
+        self.kafka = Kafka(net, '%s/16' % kafka_ip, zookeeper_ip, name_prefix=name_prefix)
+        self.mongo = Mongo(net, '%s/16' % mongo_ip, name_prefix=name_prefix)
+        self.nbi = NBI(net, '%s/16' % nbi_ip, mongo_ip, kafka_ip, version=osm_version, name_prefix=name_prefix)
+        self.ro_db = Mysql(net, '%s/16' % ro_db_ip, name_prefix=name_prefix)
+        self.ro = RO(net, '%s/16' % ro_ip, ro_db_ip, version=osm_version, name_prefix=name_prefix)
+        self.lcm = LCM(net, '%s/16' % lcm_ip, ro_ip, mongo_ip, kafka_ip,
+                       vca_host, vca_secret, version=osm_version, name_prefix=name_prefix)
+
+        net.addLink(self.zookeeper.instance, switch)
+        net.addLink(self.kafka.instance, switch)
+        net.addLink(self.mongo.instance, switch)
+        net.addLink(self.nbi.instance, switch)
+        net.addLink(self.ro_db.instance, switch)
+        net.addLink(self.ro.instance, switch)
+        net.addLink(self.lcm.instance, switch)
+
+    def start(self):
+        self.zookeeper.start()
+        self.kafka.start()
+        self.mongo.start()
+        self.nbi.start()
+        self.ro_db.start()
+        self.ro.start()
+        self.lcm.start()
+
+    # forward api related calls
+    def onboard_vnfd(self, *args, **kwargs):
+        return self.nbi.onboard_vnfd(*args, **kwargs)
+
+    def onboard_nsd(self, *args, **kwargs):
+        return self.nbi.onboard_nsd(*args, **kwargs)
+
+    def register_emulated_api(self, *args, **kwargs):
+        return self.nbi.register_emulated_api(*args, **kwargs)
+
+    def ns_list(self):
+        return self.nbi.ns_list()
+
+    def ns_create(self, *args, **kwargs):
+        return self.nbi.ns_create(*args, **kwargs)
+
+    def ns_delete(self, *args, **kwargs):
+        return self.nbi.ns_delete(*args, **kwargs)
+
+    def ns_get(self, *args, **kwargs):
+        return self.nbi.ns_get(*args, **kwargs)
+
+    def ns_action(self, *args, **kwargs):
+        return self.nbi.ns_action(*args, **kwargs)
+
+    def ns_wait_until_all_in_status(self, *args, **kwargs):
+        return self.nbi.ns_wait_until_all_in_status(*args, **kwargs)
diff --git a/src/emuvim/api/osm/osm_component_base.py b/src/emuvim/api/osm/osm_component_base.py
new file mode 100644 (file)
index 0000000..a334c8e
--- /dev/null
@@ -0,0 +1,23 @@
+#!/usr/bin/env python2
+# Copyright (c) 2019 Erik Schilling
+# ALL RIGHTS RESERVED.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+class OSMComponentBase:
+    def __init__(self):
+        self.instance = None
+
+    def start(self):
+        self.instance.start()
diff --git a/src/emuvim/api/osm/pre_configured_osm.py b/src/emuvim/api/osm/pre_configured_osm.py
new file mode 100644 (file)
index 0000000..492deb9
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/env python2
+# Copyright (c) 2019 Erik Schilling
+# ALL RIGHTS RESERVED.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+
+from emuvim.api.openstack.openstack_api_endpoint import OpenstackApiEndpoint
+from emuvim.api.osm.osm import OSM
+from emuvim.dcemulator.net import DCNetwork
+
+
+class PreConfiguredOSM:
+    def __init__(self,
+                 vca_host=os.environ.get('VCA_HOST'),
+                 vca_secret=os.environ.get('VCA_SECRET'),
+                 osm_version='releasefive-daily'):
+        self.net = DCNetwork(monitor=False, enable_learning=True)
+        dc1 = self.net.addDatacenter("dc1")
+        self.api = OpenstackApiEndpoint("0.0.0.0", 6001)
+        self.api.connect_datacenter(dc1)
+        self.api.connect_dc_network(self.net)
+
+        s1 = self.net.addSwitch('s1')
+        self.osm = OSM(self.net, s1, vca_host=vca_host, vca_secret=vca_secret, osm_version=osm_version)
+        self.vim_emu_id = None
+
+    def __enter__(self):
+        self.start()
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self.stop()
+
+    def start(self):
+        self.net.start()
+        self.api.start()
+        self.osm.start()
+        self.vim_emu_id = self.osm.register_emulated_api('emu-vim1', self.api)
+
+    def stop(self):
+        self.api.stop()
+        self.net.stop()
+
+    def ns_create(self, ns_name, nsd_id):
+        return self.osm.ns_create(ns_name, nsd_id, self.vim_emu_id)
+
+    # forward api related calls
+    def onboard_vnfd(self, *args, **kwargs):
+        return self.osm.onboard_vnfd(*args, **kwargs)
+
+    def onboard_nsd(self, *args, **kwargs):
+        return self.osm.onboard_nsd(*args, **kwargs)
+
+    def ns_list(self):
+        return self.osm.ns_list()
+
+    def ns_delete(self, *args, **kwargs):
+        return self.osm.ns_delete(*args, **kwargs)
+
+    def ns_get(self, *args, **kwargs):
+        return self.osm.ns_get(*args, **kwargs)
+
+    def ns_action(self, *args, **kwargs):
+        return self.osm.ns_action(*args, **kwargs)
+
+    def ns_wait_until_all_in_status(self, *args, **kwargs):
+        return self.osm.ns_wait_until_all_in_status(*args, **kwargs)
diff --git a/src/emuvim/api/osm/ro.py b/src/emuvim/api/osm/ro.py
new file mode 100644 (file)
index 0000000..88a632f
--- /dev/null
@@ -0,0 +1,31 @@
+#!/usr/bin/env python2
+# Copyright (c) 2019 Erik Schilling
+# ALL RIGHTS RESERVED.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from emuvim.api.osm.osm_component_base import OSMComponentBase
+from emuvim.api.util.docker_utils import wrap_debian_like
+from emuvim.api.util.process_utils import wait_until
+
+
+class RO(OSMComponentBase):
+    def __init__(self, net, ip, db_ip, version='latest', name_prefix=''):
+        OSMComponentBase.__init__(self)
+        self.instance = net.addDocker(
+            '{}ro'.format(name_prefix), ip=ip, dimage=wrap_debian_like('opensourcemano/ro:%s' % version),
+            environment={'RO_DB_HOST': db_ip, 'RO_DB_ROOT_PASSWORD': 'TEST'})
+
+    def start(self):
+        OSMComponentBase.start(self)
+        wait_until('nc -z %s 9090' % self.instance.dcinfo['NetworkSettings']['IPAddress'])
diff --git a/src/emuvim/api/osm/zookeeper.py b/src/emuvim/api/osm/zookeeper.py
new file mode 100644 (file)
index 0000000..1f51b4d
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/env python2
+# Copyright (c) 2019 Erik Schilling
+# ALL RIGHTS RESERVED.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from emuvim.api.osm.osm_component_base import OSMComponentBase
+
+
+class Zookeeper(OSMComponentBase):
+    def __init__(self, net, ip, version='latest', name_prefix=''):
+        OSMComponentBase.__init__(self)
+        self.instance = net.addDocker(
+            '{}zk'.format(name_prefix),
+            ip=ip,
+            dimage='wurstmeister/zookeeper:%s' % version)
diff --git a/src/emuvim/api/util/__init__.py b/src/emuvim/api/util/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/emuvim/api/util/docker_utils.py b/src/emuvim/api/util/docker_utils.py
new file mode 100644 (file)
index 0000000..39d8a2b
--- /dev/null
@@ -0,0 +1,40 @@
+import logging
+from io import BytesIO
+
+import docker
+
+from emuvim.api.util.path_utils import get_absolute_path
+
+
+def build_dockerfile_dir(folder, tag):
+    dcli = docker.from_env().api
+    folder = get_absolute_path(folder)
+    build_stream = dcli.build(folder, tag=tag)
+    logging.info('Docker build result:')
+    for line in build_stream:
+        logging.info(line)
+
+
+def suffix_tag_name(tag, suffix):
+    if ":" in tag:
+        return "%s_%s" % (tag, suffix)
+    return "%s:latest_%s" % (tag, suffix)
+
+
+def wrap_debian_like(image):
+    dcli = docker.from_env().api
+    dockerfile = '''
+    FROM %s
+    RUN apt update -y && apt install -y net-tools iputils-ping iproute
+    ''' % image
+    f = BytesIO(dockerfile.encode('utf-8'))
+    wrapper_name = suffix_tag_name(image, 'containernet_compatible')
+    logging.info('wrapping image: %s->%s' % (image, wrapper_name))
+    build_stream = dcli.build(fileobj=f, tag=wrapper_name)
+    build_result = [line for line in build_stream]
+    logging.debug('Docker build result:' + '\n'.join(build_result))
+    return wrapper_name
+
+
+# 172.17.0.1 is the ip of the docker0 interface on the host
+DOCKER_HOST_IP = '172.17.0.1'
diff --git a/src/emuvim/api/util/path_utils.py b/src/emuvim/api/util/path_utils.py
new file mode 100644 (file)
index 0000000..592492a
--- /dev/null
@@ -0,0 +1,8 @@
+import os
+import sys
+
+
+def get_absolute_path(absolute_or_relative_to_main_path):
+    if os.path.isabs(absolute_or_relative_to_main_path):
+        return absolute_or_relative_to_main_path
+    return os.path.join(sys.path[0], absolute_or_relative_to_main_path)
diff --git a/src/emuvim/api/util/process_utils.py b/src/emuvim/api/util/process_utils.py
new file mode 100644 (file)
index 0000000..1f90716
--- /dev/null
@@ -0,0 +1,9 @@
+import logging
+import subprocess
+import time
+
+
+def wait_until(cmd):
+    logging.debug('waiting for %s\n' % cmd)
+    while subprocess.call(cmd, shell=True) != 0:
+        time.sleep(1)