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