Provide API for allowing full stack emulation 24/7324/2
authorschillinge <ablu@mail.uni-paderborn.de>
Sun, 3 Mar 2019 18:17:23 +0000 (19:17 +0100)
committerschillinge <ablu@mail.uni-paderborn.de>
Fri, 15 Mar 2019 17:50:33 +0000 (18:50 +0100)
This commit adds an API in order to emulate the full OSM stack. Different
levels of API are added in order to allow flexible scenarios.

The lowest level API only wraps the required components in containers.
The next higher level also abstracts the composition of these low-level
into a working OSM instance.
The third level also abstracts the emulation and configuration of the VIM
layer.

Examples are added for each abstraction level.

Implements Feature 7291.

Change-Id: I156f6625d4ff4010d688c41419c4ece03a339937
Signed-off-by: schillinge <ablu@mail.uni-paderborn.de>
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)