ac40aad3b17319539784a51568d572c60d2a3655
[osm/osmclient.git] / osmclient / sol005 / ns.py
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 """
18 OSM ns API handling
19 """
20
21 from osmclient.common import utils
22 from osmclient.common import wait as WaitForStatus
23 from osmclient.common.exceptions import ClientException
24 from osmclient.common.exceptions import NotFound
25 import yaml
26 import json
27 import logging
28
29
30 class Ns(object):
31 def __init__(self, http=None, client=None):
32 self._http = http
33 self._client = client
34 self._logger = logging.getLogger("osmclient")
35 self._apiName = "/nslcm"
36 self._apiVersion = "/v1"
37 self._apiResource = "/ns_instances_content"
38 self._apiBase = "{}{}{}".format(
39 self._apiName, self._apiVersion, self._apiResource
40 )
41
42 # NS '--wait' option
43 def _wait(self, id, wait_time, deleteFlag=False):
44 self._logger.debug("")
45 # Endpoint to get operation status
46 apiUrlStatus = "{}{}{}".format(
47 self._apiName, self._apiVersion, "/ns_lcm_op_occs"
48 )
49 # Wait for status for NS instance creation/update/deletion
50 if isinstance(wait_time, bool):
51 wait_time = WaitForStatus.TIMEOUT_NS_OPERATION
52 WaitForStatus.wait_for_status(
53 "NS",
54 str(id),
55 wait_time,
56 apiUrlStatus,
57 self._http.get2_cmd,
58 deleteFlag=deleteFlag,
59 )
60
61 def list(self, filter=None):
62 """Returns a list of NS"""
63 self._logger.debug("")
64 self._client.get_token()
65 filter_string = ""
66 if filter:
67 filter_string = "?{}".format(filter)
68 _, resp = self._http.get2_cmd("{}{}".format(self._apiBase, filter_string))
69 if resp:
70 return json.loads(resp)
71 return list()
72
73 def get(self, name):
74 """Returns an NS based on name or id"""
75 self._logger.debug("")
76 self._client.get_token()
77 if utils.validate_uuid4(name):
78 for ns in self.list():
79 if name == ns["_id"]:
80 return ns
81 else:
82 for ns in self.list():
83 if name == ns["name"]:
84 return ns
85 raise NotFound("ns '{}' not found".format(name))
86
87 def get_individual(self, name):
88 self._logger.debug("")
89 self._client.get_token()
90 ns_id = name
91 if not utils.validate_uuid4(name):
92 for ns in self.list():
93 if name == ns["name"]:
94 ns_id = ns["_id"]
95 break
96 try:
97 _, resp = self._http.get2_cmd("{}/{}".format(self._apiBase, ns_id))
98 # resp = self._http.get_cmd('{}/{}/nsd_content'.format(self._apiBase, ns_id))
99 # print(yaml.safe_dump(resp))
100 if resp:
101 return json.loads(resp)
102 except NotFound:
103 raise NotFound("ns '{}' not found".format(name))
104 raise NotFound("ns '{}' not found".format(name))
105
106 def delete(self, name, force=False, config=None, wait=False):
107 """
108 Deletes a Network Service (NS)
109 :param name: name of network service
110 :param force: set force. Direct deletion without cleaning at VIM
111 :param config: parameters of deletion, as:
112 autoremove: Bool (default True)
113 timeout_ns_terminate: int
114 skip_terminate_primitives: Bool (default False) to not exec the terminate primitives
115 :param wait: Make synchronous. Wait until deletion is completed:
116 False to not wait (by default), True to wait a standard time, or int (time to wait)
117 :return: None. Exception if fail
118 """
119 self._logger.debug("")
120 ns = self.get(name)
121 querystring_list = []
122 querystring = ""
123 if config:
124 ns_config = yaml.safe_load(config)
125 querystring_list += ["{}={}".format(k, v) for k, v in ns_config.items()]
126 if force:
127 querystring_list.append("FORCE=True")
128 if querystring_list:
129 querystring = "?" + "&".join(querystring_list)
130 http_code, resp = self._http.delete_cmd(
131 "{}/{}{}".format(self._apiBase, ns["_id"], querystring)
132 )
133 # TODO change to use a POST self._http.post_cmd('{}/{}/terminate{}'.format(_apiBase, ns['_id'], querystring),
134 # postfields_dict=ns_config)
135 # seting autoremove as True by default
136 # print('HTTP CODE: {}'.format(http_code))
137 # print('RESP: {}'.format(resp))
138 if http_code == 202:
139 if wait and resp:
140 resp = json.loads(resp)
141 # For the 'delete' operation, '_id' is used
142 self._wait(resp.get("_id"), wait, deleteFlag=True)
143 else:
144 print("Deletion in progress")
145 elif http_code == 204:
146 print("Deleted")
147 else:
148 msg = resp or ""
149 # if resp:
150 # try:
151 # msg = json.loads(resp)
152 # except ValueError:
153 # msg = resp
154 raise ClientException("failed to delete ns {} - {}".format(name, msg))
155
156 def create(
157 self,
158 nsd_name,
159 nsr_name,
160 account,
161 config=None,
162 ssh_keys=None,
163 description="default description",
164 admin_status="ENABLED",
165 wait=False,
166 ):
167 self._logger.debug("")
168 self._client.get_token()
169 nsd = self._client.nsd.get(nsd_name)
170
171 vim_account_id = {}
172 wim_account_id = {}
173
174 def get_vim_account_id(vim_account):
175 self._logger.debug("")
176 if vim_account_id.get(vim_account):
177 return vim_account_id[vim_account]
178 vim = self._client.vim.get(vim_account)
179 if vim is None:
180 raise NotFound("cannot find vim account '{}'".format(vim_account))
181 vim_account_id[vim_account] = vim["_id"]
182 return vim["_id"]
183
184 def get_wim_account_id(wim_account):
185 self._logger.debug("")
186 # wim_account can be False (boolean) to indicate not use wim account
187 if not isinstance(wim_account, str):
188 return wim_account
189 if wim_account_id.get(wim_account):
190 return wim_account_id[wim_account]
191 wim = self._client.wim.get(wim_account)
192 if wim is None:
193 raise NotFound("cannot find wim account '{}'".format(wim_account))
194 wim_account_id[wim_account] = wim["_id"]
195 return wim["_id"]
196
197 vim_id = get_vim_account_id(account)
198 ns = {}
199 ns["nsdId"] = nsd["_id"]
200 ns["nsName"] = nsr_name
201 ns["nsDescription"] = description
202 ns["vimAccountId"] = vim_id
203 # ns['userdata'] = {}
204 # ns['userdata']['key1']='value1'
205 # ns['userdata']['key2']='value2'
206
207 if ssh_keys is not None:
208 ns["ssh_keys"] = []
209 for pubkeyfile in ssh_keys.split(","):
210 with open(pubkeyfile, "r") as f:
211 ns["ssh_keys"].append(f.read())
212 if config:
213 ns_config = yaml.safe_load(config)
214 if "vim-network-name" in ns_config:
215 ns_config["vld"] = ns_config.pop("vim-network-name")
216 if "vld" in ns_config:
217 if not isinstance(ns_config["vld"], list):
218 raise ClientException(
219 "Error at --config 'vld' must be a list of dictionaries"
220 )
221 for vld in ns_config["vld"]:
222 if not isinstance(vld, dict):
223 raise ClientException(
224 "Error at --config 'vld' must be a list of dictionaries"
225 )
226 if vld.get("vim-network-name"):
227 if isinstance(vld["vim-network-name"], dict):
228 vim_network_name_dict = {}
229 for vim_account, vim_net in vld["vim-network-name"].items():
230 vim_network_name_dict[
231 get_vim_account_id(vim_account)
232 ] = vim_net
233 vld["vim-network-name"] = vim_network_name_dict
234 if "wim_account" in vld and vld["wim_account"] is not None:
235 vld["wimAccountId"] = get_wim_account_id(vld.pop("wim_account"))
236 if "vnf" in ns_config:
237 for vnf in ns_config["vnf"]:
238 if vnf.get("vim_account"):
239 vnf["vimAccountId"] = get_vim_account_id(vnf.pop("vim_account"))
240
241 if "additionalParamsForNs" in ns_config:
242 if not isinstance(ns_config["additionalParamsForNs"], dict):
243 raise ClientException(
244 "Error at --config 'additionalParamsForNs' must be a dictionary"
245 )
246 if "additionalParamsForVnf" in ns_config:
247 if not isinstance(ns_config["additionalParamsForVnf"], list):
248 raise ClientException(
249 "Error at --config 'additionalParamsForVnf' must be a list"
250 )
251 for additional_param_vnf in ns_config["additionalParamsForVnf"]:
252 if not isinstance(additional_param_vnf, dict):
253 raise ClientException(
254 "Error at --config 'additionalParamsForVnf' items must be dictionaries"
255 )
256 if not additional_param_vnf.get("member-vnf-index"):
257 raise ClientException(
258 "Error at --config 'additionalParamsForVnf' items must contain "
259 "'member-vnf-index'"
260 )
261 if "wim_account" in ns_config:
262 wim_account = ns_config.pop("wim_account")
263 if wim_account is not None:
264 ns["wimAccountId"] = get_wim_account_id(wim_account)
265 # rest of parameters without any transformation or checking
266 # "timeout_ns_deploy"
267 # "placement-engine"
268 ns.update(ns_config)
269
270 # print(yaml.safe_dump(ns))
271 try:
272 self._apiResource = "/ns_instances_content"
273 self._apiBase = "{}{}{}".format(
274 self._apiName, self._apiVersion, self._apiResource
275 )
276 headers = self._client._headers
277 headers["Content-Type"] = "application/yaml"
278 http_header = [
279 "{}: {}".format(key, val) for (key, val) in list(headers.items())
280 ]
281 self._http.set_http_header(http_header)
282 http_code, resp = self._http.post_cmd(
283 endpoint=self._apiBase, postfields_dict=ns
284 )
285 # print('HTTP CODE: {}'.format(http_code))
286 # print('RESP: {}'.format(resp))
287 # if http_code in (200, 201, 202, 204):
288 if resp:
289 resp = json.loads(resp)
290 if not resp or "id" not in resp:
291 raise ClientException(
292 "unexpected response from server - {} ".format(resp)
293 )
294 if wait:
295 # Wait for status for NS instance creation
296 self._wait(resp.get("nslcmop_id"), wait)
297 print(resp["id"])
298 return resp["id"]
299 # else:
300 # msg = ""
301 # if resp:
302 # try:
303 # msg = json.loads(resp)
304 # except ValueError:
305 # msg = resp
306 # raise ClientException(msg)
307 except ClientException as exc:
308 message = "failed to create ns: {} nsd: {}\nerror:\n{}".format(
309 nsr_name, nsd_name, str(exc)
310 )
311 raise ClientException(message)
312
313 def list_op(self, name, filter=None):
314 """Returns the list of operations of a NS"""
315 self._logger.debug("")
316 ns = self.get(name)
317 try:
318 self._apiResource = "/ns_lcm_op_occs"
319 self._apiBase = "{}{}{}".format(
320 self._apiName, self._apiVersion, self._apiResource
321 )
322 filter_string = ""
323 if filter:
324 filter_string = "&{}".format(filter)
325 http_code, resp = self._http.get2_cmd(
326 "{}?nsInstanceId={}{}".format(self._apiBase, ns["_id"], filter_string)
327 )
328 # print('HTTP CODE: {}'.format(http_code))
329 # print('RESP: {}'.format(resp))
330 if http_code == 200:
331 if resp:
332 resp = json.loads(resp)
333 return resp
334 else:
335 raise ClientException("unexpected response from server")
336 else:
337 msg = resp or ""
338 # if resp:
339 # try:
340 # resp = json.loads(resp)
341 # msg = resp['detail']
342 # except ValueError:
343 # msg = resp
344 raise ClientException(msg)
345 except ClientException as exc:
346 message = "failed to get operation list of NS {}:\nerror:\n{}".format(
347 name, str(exc)
348 )
349 raise ClientException(message)
350
351 def get_op(self, operationId):
352 """Returns the status of an operation"""
353 self._logger.debug("")
354 self._client.get_token()
355 try:
356 self._apiResource = "/ns_lcm_op_occs"
357 self._apiBase = "{}{}{}".format(
358 self._apiName, self._apiVersion, self._apiResource
359 )
360 http_code, resp = self._http.get2_cmd(
361 "{}/{}".format(self._apiBase, operationId)
362 )
363 # print('HTTP CODE: {}'.format(http_code))
364 # print('RESP: {}'.format(resp))
365 if http_code == 200:
366 if resp:
367 resp = json.loads(resp)
368 return resp
369 else:
370 raise ClientException("unexpected response from server")
371 else:
372 msg = resp or ""
373 # if resp:
374 # try:
375 # resp = json.loads(resp)
376 # msg = resp['detail']
377 # except ValueError:
378 # msg = resp
379 raise ClientException(msg)
380 except ClientException as exc:
381 message = "failed to get status of operation {}:\nerror:\n{}".format(
382 operationId, str(exc)
383 )
384 raise ClientException(message)
385
386 def exec_op(
387 self,
388 name,
389 op_name,
390 op_data=None,
391 wait=False,
392 ):
393 """Executes an operation on a NS"""
394 self._logger.debug("")
395 ns = self.get(name)
396 try:
397 ns = self.get(name)
398 self._apiResource = "/ns_instances"
399 self._apiBase = "{}{}{}".format(
400 self._apiName, self._apiVersion, self._apiResource
401 )
402 endpoint = "{}/{}/{}".format(self._apiBase, ns["_id"], op_name)
403 # print('OP_NAME: {}'.format(op_name))
404 # print('OP_DATA: {}'.format(json.dumps(op_data)))
405 http_code, resp = self._http.post_cmd(
406 endpoint=endpoint, postfields_dict=op_data
407 )
408 # print('HTTP CODE: {}'.format(http_code))
409 # print('RESP: {}'.format(resp))
410 # if http_code in (200, 201, 202, 204):
411 if resp:
412 resp = json.loads(resp)
413 if not resp or "id" not in resp:
414 raise ClientException(
415 "unexpected response from server - {}".format(resp)
416 )
417 if wait:
418 # Wait for status for NS instance action
419 # For the 'action' operation, 'id' is used
420 self._wait(resp.get("id"), wait)
421 return resp["id"]
422 # else:
423 # msg = ""
424 # if resp:
425 # try:
426 # msg = json.loads(resp)
427 # except ValueError:
428 # msg = resp
429 # raise ClientException(msg)
430 except ClientException as exc:
431 message = "failed to exec operation {}:\nerror:\n{}".format(name, str(exc))
432 raise ClientException(message)
433
434 def scale_vnf(
435 self,
436 ns_name,
437 vnf_name,
438 scaling_group,
439 scale_in,
440 scale_out,
441 wait=False,
442 timeout=None,
443 ):
444 """Scales a VNF by adding/removing VDUs"""
445 self._logger.debug("")
446 self._client.get_token()
447 try:
448 op_data = {}
449 op_data["scaleType"] = "SCALE_VNF"
450 op_data["scaleVnfData"] = {}
451 if scale_in and not scale_out:
452 op_data["scaleVnfData"]["scaleVnfType"] = "SCALE_IN"
453 elif not scale_in and scale_out:
454 op_data["scaleVnfData"]["scaleVnfType"] = "SCALE_OUT"
455 else:
456 raise ClientException("you must set either 'scale_in' or 'scale_out'")
457 op_data["scaleVnfData"]["scaleByStepData"] = {
458 "member-vnf-index": vnf_name,
459 "scaling-group-descriptor": scaling_group,
460 }
461 if timeout:
462 op_data["timeout_ns_scale"] = timeout
463 op_id = self.exec_op(ns_name, op_name="scale", op_data=op_data, wait=wait)
464 print(str(op_id))
465 except ClientException as exc:
466 message = "failed to scale vnf {} of ns {}:\nerror:\n{}".format(
467 vnf_name, ns_name, str(exc)
468 )
469 raise ClientException(message)
470
471 def create_alarm(self, alarm):
472 self._logger.debug("")
473 self._client.get_token()
474 data = {}
475 data["create_alarm_request"] = {}
476 data["create_alarm_request"]["alarm_create_request"] = alarm
477 try:
478 http_code, resp = self._http.post_cmd(
479 endpoint="/test/message/alarm_request", postfields_dict=data
480 )
481 # print('HTTP CODE: {}'.format(http_code))
482 # print('RESP: {}'.format(resp))
483 # if http_code in (200, 201, 202, 204):
484 # resp = json.loads(resp)
485 print("Alarm created")
486 # else:
487 # msg = ""
488 # if resp:
489 # try:
490 # msg = json.loads(resp)
491 # except ValueError:
492 # msg = resp
493 # raise ClientException('error: code: {}, resp: {}'.format(
494 # http_code, msg))
495 except ClientException as exc:
496 message = "failed to create alarm: alarm {}\n{}".format(alarm, str(exc))
497 raise ClientException(message)
498
499 def delete_alarm(self, name):
500 self._logger.debug("")
501 self._client.get_token()
502 data = {}
503 data["delete_alarm_request"] = {}
504 data["delete_alarm_request"]["alarm_delete_request"] = {}
505 data["delete_alarm_request"]["alarm_delete_request"]["alarm_uuid"] = name
506 try:
507 http_code, resp = self._http.post_cmd(
508 endpoint="/test/message/alarm_request", postfields_dict=data
509 )
510 # print('HTTP CODE: {}'.format(http_code))
511 # print('RESP: {}'.format(resp))
512 # if http_code in (200, 201, 202, 204):
513 # resp = json.loads(resp)
514 print("Alarm deleted")
515 # else:
516 # msg = ""
517 # if resp:
518 # try:
519 # msg = json.loads(resp)
520 # except ValueError:
521 # msg = resp
522 # raise ClientException('error: code: {}, resp: {}'.format(
523 # http_code, msg))
524 except ClientException as exc:
525 message = "failed to delete alarm: alarm {}\n{}".format(name, str(exc))
526 raise ClientException(message)
527
528 def get_alarm(self, project_name=None, ns_id=None, uuid=None):
529 self._client.get_token()
530 try:
531 self._apiName = "/nsfm"
532 self._apiResource = "/alarms"
533 self._apiBase = "{}{}{}".format(
534 self._apiName, self._apiVersion, self._apiResource
535 )
536 if uuid:
537 # if request is for any uuid
538 http_code, resp = self._http.get2_cmd(
539 "{}/{}".format(self._apiBase, uuid)
540 )
541 if not uuid:
542 http_code, resp = self._http.get2_cmd(
543 "{}/{}/{}/{}".format(self._apiBase, uuid, project_name, ns_id)
544 )
545 if http_code == 200:
546 if resp:
547 resp = json.loads(resp)
548 return resp
549 else:
550 raise ClientException("unexpected response from server")
551 else:
552 msg = resp or ""
553 raise ClientException(msg)
554 except ClientException as exc:
555 message = "failed to get alarm :\nerror:\n{}".format(str(exc))
556 raise ClientException(message)
557
558 def update_alarm(self, uuid, threshold=None, is_enable=None, wait=None):
559 self._client.get_token()
560 try:
561 op_data = {}
562 op_data["uuid"] = uuid
563 op_data["threshold"] = threshold
564 op_data["is_enable"] = is_enable
565 self._apiName = "/nsfm"
566 self._apiResource = "/alarms"
567 self._apiBase = "{}{}{}".format(
568 self._apiName, self._apiVersion, self._apiResource
569 )
570 http_code, resp = self._http.patch_cmd(
571 endpoint="{}".format(self._apiBase), postfields_dict=op_data
572 )
573 if resp:
574 resp = json.loads(resp)
575 print(resp)
576 return resp
577 except ClientException as exc:
578 message = "failed to update alarm :\nerror:\n{}".format(str(exc))
579 raise ClientException(message)
580
581 def export_metric(self, metric):
582 self._logger.debug("")
583 self._client.get_token()
584 data = {}
585 data["read_metric_data_request"] = metric
586 try:
587 http_code, resp = self._http.post_cmd(
588 endpoint="/test/message/metric_request", postfields_dict=data
589 )
590 # print('HTTP CODE: {}'.format(http_code))
591 # print('RESP: {}'.format(resp))
592 # if http_code in (200, 201, 202, 204):
593 # resp = json.loads(resp)
594 return "Metric exported"
595 # else:
596 # msg = ""
597 # if resp:
598 # try:
599 # msg = json.loads(resp)
600 # except ValueError:
601 # msg = resp
602 # raise ClientException('error: code: {}, resp: {}'.format(
603 # http_code, msg))
604 except ClientException as exc:
605 message = "failed to export metric: metric {}\n{}".format(metric, str(exc))
606 raise ClientException(message)
607
608 def get_field(self, ns_name, field):
609 self._logger.debug("")
610 nsr = self.get(ns_name)
611 print(yaml.safe_dump(nsr))
612 if nsr is None:
613 raise NotFound("failed to retrieve ns {}".format(ns_name))
614
615 if field in nsr:
616 return nsr[field]
617
618 raise NotFound("failed to find {} in ns {}".format(field, ns_name))