fe3a2bac7dbe3763491d182191c0285c27651845
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
, op_id
, wait_time
, delete_flag
=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
=delete_flag
,
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 termination 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
, delete_flag
=True)
144 print("Deletion in progress")
145 elif http_code
== 204:
149 raise ClientException("failed to delete ns {} - {}".format(name
, msg
))
151 def _get_vim_account_id(self
, vim_account
: str, vim_account_dict
: dict) -> str:
152 """Get VIM account ID.
154 vim_account (str): VIM account id as string
155 vim_account_dict (dict): A dictionary which includes vim account id
158 vim_id (str): VIM account id as string
163 self
._logger
.debug("")
164 if vim_account_dict
.get(vim_account
):
165 return vim_account_dict
[vim_account
]
166 vim
= self
._client
.vim
.get(vim_account
)
168 raise NotFound("cannot find vim account '{}'".format(vim_account
))
169 vim_account_dict
[vim_account
] = vim
["_id"]
172 def _get_wim_account_id(self
, wim_account
: str, wim_account_dict
: dict) -> str:
173 """Get WIM account ID.
175 wim_account (str): WIM account id as string
176 wim_account_dict (dict): A dictionary which includes wim account id
179 wim_id (str): WIM account id as string
184 self
._logger
.debug("")
185 # wim_account can be False (boolean) to indicate not use wim account
186 if not isinstance(wim_account
, str):
188 if wim_account_dict
.get(wim_account
):
189 return wim_account_dict
[wim_account
]
190 wim
= self
._client
.wim
.get(wim_account
)
192 raise NotFound("cannot find wim account '{}'".format(wim_account
))
193 wim_account_dict
[wim_account
] = wim
["_id"]
196 def _get_paas_account_id(self
, paas_account
: str) -> str:
197 """Get PaaS account ID.
199 paas_account (str): PaaS account id as string
202 paas_id (str): PaaS account id as string
207 self
._logger
.debug("")
208 paas
= self
._client
.paas
.get(paas_account
)
210 raise NotFound("cannot find PaaS account '{}'".format(paas_account
))
213 def _update_vnf_in_ns_config(self
, ns_config
: dict, vim_account_dict
: dict) -> dict:
214 """Update vnf field in ns_config.
216 ns_config (dict): NS config dictionary which includes additional params
217 vim_account_dict (dict): A dictionary which includes vim account id
220 ns (dict): NS dictionary
222 if "vnf" in ns_config
:
223 for vnf
in ns_config
["vnf"]:
224 if vnf
.get("vim_account"):
225 vnf
["vimAccountId"] = self
._get
_vim
_account
_id
(
226 vnf
.pop("vim_account"), vim_account_dict
230 def _update_wim_account_in_ns(
231 self
, ns_config
: dict, wim_account_dict
: dict, ns
: dict
233 """Update WIM_account in NS dictionary.
235 ns_config (dict): NS config dictionary which includes additional params
236 wim_account_dict (dict): A dictionary which includes wim account id
237 ns (dict): NS dictionary which includes ns_id, ns_name, description etc.
240 ns (dict): NS dictionary
242 if "wim_account" in ns_config
:
243 wim_account
= ns_config
.pop("wim_account")
244 if wim_account
is not None:
245 ns
["wimAccountId"] = self
._get
_wim
_account
_id
(
246 wim_account
, wim_account_dict
250 def _update_vld_in_ns_config(
251 self
, ns_config
: dict, vim_account_dict
: dict, wim_account_dict
: dict
253 """Validating the additionalParamsForNs and additionalParamsForVnf in ns_config.
256 ns_config (dict): NS config dictionary which includes additional params
257 vim_account_dict (dict): A dictionary which includes vim account id
258 wim_account_dict (dict): A dictionary which includes wim account id
261 ns_config (dict): NS config dictionary which includes additional params
266 if "vld" in ns_config
:
267 if not isinstance(ns_config
["vld"], list):
268 raise ClientException(
269 "Error at --config 'vld' must be a list of dictionaries"
271 for vld
in ns_config
["vld"]:
272 if not isinstance(vld
, dict):
273 raise ClientException(
274 "Error at --config 'vld' must be a list of dictionaries"
276 if vld
.get("vim-network-name"):
277 if isinstance(vld
["vim-network-name"], dict):
278 vim_network_name_dict
= {}
279 for vim_account
, vim_net
in vld
["vim-network-name"].items():
280 vim_network_name_dict
[
281 self
._get
_vim
_account
_id
(vim_account
, vim_account_dict
)
283 vld
["vim-network-name"] = vim_network_name_dict
284 if "wim_account" in vld
and vld
["wim_account"] is not None:
285 vld
["wimAccountId"] = self
._get
_wim
_account
_id
(
286 vld
.pop("wim_account"), wim_account_dict
290 def _validate_additional_params_in_ns_config(self
, ns_config
: dict) -> None:
291 """Validating the additionalParamsForNs and additionalParamsForVnf in ns_config.
293 ns_config (dict): NS config dictionary which includes additional params
298 if "additionalParamsForNs" in ns_config
:
299 if not isinstance(ns_config
["additionalParamsForNs"], dict):
300 raise ClientException(
301 "Error at --config 'additionalParamsForNs' must be a dictionary"
303 if "additionalParamsForVnf" in ns_config
:
304 if not isinstance(ns_config
["additionalParamsForVnf"], list):
305 raise ClientException(
306 "Error at --config 'additionalParamsForVnf' must be a list"
308 for additional_param_vnf
in ns_config
["additionalParamsForVnf"]:
309 if not isinstance(additional_param_vnf
, dict):
310 raise ClientException(
311 "Error at --config 'additionalParamsForVnf' items must be dictionaries"
313 if not additional_param_vnf
.get("member-vnf-index"):
314 raise ClientException(
315 "Error at --config 'additionalParamsForVnf' items must contain "
319 def process_ns_create_with_vim_account(
326 ssh_keys
: str = None,
329 """Process NS create request which includes VIM Account.
331 vim_account (str): VIM Account id as string
332 nsd (dict): A dictionary which includes network service description
333 nsr_name (str): Network service record name
334 description (str): Service description
335 config (dict): Placeholder for additional configuration
336 ssh_keys (str): ssh-key file
337 timeout (int): Max time to wait (seconds)
340 ns (dict): Payload for ns create request
345 vim_account_dict
= {}
346 wim_account_dict
= {}
347 vim_id
= self
._get
_vim
_account
_id
(vim_account
, vim_account_dict
)
351 "nsDescription": description
,
352 "vimAccountId": vim_id
,
355 if ssh_keys
is not None:
357 for pubkeyfile
in ssh_keys
.split(","):
358 with
open(pubkeyfile
, "r") as f
:
359 ns
["ssh_keys"].append(f
.read())
362 ns
["timeout_ns_deploy"] = timeout
365 ns_config
= yaml
.safe_load(config
)
366 if "vim-network-name" in ns_config
:
367 ns_config
["vld"] = ns_config
.pop("vim-network-name")
369 ns_config
= self
._update
_vld
_in
_ns
_config
(
370 ns_config
, vim_account_dict
, wim_account_dict
372 ns_config
= self
._update
_vnf
_in
_ns
_config
(ns_config
, vim_account_dict
)
373 self
._validate
_additional
_params
_in
_ns
_config
(ns_config
)
374 ns
= self
._update
_wim
_account
_in
_ns
(ns_config
, vim_account_dict
, ns
)
379 def process_ns_create_with_paas_account(
388 """Process NS create request which includes PaaS Account.
390 paas_account (str): PaaS Account id as string
391 nsd (dict): A dictionary which includes network service description
392 nsr_name (str): Network service record name
393 description (str): Service description
394 config (dict): Placeholder for additional configuration
395 timeout (int): Max time to wait (seconds)
398 ns (dict): Payload for ns create request
403 paas_id
= self
._get
_paas
_account
_id
(paas_account
)
407 "nsDescription": description
,
408 "paasAccountId": paas_id
,
412 ns
["timeout_ns_deploy"] = timeout
415 ns_config
= yaml
.safe_load(config
)
416 self
._validate
_additional
_params
_in
_ns
_config
(ns_config
)
425 vim_account
: str = None,
426 paas_account
: str = None,
428 ssh_keys
: str = None,
429 description
: str = "default description",
430 admin_status
: str = "ENABLED",
434 """NS create request which includes PaaS Account or VIM account.
436 nsd_name (dict): A dictionary which includes network service description
437 nsr_name (str): Network service record name
438 vim_account (str): VIM account ID as string
439 paas_account (str): PaaS Account id as string
440 config (dict): Placeholder for additional configuration
441 ssh_keys (str): ssh-key file
442 description (str): Service description
443 admin_status (str): Administration Status
444 wait (Boolean): True or False
445 timeout (int): Max time to wait (seconds)
448 response id (str): Response ID
453 self
._logger
.debug("")
455 if not (vim_account
or paas_account
):
456 raise ClientException(
457 "Both of vim_account and paas_account options are empty."
460 if vim_account
and paas_account
:
461 raise ClientException(
462 "Both of vim_account and paas_account options are set."
465 self
._client
.get_token()
466 nsd
= self
._client
.nsd
.get(nsd_name
)
469 # VIM account is provided as input parameter.
470 ns
= self
.process_ns_create_with_vim_account(
481 # PaaS account is provided as input parameter.
482 ns
= self
.process_ns_create_with_paas_account(
483 paas_account
, nsd
, nsr_name
, description
, config
=config
, timeout
=timeout
487 self
._apiResource
= "/ns_instances_content"
488 self
._apiBase
= "{}{}{}".format(
489 self
._apiName
, self
._apiVersion
, self
._apiResource
491 headers
= self
._client
._headers
492 headers
["Content-Type"] = "application/yaml"
494 "{}: {}".format(key
, val
) for (key
, val
) in list(headers
.items())
496 self
._http
.set_http_header(http_header
)
497 http_code
, resp
= self
._http
.post_cmd(
498 endpoint
=self
._apiBase
, postfields_dict
=ns
502 resp
= json
.loads(resp
)
503 print(str(resp
["id"]))
505 if not resp
or "id" not in resp
:
506 raise ClientException(
507 "unexpected response from server - {} ".format(resp
)
510 # Wait for status for NS instance creation
511 self
._wait
(resp
.get("nslcmop_id"), wait
)
515 except ClientException
as exc
:
516 message
= "failed to create ns: {} nsd: {}\nerror:\n{}".format(
517 nsr_name
, nsd_name
, str(exc
)
519 raise ClientException(message
)
521 def list_op(self
, name
, filter=None):
522 """Returns the list of operations of a NS"""
523 self
._logger
.debug("")
526 self
._apiResource
= "/ns_lcm_op_occs"
527 self
._apiBase
= "{}{}{}".format(
528 self
._apiName
, self
._apiVersion
, self
._apiResource
532 filter_string
= "&{}".format(filter)
533 http_code
, resp
= self
._http
.get2_cmd(
534 "{}?nsInstanceId={}{}".format(self
._apiBase
, ns
["_id"], filter_string
)
536 # print('HTTP CODE: {}'.format(http_code))
537 # print('RESP: {}'.format(resp))
540 resp
= json
.loads(resp
)
543 raise ClientException("unexpected response from server")
548 # resp = json.loads(resp)
549 # msg = resp['detail']
552 raise ClientException(msg
)
553 except ClientException
as exc
:
554 message
= "failed to get operation list of NS {}:\nerror:\n{}".format(
557 raise ClientException(message
)
559 def get_op(self
, operationId
):
560 """Returns the status of an operation"""
561 self
._logger
.debug("")
562 self
._client
.get_token()
564 self
._apiResource
= "/ns_lcm_op_occs"
565 self
._apiBase
= "{}{}{}".format(
566 self
._apiName
, self
._apiVersion
, self
._apiResource
568 http_code
, resp
= self
._http
.get2_cmd(
569 "{}/{}".format(self
._apiBase
, operationId
)
571 # print('HTTP CODE: {}'.format(http_code))
572 # print('RESP: {}'.format(resp))
575 resp
= json
.loads(resp
)
578 raise ClientException("unexpected response from server")
583 # resp = json.loads(resp)
584 # msg = resp['detail']
587 raise ClientException(msg
)
588 except ClientException
as exc
:
589 message
= "failed to get status of operation {}:\nerror:\n{}".format(
590 operationId
, str(exc
)
592 raise ClientException(message
)
601 """Executes an operation on a NS"""
602 self
._logger
.debug("")
606 self
._apiResource
= "/ns_instances"
607 self
._apiBase
= "{}{}{}".format(
608 self
._apiName
, self
._apiVersion
, self
._apiResource
610 endpoint
= "{}/{}/{}".format(self
._apiBase
, ns
["_id"], op_name
)
611 # print('OP_NAME: {}'.format(op_name))
612 # print('OP_DATA: {}'.format(json.dumps(op_data)))
613 http_code
, resp
= self
._http
.post_cmd(
614 endpoint
=endpoint
, postfields_dict
=op_data
616 # print('HTTP CODE: {}'.format(http_code))
617 # print('RESP: {}'.format(resp))
618 # if http_code in (200, 201, 202, 204):
620 resp
= json
.loads(resp
)
621 if not resp
or "id" not in resp
:
622 raise ClientException(
623 "unexpected response from server - {}".format(resp
)
626 # Wait for status for NS instance action
627 # For the 'action' operation, 'id' is used
628 self
._wait
(resp
.get("id"), wait
)
634 # msg = json.loads(resp)
637 # raise ClientException(msg)
638 except ClientException
as exc
:
639 message
= "failed to exec operation {}:\nerror:\n{}".format(name
, str(exc
))
640 raise ClientException(message
)
652 """Scales a VNF by adding/removing VDUs"""
653 self
._logger
.debug("")
654 self
._client
.get_token()
657 op_data
["scaleType"] = "SCALE_VNF"
658 op_data
["scaleVnfData"] = {}
659 if scale_in
and not scale_out
:
660 op_data
["scaleVnfData"]["scaleVnfType"] = "SCALE_IN"
661 elif not scale_in
and scale_out
:
662 op_data
["scaleVnfData"]["scaleVnfType"] = "SCALE_OUT"
664 raise ClientException("you must set either 'scale_in' or 'scale_out'")
665 op_data
["scaleVnfData"]["scaleByStepData"] = {
666 "member-vnf-index": vnf_name
,
667 "scaling-group-descriptor": scaling_group
,
670 op_data
["timeout_ns_scale"] = timeout
671 op_id
= self
.exec_op(ns_name
, op_name
="scale", op_data
=op_data
, wait
=wait
)
673 except ClientException
as exc
:
674 message
= "failed to scale vnf {} of ns {}:\nerror:\n{}".format(
675 vnf_name
, ns_name
, str(exc
)
677 raise ClientException(message
)
679 def update(self
, ns_name
, data
, wait
=False):
680 """Update NS instance.
682 This function calls the NBI in order to perform an update operation
683 on a Network Service instance.
694 self
._logger
.debug("")
695 self
._client
.get_token()
697 op_data
= {"updateType": data
.pop("updateType")}
699 # Check update parameters availability according to update type
700 if op_data
["updateType"] == "CHANGE_VNFPKG":
702 data
["config"]["changeVnfPackageData"][0].get("vnfInstanceId")
703 and data
["config"]["changeVnfPackageData"][0].get("vnfdId")
705 raise ClientException("you must set both vnfInstanceId and vnfdId")
708 op_data
["changeVnfPackageData"] = {}
709 op_data
["changeVnfPackageData"]["vnfInstanceId"] = data
["config"][
710 "changeVnfPackageData"
711 ][0].get("vnfInstanceId")
713 op_data
["changeVnfPackageData"]["vnfdId"] = data
["config"][
714 "changeVnfPackageData"
717 if data
.get("timeout"):
718 op_data
["timeout_ns_update"] = data
["timeout"]
720 op_id
= self
.exec_op(ns_name
, op_name
="update", op_data
=op_data
, wait
=wait
)
723 except ClientException
as exc
:
724 message
= "failed to update ns {}:\nerror:\n{}".format(ns_name
, str(exc
))
725 raise ClientException(message
)
727 def create_alarm(self
, alarm
):
728 self
._logger
.debug("")
729 self
._client
.get_token()
731 data
["create_alarm_request"] = {}
732 data
["create_alarm_request"]["alarm_create_request"] = alarm
734 http_code
, resp
= self
._http
.post_cmd(
735 endpoint
="/test/message/alarm_request", postfields_dict
=data
737 # print('HTTP CODE: {}'.format(http_code))
738 # print('RESP: {}'.format(resp))
739 # if http_code in (200, 201, 202, 204):
740 # resp = json.loads(resp)
741 print("Alarm created")
746 # msg = json.loads(resp)
749 # raise ClientException('error: code: {}, resp: {}'.format(
751 except ClientException
as exc
:
752 message
= "failed to create alarm: alarm {}\n{}".format(alarm
, str(exc
))
753 raise ClientException(message
)
755 def delete_alarm(self
, name
):
756 self
._logger
.debug("")
757 self
._client
.get_token()
759 data
["delete_alarm_request"] = {}
760 data
["delete_alarm_request"]["alarm_delete_request"] = {}
761 data
["delete_alarm_request"]["alarm_delete_request"]["alarm_uuid"] = name
763 http_code
, resp
= self
._http
.post_cmd(
764 endpoint
="/test/message/alarm_request", postfields_dict
=data
766 # print('HTTP CODE: {}'.format(http_code))
767 # print('RESP: {}'.format(resp))
768 # if http_code in (200, 201, 202, 204):
769 # resp = json.loads(resp)
770 print("Alarm deleted")
775 # msg = json.loads(resp)
778 # raise ClientException('error: code: {}, resp: {}'.format(
780 except ClientException
as exc
:
781 message
= "failed to delete alarm: alarm {}\n{}".format(name
, str(exc
))
782 raise ClientException(message
)
784 def get_alarm(self
, project_name
=None, ns_id
=None, uuid
=None):
785 self
._client
.get_token()
787 self
._apiName
= "/nsfm"
788 self
._apiResource
= "/alarms"
789 self
._apiBase
= "{}{}{}".format(
790 self
._apiName
, self
._apiVersion
, self
._apiResource
793 # if request is for any uuid
794 http_code
, resp
= self
._http
.get2_cmd(
795 "{}/{}".format(self
._apiBase
, uuid
)
798 http_code
, resp
= self
._http
.get2_cmd(
799 "{}/{}/{}/{}".format(self
._apiBase
, uuid
, project_name
, ns_id
)
803 resp
= json
.loads(resp
)
806 raise ClientException("unexpected response from server")
809 raise ClientException(msg
)
810 except ClientException
as exc
:
811 message
= "failed to get alarm :\nerror:\n{}".format(str(exc
))
812 raise ClientException(message
)
814 def update_alarm(self
, uuid
, threshold
=None, is_enable
=None, wait
=None):
815 self
._client
.get_token()
818 op_data
["uuid"] = uuid
819 op_data
["threshold"] = threshold
820 op_data
["is_enable"] = is_enable
821 self
._apiName
= "/nsfm"
822 self
._apiResource
= "/alarms"
823 self
._apiBase
= "{}{}{}".format(
824 self
._apiName
, self
._apiVersion
, self
._apiResource
826 http_code
, resp
= self
._http
.patch_cmd(
827 endpoint
="{}".format(self
._apiBase
), postfields_dict
=op_data
830 resp
= json
.loads(resp
)
833 except ClientException
as exc
:
834 message
= "failed to update alarm :\nerror:\n{}".format(str(exc
))
835 raise ClientException(message
)
837 def export_metric(self
, metric
):
838 self
._logger
.debug("")
839 self
._client
.get_token()
841 data
["read_metric_data_request"] = metric
843 http_code
, resp
= self
._http
.post_cmd(
844 endpoint
="/test/message/metric_request", postfields_dict
=data
846 # print('HTTP CODE: {}'.format(http_code))
847 # print('RESP: {}'.format(resp))
848 # if http_code in (200, 201, 202, 204):
849 # resp = json.loads(resp)
850 return "Metric exported"
855 # msg = json.loads(resp)
858 # raise ClientException('error: code: {}, resp: {}'.format(
860 except ClientException
as exc
:
861 message
= "failed to export metric: metric {}\n{}".format(metric
, str(exc
))
862 raise ClientException(message
)
864 def get_field(self
, ns_name
, field
):
865 self
._logger
.debug("")
866 nsr
= self
.get(ns_name
)
867 print(yaml
.safe_dump(nsr
))
869 raise NotFound("failed to retrieve ns {}".format(ns_name
))
874 raise NotFound("failed to find {} in ns {}".format(field
, ns_name
))
884 self
._logger
.debug("")
885 self
._client
.get_token()
889 op_data
["timeout_ns_heal"] = timeout
890 op_id
= self
.exec_op(ns_name
, op_name
="heal", op_data
=op_data
, wait
=wait
)
892 except ClientException
as exc
:
893 message
= "failed to heal ns {}:\nerror:\n{}".format(ns_name
, str(exc
))
894 raise ClientException(message
)