From: peusterm Date: Mon, 18 Mar 2019 13:47:19 +0000 (+0100) Subject: Merge "Provide API for allowing full stack emulation" X-Git-Tag: v6.0.0~20 X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2Fvim-emu.git;a=commitdiff_plain;h=060c189f958d992a6eccaa9204ab86fc1089e3a4;hp=3bb8b25b4584b62a56303404a39b085c4298529a Merge "Provide API for allowing full stack emulation" --- diff --git a/examples/full_stack_emulation_complex.py b/examples/full_stack_emulation_complex.py new file mode 100755 index 0000000..011c1a4 --- /dev/null +++ b/examples/full_stack_emulation_complex.py @@ -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 index 0000000..bd3927d --- /dev/null +++ b/examples/full_stack_emulation_multiple_osm.py @@ -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 index 0000000..d477a57 --- /dev/null +++ b/examples/full_stack_emulation_simple.py @@ -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 index 0000000..97bc953 --- /dev/null +++ b/src/emuvim/api/osm/__init__.py @@ -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 index 0000000..4443621 --- /dev/null +++ b/src/emuvim/api/osm/kafka.py @@ -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 index 0000000..dd5f6e5 --- /dev/null +++ b/src/emuvim/api/osm/lcm.py @@ -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 index 0000000..7e10683 --- /dev/null +++ b/src/emuvim/api/osm/mongo.py @@ -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 index 0000000..80f187a --- /dev/null +++ b/src/emuvim/api/osm/mysql.py @@ -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 index 0000000..0c5c914 --- /dev/null +++ b/src/emuvim/api/osm/nbi.py @@ -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 index 0000000..f4de8b9 --- /dev/null +++ b/src/emuvim/api/osm/osm.py @@ -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 index 0000000..a334c8e --- /dev/null +++ b/src/emuvim/api/osm/osm_component_base.py @@ -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 index 0000000..492deb9 --- /dev/null +++ b/src/emuvim/api/osm/pre_configured_osm.py @@ -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 index 0000000..88a632f --- /dev/null +++ b/src/emuvim/api/osm/ro.py @@ -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 index 0000000..1f51b4d --- /dev/null +++ b/src/emuvim/api/osm/zookeeper.py @@ -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 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 index 0000000..39d8a2b --- /dev/null +++ b/src/emuvim/api/util/docker_utils.py @@ -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 index 0000000..592492a --- /dev/null +++ b/src/emuvim/api/util/path_utils.py @@ -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 index 0000000..1f90716 --- /dev/null +++ b/src/emuvim/api/util/process_utils.py @@ -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)