Code Coverage

Cobertura Coverage Report > osmclient.sol005 >

ns.py

Trend

Classes100%
 
Lines8%
   
Conditionals100%
 

File Coverage summary

NameClassesLinesConditionals
ns.py
100%
1/1
8%
28/367
100%
0/0

Coverage Breakdown by Class

NameLinesConditionals
ns.py
8%
28/367
N/A

Source

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 1 """
18 OSM ns 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 Ns(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 = "/nslcm"
36 0         self._apiVersion = "/v1"
37 0         self._apiResource = "/ns_instances_content"
38 0         self._apiBase = "{}{}{}".format(
39             self._apiName, self._apiVersion, self._apiResource
40         )
41
42     # NS '--wait' option
43 1     def _wait(self, id, wait_time, deleteFlag=False):
44 0         self._logger.debug("")
45         # Endpoint to get operation status
46 0         apiUrlStatus = "{}{}{}".format(
47             self._apiName, self._apiVersion, "/ns_lcm_op_occs"
48         )
49         # Wait for status for NS instance creation/update/deletion
50 0         if isinstance(wait_time, bool):
51 0             wait_time = WaitForStatus.TIMEOUT_NS_OPERATION
52 0         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 1     def list(self, filter=None):
62         """Returns a list of NS"""
63 0         self._logger.debug("")
64 0         self._client.get_token()
65 0         filter_string = ""
66 0         if filter:
67 0             filter_string = "?{}".format(filter)
68 0         _, resp = self._http.get2_cmd("{}{}".format(self._apiBase, filter_string))
69 0         if resp:
70 0             return json.loads(resp)
71 0         return list()
72
73 1     def get(self, name):
74         """Returns an NS based on name or id"""
75 0         self._logger.debug("")
76 0         self._client.get_token()
77 0         if utils.validate_uuid4(name):
78 0             for ns in self.list():
79 0                 if name == ns["_id"]:
80 0                     return ns
81         else:
82 0             for ns in self.list():
83 0                 if name == ns["name"]:
84 0                     return ns
85 0         raise NotFound("ns '{}' not found".format(name))
86
87 1     def get_individual(self, name):
88 0         self._logger.debug("")
89 0         self._client.get_token()
90 0         ns_id = name
91 0         if not utils.validate_uuid4(name):
92 0             for ns in self.list():
93 0                 if name == ns["name"]:
94 0                     ns_id = ns["_id"]
95 0                     break
96 0         try:
97 0             _, 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 0             if resp:
101 0                 return json.loads(resp)
102 0         except NotFound:
103 0             raise NotFound("ns '{}' not found".format(name))
104 0         raise NotFound("ns '{}' not found".format(name))
105
106 1     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 0         self._logger.debug("")
120 0         ns = self.get(name)
121 0         querystring_list = []
122 0         querystring = ""
123 0         if config:
124 0             ns_config = yaml.safe_load(config)
125 0             querystring_list += ["{}={}".format(k, v) for k, v in ns_config.items()]
126 0         if force:
127 0             querystring_list.append("FORCE=True")
128 0         if querystring_list:
129 0             querystring = "?" + "&".join(querystring_list)
130 0         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 0         if http_code == 202:
139 0             if wait and resp:
140 0                 resp = json.loads(resp)
141                 # For the 'delete' operation, '_id' is used
142 0                 self._wait(resp.get("_id"), wait, deleteFlag=True)
143             else:
144 0                 print("Deletion in progress")
145 0         elif http_code == 204:
146 0             print("Deleted")
147         else:
148 0             msg = resp or ""
149             # if resp:
150             #     try:
151             #         msg = json.loads(resp)
152             #     except ValueError:
153             #         msg = resp
154 0             raise ClientException("failed to delete ns {} - {}".format(name, msg))
155
156 1     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         timeout=None,
167     ):
168 0         self._logger.debug("")
169 0         self._client.get_token()
170 0         nsd = self._client.nsd.get(nsd_name)
171
172 0         vim_account_id = {}
173 0         wim_account_id = {}
174
175 0         def get_vim_account_id(vim_account):
176 0             self._logger.debug("")
177 0             if vim_account_id.get(vim_account):
178 0                 return vim_account_id[vim_account]
179 0             vim = self._client.vim.get(vim_account)
180 0             if vim is None:
181 0                 raise NotFound("cannot find vim account '{}'".format(vim_account))
182 0             vim_account_id[vim_account] = vim["_id"]
183 0             return vim["_id"]
184
185 0         def get_wim_account_id(wim_account):
186 0             self._logger.debug("")
187             # wim_account can be False (boolean) to indicate not use wim account
188 0             if not isinstance(wim_account, str):
189 0                 return wim_account
190 0             if wim_account_id.get(wim_account):
191 0                 return wim_account_id[wim_account]
192 0             wim = self._client.wim.get(wim_account)
193 0             if wim is None:
194 0                 raise NotFound("cannot find wim account '{}'".format(wim_account))
195 0             wim_account_id[wim_account] = wim["_id"]
196 0             return wim["_id"]
197
198 0         vim_id = get_vim_account_id(account)
199 0         ns = {}
200 0         ns["nsdId"] = nsd["_id"]
201 0         ns["nsName"] = nsr_name
202 0         ns["nsDescription"] = description
203 0         ns["vimAccountId"] = vim_id
204         # ns['userdata'] = {}
205         # ns['userdata']['key1']='value1'
206         # ns['userdata']['key2']='value2'
207
208 0         if ssh_keys is not None:
209 0             ns["ssh_keys"] = []
210 0             for pubkeyfile in ssh_keys.split(","):
211 0                 with open(pubkeyfile, "r") as f:
212 0                     ns["ssh_keys"].append(f.read())
213 0         if timeout:
214 0             ns["timeout_ns_deploy"] = timeout
215 0         if config:
216 0             ns_config = yaml.safe_load(config)
217 0             if "vim-network-name" in ns_config:
218 0                 ns_config["vld"] = ns_config.pop("vim-network-name")
219 0             if "vld" in ns_config:
220 0                 if not isinstance(ns_config["vld"], list):
221 0                     raise ClientException(
222                         "Error at --config 'vld' must be a list of dictionaries"
223                     )
224 0                 for vld in ns_config["vld"]:
225 0                     if not isinstance(vld, dict):
226 0                         raise ClientException(
227                             "Error at --config 'vld' must be a list of dictionaries"
228                         )
229 0                     if vld.get("vim-network-name"):
230 0                         if isinstance(vld["vim-network-name"], dict):
231 0                             vim_network_name_dict = {}
232 0                             for vim_account, vim_net in vld["vim-network-name"].items():
233 0                                 vim_network_name_dict[
234                                     get_vim_account_id(vim_account)
235                                 ] = vim_net
236 0                             vld["vim-network-name"] = vim_network_name_dict
237 0                     if "wim_account" in vld and vld["wim_account"] is not None:
238 0                         vld["wimAccountId"] = get_wim_account_id(vld.pop("wim_account"))
239 0             if "vnf" in ns_config:
240 0                 for vnf in ns_config["vnf"]:
241 0                     if vnf.get("vim_account"):
242 0                         vnf["vimAccountId"] = get_vim_account_id(vnf.pop("vim_account"))
243
244 0             if "additionalParamsForNs" in ns_config:
245 0                 if not isinstance(ns_config["additionalParamsForNs"], dict):
246 0                     raise ClientException(
247                         "Error at --config 'additionalParamsForNs' must be a dictionary"
248                     )
249 0             if "additionalParamsForVnf" in ns_config:
250 0                 if not isinstance(ns_config["additionalParamsForVnf"], list):
251 0                     raise ClientException(
252                         "Error at --config 'additionalParamsForVnf' must be a list"
253                     )
254 0                 for additional_param_vnf in ns_config["additionalParamsForVnf"]:
255 0                     if not isinstance(additional_param_vnf, dict):
256 0                         raise ClientException(
257                             "Error at --config 'additionalParamsForVnf' items must be dictionaries"
258                         )
259 0                     if not additional_param_vnf.get("member-vnf-index"):
260 0                         raise ClientException(
261                             "Error at --config 'additionalParamsForVnf' items must contain "
262                             "'member-vnf-index'"
263                         )
264 0             if "wim_account" in ns_config:
265 0                 wim_account = ns_config.pop("wim_account")
266 0                 if wim_account is not None:
267 0                     ns["wimAccountId"] = get_wim_account_id(wim_account)
268             # rest of parameters without any transformation or checking
269             # "timeout_ns_deploy"
270             # "placement-engine"
271 0             ns.update(ns_config)
272
273         # print(yaml.safe_dump(ns))
274 0         try:
275 0             self._apiResource = "/ns_instances_content"
276 0             self._apiBase = "{}{}{}".format(
277                 self._apiName, self._apiVersion, self._apiResource
278             )
279 0             headers = self._client._headers
280 0             headers["Content-Type"] = "application/yaml"
281 0             http_header = [
282                 "{}: {}".format(key, val) for (key, val) in list(headers.items())
283             ]
284 0             self._http.set_http_header(http_header)
285 0             http_code, resp = self._http.post_cmd(
286                 endpoint=self._apiBase, postfields_dict=ns
287             )
288             # print('HTTP CODE: {}'.format(http_code))
289             # print('RESP: {}'.format(resp))
290             # if http_code in (200, 201, 202, 204):
291 0             if resp:
292 0                 resp = json.loads(resp)
293 0             if not resp or "id" not in resp:
294 0                 raise ClientException(
295                     "unexpected response from server - {} ".format(resp)
296                 )
297 0             if wait:
298                 # Wait for status for NS instance creation
299 0                 self._wait(resp.get("nslcmop_id"), wait)
300 0             print(resp["id"])
301 0             return resp["id"]
302             # else:
303             #    msg = ""
304             #    if resp:
305             #        try:
306             #            msg = json.loads(resp)
307             #        except ValueError:
308             #            msg = resp
309             #    raise ClientException(msg)
310 0         except ClientException as exc:
311 0             message = "failed to create ns: {} nsd: {}\nerror:\n{}".format(
312                 nsr_name, nsd_name, str(exc)
313             )
314 0             raise ClientException(message)
315
316 1     def list_op(self, name, filter=None):
317         """Returns the list of operations of a NS"""
318 0         self._logger.debug("")
319 0         ns = self.get(name)
320 0         try:
321 0             self._apiResource = "/ns_lcm_op_occs"
322 0             self._apiBase = "{}{}{}".format(
323                 self._apiName, self._apiVersion, self._apiResource
324             )
325 0             filter_string = ""
326 0             if filter:
327 0                 filter_string = "&{}".format(filter)
328 0             http_code, resp = self._http.get2_cmd(
329                 "{}?nsInstanceId={}{}".format(self._apiBase, ns["_id"], filter_string)
330             )
331             # print('HTTP CODE: {}'.format(http_code))
332             # print('RESP: {}'.format(resp))
333 0             if http_code == 200:
334 0                 if resp:
335 0                     resp = json.loads(resp)
336 0                     return resp
337                 else:
338 0                     raise ClientException("unexpected response from server")
339             else:
340 0                 msg = resp or ""
341                 #    if resp:
342                 #        try:
343                 #            resp = json.loads(resp)
344                 #            msg = resp['detail']
345                 #        except ValueError:
346                 #            msg = resp
347 0                 raise ClientException(msg)
348 0         except ClientException as exc:
349 0             message = "failed to get operation list of NS {}:\nerror:\n{}".format(
350                 name, str(exc)
351             )
352 0             raise ClientException(message)
353
354 1     def get_op(self, operationId):
355         """Returns the status of an operation"""
356 0         self._logger.debug("")
357 0         self._client.get_token()
358 0         try:
359 0             self._apiResource = "/ns_lcm_op_occs"
360 0             self._apiBase = "{}{}{}".format(
361                 self._apiName, self._apiVersion, self._apiResource
362             )
363 0             http_code, resp = self._http.get2_cmd(
364                 "{}/{}".format(self._apiBase, operationId)
365             )
366             # print('HTTP CODE: {}'.format(http_code))
367             # print('RESP: {}'.format(resp))
368 0             if http_code == 200:
369 0                 if resp:
370 0                     resp = json.loads(resp)
371 0                     return resp
372                 else:
373 0                     raise ClientException("unexpected response from server")
374             else:
375 0                 msg = resp or ""
376                 #    if resp:
377                 #        try:
378                 #            resp = json.loads(resp)
379                 #            msg = resp['detail']
380                 #        except ValueError:
381                 #            msg = resp
382 0                 raise ClientException(msg)
383 0         except ClientException as exc:
384 0             message = "failed to get status of operation {}:\nerror:\n{}".format(
385                 operationId, str(exc)
386             )
387 0             raise ClientException(message)
388
389 1     def exec_op(
390         self,
391         name,
392         op_name,
393         op_data=None,
394         wait=False,
395     ):
396         """Executes an operation on a NS"""
397 0         self._logger.debug("")
398 0         ns = self.get(name)
399 0         try:
400 0             ns = self.get(name)
401 0             self._apiResource = "/ns_instances"
402 0             self._apiBase = "{}{}{}".format(
403                 self._apiName, self._apiVersion, self._apiResource
404             )
405 0             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 0             http_code, resp = self._http.post_cmd(
409                 endpoint=endpoint, postfields_dict=op_data
410             )
411             # print('HTTP CODE: {}'.format(http_code))
412             # print('RESP: {}'.format(resp))
413             # if http_code in (200, 201, 202, 204):
414 0             if resp:
415 0                 resp = json.loads(resp)
416 0             if not resp or "id" not in resp:
417 0                 raise ClientException(
418                     "unexpected response from server - {}".format(resp)
419                 )
420 0             if wait:
421                 # Wait for status for NS instance action
422                 # For the 'action' operation, 'id' is used
423 0                 self._wait(resp.get("id"), wait)
424 0             return resp["id"]
425             # else:
426             #    msg = ""
427             #    if resp:
428             #        try:
429             #            msg = json.loads(resp)
430             #        except ValueError:
431             #            msg = resp
432             #    raise ClientException(msg)
433 0         except ClientException as exc:
434 0             message = "failed to exec operation {}:\nerror:\n{}".format(name, str(exc))
435 0             raise ClientException(message)
436
437 1     def scale_vnf(
438         self,
439         ns_name,
440         vnf_name,
441         scaling_group,
442         scale_in,
443         scale_out,
444         wait=False,
445         timeout=None,
446     ):
447         """Scales a VNF by adding/removing VDUs"""
448 0         self._logger.debug("")
449 0         self._client.get_token()
450 0         try:
451 0             op_data = {}
452 0             op_data["scaleType"] = "SCALE_VNF"
453 0             op_data["scaleVnfData"] = {}
454 0             if scale_in and not scale_out:
455 0                 op_data["scaleVnfData"]["scaleVnfType"] = "SCALE_IN"
456 0             elif not scale_in and scale_out:
457 0                 op_data["scaleVnfData"]["scaleVnfType"] = "SCALE_OUT"
458             else:
459 0                 raise ClientException("you must set either 'scale_in' or 'scale_out'")
460 0             op_data["scaleVnfData"]["scaleByStepData"] = {
461                 "member-vnf-index": vnf_name,
462                 "scaling-group-descriptor": scaling_group,
463             }
464 0             if timeout:
465 0                 op_data["timeout_ns_scale"] = timeout
466 0             op_id = self.exec_op(ns_name, op_name="scale", op_data=op_data, wait=wait)
467 0             print(str(op_id))
468 0         except ClientException as exc:
469 0             message = "failed to scale vnf {} of ns {}:\nerror:\n{}".format(
470                 vnf_name, ns_name, str(exc)
471             )
472 0             raise ClientException(message)
473
474 1     def update(self, ns_name, data, wait=False):
475         """Update NS instance.
476
477         This function calls the NBI in order to perform an update operation
478         on a Network Service instance.
479
480         Args:
481             ns_name: (str)
482             data: (dict)
483             wait: (boolean)
484
485         Returns:
486             None
487
488         """
489 0         self._logger.debug("")
490 0         self._client.get_token()
491 0         try:
492 0             op_data = {"updateType": data.pop("updateType")}
493
494             # Check update parameters availability according to update type
495 0             if op_data["updateType"] == "CHANGE_VNFPKG":
496 0                 if not (
497                     data["config"]["changeVnfPackageData"][0].get("vnfInstanceId")
498                     and data["config"]["changeVnfPackageData"][0].get("vnfdId")
499                 ):
500 0                     raise ClientException("you must set both vnfInstanceId and vnfdId")
501
502             # Fill up op_data
503 0             op_data["changeVnfPackageData"] = {}
504 0             op_data["changeVnfPackageData"]["vnfInstanceId"] = data["config"][
505                 "changeVnfPackageData"
506             ][0].get("vnfInstanceId")
507
508 0             op_data["changeVnfPackageData"]["vnfdId"] = data["config"][
509                 "changeVnfPackageData"
510             ][0].get("vnfdId")
511
512 0             if data.get("timeout"):
513 0                 op_data["timeout_ns_update"] = data["timeout"]
514
515 0             op_id = self.exec_op(ns_name, op_name="update", op_data=op_data, wait=wait)
516 0             print(str(op_id))
517
518 0         except ClientException as exc:
519 0             message = "failed to update ns {}:\nerror:\n{}".format(ns_name, str(exc))
520 0             raise ClientException(message)
521
522 1     def create_alarm(self, alarm):
523 0         self._logger.debug("")
524 0         self._client.get_token()
525 0         data = {}
526 0         data["create_alarm_request"] = {}
527 0         data["create_alarm_request"]["alarm_create_request"] = alarm
528 0         try:
529 0             http_code, resp = self._http.post_cmd(
530                 endpoint="/test/message/alarm_request", postfields_dict=data
531             )
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 0             print("Alarm created")
537             # else:
538             #    msg = ""
539             #    if resp:
540             #        try:
541             #            msg = json.loads(resp)
542             #        except ValueError:
543             #            msg = resp
544             #    raise ClientException('error: code: {}, resp: {}'.format(
545             #                          http_code, msg))
546 0         except ClientException as exc:
547 0             message = "failed to create alarm: alarm {}\n{}".format(alarm, str(exc))
548 0             raise ClientException(message)
549
550 1     def delete_alarm(self, name):
551 0         self._logger.debug("")
552 0         self._client.get_token()
553 0         data = {}
554 0         data["delete_alarm_request"] = {}
555 0         data["delete_alarm_request"]["alarm_delete_request"] = {}
556 0         data["delete_alarm_request"]["alarm_delete_request"]["alarm_uuid"] = name
557 0         try:
558 0             http_code, resp = self._http.post_cmd(
559                 endpoint="/test/message/alarm_request", postfields_dict=data
560             )
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 0             print("Alarm deleted")
566             # else:
567             #    msg = ""
568             #    if resp:
569             #        try:
570             #            msg = json.loads(resp)
571             #        except ValueError:
572             #            msg = resp
573             #    raise ClientException('error: code: {}, resp: {}'.format(
574             #                          http_code, msg))
575 0         except ClientException as exc:
576 0             message = "failed to delete alarm: alarm {}\n{}".format(name, str(exc))
577 0             raise ClientException(message)
578
579 1     def get_alarm(self, project_name=None, ns_id=None, uuid=None):
580 0         self._client.get_token()
581 0         try:
582 0             self._apiName = "/nsfm"
583 0             self._apiResource = "/alarms"
584 0             self._apiBase = "{}{}{}".format(
585                 self._apiName, self._apiVersion, self._apiResource
586             )
587 0             if uuid:
588                 # if request is for any uuid
589 0                 http_code, resp = self._http.get2_cmd(
590                     "{}/{}".format(self._apiBase, uuid)
591                 )
592 0             if not uuid:
593 0                 http_code, resp = self._http.get2_cmd(
594                     "{}/{}/{}/{}".format(self._apiBase, uuid, project_name, ns_id)
595                 )
596 0             if http_code == 200:
597 0                 if resp:
598 0                     resp = json.loads(resp)
599 0                     return resp
600                 else:
601 0                     raise ClientException("unexpected response from server")
602             else:
603 0                 msg = resp or ""
604 0                 raise ClientException(msg)
605 0         except ClientException as exc:
606 0             message = "failed to get alarm :\nerror:\n{}".format(str(exc))
607 0             raise ClientException(message)
608
609 1     def update_alarm(self, uuid, threshold=None, is_enable=None, wait=None):
610 0         self._client.get_token()
611 0         try:
612 0             op_data = {}
613 0             op_data["uuid"] = uuid
614 0             op_data["threshold"] = threshold
615 0             op_data["is_enable"] = is_enable
616 0             self._apiName = "/nsfm"
617 0             self._apiResource = "/alarms"
618 0             self._apiBase = "{}{}{}".format(
619                 self._apiName, self._apiVersion, self._apiResource
620             )
621 0             http_code, resp = self._http.patch_cmd(
622                 endpoint="{}".format(self._apiBase), postfields_dict=op_data
623             )
624 0             if resp:
625 0                 resp = json.loads(resp)
626 0             print(resp)
627 0             return resp
628 0         except ClientException as exc:
629 0             message = "failed to update alarm :\nerror:\n{}".format(str(exc))
630 0             raise ClientException(message)
631
632 1     def export_metric(self, metric):
633 0         self._logger.debug("")
634 0         self._client.get_token()
635 0         data = {}
636 0         data["read_metric_data_request"] = metric
637 0         try:
638 0             http_code, resp = self._http.post_cmd(
639                 endpoint="/test/message/metric_request", postfields_dict=data
640             )
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 0             return "Metric exported"
646             # else:
647             #    msg = ""
648             #    if resp:
649             #        try:
650             #            msg = json.loads(resp)
651             #        except ValueError:
652             #            msg = resp
653             #    raise ClientException('error: code: {}, resp: {}'.format(
654             #                          http_code, msg))
655 0         except ClientException as exc:
656 0             message = "failed to export metric: metric {}\n{}".format(metric, str(exc))
657 0             raise ClientException(message)
658
659 1     def get_field(self, ns_name, field):
660 0         self._logger.debug("")
661 0         nsr = self.get(ns_name)
662 0         print(yaml.safe_dump(nsr))
663 0         if nsr is None:
664 0             raise NotFound("failed to retrieve ns {}".format(ns_name))
665
666 0         if field in nsr:
667 0             return nsr[field]
668
669 0         raise NotFound("failed to find {} in ns {}".format(field, ns_name))
670
671 1     def heal(
672         self,
673         ns_name,
674         heal_dict,
675         wait=False,
676         timeout=None,
677     ):
678         """Heals a NS"""
679 0         self._logger.debug("")
680 0         self._client.get_token()
681 0         try:
682 0             op_data = heal_dict
683 0             if timeout:
684 0                 op_data["timeout_ns_heal"] = timeout
685 0             op_id = self.exec_op(ns_name, op_name="heal", op_data=op_data, wait=wait)
686 0             print(str(op_id))
687 0         except ClientException as exc:
688 0             message = "failed to heal ns {}:\nerror:\n{}".format(ns_name, str(exc))
689 0             raise ClientException(message)