1 |
|
# Copyright 2018 Telefonica |
2 |
|
# |
3 |
|
# All Rights Reserved. |
4 |
|
# |
5 |
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may |
6 |
|
# not use this file except in compliance with the License. You may obtain |
7 |
|
# 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, WITHOUT |
13 |
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
14 |
|
# License for the specific language governing permissions and limitations |
15 |
|
# under the License. |
16 |
|
|
17 |
0 |
""" |
18 |
|
OSM NSI (Network Slice Instance) API handling |
19 |
|
""" |
20 |
|
|
21 |
0 |
from osmclient.common import utils |
22 |
0 |
from osmclient.common import wait as WaitForStatus |
23 |
0 |
from osmclient.common.exceptions import ClientException |
24 |
0 |
from osmclient.common.exceptions import NotFound |
25 |
0 |
import yaml |
26 |
0 |
import json |
27 |
0 |
import logging |
28 |
|
|
29 |
|
|
30 |
0 |
class Nsi(object): |
31 |
|
|
32 |
0 |
def __init__(self, http=None, client=None): |
33 |
0 |
self._http = http |
34 |
0 |
self._client = client |
35 |
0 |
self._logger = logging.getLogger('osmclient') |
36 |
0 |
self._apiName = '/nsilcm' |
37 |
0 |
self._apiVersion = '/v1' |
38 |
0 |
self._apiResource = '/netslice_instances_content' |
39 |
0 |
self._apiBase = '{}{}{}'.format(self._apiName, |
40 |
|
self._apiVersion, self._apiResource) |
41 |
|
|
42 |
|
# NSI '--wait' option |
43 |
0 |
def _wait(self, id, wait_time, deleteFlag=False): |
44 |
0 |
self._logger.debug("") |
45 |
0 |
self._client.get_token() |
46 |
|
# Endpoint to get operation status |
47 |
0 |
apiUrlStatus = '{}{}{}'.format(self._apiName, self._apiVersion, '/nsi_lcm_op_occs') |
48 |
|
# Wait for status for NSI instance creation/update/deletion |
49 |
0 |
if isinstance(wait_time, bool): |
50 |
0 |
wait_time = WaitForStatus.TIMEOUT_NSI_OPERATION |
51 |
0 |
WaitForStatus.wait_for_status( |
52 |
|
'NSI', |
53 |
|
str(id), |
54 |
|
wait_time, |
55 |
|
apiUrlStatus, |
56 |
|
self._http.get2_cmd, |
57 |
|
deleteFlag=deleteFlag) |
58 |
|
|
59 |
0 |
def list(self, filter=None): |
60 |
|
"""Returns a list of NSI |
61 |
|
""" |
62 |
0 |
self._logger.debug("") |
63 |
0 |
self._client.get_token() |
64 |
0 |
filter_string = '' |
65 |
0 |
if filter: |
66 |
0 |
filter_string = '?{}'.format(filter) |
67 |
0 |
_, resp = self._http.get2_cmd('{}{}'.format(self._apiBase,filter_string)) |
68 |
0 |
if resp: |
69 |
0 |
return json.loads(resp) |
70 |
0 |
return list() |
71 |
|
|
72 |
0 |
def get(self, name): |
73 |
|
"""Returns an NSI based on name or id |
74 |
|
""" |
75 |
0 |
self._logger.debug("") |
76 |
0 |
self._client.get_token() |
77 |
0 |
if utils.validate_uuid4(name): |
78 |
0 |
for nsi in self.list(): |
79 |
0 |
if name == nsi['_id']: |
80 |
0 |
return nsi |
81 |
|
else: |
82 |
0 |
for nsi in self.list(): |
83 |
0 |
if name == nsi['name']: |
84 |
0 |
return nsi |
85 |
0 |
raise NotFound("nsi {} not found".format(name)) |
86 |
|
|
87 |
0 |
def get_individual(self, name): |
88 |
0 |
self._logger.debug("") |
89 |
0 |
nsi_id = name |
90 |
0 |
self._client.get_token() |
91 |
0 |
if not utils.validate_uuid4(name): |
92 |
0 |
for nsi in self.list(): |
93 |
0 |
if name == nsi['name']: |
94 |
0 |
nsi_id = nsi['_id'] |
95 |
0 |
break |
96 |
0 |
try: |
97 |
0 |
_, resp = self._http.get2_cmd('{}/{}'.format(self._apiBase, nsi_id)) |
98 |
|
#resp = self._http.get_cmd('{}/{}/nsd_content'.format(self._apiBase, nsi_id)) |
99 |
|
#print(yaml.safe_dump(resp)) |
100 |
0 |
if resp: |
101 |
0 |
return json.loads(resp) |
102 |
0 |
except NotFound: |
103 |
0 |
raise NotFound("nsi '{}' not found".format(name)) |
104 |
0 |
raise NotFound("nsi {} not found".format(name)) |
105 |
|
|
106 |
0 |
def delete(self, name, force=False, wait=False): |
107 |
0 |
self._logger.debug("") |
108 |
0 |
nsi = self.get(name) |
109 |
0 |
querystring = '' |
110 |
0 |
if force: |
111 |
0 |
querystring = '?FORCE=True' |
112 |
0 |
http_code, resp = self._http.delete_cmd('{}/{}{}'.format(self._apiBase, |
113 |
|
nsi['_id'], querystring)) |
114 |
|
# print('HTTP CODE: {}'.format(http_code)) |
115 |
|
# print('RESP: {}'.format(resp)) |
116 |
0 |
if http_code == 202: |
117 |
0 |
if wait and resp: |
118 |
0 |
resp = json.loads(resp) |
119 |
|
# Wait for status for NSI instance deletion |
120 |
|
# For the 'delete' operation, '_id' is used |
121 |
0 |
self._wait(resp.get('_id'), wait, deleteFlag=True) |
122 |
|
else: |
123 |
0 |
print('Deletion in progress') |
124 |
0 |
elif http_code == 204: |
125 |
0 |
print('Deleted') |
126 |
|
else: |
127 |
0 |
msg = resp or "" |
128 |
|
# if resp: |
129 |
|
# try: |
130 |
|
# msg = json.loads(resp) |
131 |
|
# except ValueError: |
132 |
|
# msg = resp |
133 |
0 |
raise ClientException("failed to delete nsi {} - {}".format(name, msg)) |
134 |
|
|
135 |
0 |
def create(self, nst_name, nsi_name, account, config=None, |
136 |
|
ssh_keys=None, description='default description', |
137 |
|
admin_status='ENABLED', wait=False): |
138 |
|
|
139 |
0 |
self._logger.debug("") |
140 |
0 |
self._client.get_token() |
141 |
0 |
nst = self._client.nst.get(nst_name) |
142 |
|
|
143 |
0 |
vim_account_id = {} |
144 |
|
|
145 |
0 |
def get_vim_account_id(vim_account): |
146 |
0 |
self._logger.debug("") |
147 |
0 |
if vim_account_id.get(vim_account): |
148 |
0 |
return vim_account_id[vim_account] |
149 |
|
|
150 |
0 |
vim = self._client.vim.get(vim_account) |
151 |
0 |
if vim is None: |
152 |
0 |
raise NotFound("cannot find vim account '{}'".format(vim_account)) |
153 |
0 |
vim_account_id[vim_account] = vim['_id'] |
154 |
0 |
return vim['_id'] |
155 |
|
|
156 |
0 |
nsi = {} |
157 |
0 |
nsi['nstId'] = nst['_id'] |
158 |
0 |
nsi['nsiName'] = nsi_name |
159 |
0 |
nsi['nsiDescription'] = description |
160 |
0 |
nsi['vimAccountId'] = get_vim_account_id(account) |
161 |
|
#nsi['userdata'] = {} |
162 |
|
#nsi['userdata']['key1']='value1' |
163 |
|
#nsi['userdata']['key2']='value2' |
164 |
|
|
165 |
0 |
if ssh_keys is not None: |
166 |
|
# ssh_keys is comma separate list |
167 |
|
# ssh_keys_format = [] |
168 |
|
# for key in ssh_keys.split(','): |
169 |
|
# ssh_keys_format.append({'key-pair-ref': key}) |
170 |
|
# |
171 |
|
# ns['ssh-authorized-key'] = ssh_keys_format |
172 |
0 |
nsi['ssh_keys'] = [] |
173 |
0 |
for pubkeyfile in ssh_keys.split(','): |
174 |
0 |
with open(pubkeyfile, 'r') as f: |
175 |
0 |
nsi['ssh_keys'].append(f.read()) |
176 |
0 |
if config: |
177 |
0 |
nsi_config = yaml.safe_load(config) |
178 |
0 |
if "netslice-vld" in nsi_config: |
179 |
0 |
for vld in nsi_config["netslice-vld"]: |
180 |
0 |
if vld.get("vim-network-name"): |
181 |
0 |
if isinstance(vld["vim-network-name"], dict): |
182 |
0 |
vim_network_name_dict = {} |
183 |
0 |
for vim_account, vim_net in list(vld["vim-network-name"].items()): |
184 |
0 |
vim_network_name_dict[get_vim_account_id(vim_account)] = vim_net |
185 |
0 |
vld["vim-network-name"] = vim_network_name_dict |
186 |
0 |
nsi["netslice-vld"] = nsi_config["netslice-vld"] |
187 |
0 |
if "netslice-subnet" in nsi_config: |
188 |
0 |
for nssubnet in nsi_config["netslice-subnet"]: |
189 |
0 |
if "vld" in nssubnet: |
190 |
0 |
for vld in nssubnet["vld"]: |
191 |
0 |
if vld.get("vim-network-name"): |
192 |
0 |
if isinstance(vld["vim-network-name"], dict): |
193 |
0 |
vim_network_name_dict = {} |
194 |
0 |
for vim_account, vim_net in list(vld["vim-network-name"].items()): |
195 |
0 |
vim_network_name_dict[get_vim_account_id(vim_account)] = vim_net |
196 |
0 |
vld["vim-network-name"] = vim_network_name_dict |
197 |
0 |
if "vnf" in nssubnet: |
198 |
0 |
for vnf in nssubnet["vnf"]: |
199 |
0 |
if vnf.get("vim_account"): |
200 |
0 |
vnf["vimAccountId"] = get_vim_account_id(vnf.pop("vim_account")) |
201 |
0 |
nsi["netslice-subnet"] = nsi_config["netslice-subnet"] |
202 |
|
|
203 |
0 |
if "additionalParamsForNsi" in nsi_config: |
204 |
0 |
nsi["additionalParamsForNsi"] = nsi_config.pop("additionalParamsForNsi") |
205 |
0 |
if not isinstance(nsi["additionalParamsForNsi"], dict): |
206 |
0 |
raise ValueError("Error at --config 'additionalParamsForNsi' must be a dictionary") |
207 |
0 |
if "additionalParamsForSubnet" in nsi_config: |
208 |
0 |
nsi["additionalParamsForSubnet"] = nsi_config.pop("additionalParamsForSubnet") |
209 |
0 |
if not isinstance(nsi["additionalParamsForSubnet"], list): |
210 |
0 |
raise ValueError("Error at --config 'additionalParamsForSubnet' must be a list") |
211 |
0 |
for additional_param_subnet in nsi["additionalParamsForSubnet"]: |
212 |
0 |
if not isinstance(additional_param_subnet, dict): |
213 |
0 |
raise ValueError("Error at --config 'additionalParamsForSubnet' items must be dictionaries") |
214 |
0 |
if not additional_param_subnet.get("id"): |
215 |
0 |
raise ValueError("Error at --config 'additionalParamsForSubnet' items must contain subnet 'id'") |
216 |
0 |
if not additional_param_subnet.get("additionalParamsForNs") and\ |
217 |
|
not additional_param_subnet.get("additionalParamsForVnf"): |
218 |
0 |
raise ValueError("Error at --config 'additionalParamsForSubnet' items must contain " |
219 |
|
"'additionalParamsForNs' and/or 'additionalParamsForVnf'") |
220 |
0 |
if "timeout_nsi_deploy" in nsi_config: |
221 |
0 |
nsi["timeout_nsi_deploy"] = nsi_config.pop("timeout_nsi_deploy") |
222 |
|
|
223 |
|
# print(yaml.safe_dump(nsi)) |
224 |
0 |
try: |
225 |
0 |
self._apiResource = '/netslice_instances_content' |
226 |
0 |
self._apiBase = '{}{}{}'.format(self._apiName, |
227 |
|
self._apiVersion, self._apiResource) |
228 |
0 |
headers = self._client._headers |
229 |
0 |
headers['Content-Type'] = 'application/yaml' |
230 |
0 |
http_header = ['{}: {}'.format(key,val) |
231 |
|
for (key,val) in list(headers.items())] |
232 |
0 |
self._http.set_http_header(http_header) |
233 |
0 |
http_code, resp = self._http.post_cmd(endpoint=self._apiBase, |
234 |
|
postfields_dict=nsi) |
235 |
|
#print('HTTP CODE: {}'.format(http_code)) |
236 |
|
#print('RESP: {}'.format(resp)) |
237 |
|
#if http_code in (200, 201, 202, 204): |
238 |
0 |
if resp: |
239 |
0 |
resp = json.loads(resp) |
240 |
0 |
if not resp or 'id' not in resp: |
241 |
0 |
raise ClientException('unexpected response from server - {} '.format( |
242 |
|
resp)) |
243 |
0 |
if wait: |
244 |
|
# Wait for status for NSI instance creation |
245 |
0 |
self._wait(resp.get('nsilcmop_id'), wait) |
246 |
0 |
print(resp['id']) |
247 |
|
#else: |
248 |
|
# msg = "" |
249 |
|
# if resp: |
250 |
|
# try: |
251 |
|
# msg = json.loads(resp) |
252 |
|
# except ValueError: |
253 |
|
# msg = resp |
254 |
|
# raise ClientException(msg) |
255 |
0 |
except ClientException as exc: |
256 |
0 |
message="failed to create nsi: {} nst: {}\nerror:\n{}".format( |
257 |
|
nsi_name, |
258 |
|
nst_name, |
259 |
|
str(exc)) |
260 |
0 |
raise ClientException(message) |
261 |
|
|
262 |
0 |
def list_op(self, name, filter=None): |
263 |
|
"""Returns the list of operations of a NSI |
264 |
|
""" |
265 |
0 |
self._logger.debug("") |
266 |
0 |
nsi = self.get(name) |
267 |
0 |
try: |
268 |
0 |
self._apiResource = '/nsi_lcm_op_occs' |
269 |
0 |
self._apiBase = '{}{}{}'.format(self._apiName, |
270 |
|
self._apiVersion, self._apiResource) |
271 |
0 |
filter_string = '' |
272 |
0 |
if filter: |
273 |
0 |
filter_string = '&{}'.format(filter) |
274 |
0 |
http_code, resp = self._http.get2_cmd('{}?netsliceInstanceId={}{}'.format( |
275 |
|
self._apiBase, nsi['_id'], |
276 |
|
filter_string) ) |
277 |
|
#print('HTTP CODE: {}'.format(http_code)) |
278 |
|
#print('RESP: {}'.format(resp)) |
279 |
|
#if http_code == 200: |
280 |
0 |
if resp: |
281 |
0 |
resp = json.loads(resp) |
282 |
0 |
return resp |
283 |
|
else: |
284 |
0 |
raise ClientException('unexpected response from server') |
285 |
|
#else: |
286 |
|
# msg = "" |
287 |
|
# if resp: |
288 |
|
# try: |
289 |
|
# resp = json.loads(resp) |
290 |
|
# msg = resp['detail'] |
291 |
|
# except ValueError: |
292 |
|
# msg = resp |
293 |
|
# raise ClientException(msg) |
294 |
0 |
except ClientException as exc: |
295 |
0 |
message="failed to get operation list of NSI {}:\nerror:\n{}".format( |
296 |
|
name, |
297 |
|
str(exc)) |
298 |
0 |
raise ClientException(message) |
299 |
|
|
300 |
0 |
def get_op(self, operationId): |
301 |
|
"""Returns the status of an operation |
302 |
|
""" |
303 |
0 |
self._logger.debug("") |
304 |
0 |
self._client.get_token() |
305 |
0 |
try: |
306 |
0 |
self._apiResource = '/nsi_lcm_op_occs' |
307 |
0 |
self._apiBase = '{}{}{}'.format(self._apiName, |
308 |
|
self._apiVersion, self._apiResource) |
309 |
0 |
http_code, resp = self._http.get2_cmd('{}/{}'.format(self._apiBase, operationId)) |
310 |
|
#print('HTTP CODE: {}'.format(http_code)) |
311 |
|
#print('RESP: {}'.format(resp)) |
312 |
|
#if http_code == 200: |
313 |
0 |
if resp: |
314 |
0 |
resp = json.loads(resp) |
315 |
0 |
return resp |
316 |
|
else: |
317 |
0 |
raise ClientException('unexpected response from server') |
318 |
|
#else: |
319 |
|
# msg = "" |
320 |
|
# if resp: |
321 |
|
# try: |
322 |
|
# resp = json.loads(resp) |
323 |
|
# msg = resp['detail'] |
324 |
|
# except ValueError: |
325 |
|
# msg = resp |
326 |
|
# raise ClientException(msg) |
327 |
0 |
except ClientException as exc: |
328 |
0 |
message="failed to get status of operation {}:\nerror:\n{}".format( |
329 |
|
operationId, |
330 |
|
str(exc)) |
331 |
0 |
raise ClientException(message) |
332 |
|
|
333 |
0 |
def exec_op(self, name, op_name, op_data=None): |
334 |
|
"""Executes an operation on a NSI |
335 |
|
""" |
336 |
0 |
self._logger.debug("") |
337 |
0 |
nsi = self.get(name) |
338 |
0 |
try: |
339 |
0 |
self._apiResource = '/netslice_instances' |
340 |
0 |
self._apiBase = '{}{}{}'.format(self._apiName, |
341 |
|
self._apiVersion, self._apiResource) |
342 |
0 |
endpoint = '{}/{}/{}'.format(self._apiBase, nsi['_id'], op_name) |
343 |
|
#print('OP_NAME: {}'.format(op_name)) |
344 |
|
#print('OP_DATA: {}'.format(json.dumps(op_data))) |
345 |
0 |
http_code, resp = self._http.post_cmd(endpoint=endpoint, postfields_dict=op_data) |
346 |
|
#print('HTTP CODE: {}'.format(http_code)) |
347 |
|
#print('RESP: {}'.format(resp)) |
348 |
|
#if http_code in (200, 201, 202, 204): |
349 |
0 |
if resp: |
350 |
0 |
resp = json.loads(resp) |
351 |
0 |
if not resp or 'id' not in resp: |
352 |
0 |
raise ClientException('unexpected response from server - {}'.format( |
353 |
|
resp)) |
354 |
0 |
print(resp['id']) |
355 |
|
#else: |
356 |
|
# msg = "" |
357 |
|
# if resp: |
358 |
|
# try: |
359 |
|
# msg = json.loads(resp) |
360 |
|
# except ValueError: |
361 |
|
# msg = resp |
362 |
|
# raise ClientException(msg) |
363 |
0 |
except ClientException as exc: |
364 |
0 |
message="failed to exec operation {}:\nerror:\n{}".format( |
365 |
|
name, |
366 |
|
str(exc)) |
367 |
0 |
raise ClientException(message) |
368 |
|
|