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