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
32 def __init__(self
, http
=None, client
=None):
35 self
._logger
= logging
.getLogger('osmclient')
36 self
._apiName
= '/nslcm'
37 self
._apiVersion
= '/v1'
38 self
._apiResource
= '/ns_instances_content'
39 self
._apiBase
= '{}{}{}'.format(self
._apiName
,
40 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(self
._apiName
, self
._apiVersion
, '/ns_lcm_op_occs')
47 # Wait for status for NS instance creation/update/deletion
48 if isinstance(wait_time
, bool):
49 wait_time
= WaitForStatus
.TIMEOUT_NS_OPERATION
50 WaitForStatus
.wait_for_status(
56 deleteFlag
=deleteFlag
)
58 def list(self
, filter=None):
59 """Returns a list of NS
61 self
._logger
.debug("")
62 self
._client
.get_token()
65 filter_string
= '?{}'.format(filter)
66 _
, resp
= self
._http
.get2_cmd('{}{}'.format(self
._apiBase
,filter_string
))
68 return json
.loads(resp
)
72 """Returns an NS based on name or id
74 self
._logger
.debug("")
75 self
._client
.get_token()
76 if utils
.validate_uuid4(name
):
77 for ns
in self
.list():
81 for ns
in self
.list():
82 if name
== ns
['name']:
84 raise NotFound("ns '{}' not found".format(name
))
86 def get_individual(self
, name
):
87 self
._logger
.debug("")
88 self
._client
.get_token()
90 if not utils
.validate_uuid4(name
):
91 for ns
in self
.list():
92 if name
== ns
['name']:
96 _
, resp
= self
._http
.get2_cmd('{}/{}'.format(self
._apiBase
, ns_id
))
97 #resp = self._http.get_cmd('{}/{}/nsd_content'.format(self._apiBase, ns_id))
98 #print(yaml.safe_dump(resp))
100 return json
.loads(resp
)
102 raise NotFound("ns '{}' not found".format(name
))
103 raise NotFound("ns '{}' not found".format(name
))
105 def delete(self
, name
, force
=False, config
=None, wait
=False):
107 Deletes a Network Service (NS)
108 :param name: name of network service
109 :param force: set force. Direct deletion without cleaning at VIM
110 :param config: parameters of deletion, as:
111 autoremove: Bool (default True)
112 timeout_ns_terminate: int
113 skip_terminate_primitives: Bool (default False) to not exec the terminate primitives
114 :param wait: Make synchronous. Wait until deletion is completed:
115 False to not wait (by default), True to wait a standard time, or int (time to wait)
116 :return: None. Exception if fail
118 self
._logger
.debug("")
120 querystring_list
= []
123 ns_config
= yaml
.safe_load(config
)
124 querystring_list
+= ["{}={}".format(k
, v
) for k
, v
in ns_config
.items()]
126 querystring_list
.append('FORCE=True')
128 querystring
= "?" + "&".join(querystring_list
)
129 http_code
, resp
= self
._http
.delete_cmd('{}/{}{}'.format(self
._apiBase
,
130 ns
['_id'], querystring
))
131 # TODO change to use a POST self._http.post_cmd('{}/{}/terminate{}'.format(_apiBase, ns['_id'], querystring),
132 # postfields_dict=ns_config)
133 # seting autoremove as True by default
134 # print('HTTP CODE: {}'.format(http_code))
135 # print('RESP: {}'.format(resp))
138 resp
= json
.loads(resp
)
139 # For the 'delete' operation, '_id' is used
140 self
._wait
(resp
.get('_id'), wait
, deleteFlag
=True)
142 print('Deletion in progress')
143 elif http_code
== 204:
149 # msg = json.loads(resp)
152 raise ClientException("failed to delete ns {} - {}".format(name
, msg
))
154 def create(self
, nsd_name
, nsr_name
, account
, config
=None,
155 ssh_keys
=None, description
='default description',
156 admin_status
='ENABLED', wait
=False):
157 self
._logger
.debug("")
158 self
._client
.get_token()
159 nsd
= self
._client
.nsd
.get(nsd_name
)
164 def get_vim_account_id(vim_account
):
165 self
._logger
.debug("")
166 if vim_account_id
.get(vim_account
):
167 return vim_account_id
[vim_account
]
168 vim
= self
._client
.vim
.get(vim_account
)
170 raise NotFound("cannot find vim account '{}'".format(vim_account
))
171 vim_account_id
[vim_account
] = vim
['_id']
174 def get_wim_account_id(wim_account
):
175 self
._logger
.debug("")
176 # wim_account can be False (boolean) to indicate not use wim account
177 if not isinstance(wim_account
, str):
179 if wim_account_id
.get(wim_account
):
180 return wim_account_id
[wim_account
]
181 wim
= self
._client
.wim
.get(wim_account
)
183 raise NotFound("cannot find wim account '{}'".format(wim_account
))
184 wim_account_id
[wim_account
] = wim
['_id']
188 ns
['nsdId'] = nsd
['_id']
189 ns
['nsName'] = nsr_name
190 ns
['nsDescription'] = description
191 ns
['vimAccountId'] = get_vim_account_id(account
)
193 #ns['userdata']['key1']='value1'
194 #ns['userdata']['key2']='value2'
196 if ssh_keys
is not None:
198 for pubkeyfile
in ssh_keys
.split(','):
199 with
open(pubkeyfile
, 'r') as f
:
200 ns
['ssh_keys'].append(f
.read())
202 ns_config
= yaml
.safe_load(config
)
203 if "vim-network-name" in ns_config
:
204 ns_config
["vld"] = ns_config
.pop("vim-network-name")
205 if "vld" in ns_config
:
206 if not isinstance(ns_config
["vld"], list):
207 raise ClientException("Error at --config 'vld' must be a list of dictionaries")
208 for vld
in ns_config
["vld"]:
209 if not isinstance(vld
, dict):
210 raise ClientException("Error at --config 'vld' must be a list of dictionaries")
211 if vld
.get("vim-network-name"):
212 if isinstance(vld
["vim-network-name"], dict):
213 vim_network_name_dict
= {}
214 for vim_account
, vim_net
in vld
["vim-network-name"].items():
215 vim_network_name_dict
[get_vim_account_id(vim_account
)] = vim_net
216 vld
["vim-network-name"] = vim_network_name_dict
217 if "wim_account" in vld
and vld
["wim_account"] is not None:
218 vld
["wimAccountId"] = get_wim_account_id(vld
.pop("wim_account"))
219 if "vnf" in ns_config
:
220 for vnf
in ns_config
["vnf"]:
221 if vnf
.get("vim_account"):
222 vnf
["vimAccountId"] = get_vim_account_id(vnf
.pop("vim_account"))
224 if "additionalParamsForNs" in ns_config
:
225 if not isinstance(ns_config
["additionalParamsForNs"], dict):
226 raise ClientException("Error at --config 'additionalParamsForNs' must be a dictionary")
227 if "additionalParamsForVnf" in ns_config
:
228 if not isinstance(ns_config
["additionalParamsForVnf"], list):
229 raise ClientException("Error at --config 'additionalParamsForVnf' must be a list")
230 for additional_param_vnf
in ns_config
["additionalParamsForVnf"]:
231 if not isinstance(additional_param_vnf
, dict):
232 raise ClientException("Error at --config 'additionalParamsForVnf' items must be dictionaries")
233 if not additional_param_vnf
.get("member-vnf-index"):
234 raise ClientException("Error at --config 'additionalParamsForVnf' items must contain "
235 "'member-vnf-index'")
236 if "wim_account" in ns_config
:
237 wim_account
= ns_config
.pop("wim_account")
238 if wim_account
is not None:
239 ns
['wimAccountId'] = get_wim_account_id(wim_account
)
240 # rest of parameters without any transformation or checking
241 # "timeout_ns_deploy"
245 # print(yaml.safe_dump(ns))
247 self
._apiResource
= '/ns_instances_content'
248 self
._apiBase
= '{}{}{}'.format(self
._apiName
,
249 self
._apiVersion
, self
._apiResource
)
250 headers
= self
._client
._headers
251 headers
['Content-Type'] = 'application/yaml'
252 http_header
= ['{}: {}'.format(key
,val
)
253 for (key
,val
) in list(headers
.items())]
254 self
._http
.set_http_header(http_header
)
255 http_code
, resp
= self
._http
.post_cmd(endpoint
=self
._apiBase
,
257 # print('HTTP CODE: {}'.format(http_code))
258 # print('RESP: {}'.format(resp))
259 #if http_code in (200, 201, 202, 204):
261 resp
= json
.loads(resp
)
262 if not resp
or 'id' not in resp
:
263 raise ClientException('unexpected response from server - {} '.format(
266 # Wait for status for NS instance creation
267 self
._wait
(resp
.get('nslcmop_id'), wait
)
274 # msg = json.loads(resp)
277 # raise ClientException(msg)
278 except ClientException
as exc
:
279 message
="failed to create ns: {} nsd: {}\nerror:\n{}".format(
283 raise ClientException(message
)
285 def list_op(self
, name
, filter=None):
286 """Returns the list of operations of a NS
288 self
._logger
.debug("")
291 self
._apiResource
= '/ns_lcm_op_occs'
292 self
._apiBase
= '{}{}{}'.format(self
._apiName
,
293 self
._apiVersion
, self
._apiResource
)
296 filter_string
= '&{}'.format(filter)
297 http_code
, resp
= self
._http
.get2_cmd('{}?nsInstanceId={}{}'.format(
298 self
._apiBase
, ns
['_id'],
300 #print('HTTP CODE: {}'.format(http_code))
301 #print('RESP: {}'.format(resp))
304 resp
= json
.loads(resp
)
307 raise ClientException('unexpected response from server')
312 # resp = json.loads(resp)
313 # msg = resp['detail']
316 raise ClientException(msg
)
317 except ClientException
as exc
:
318 message
="failed to get operation list of NS {}:\nerror:\n{}".format(
321 raise ClientException(message
)
323 def get_op(self
, operationId
):
324 """Returns the status of an operation
326 self
._logger
.debug("")
327 self
._client
.get_token()
329 self
._apiResource
= '/ns_lcm_op_occs'
330 self
._apiBase
= '{}{}{}'.format(self
._apiName
,
331 self
._apiVersion
, self
._apiResource
)
332 http_code
, resp
= self
._http
.get2_cmd('{}/{}'.format(self
._apiBase
, operationId
))
333 #print('HTTP CODE: {}'.format(http_code))
334 #print('RESP: {}'.format(resp))
337 resp
= json
.loads(resp
)
340 raise ClientException('unexpected response from server')
345 # resp = json.loads(resp)
346 # msg = resp['detail']
349 raise ClientException(msg
)
350 except ClientException
as exc
:
351 message
="failed to get status of operation {}:\nerror:\n{}".format(
354 raise ClientException(message
)
356 def exec_op(self
, name
, op_name
, op_data
=None, wait
=False, ):
357 """Executes an operation on a NS
359 self
._logger
.debug("")
363 self
._apiResource
= '/ns_instances'
364 self
._apiBase
= '{}{}{}'.format(self
._apiName
,
365 self
._apiVersion
, self
._apiResource
)
366 endpoint
= '{}/{}/{}'.format(self
._apiBase
, ns
['_id'], op_name
)
367 #print('OP_NAME: {}'.format(op_name))
368 #print('OP_DATA: {}'.format(json.dumps(op_data)))
369 http_code
, resp
= self
._http
.post_cmd(endpoint
=endpoint
, postfields_dict
=op_data
)
370 #print('HTTP CODE: {}'.format(http_code))
371 #print('RESP: {}'.format(resp))
372 #if http_code in (200, 201, 202, 204):
374 resp
= json
.loads(resp
)
375 if not resp
or 'id' not in resp
:
376 raise ClientException('unexpected response from server - {}'.format(
379 # Wait for status for NS instance action
380 # For the 'action' operation, 'id' is used
381 self
._wait
(resp
.get('id'), wait
)
387 # msg = json.loads(resp)
390 # raise ClientException(msg)
391 except ClientException
as exc
:
392 message
="failed to exec operation {}:\nerror:\n{}".format(
395 raise ClientException(message
)
397 def scale_vnf(self
, ns_name
, vnf_name
, scaling_group
, scale_in
, scale_out
, wait
=False, timeout
=None):
398 """Scales a VNF by adding/removing VDUs
400 self
._logger
.debug("")
401 self
._client
.get_token()
404 op_data
["scaleType"] = "SCALE_VNF"
405 op_data
["scaleVnfData"] = {}
406 if scale_in
and not scale_out
:
407 op_data
["scaleVnfData"]["scaleVnfType"] = "SCALE_IN"
408 elif not scale_in
and scale_out
:
409 op_data
["scaleVnfData"]["scaleVnfType"] = "SCALE_OUT"
411 raise ClientException("you must set either 'scale_in' or 'scale_out'")
412 op_data
["scaleVnfData"]["scaleByStepData"] = {
413 "member-vnf-index": vnf_name
,
414 "scaling-group-descriptor": scaling_group
,
417 op_data
["timeout_ns_scale"] = timeout
418 op_id
= self
.exec_op(ns_name
, op_name
='scale', op_data
=op_data
, wait
=wait
)
420 except ClientException
as exc
:
421 message
="failed to scale vnf {} of ns {}:\nerror:\n{}".format(
422 vnf_name
, ns_name
, str(exc
))
423 raise ClientException(message
)
425 def create_alarm(self
, alarm
):
426 self
._logger
.debug("")
427 self
._client
.get_token()
429 data
["create_alarm_request"] = {}
430 data
["create_alarm_request"]["alarm_create_request"] = alarm
432 http_code
, resp
= self
._http
.post_cmd(endpoint
='/test/message/alarm_request',
433 postfields_dict
=data
)
434 #print('HTTP CODE: {}'.format(http_code))
435 #print('RESP: {}'.format(resp))
436 # if http_code in (200, 201, 202, 204):
437 # resp = json.loads(resp)
438 print('Alarm created')
443 # msg = json.loads(resp)
446 # raise ClientException('error: code: {}, resp: {}'.format(
448 except ClientException
as exc
:
449 message
="failed to create alarm: alarm {}\n{}".format(
452 raise ClientException(message
)
454 def delete_alarm(self
, name
):
455 self
._logger
.debug("")
456 self
._client
.get_token()
458 data
["delete_alarm_request"] = {}
459 data
["delete_alarm_request"]["alarm_delete_request"] = {}
460 data
["delete_alarm_request"]["alarm_delete_request"]["alarm_uuid"] = name
462 http_code
, resp
= self
._http
.post_cmd(endpoint
='/test/message/alarm_request',
463 postfields_dict
=data
)
464 #print('HTTP CODE: {}'.format(http_code))
465 #print('RESP: {}'.format(resp))
466 # if http_code in (200, 201, 202, 204):
467 # resp = json.loads(resp)
468 print('Alarm deleted')
473 # msg = json.loads(resp)
476 # raise ClientException('error: code: {}, resp: {}'.format(
478 except ClientException
as exc
:
479 message
="failed to delete alarm: alarm {}\n{}".format(
482 raise ClientException(message
)
484 def export_metric(self
, metric
):
485 self
._logger
.debug("")
486 self
._client
.get_token()
488 data
["read_metric_data_request"] = metric
490 http_code
, resp
= self
._http
.post_cmd(endpoint
='/test/message/metric_request',
491 postfields_dict
=data
)
492 #print('HTTP CODE: {}'.format(http_code))
493 #print('RESP: {}'.format(resp))
494 # if http_code in (200, 201, 202, 204):
495 # resp = json.loads(resp)
496 return 'Metric exported'
501 # msg = json.loads(resp)
504 # raise ClientException('error: code: {}, resp: {}'.format(
506 except ClientException
as exc
:
507 message
="failed to export metric: metric {}\n{}".format(
510 raise ClientException(message
)
512 def get_field(self
, ns_name
, field
):
513 self
._logger
.debug("")
514 nsr
= self
.get(ns_name
)
515 print(yaml
.safe_dump(nsr
))
517 raise NotFound("failed to retrieve ns {}".format(ns_name
))
522 raise NotFound("failed to find {} in ns {}".format(field
, ns_name
))