Standardize Formatting
[osm/osmclient.git] / 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 """
18 OSM ns API handling
19 """
20
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
25 import yaml
26 import json
27 import logging
28
29
30 class Ns(object):
31 def __init__(self, http=None, client=None):
32 self._http = http
33 self._client = client
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
40 )
41
42 # NS '--wait' option
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"
48 )
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(
53 "NS",
54 str(id),
55 wait_time,
56 apiUrlStatus,
57 self._http.get2_cmd,
58 deleteFlag=deleteFlag,
59 )
60
61 def list(self, filter=None):
62 """Returns a list of NS"""
63 self._logger.debug("")
64 self._client.get_token()
65 filter_string = ""
66 if filter:
67 filter_string = "?{}".format(filter)
68 _, resp = self._http.get2_cmd("{}{}".format(self._apiBase, filter_string))
69 if resp:
70 return json.loads(resp)
71 return list()
72
73 def get(self, name):
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():
79 if name == ns["_id"]:
80 return ns
81 else:
82 for ns in self.list():
83 if name == ns["name"]:
84 return ns
85 raise NotFound("ns '{}' not found".format(name))
86
87 def get_individual(self, name):
88 self._logger.debug("")
89 self._client.get_token()
90 ns_id = name
91 if not utils.validate_uuid4(name):
92 for ns in self.list():
93 if name == ns["name"]:
94 ns_id = ns["_id"]
95 break
96 try:
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))
100 if resp:
101 return json.loads(resp)
102 except NotFound:
103 raise NotFound("ns '{}' not found".format(name))
104 raise NotFound("ns '{}' not found".format(name))
105
106 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 self._logger.debug("")
120 ns = self.get(name)
121 querystring_list = []
122 querystring = ""
123 if config:
124 ns_config = yaml.safe_load(config)
125 querystring_list += ["{}={}".format(k, v) for k, v in ns_config.items()]
126 if force:
127 querystring_list.append("FORCE=True")
128 if querystring_list:
129 querystring = "?" + "&".join(querystring_list)
130 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 if http_code == 202:
139 if wait and resp:
140 resp = json.loads(resp)
141 # For the 'delete' operation, '_id' is used
142 self._wait(resp.get("_id"), wait, deleteFlag=True)
143 else:
144 print("Deletion in progress")
145 elif http_code == 204:
146 print("Deleted")
147 else:
148 msg = resp or ""
149 # if resp:
150 # try:
151 # msg = json.loads(resp)
152 # except ValueError:
153 # msg = resp
154 raise ClientException("failed to delete ns {} - {}".format(name, msg))
155
156 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 ):
167 self._logger.debug("")
168 self._client.get_token()
169 nsd = self._client.nsd.get(nsd_name)
170
171 vim_account_id = {}
172 wim_account_id = {}
173
174 def get_vim_account_id(vim_account):
175 self._logger.debug("")
176 if vim_account_id.get(vim_account):
177 return vim_account_id[vim_account]
178 vim = self._client.vim.get(vim_account)
179 if vim is None:
180 raise NotFound("cannot find vim account '{}'".format(vim_account))
181 vim_account_id[vim_account] = vim["_id"]
182 return vim["_id"]
183
184 def get_wim_account_id(wim_account):
185 self._logger.debug("")
186 # wim_account can be False (boolean) to indicate not use wim account
187 if not isinstance(wim_account, str):
188 return wim_account
189 if wim_account_id.get(wim_account):
190 return wim_account_id[wim_account]
191 wim = self._client.wim.get(wim_account)
192 if wim is None:
193 raise NotFound("cannot find wim account '{}'".format(wim_account))
194 wim_account_id[wim_account] = wim["_id"]
195 return wim["_id"]
196
197 ns = {}
198 ns["nsdId"] = nsd["_id"]
199 ns["nsName"] = nsr_name
200 ns["nsDescription"] = description
201 ns["vimAccountId"] = get_vim_account_id(account)
202 # ns['userdata'] = {}
203 # ns['userdata']['key1']='value1'
204 # ns['userdata']['key2']='value2'
205
206 if ssh_keys is not None:
207 ns["ssh_keys"] = []
208 for pubkeyfile in ssh_keys.split(","):
209 with open(pubkeyfile, "r") as f:
210 ns["ssh_keys"].append(f.read())
211 if config:
212 ns_config = yaml.safe_load(config)
213 if "vim-network-name" in ns_config:
214 ns_config["vld"] = ns_config.pop("vim-network-name")
215 if "vld" in ns_config:
216 if not isinstance(ns_config["vld"], list):
217 raise ClientException(
218 "Error at --config 'vld' must be a list of dictionaries"
219 )
220 for vld in ns_config["vld"]:
221 if not isinstance(vld, dict):
222 raise ClientException(
223 "Error at --config 'vld' must be a list of dictionaries"
224 )
225 if vld.get("vim-network-name"):
226 if isinstance(vld["vim-network-name"], dict):
227 vim_network_name_dict = {}
228 for vim_account, vim_net in vld["vim-network-name"].items():
229 vim_network_name_dict[
230 get_vim_account_id(vim_account)
231 ] = vim_net
232 vld["vim-network-name"] = vim_network_name_dict
233 if "wim_account" in vld and vld["wim_account"] is not None:
234 vld["wimAccountId"] = get_wim_account_id(vld.pop("wim_account"))
235 if "vnf" in ns_config:
236 for vnf in ns_config["vnf"]:
237 if vnf.get("vim_account"):
238 vnf["vimAccountId"] = get_vim_account_id(vnf.pop("vim_account"))
239
240 if "additionalParamsForNs" in ns_config:
241 if not isinstance(ns_config["additionalParamsForNs"], dict):
242 raise ClientException(
243 "Error at --config 'additionalParamsForNs' must be a dictionary"
244 )
245 if "additionalParamsForVnf" in ns_config:
246 if not isinstance(ns_config["additionalParamsForVnf"], list):
247 raise ClientException(
248 "Error at --config 'additionalParamsForVnf' must be a list"
249 )
250 for additional_param_vnf in ns_config["additionalParamsForVnf"]:
251 if not isinstance(additional_param_vnf, dict):
252 raise ClientException(
253 "Error at --config 'additionalParamsForVnf' items must be dictionaries"
254 )
255 if not additional_param_vnf.get("member-vnf-index"):
256 raise ClientException(
257 "Error at --config 'additionalParamsForVnf' items must contain "
258 "'member-vnf-index'"
259 )
260 if "wim_account" in ns_config:
261 wim_account = ns_config.pop("wim_account")
262 if wim_account is not None:
263 ns["wimAccountId"] = get_wim_account_id(wim_account)
264 # rest of parameters without any transformation or checking
265 # "timeout_ns_deploy"
266 # "placement-engine"
267 ns.update(ns_config)
268
269 # print(yaml.safe_dump(ns))
270 try:
271 self._apiResource = "/ns_instances_content"
272 self._apiBase = "{}{}{}".format(
273 self._apiName, self._apiVersion, self._apiResource
274 )
275 headers = self._client._headers
276 headers["Content-Type"] = "application/yaml"
277 http_header = [
278 "{}: {}".format(key, val) for (key, val) in list(headers.items())
279 ]
280 self._http.set_http_header(http_header)
281 http_code, resp = self._http.post_cmd(
282 endpoint=self._apiBase, postfields_dict=ns
283 )
284 # print('HTTP CODE: {}'.format(http_code))
285 # print('RESP: {}'.format(resp))
286 # if http_code in (200, 201, 202, 204):
287 if resp:
288 resp = json.loads(resp)
289 if not resp or "id" not in resp:
290 raise ClientException(
291 "unexpected response from server - {} ".format(resp)
292 )
293 if wait:
294 # Wait for status for NS instance creation
295 self._wait(resp.get("nslcmop_id"), wait)
296 print(resp["id"])
297 return resp["id"]
298 # else:
299 # msg = ""
300 # if resp:
301 # try:
302 # msg = json.loads(resp)
303 # except ValueError:
304 # msg = resp
305 # raise ClientException(msg)
306 except ClientException as exc:
307 message = "failed to create ns: {} nsd: {}\nerror:\n{}".format(
308 nsr_name, nsd_name, str(exc)
309 )
310 raise ClientException(message)
311
312 def list_op(self, name, filter=None):
313 """Returns the list of operations of a NS"""
314 self._logger.debug("")
315 ns = self.get(name)
316 try:
317 self._apiResource = "/ns_lcm_op_occs"
318 self._apiBase = "{}{}{}".format(
319 self._apiName, self._apiVersion, self._apiResource
320 )
321 filter_string = ""
322 if filter:
323 filter_string = "&{}".format(filter)
324 http_code, resp = self._http.get2_cmd(
325 "{}?nsInstanceId={}{}".format(self._apiBase, ns["_id"], filter_string)
326 )
327 # print('HTTP CODE: {}'.format(http_code))
328 # print('RESP: {}'.format(resp))
329 if http_code == 200:
330 if resp:
331 resp = json.loads(resp)
332 return resp
333 else:
334 raise ClientException("unexpected response from server")
335 else:
336 msg = resp or ""
337 # if resp:
338 # try:
339 # resp = json.loads(resp)
340 # msg = resp['detail']
341 # except ValueError:
342 # msg = resp
343 raise ClientException(msg)
344 except ClientException as exc:
345 message = "failed to get operation list of NS {}:\nerror:\n{}".format(
346 name, str(exc)
347 )
348 raise ClientException(message)
349
350 def get_op(self, operationId):
351 """Returns the status of an operation"""
352 self._logger.debug("")
353 self._client.get_token()
354 try:
355 self._apiResource = "/ns_lcm_op_occs"
356 self._apiBase = "{}{}{}".format(
357 self._apiName, self._apiVersion, self._apiResource
358 )
359 http_code, resp = self._http.get2_cmd(
360 "{}/{}".format(self._apiBase, operationId)
361 )
362 # print('HTTP CODE: {}'.format(http_code))
363 # print('RESP: {}'.format(resp))
364 if http_code == 200:
365 if resp:
366 resp = json.loads(resp)
367 return resp
368 else:
369 raise ClientException("unexpected response from server")
370 else:
371 msg = resp or ""
372 # if resp:
373 # try:
374 # resp = json.loads(resp)
375 # msg = resp['detail']
376 # except ValueError:
377 # msg = resp
378 raise ClientException(msg)
379 except ClientException as exc:
380 message = "failed to get status of operation {}:\nerror:\n{}".format(
381 operationId, str(exc)
382 )
383 raise ClientException(message)
384
385 def exec_op(
386 self,
387 name,
388 op_name,
389 op_data=None,
390 wait=False,
391 ):
392 """Executes an operation on a NS"""
393 self._logger.debug("")
394 ns = self.get(name)
395 try:
396 ns = self.get(name)
397 self._apiResource = "/ns_instances"
398 self._apiBase = "{}{}{}".format(
399 self._apiName, self._apiVersion, self._apiResource
400 )
401 endpoint = "{}/{}/{}".format(self._apiBase, ns["_id"], op_name)
402 # print('OP_NAME: {}'.format(op_name))
403 # print('OP_DATA: {}'.format(json.dumps(op_data)))
404 http_code, resp = self._http.post_cmd(
405 endpoint=endpoint, postfields_dict=op_data
406 )
407 # print('HTTP CODE: {}'.format(http_code))
408 # print('RESP: {}'.format(resp))
409 # if http_code in (200, 201, 202, 204):
410 if resp:
411 resp = json.loads(resp)
412 if not resp or "id" not in resp:
413 raise ClientException(
414 "unexpected response from server - {}".format(resp)
415 )
416 if wait:
417 # Wait for status for NS instance action
418 # For the 'action' operation, 'id' is used
419 self._wait(resp.get("id"), wait)
420 return resp["id"]
421 # else:
422 # msg = ""
423 # if resp:
424 # try:
425 # msg = json.loads(resp)
426 # except ValueError:
427 # msg = resp
428 # raise ClientException(msg)
429 except ClientException as exc:
430 message = "failed to exec operation {}:\nerror:\n{}".format(name, str(exc))
431 raise ClientException(message)
432
433 def scale_vnf(
434 self,
435 ns_name,
436 vnf_name,
437 scaling_group,
438 scale_in,
439 scale_out,
440 wait=False,
441 timeout=None,
442 ):
443 """Scales a VNF by adding/removing VDUs"""
444 self._logger.debug("")
445 self._client.get_token()
446 try:
447 op_data = {}
448 op_data["scaleType"] = "SCALE_VNF"
449 op_data["scaleVnfData"] = {}
450 if scale_in and not scale_out:
451 op_data["scaleVnfData"]["scaleVnfType"] = "SCALE_IN"
452 elif not scale_in and scale_out:
453 op_data["scaleVnfData"]["scaleVnfType"] = "SCALE_OUT"
454 else:
455 raise ClientException("you must set either 'scale_in' or 'scale_out'")
456 op_data["scaleVnfData"]["scaleByStepData"] = {
457 "member-vnf-index": vnf_name,
458 "scaling-group-descriptor": scaling_group,
459 }
460 if timeout:
461 op_data["timeout_ns_scale"] = timeout
462 op_id = self.exec_op(ns_name, op_name="scale", op_data=op_data, wait=wait)
463 print(str(op_id))
464 except ClientException as exc:
465 message = "failed to scale vnf {} of ns {}:\nerror:\n{}".format(
466 vnf_name, ns_name, str(exc)
467 )
468 raise ClientException(message)
469
470 def create_alarm(self, alarm):
471 self._logger.debug("")
472 self._client.get_token()
473 data = {}
474 data["create_alarm_request"] = {}
475 data["create_alarm_request"]["alarm_create_request"] = alarm
476 try:
477 http_code, resp = self._http.post_cmd(
478 endpoint="/test/message/alarm_request", postfields_dict=data
479 )
480 # print('HTTP CODE: {}'.format(http_code))
481 # print('RESP: {}'.format(resp))
482 # if http_code in (200, 201, 202, 204):
483 # resp = json.loads(resp)
484 print("Alarm created")
485 # else:
486 # msg = ""
487 # if resp:
488 # try:
489 # msg = json.loads(resp)
490 # except ValueError:
491 # msg = resp
492 # raise ClientException('error: code: {}, resp: {}'.format(
493 # http_code, msg))
494 except ClientException as exc:
495 message = "failed to create alarm: alarm {}\n{}".format(alarm, str(exc))
496 raise ClientException(message)
497
498 def delete_alarm(self, name):
499 self._logger.debug("")
500 self._client.get_token()
501 data = {}
502 data["delete_alarm_request"] = {}
503 data["delete_alarm_request"]["alarm_delete_request"] = {}
504 data["delete_alarm_request"]["alarm_delete_request"]["alarm_uuid"] = name
505 try:
506 http_code, resp = self._http.post_cmd(
507 endpoint="/test/message/alarm_request", postfields_dict=data
508 )
509 # print('HTTP CODE: {}'.format(http_code))
510 # print('RESP: {}'.format(resp))
511 # if http_code in (200, 201, 202, 204):
512 # resp = json.loads(resp)
513 print("Alarm deleted")
514 # else:
515 # msg = ""
516 # if resp:
517 # try:
518 # msg = json.loads(resp)
519 # except ValueError:
520 # msg = resp
521 # raise ClientException('error: code: {}, resp: {}'.format(
522 # http_code, msg))
523 except ClientException as exc:
524 message = "failed to delete alarm: alarm {}\n{}".format(name, str(exc))
525 raise ClientException(message)
526
527 def export_metric(self, metric):
528 self._logger.debug("")
529 self._client.get_token()
530 data = {}
531 data["read_metric_data_request"] = metric
532 try:
533 http_code, resp = self._http.post_cmd(
534 endpoint="/test/message/metric_request", postfields_dict=data
535 )
536 # print('HTTP CODE: {}'.format(http_code))
537 # print('RESP: {}'.format(resp))
538 # if http_code in (200, 201, 202, 204):
539 # resp = json.loads(resp)
540 return "Metric exported"
541 # else:
542 # msg = ""
543 # if resp:
544 # try:
545 # msg = json.loads(resp)
546 # except ValueError:
547 # msg = resp
548 # raise ClientException('error: code: {}, resp: {}'.format(
549 # http_code, msg))
550 except ClientException as exc:
551 message = "failed to export metric: metric {}\n{}".format(metric, str(exc))
552 raise ClientException(message)
553
554 def get_field(self, ns_name, field):
555 self._logger.debug("")
556 nsr = self.get(ns_name)
557 print(yaml.safe_dump(nsr))
558 if nsr is None:
559 raise NotFound("failed to retrieve ns {}".format(ns_name))
560
561 if field in nsr:
562 return nsr[field]
563
564 raise NotFound("failed to find {} in ns {}".format(field, ns_name))