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):
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"
282 "{}: {}".format(key
, val
) for (key
, val
) in list(headers
.items())
284 self
._http
.set_http_header(http_header
)
285 http_code
, resp
= self
._http
.post_cmd(
286 endpoint
=self
._apiBase
, postfields_dict
=ns
288 # print('HTTP CODE: {}'.format(http_code))
289 # print('RESP: {}'.format(resp))
290 # if http_code in (200, 201, 202, 204):
292 resp
= json
.loads(resp
)
293 if not resp
or "id" not in resp
:
294 raise ClientException(
295 "unexpected response from server - {} ".format(resp
)
298 # Wait for status for NS instance creation
299 self
._wait
(resp
.get("nslcmop_id"), wait
)
306 # msg = json.loads(resp)
309 # raise ClientException(msg)
310 except ClientException
as exc
:
311 message
= "failed to create ns: {} nsd: {}\nerror:\n{}".format(
312 nsr_name
, nsd_name
, str(exc
)
314 raise ClientException(message
)
316 def list_op(self
, name
, filter=None):
317 """Returns the list of operations of a NS"""
318 self
._logger
.debug("")
321 self
._apiResource
= "/ns_lcm_op_occs"
322 self
._apiBase
= "{}{}{}".format(
323 self
._apiName
, self
._apiVersion
, self
._apiResource
327 filter_string
= "&{}".format(filter)
328 http_code
, resp
= self
._http
.get2_cmd(
329 "{}?nsInstanceId={}{}".format(self
._apiBase
, ns
["_id"], filter_string
)
331 # print('HTTP CODE: {}'.format(http_code))
332 # print('RESP: {}'.format(resp))
335 resp
= json
.loads(resp
)
338 raise ClientException("unexpected response from server")
343 # resp = json.loads(resp)
344 # msg = resp['detail']
347 raise ClientException(msg
)
348 except ClientException
as exc
:
349 message
= "failed to get operation list of NS {}:\nerror:\n{}".format(
352 raise ClientException(message
)
354 def get_op(self
, operationId
):
355 """Returns the status of an operation"""
356 self
._logger
.debug("")
357 self
._client
.get_token()
359 self
._apiResource
= "/ns_lcm_op_occs"
360 self
._apiBase
= "{}{}{}".format(
361 self
._apiName
, self
._apiVersion
, self
._apiResource
363 http_code
, resp
= self
._http
.get2_cmd(
364 "{}/{}".format(self
._apiBase
, operationId
)
366 # print('HTTP CODE: {}'.format(http_code))
367 # print('RESP: {}'.format(resp))
370 resp
= json
.loads(resp
)
373 raise ClientException("unexpected response from server")
378 # resp = json.loads(resp)
379 # msg = resp['detail']
382 raise ClientException(msg
)
383 except ClientException
as exc
:
384 message
= "failed to get status of operation {}:\nerror:\n{}".format(
385 operationId
, str(exc
)
387 raise ClientException(message
)
396 """Executes an operation on a NS"""
397 self
._logger
.debug("")
401 self
._apiResource
= "/ns_instances"
402 self
._apiBase
= "{}{}{}".format(
403 self
._apiName
, self
._apiVersion
, self
._apiResource
405 endpoint
= "{}/{}/{}".format(self
._apiBase
, ns
["_id"], op_name
)
406 # print('OP_NAME: {}'.format(op_name))
407 # print('OP_DATA: {}'.format(json.dumps(op_data)))
408 http_code
, resp
= self
._http
.post_cmd(
409 endpoint
=endpoint
, postfields_dict
=op_data
411 # print('HTTP CODE: {}'.format(http_code))
412 # print('RESP: {}'.format(resp))
413 # if http_code in (200, 201, 202, 204):
415 resp
= json
.loads(resp
)
416 if not resp
or "id" not in resp
:
417 raise ClientException(
418 "unexpected response from server - {}".format(resp
)
421 # Wait for status for NS instance action
422 # For the 'action' operation, 'id' is used
423 self
._wait
(resp
.get("id"), wait
)
429 # msg = json.loads(resp)
432 # raise ClientException(msg)
433 except ClientException
as exc
:
434 message
= "failed to exec operation {}:\nerror:\n{}".format(name
, str(exc
))
435 raise ClientException(message
)
447 """Scales a VNF by adding/removing VDUs"""
448 self
._logger
.debug("")
449 self
._client
.get_token()
452 op_data
["scaleType"] = "SCALE_VNF"
453 op_data
["scaleVnfData"] = {}
454 if scale_in
and not scale_out
:
455 op_data
["scaleVnfData"]["scaleVnfType"] = "SCALE_IN"
456 elif not scale_in
and scale_out
:
457 op_data
["scaleVnfData"]["scaleVnfType"] = "SCALE_OUT"
459 raise ClientException("you must set either 'scale_in' or 'scale_out'")
460 op_data
["scaleVnfData"]["scaleByStepData"] = {
461 "member-vnf-index": vnf_name
,
462 "scaling-group-descriptor": scaling_group
,
465 op_data
["timeout_ns_scale"] = timeout
466 op_id
= self
.exec_op(ns_name
, op_name
="scale", op_data
=op_data
, wait
=wait
)
468 except ClientException
as exc
:
469 message
= "failed to scale vnf {} of ns {}:\nerror:\n{}".format(
470 vnf_name
, ns_name
, str(exc
)
472 raise ClientException(message
)
474 def update(self
, ns_name
, data
, wait
=False):
475 """Update NS instance.
477 This function calls the NBI in order to perform an update operation
478 on a Network Service instance.
489 self
._logger
.debug("")
490 self
._client
.get_token()
492 op_data
= {"updateType": data
.pop("updateType")}
494 # Check update parameters availability according to update type
495 if op_data
["updateType"] == "CHANGE_VNFPKG":
497 data
["config"]["changeVnfPackageData"][0].get("vnfInstanceId")
498 and data
["config"]["changeVnfPackageData"][0].get("vnfdId")
500 raise ClientException("you must set both vnfInstanceId and vnfdId")
503 op_data
["changeVnfPackageData"] = {}
504 op_data
["changeVnfPackageData"]["vnfInstanceId"] = data
["config"][
505 "changeVnfPackageData"
506 ][0].get("vnfInstanceId")
508 op_data
["changeVnfPackageData"]["vnfdId"] = data
["config"][
509 "changeVnfPackageData"
512 if data
.get("timeout"):
513 op_data
["timeout_ns_update"] = data
["timeout"]
515 op_id
= self
.exec_op(ns_name
, op_name
="update", op_data
=op_data
, wait
=wait
)
518 except ClientException
as exc
:
519 message
= "failed to update ns {}:\nerror:\n{}".format(ns_name
, str(exc
))
520 raise ClientException(message
)
522 def create_alarm(self
, alarm
):
523 self
._logger
.debug("")
524 self
._client
.get_token()
526 data
["create_alarm_request"] = {}
527 data
["create_alarm_request"]["alarm_create_request"] = alarm
529 http_code
, resp
= self
._http
.post_cmd(
530 endpoint
="/test/message/alarm_request", postfields_dict
=data
532 # print('HTTP CODE: {}'.format(http_code))
533 # print('RESP: {}'.format(resp))
534 # if http_code in (200, 201, 202, 204):
535 # resp = json.loads(resp)
536 print("Alarm created")
541 # msg = json.loads(resp)
544 # raise ClientException('error: code: {}, resp: {}'.format(
546 except ClientException
as exc
:
547 message
= "failed to create alarm: alarm {}\n{}".format(alarm
, str(exc
))
548 raise ClientException(message
)
550 def delete_alarm(self
, name
):
551 self
._logger
.debug("")
552 self
._client
.get_token()
554 data
["delete_alarm_request"] = {}
555 data
["delete_alarm_request"]["alarm_delete_request"] = {}
556 data
["delete_alarm_request"]["alarm_delete_request"]["alarm_uuid"] = name
558 http_code
, resp
= self
._http
.post_cmd(
559 endpoint
="/test/message/alarm_request", postfields_dict
=data
561 # print('HTTP CODE: {}'.format(http_code))
562 # print('RESP: {}'.format(resp))
563 # if http_code in (200, 201, 202, 204):
564 # resp = json.loads(resp)
565 print("Alarm deleted")
570 # msg = json.loads(resp)
573 # raise ClientException('error: code: {}, resp: {}'.format(
575 except ClientException
as exc
:
576 message
= "failed to delete alarm: alarm {}\n{}".format(name
, str(exc
))
577 raise ClientException(message
)
579 def get_alarm(self
, project_name
=None, ns_id
=None, uuid
=None):
580 self
._client
.get_token()
582 self
._apiName
= "/nsfm"
583 self
._apiResource
= "/alarms"
584 self
._apiBase
= "{}{}{}".format(
585 self
._apiName
, self
._apiVersion
, self
._apiResource
588 # if request is for any uuid
589 http_code
, resp
= self
._http
.get2_cmd(
590 "{}/{}".format(self
._apiBase
, uuid
)
593 http_code
, resp
= self
._http
.get2_cmd(
594 "{}/{}/{}/{}".format(self
._apiBase
, uuid
, project_name
, ns_id
)
598 resp
= json
.loads(resp
)
601 raise ClientException("unexpected response from server")
604 raise ClientException(msg
)
605 except ClientException
as exc
:
606 message
= "failed to get alarm :\nerror:\n{}".format(str(exc
))
607 raise ClientException(message
)
609 def update_alarm(self
, uuid
, threshold
=None, is_enable
=None, wait
=None):
610 self
._client
.get_token()
613 op_data
["uuid"] = uuid
614 op_data
["threshold"] = threshold
615 op_data
["is_enable"] = is_enable
616 self
._apiName
= "/nsfm"
617 self
._apiResource
= "/alarms"
618 self
._apiBase
= "{}{}{}".format(
619 self
._apiName
, self
._apiVersion
, self
._apiResource
621 http_code
, resp
= self
._http
.patch_cmd(
622 endpoint
="{}".format(self
._apiBase
), postfields_dict
=op_data
625 resp
= json
.loads(resp
)
628 except ClientException
as exc
:
629 message
= "failed to update alarm :\nerror:\n{}".format(str(exc
))
630 raise ClientException(message
)
632 def export_metric(self
, metric
):
633 self
._logger
.debug("")
634 self
._client
.get_token()
636 data
["read_metric_data_request"] = metric
638 http_code
, resp
= self
._http
.post_cmd(
639 endpoint
="/test/message/metric_request", postfields_dict
=data
641 # print('HTTP CODE: {}'.format(http_code))
642 # print('RESP: {}'.format(resp))
643 # if http_code in (200, 201, 202, 204):
644 # resp = json.loads(resp)
645 return "Metric exported"
650 # msg = json.loads(resp)
653 # raise ClientException('error: code: {}, resp: {}'.format(
655 except ClientException
as exc
:
656 message
= "failed to export metric: metric {}\n{}".format(metric
, str(exc
))
657 raise ClientException(message
)
659 def get_field(self
, ns_name
, field
):
660 self
._logger
.debug("")
661 nsr
= self
.get(ns_name
)
662 print(yaml
.safe_dump(nsr
))
664 raise NotFound("failed to retrieve ns {}".format(ns_name
))
669 raise NotFound("failed to find {} in ns {}".format(field
, ns_name
))
679 self
._logger
.debug("")
680 self
._client
.get_token()
684 op_data
["timeout_ns_heal"] = timeout
685 op_id
= self
.exec_op(ns_name
, op_name
="heal", op_data
=op_data
, wait
=wait
)
687 except ClientException
as exc
:
688 message
= "failed to heal ns {}:\nerror:\n{}".format(ns_name
, str(exc
))
689 raise ClientException(message
)