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