Provide API for allowing full stack emulation
[osm/vim-emu.git] / src / emuvim / api / osm / nbi.py
1 #!/usr/bin/env python2
2 # Copyright (c) 2019 Erik Schilling
3 # ALL RIGHTS RESERVED.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 import logging
18 import os
19 import subprocess
20
21 import requests
22 import time
23 from pipes import quote
24 from tempfile import NamedTemporaryFile
25
26 import urllib3
27 import yaml
28
29 from emuvim.api.osm.osm_component_base import OSMComponentBase
30 from emuvim.api.util.docker_utils import wrap_debian_like, DOCKER_HOST_IP
31 from emuvim.api.util.path_utils import get_absolute_path
32 from emuvim.api.util.process_utils import wait_until
33 from mininet.log import debug
34
35 # disables warnings about verify=False for TLS
36 # since NBI runs with self-signed certificates this otherwise spams the log
37 urllib3.disable_warnings()
38
39 LOG = logging.getLogger(__name__)
40
41
42 class NBI(OSMComponentBase):
43
44 def __init__(self, net, ip, mongo_ip, kafka_ip, version='latest', name_prefix=''):
45 OSMComponentBase.__init__(self)
46 self.instance = net.addDocker(
47 '{}nbi'.format(name_prefix), ip=ip, dimage=wrap_debian_like('opensourcemano/nbi:%s' % version),
48 volumes=['osm_packages:/app/storage'],
49 environment={'OSMNBI_DATABASE_URI': 'mongodb://%s:27017' % mongo_ip,
50 'OSMNBI_MESSAGE_HOST': kafka_ip})
51 self._ip = self.instance.dcinfo['NetworkSettings']['IPAddress']
52
53 def start(self):
54 OSMComponentBase.start(self)
55 wait_until('nc -z %s 9999' % self._ip)
56
57 def _osm(self, command):
58 prefixed_command = 'osm %s' % command
59 debug('executing command: %s\n' % prefixed_command)
60 output = subprocess.check_output(prefixed_command, env={'OSM_HOSTNAME': self._ip}, shell=True)
61 debug('output: \n%s\n' % output)
62 return output
63
64 @staticmethod
65 def _create_archive(folder, tmp_file):
66 folder = get_absolute_path(folder)
67 parent_folder = os.path.dirname(folder)
68 basename = os.path.basename(folder)
69 subprocess.call('tar czf %s %s' % (quote(tmp_file.name), quote(basename)), cwd=parent_folder, shell=True)
70
71 def onboard_vnfd(self, folder):
72 try:
73 with NamedTemporaryFile() as tmp_archive:
74 self._create_archive(folder, tmp_archive)
75 return self._osm('vnfd-create %s' % quote(tmp_archive.name)).strip()
76 except subprocess.CalledProcessError as e:
77 raise RuntimeError('creating vnfd failed: %s' % e.output)
78
79 def onboard_nsd(self, folder):
80 try:
81 with NamedTemporaryFile() as tmp_archive:
82 self._create_archive(folder, tmp_archive)
83 return self._osm('nsd-create %s' % quote(tmp_archive.name)).strip()
84 except subprocess.CalledProcessError as e:
85 raise RuntimeError('creating nsd failed: %s' % e.output)
86
87 def _get_api_token(self):
88 token_request = requests.post('https://%s:9999/osm/admin/v1/tokens' % self._ip,
89 data={'username': 'admin', 'password': 'admin'}, verify=False)
90 if not token_request.ok:
91 raise RuntimeError('getting token failed with: %s' % token_request.text)
92 token = yaml.safe_load(token_request.text)
93 return token['id']
94
95 def _api_request_args(self):
96 return {'headers': {'Authorization': 'Bearer %s' % self._get_api_token()}, 'verify': False}
97
98 def _api_post_request(self, endpoint, data):
99 r = requests.post('https://%s:9999/%s' % (self._ip, endpoint),
100 json=data,
101 **self._api_request_args())
102 if not r.ok:
103 raise RuntimeError('POST request failed with: %s' % r.text)
104 result = yaml.safe_load(r.text)
105 return result
106
107 def _api_get_request(self, endpoint):
108 r = requests.get('https://%s:9999/%s' % (self._ip, endpoint),
109 **self._api_request_args())
110 if not r.ok:
111 raise RuntimeError('GET request failed with: %s' % r.text)
112 result = yaml.safe_load(r.text)
113 return result
114
115 def _api_delete_request(self, endpoint):
116 r = requests.delete('https://%s:9999/%s' % (self._ip, endpoint),
117 **self._api_request_args())
118 if not r.ok:
119 raise RuntimeError('DELETE request failed with: %s' % r.text)
120
121 def register_emulated_api(self, name, api):
122 output = self._osm('vim-create --name %s '
123 '--user username '
124 '--password password '
125 '--auth_url http://%s:%d/v2.0 '
126 '--tenant tenantName '
127 '--account_type openstack' % (
128 quote(name),
129 quote(DOCKER_HOST_IP),
130 api.port
131 ))
132 vim_id = output.strip()
133 while self._api_get_request('osm/admin/v1/vim_accounts/%s' % vim_id)['_admin']['detailed-status'] != 'Done':
134 time.sleep(1)
135 return vim_id
136
137 def ns_create(self, ns_name, nsd_id, vim_id):
138 result = self._api_post_request('osm/nslcm/v1/ns_instances_content', {
139 'nsdId': nsd_id,
140 'nsName': ns_name,
141 # dummy text since this cannot be empty
142 'nsDescription': 'created by vim-emu',
143 'vimAccountId': vim_id,
144 })
145 return result['id']
146
147 def ns_get(self, ns_id):
148 return self._api_get_request('osm/nslcm/v1/ns_instances_content/%s' % ns_id)
149
150 def ns_action(self, ns_id, vnf_member_index, action, args=None):
151 if args is None:
152 args = {}
153 result = self._api_post_request('osm/nslcm/v1/ns_instances/%s/action' % ns_id, {
154 'vnf_member_index': str(vnf_member_index),
155 'primitive': action,
156 'primitive_params': args,
157 })
158 return result['id']
159
160 def ns_delete(self, ns_id):
161 self._api_delete_request('osm/nslcm/v1/ns_instances_content/%s' % ns_id)
162
163 def ns_list(self):
164 return self._api_get_request('osm/nslcm/v1/ns_instances_content')
165
166 @staticmethod
167 def _count_all_in_operational_status(ns_list, status):
168 return len(filter(lambda x: x['operational-status'] == status, ns_list))
169
170 def ns_wait_until_all_in_status(self, *statuses):
171 """Waits for all NSs to be in one of the specified stati.
172 Returns a tuple with the counts of the NSs in the invididual
173 stati in the same order as the specified stati"""
174
175 LOG.debug('Waiting for all NS to be in status {}'.format(statuses))
176 while True:
177 ns_list = self.ns_list()
178 state = ()
179 for status in statuses:
180 state += (self._count_all_in_operational_status(ns_list, status),)
181 number_correct = sum(state)
182 missing = len(ns_list) - number_correct
183 if missing == 0:
184 break
185 logging.debug('waiting for the status of %s services to change to %s' % (missing, statuses))
186 time.sleep(1)
187
188 logging.debug('all %d NSs in status %s' % (len(ns_list), statuses))
189 return state