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 create_alarm(self
, alarm
):
475 self
._logger
.debug("")
476 self
._client
.get_token()
478 data
["create_alarm_request"] = {}
479 data
["create_alarm_request"]["alarm_create_request"] = alarm
481 http_code
, resp
= self
._http
.post_cmd(
482 endpoint
="/test/message/alarm_request", postfields_dict
=data
484 # print('HTTP CODE: {}'.format(http_code))
485 # print('RESP: {}'.format(resp))
486 # if http_code in (200, 201, 202, 204):
487 # resp = json.loads(resp)
488 print("Alarm created")
493 # msg = json.loads(resp)
496 # raise ClientException('error: code: {}, resp: {}'.format(
498 except ClientException
as exc
:
499 message
= "failed to create alarm: alarm {}\n{}".format(alarm
, str(exc
))
500 raise ClientException(message
)
502 def delete_alarm(self
, name
):
503 self
._logger
.debug("")
504 self
._client
.get_token()
506 data
["delete_alarm_request"] = {}
507 data
["delete_alarm_request"]["alarm_delete_request"] = {}
508 data
["delete_alarm_request"]["alarm_delete_request"]["alarm_uuid"] = name
510 http_code
, resp
= self
._http
.post_cmd(
511 endpoint
="/test/message/alarm_request", postfields_dict
=data
513 # print('HTTP CODE: {}'.format(http_code))
514 # print('RESP: {}'.format(resp))
515 # if http_code in (200, 201, 202, 204):
516 # resp = json.loads(resp)
517 print("Alarm deleted")
522 # msg = json.loads(resp)
525 # raise ClientException('error: code: {}, resp: {}'.format(
527 except ClientException
as exc
:
528 message
= "failed to delete alarm: alarm {}\n{}".format(name
, str(exc
))
529 raise ClientException(message
)
531 def get_alarm(self
, project_name
=None, ns_id
=None, uuid
=None):
532 self
._client
.get_token()
534 self
._apiName
= "/nsfm"
535 self
._apiResource
= "/alarms"
536 self
._apiBase
= "{}{}{}".format(
537 self
._apiName
, self
._apiVersion
, self
._apiResource
540 # if request is for any uuid
541 http_code
, resp
= self
._http
.get2_cmd(
542 "{}/{}".format(self
._apiBase
, uuid
)
545 http_code
, resp
= self
._http
.get2_cmd(
546 "{}/{}/{}/{}".format(self
._apiBase
, uuid
, project_name
, ns_id
)
550 resp
= json
.loads(resp
)
553 raise ClientException("unexpected response from server")
556 raise ClientException(msg
)
557 except ClientException
as exc
:
558 message
= "failed to get alarm :\nerror:\n{}".format(str(exc
))
559 raise ClientException(message
)
561 def update_alarm(self
, uuid
, threshold
=None, is_enable
=None, wait
=None):
562 self
._client
.get_token()
565 op_data
["uuid"] = uuid
566 op_data
["threshold"] = threshold
567 op_data
["is_enable"] = is_enable
568 self
._apiName
= "/nsfm"
569 self
._apiResource
= "/alarms"
570 self
._apiBase
= "{}{}{}".format(
571 self
._apiName
, self
._apiVersion
, self
._apiResource
573 http_code
, resp
= self
._http
.patch_cmd(
574 endpoint
="{}".format(self
._apiBase
), postfields_dict
=op_data
577 resp
= json
.loads(resp
)
580 except ClientException
as exc
:
581 message
= "failed to update alarm :\nerror:\n{}".format(str(exc
))
582 raise ClientException(message
)
584 def export_metric(self
, metric
):
585 self
._logger
.debug("")
586 self
._client
.get_token()
588 data
["read_metric_data_request"] = metric
590 http_code
, resp
= self
._http
.post_cmd(
591 endpoint
="/test/message/metric_request", postfields_dict
=data
593 # print('HTTP CODE: {}'.format(http_code))
594 # print('RESP: {}'.format(resp))
595 # if http_code in (200, 201, 202, 204):
596 # resp = json.loads(resp)
597 return "Metric exported"
602 # msg = json.loads(resp)
605 # raise ClientException('error: code: {}, resp: {}'.format(
607 except ClientException
as exc
:
608 message
= "failed to export metric: metric {}\n{}".format(metric
, str(exc
))
609 raise ClientException(message
)
611 def get_field(self
, ns_name
, field
):
612 self
._logger
.debug("")
613 nsr
= self
.get(ns_name
)
614 print(yaml
.safe_dump(nsr
))
616 raise NotFound("failed to retrieve ns {}".format(ns_name
))
621 raise NotFound("failed to find {} in ns {}".format(field
, ns_name
))