1 # Copyright 2018 Telefonica
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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
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
31 def __init__(self
, http
=None, client
=None):
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
43 def _wait(self
, id, wait_time
, deleteFlag
=False, entity
="NS"):
44 self
._logger
.debug("")
45 # Endpoint to get operation status
46 apiUrlStatus
= "{}{}{}".format(
47 self
._apiName
, self
._apiVersion
, "/ns_lcm_op_occs"
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(
58 deleteFlag
=deleteFlag
,
61 def list(self
, filter=None):
62 """Returns a list of NS"""
63 self
._logger
.debug("")
64 self
._client
.get_token()
67 filter_string
= "?{}".format(filter)
68 _
, resp
= self
._http
.get2_cmd("{}{}".format(self
._apiBase
, filter_string
))
70 return json
.loads(resp
)
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():
82 for ns
in self
.list():
83 if name
== ns
["name"]:
85 raise NotFound("ns '{}' not found".format(name
))
87 def get_individual(self
, name
):
88 self
._logger
.debug("")
89 self
._client
.get_token()
91 if not utils
.validate_uuid4(name
):
92 for ns
in self
.list():
93 if name
== ns
["name"]:
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))
101 return json
.loads(resp
)
103 raise NotFound("ns '{}' not found".format(name
))
104 raise NotFound("ns '{}' not found".format(name
))
106 def delete(self
, name
, force
=False, config
=None, wait
=False):
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
119 self
._logger
.debug("")
121 querystring_list
= []
124 ns_config
= yaml
.safe_load(config
)
125 querystring_list
+= ["{}={}".format(k
, v
) for k
, v
in ns_config
.items()]
127 querystring_list
.append("FORCE=True")
129 querystring
= "?" + "&".join(querystring_list
)
130 http_code
, resp
= self
._http
.delete_cmd(
131 "{}/{}{}".format(self
._apiBase
, ns
["_id"], querystring
)
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))
140 resp
= json
.loads(resp
)
141 # For the 'delete' operation, '_id' is used
142 self
._wait
(resp
.get("_id"), wait
, deleteFlag
=True)
144 print("Deletion in progress")
145 elif http_code
== 204:
151 # msg = json.loads(resp)
154 raise ClientException("failed to delete ns {} - {}".format(name
, msg
))
163 description
="default description",
164 admin_status
="ENABLED",
168 self
._logger
.debug("")
169 self
._client
.get_token()
170 nsd
= self
._client
.nsd
.get(nsd_name
)
175 def get_vim_account_id(vim_account
):
176 self
._logger
.debug("")
177 if vim_account_id
.get(vim_account
):
178 return vim_account_id
[vim_account
]
179 vim
= self
._client
.vim
.get(vim_account
)
181 raise NotFound("cannot find vim account '{}'".format(vim_account
))
182 vim_account_id
[vim_account
] = vim
["_id"]
185 def get_wim_account_id(wim_account
):
186 self
._logger
.debug("")
187 # wim_account can be False (boolean) to indicate not use wim account
188 if not isinstance(wim_account
, str):
190 if wim_account_id
.get(wim_account
):
191 return wim_account_id
[wim_account
]
192 wim
= self
._client
.wim
.get(wim_account
)
194 raise NotFound("cannot find wim account '{}'".format(wim_account
))
195 wim_account_id
[wim_account
] = wim
["_id"]
198 vim_id
= get_vim_account_id(account
)
200 ns
["nsdId"] = nsd
["_id"]
201 ns
["nsName"] = nsr_name
202 ns
["nsDescription"] = description
203 ns
["vimAccountId"] = vim_id
204 # ns['userdata'] = {}
205 # ns['userdata']['key1']='value1'
206 # ns['userdata']['key2']='value2'
208 if ssh_keys
is not None:
210 for pubkeyfile
in ssh_keys
.split(","):
211 with
open(pubkeyfile
, "r") as f
:
212 ns
["ssh_keys"].append(f
.read())
214 ns
["timeout_ns_deploy"] = timeout
216 ns_config
= yaml
.safe_load(config
)
217 if "vim-network-name" in ns_config
:
218 ns_config
["vld"] = ns_config
.pop("vim-network-name")
219 if "vld" in ns_config
:
220 if not isinstance(ns_config
["vld"], list):
221 raise ClientException(
222 "Error at --config 'vld' must be a list of dictionaries"
224 for vld
in ns_config
["vld"]:
225 if not isinstance(vld
, dict):
226 raise ClientException(
227 "Error at --config 'vld' must be a list of dictionaries"
229 if vld
.get("vim-network-name"):
230 if isinstance(vld
["vim-network-name"], dict):
231 vim_network_name_dict
= {}
232 for vim_account
, vim_net
in vld
["vim-network-name"].items():
233 vim_network_name_dict
[
234 get_vim_account_id(vim_account
)
236 vld
["vim-network-name"] = vim_network_name_dict
237 if "wim_account" in vld
and vld
["wim_account"] is not None:
238 vld
["wimAccountId"] = get_wim_account_id(vld
.pop("wim_account"))
239 if "vnf" in ns_config
:
240 for vnf
in ns_config
["vnf"]:
241 if vnf
.get("vim_account"):
242 vnf
["vimAccountId"] = get_vim_account_id(vnf
.pop("vim_account"))
244 if "additionalParamsForNs" in ns_config
:
245 if not isinstance(ns_config
["additionalParamsForNs"], dict):
246 raise ClientException(
247 "Error at --config 'additionalParamsForNs' must be a dictionary"
249 if "additionalParamsForVnf" in ns_config
:
250 if not isinstance(ns_config
["additionalParamsForVnf"], list):
251 raise ClientException(
252 "Error at --config 'additionalParamsForVnf' must be a list"
254 for additional_param_vnf
in ns_config
["additionalParamsForVnf"]:
255 if not isinstance(additional_param_vnf
, dict):
256 raise ClientException(
257 "Error at --config 'additionalParamsForVnf' items must be dictionaries"
259 if not additional_param_vnf
.get("member-vnf-index"):
260 raise ClientException(
261 "Error at --config 'additionalParamsForVnf' items must contain "
264 if "wim_account" in ns_config
:
265 wim_account
= ns_config
.pop("wim_account")
266 if wim_account
is not None:
267 ns
["wimAccountId"] = get_wim_account_id(wim_account
)
268 # rest of parameters without any transformation or checking
269 # "timeout_ns_deploy"
273 # print(yaml.safe_dump(ns))
275 self
._apiResource
= "/ns_instances_content"
276 self
._apiBase
= "{}{}{}".format(
277 self
._apiName
, self
._apiVersion
, self
._apiResource
279 headers
= self
._client
._headers
280 headers
["Content-Type"] = "application/yaml"
281 self
._http
.set_http_header(headers
)
282 http_code
, resp
= self
._http
.post_cmd(
283 endpoint
=self
._apiBase
, postfields_dict
=ns
285 # print('HTTP CODE: {}'.format(http_code))
286 # print('RESP: {}'.format(resp))
287 # if http_code in (200, 201, 202, 204):
289 resp
= json
.loads(resp
)
290 if not resp
or "id" not in resp
:
291 raise ClientException(
292 "unexpected response from server - {} ".format(resp
)
295 # Wait for status for NS instance creation
296 self
._wait
(resp
.get("nslcmop_id"), wait
)
303 # msg = json.loads(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
)
311 raise ClientException(message
)
313 def list_op(self
, name
, filter=None):
314 """Returns the list of operations of a NS"""
315 self
._logger
.debug("")
318 self
._apiResource
= "/ns_lcm_op_occs"
319 self
._apiBase
= "{}{}{}".format(
320 self
._apiName
, self
._apiVersion
, self
._apiResource
324 filter_string
= "&{}".format(filter)
325 http_code
, resp
= self
._http
.get2_cmd(
326 "{}?nsInstanceId={}{}".format(self
._apiBase
, ns
["_id"], filter_string
)
328 # print('HTTP CODE: {}'.format(http_code))
329 # print('RESP: {}'.format(resp))
332 resp
= json
.loads(resp
)
335 raise ClientException("unexpected response from server")
340 # resp = json.loads(resp)
341 # msg = resp['detail']
344 raise ClientException(msg
)
345 except ClientException
as exc
:
346 message
= "failed to get operation list of NS {}:\nerror:\n{}".format(
349 raise ClientException(message
)
351 def get_op(self
, operationId
):
352 """Returns the status of an operation"""
353 self
._logger
.debug("")
354 self
._client
.get_token()
356 self
._apiResource
= "/ns_lcm_op_occs"
357 self
._apiBase
= "{}{}{}".format(
358 self
._apiName
, self
._apiVersion
, self
._apiResource
360 http_code
, resp
= self
._http
.get2_cmd(
361 "{}/{}".format(self
._apiBase
, operationId
)
363 # print('HTTP CODE: {}'.format(http_code))
364 # print('RESP: {}'.format(resp))
367 resp
= json
.loads(resp
)
370 raise ClientException("unexpected response from server")
375 # resp = json.loads(resp)
376 # msg = resp['detail']
379 raise ClientException(msg
)
380 except ClientException
as exc
:
381 message
= "failed to get status of operation {}:\nerror:\n{}".format(
382 operationId
, str(exc
)
384 raise ClientException(message
)
393 """Executes an operation on a NS"""
394 self
._logger
.debug("")
398 self
._apiResource
= "/ns_instances"
399 self
._apiBase
= "{}{}{}".format(
400 self
._apiName
, self
._apiVersion
, self
._apiResource
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
408 # print('HTTP CODE: {}'.format(http_code))
409 # print('RESP: {}'.format(resp))
410 # if http_code in (200, 201, 202, 204):
412 resp
= json
.loads(resp
)
413 if not resp
or "id" not in resp
:
414 raise ClientException(
415 "unexpected response from server - {}".format(resp
)
418 # Wait for status for NS instance action
419 # For the 'action' operation, 'id' is used
420 self
._wait
(resp
.get("id"), wait
)
426 # msg = json.loads(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
)
434 def cancel_op(self
, operation_id
, cancel_mode
, wait
=False):
435 """Cancels an LCM operation"""
436 self
._client
.get_token()
437 self
._apiResource
= "/ns_lcm_op_occs"
438 self
._apiBase
= "{}{}{}".format(
439 self
._apiName
, self
._apiVersion
, self
._apiResource
441 endpoint
= "{}/{}/cancel".format(self
._apiBase
, operation_id
)
442 op_data
= {"cancelMode": cancel_mode
}
444 http_code
, resp
= self
._http
.post_cmd(
445 endpoint
=endpoint
, postfields_dict
=op_data
449 self
._wait
(operation_id
, wait
, deleteFlag
=True, entity
="OPCANCEL")
451 print("Cancellation in progress")
454 raise ClientException(msg
)
455 except ClientException
as exc
:
456 message
= "failed to exec operation {}:\nerror:\n{}".format(
457 operation_id
, str(exc
)
459 raise ClientException(message
)
471 """Scales a VNF by adding/removing VDUs"""
472 self
._logger
.debug("")
473 self
._client
.get_token()
476 op_data
["scaleType"] = "SCALE_VNF"
477 op_data
["scaleVnfData"] = {}
478 if scale_in
and not scale_out
:
479 op_data
["scaleVnfData"]["scaleVnfType"] = "SCALE_IN"
480 elif not scale_in
and scale_out
:
481 op_data
["scaleVnfData"]["scaleVnfType"] = "SCALE_OUT"
483 raise ClientException("you must set either 'scale_in' or 'scale_out'")
484 op_data
["scaleVnfData"]["scaleByStepData"] = {
485 "member-vnf-index": vnf_name
,
486 "scaling-group-descriptor": scaling_group
,
489 op_data
["timeout_ns_scale"] = timeout
490 op_id
= self
.exec_op(ns_name
, op_name
="scale", op_data
=op_data
, wait
=wait
)
492 except ClientException
as exc
:
493 message
= "failed to scale vnf {} of ns {}:\nerror:\n{}".format(
494 vnf_name
, ns_name
, str(exc
)
496 raise ClientException(message
)
498 def update(self
, ns_name
, data
, wait
=False):
499 """Update NS instance.
501 This function calls the NBI in order to perform an update operation
502 on a Network Service instance.
513 self
._logger
.debug("")
514 self
._client
.get_token()
516 op_data
= {"updateType": data
.pop("updateType")}
518 # Check update parameters availability according to update type
519 if op_data
["updateType"] == "CHANGE_VNFPKG":
521 data
["config"]["changeVnfPackageData"][0].get("vnfInstanceId")
522 and data
["config"]["changeVnfPackageData"][0].get("vnfdId")
524 raise ClientException("you must set both vnfInstanceId and vnfdId")
527 op_data
["changeVnfPackageData"] = {}
528 op_data
["changeVnfPackageData"]["vnfInstanceId"] = data
["config"][
529 "changeVnfPackageData"
530 ][0].get("vnfInstanceId")
532 op_data
["changeVnfPackageData"]["vnfdId"] = data
["config"][
533 "changeVnfPackageData"
536 if data
.get("timeout"):
537 op_data
["timeout_ns_update"] = data
["timeout"]
539 op_id
= self
.exec_op(ns_name
, op_name
="update", op_data
=op_data
, wait
=wait
)
542 except ClientException
as exc
:
543 message
= "failed to update ns {}:\nerror:\n{}".format(ns_name
, str(exc
))
544 raise ClientException(message
)
546 def create_alarm(self
, alarm
):
547 self
._logger
.debug("")
548 self
._client
.get_token()
550 data
["create_alarm_request"] = {}
551 data
["create_alarm_request"]["alarm_create_request"] = alarm
553 http_code
, resp
= self
._http
.post_cmd(
554 endpoint
="/test/message/alarm_request", postfields_dict
=data
556 # print('HTTP CODE: {}'.format(http_code))
557 # print('RESP: {}'.format(resp))
558 # if http_code in (200, 201, 202, 204):
559 # resp = json.loads(resp)
560 print("Alarm created")
565 # msg = json.loads(resp)
568 # raise ClientException('error: code: {}, resp: {}'.format(
570 except ClientException
as exc
:
571 message
= "failed to create alarm: alarm {}\n{}".format(alarm
, str(exc
))
572 raise ClientException(message
)
574 def delete_alarm(self
, name
):
575 self
._logger
.debug("")
576 self
._client
.get_token()
578 data
["delete_alarm_request"] = {}
579 data
["delete_alarm_request"]["alarm_delete_request"] = {}
580 data
["delete_alarm_request"]["alarm_delete_request"]["alarm_uuid"] = name
582 http_code
, resp
= self
._http
.post_cmd(
583 endpoint
="/test/message/alarm_request", postfields_dict
=data
585 # print('HTTP CODE: {}'.format(http_code))
586 # print('RESP: {}'.format(resp))
587 # if http_code in (200, 201, 202, 204):
588 # resp = json.loads(resp)
589 print("Alarm deleted")
594 # msg = json.loads(resp)
597 # raise ClientException('error: code: {}, resp: {}'.format(
599 except ClientException
as exc
:
600 message
= "failed to delete alarm: alarm {}\n{}".format(name
, str(exc
))
601 raise ClientException(message
)
603 def get_alarm(self
, project_name
=None, ns_id
=None, uuid
=None):
604 self
._client
.get_token()
606 self
._apiName
= "/nsfm"
607 self
._apiResource
= "/alarms"
608 self
._apiBase
= "{}{}{}".format(
609 self
._apiName
, self
._apiVersion
, self
._apiResource
612 # if request is for any uuid
613 http_code
, resp
= self
._http
.get2_cmd(
614 "{}/{}".format(self
._apiBase
, uuid
)
617 http_code
, resp
= self
._http
.get2_cmd(
618 "{}/{}/{}/{}".format(self
._apiBase
, uuid
, project_name
, ns_id
)
622 resp
= json
.loads(resp
)
625 raise ClientException("unexpected response from server")
628 raise ClientException(msg
)
629 except ClientException
as exc
:
630 message
= "failed to get alarm :\nerror:\n{}".format(str(exc
))
631 raise ClientException(message
)
633 def update_alarm(self
, uuid
, threshold
=None, is_enable
=None, wait
=None):
634 self
._client
.get_token()
637 op_data
["uuid"] = uuid
638 op_data
["threshold"] = threshold
639 op_data
["is_enable"] = is_enable
640 self
._apiName
= "/nsfm"
641 self
._apiResource
= "/alarms"
642 self
._apiBase
= "{}{}{}".format(
643 self
._apiName
, self
._apiVersion
, self
._apiResource
645 http_code
, resp
= self
._http
.patch_cmd(
646 endpoint
="{}".format(self
._apiBase
), postfields_dict
=op_data
649 resp
= json
.loads(resp
)
652 except ClientException
as exc
:
653 message
= "failed to update alarm :\nerror:\n{}".format(str(exc
))
654 raise ClientException(message
)
656 def export_metric(self
, metric
):
657 self
._logger
.debug("")
658 self
._client
.get_token()
660 data
["read_metric_data_request"] = metric
662 http_code
, resp
= self
._http
.post_cmd(
663 endpoint
="/test/message/metric_request", postfields_dict
=data
665 # print('HTTP CODE: {}'.format(http_code))
666 # print('RESP: {}'.format(resp))
667 # if http_code in (200, 201, 202, 204):
668 # resp = json.loads(resp)
669 return "Metric exported"
674 # msg = json.loads(resp)
677 # raise ClientException('error: code: {}, resp: {}'.format(
679 except ClientException
as exc
:
680 message
= "failed to export metric: metric {}\n{}".format(metric
, str(exc
))
681 raise ClientException(message
)
683 def get_field(self
, ns_name
, field
):
684 self
._logger
.debug("")
685 nsr
= self
.get(ns_name
)
686 print(yaml
.safe_dump(nsr
))
688 raise NotFound("failed to retrieve ns {}".format(ns_name
))
693 raise NotFound("failed to find {} in ns {}".format(field
, ns_name
))
703 self
._logger
.debug("")
704 self
._client
.get_token()
708 op_data
["timeout_ns_heal"] = timeout
709 op_id
= self
.exec_op(ns_name
, op_name
="heal", op_data
=op_data
, wait
=wait
)
711 except ClientException
as exc
:
712 message
= "failed to heal ns {}:\nerror:\n{}".format(ns_name
, str(exc
))
713 raise ClientException(message
)