4b522a8bbaa4018761ff7613944a151df2287e98
[osm/osmclient.git] / osmclient / sol005 / nsi.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 NSI (Network Slice Instance) 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 Nsi(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 = '/nsilcm'
37 self._apiVersion = '/v1'
38 self._apiResource = '/netslice_instances_content'
39 self._apiBase = '{}{}{}'.format(self._apiName,
40 self._apiVersion, self._apiResource)
41
42 # NSI '--wait' option
43 def _wait(self, id, deleteFlag=False):
44 self._logger.debug("")
45 self._client.get_token()
46 # Endpoint to get operation status
47 apiUrlStatus = '{}{}{}'.format(self._apiName, self._apiVersion, '/nsi_lcm_op_occs')
48 # Wait for status for NSI instance creation/update/deletion
49 WaitForStatus.wait_for_status(
50 'NSI',
51 str(id),
52 WaitForStatus.TIMEOUT_NSI_OPERATION,
53 apiUrlStatus,
54 self._http.get2_cmd,
55 deleteFlag=deleteFlag)
56
57 def list(self, filter=None):
58 """Returns a list of NSI
59 """
60 self._logger.debug("")
61 self._client.get_token()
62 filter_string = ''
63 if filter:
64 filter_string = '?{}'.format(filter)
65 _, resp = self._http.get2_cmd('{}{}'.format(self._apiBase,filter_string))
66 if resp:
67 return json.loads(resp)
68 return list()
69
70 def get(self, name):
71 """Returns an NSI based on name or id
72 """
73 self._logger.debug("")
74 self._client.get_token()
75 if utils.validate_uuid4(name):
76 for nsi in self.list():
77 if name == nsi['_id']:
78 return nsi
79 else:
80 for nsi in self.list():
81 if name == nsi['name']:
82 return nsi
83 raise NotFound("nsi {} not found".format(name))
84
85 def get_individual(self, name):
86 self._logger.debug("")
87 nsi_id = name
88 self._client.get_token()
89 if not utils.validate_uuid4(name):
90 for nsi in self.list():
91 if name == nsi['name']:
92 nsi_id = nsi['_id']
93 break
94 try:
95 _, resp = self._http.get2_cmd('{}/{}'.format(self._apiBase, nsi_id))
96 #resp = self._http.get_cmd('{}/{}/nsd_content'.format(self._apiBase, nsi_id))
97 #print(yaml.safe_dump(resp))
98 if resp:
99 return json.loads(resp)
100 except NotFound:
101 raise NotFound("nsi '{}' not found".format(name))
102 raise NotFound("nsi {} not found".format(name))
103
104 def delete(self, name, force=False, wait=False):
105 self._logger.debug("")
106 nsi = self.get(name)
107 querystring = ''
108 if force:
109 querystring = '?FORCE=True'
110 http_code, resp = self._http.delete_cmd('{}/{}{}'.format(self._apiBase,
111 nsi['_id'], querystring))
112 # print('HTTP CODE: {}'.format(http_code))
113 # print('RESP: {}'.format(resp))
114 if http_code == 202:
115 if wait and resp:
116 resp = json.loads(resp)
117 # Wait for status for NSI instance deletion
118 # For the 'delete' operation, '_id' is used
119 self._wait(resp.get('_id'), deleteFlag=True)
120 else:
121 print('Deletion in progress')
122 elif http_code == 204:
123 print('Deleted')
124 else:
125 msg = resp or ""
126 # if resp:
127 # try:
128 # msg = json.loads(resp)
129 # except ValueError:
130 # msg = resp
131 raise ClientException("failed to delete nsi {} - {}".format(name, msg))
132
133 def create(self, nst_name, nsi_name, account, config=None,
134 ssh_keys=None, description='default description',
135 admin_status='ENABLED', wait=False):
136
137 self._logger.debug("")
138 self._client.get_token()
139 nst = self._client.nst.get(nst_name)
140
141 vim_account_id = {}
142
143 def get_vim_account_id(vim_account):
144 self._logger.debug("")
145 if vim_account_id.get(vim_account):
146 return vim_account_id[vim_account]
147
148 vim = self._client.vim.get(vim_account)
149 if vim is None:
150 raise NotFound("cannot find vim account '{}'".format(vim_account))
151 vim_account_id[vim_account] = vim['_id']
152 return vim['_id']
153
154 nsi = {}
155 nsi['nstId'] = nst['_id']
156 nsi['nsiName'] = nsi_name
157 nsi['nsiDescription'] = description
158 nsi['vimAccountId'] = get_vim_account_id(account)
159 #nsi['userdata'] = {}
160 #nsi['userdata']['key1']='value1'
161 #nsi['userdata']['key2']='value2'
162
163 if ssh_keys is not None:
164 # ssh_keys is comma separate list
165 # ssh_keys_format = []
166 # for key in ssh_keys.split(','):
167 # ssh_keys_format.append({'key-pair-ref': key})
168 #
169 # ns['ssh-authorized-key'] = ssh_keys_format
170 nsi['ssh_keys'] = []
171 for pubkeyfile in ssh_keys.split(','):
172 with open(pubkeyfile, 'r') as f:
173 nsi['ssh_keys'].append(f.read())
174 if config:
175 nsi_config = yaml.safe_load(config)
176 if "netslice-vld" in nsi_config:
177 for vld in nsi_config["netslice-vld"]:
178 if vld.get("vim-network-name"):
179 if isinstance(vld["vim-network-name"], dict):
180 vim_network_name_dict = {}
181 for vim_account, vim_net in list(vld["vim-network-name"].items()):
182 vim_network_name_dict[get_vim_account_id(vim_account)] = vim_net
183 vld["vim-network-name"] = vim_network_name_dict
184 nsi["netslice-vld"] = nsi_config["netslice-vld"]
185 if "netslice-subnet" in nsi_config:
186 for nssubnet in nsi_config["netslice-subnet"]:
187 if "vld" in nssubnet:
188 for vld in nssubnet["vld"]:
189 if vld.get("vim-network-name"):
190 if isinstance(vld["vim-network-name"], dict):
191 vim_network_name_dict = {}
192 for vim_account, vim_net in list(vld["vim-network-name"].items()):
193 vim_network_name_dict[get_vim_account_id(vim_account)] = vim_net
194 vld["vim-network-name"] = vim_network_name_dict
195 if "vnf" in nssubnet:
196 for vnf in nsi_config["vnf"]:
197 if vnf.get("vim_account"):
198 vnf["vimAccountId"] = get_vim_account_id(vnf.pop("vim_account"))
199 nsi["netslice-subnet"] = nsi_config["netslice-subnet"]
200
201 if "additionalParamsForNsi" in nsi_config:
202 nsi["additionalParamsForNsi"] = nsi_config.pop("additionalParamsForNsi")
203 if not isinstance(nsi["additionalParamsForNsi"], dict):
204 raise ValueError("Error at --config 'additionalParamsForNsi' must be a dictionary")
205 if "additionalParamsForSubnet" in nsi_config:
206 nsi["additionalParamsForSubnet"] = nsi_config.pop("additionalParamsForSubnet")
207 if not isinstance(nsi["additionalParamsForSubnet"], list):
208 raise ValueError("Error at --config 'additionalParamsForSubnet' must be a list")
209 for additional_param_subnet in nsi["additionalParamsForSubnet"]:
210 if not isinstance(additional_param_subnet, dict):
211 raise ValueError("Error at --config 'additionalParamsForSubnet' items must be dictionaries")
212 if not additional_param_subnet.get("id"):
213 raise ValueError("Error at --config 'additionalParamsForSubnet' items must contain subnet 'id'")
214 if not additional_param_subnet.get("additionalParamsForNs") and\
215 not additional_param_subnet.get("additionalParamsForVnf"):
216 raise ValueError("Error at --config 'additionalParamsForSubnet' items must contain "
217 "'additionalParamsForNs' and/or 'additionalParamsForVnf'")
218 if "timeout_nsi_deploy" in nsi_config:
219 nsi["timeout_nsi_deploy"] = nsi_config.pop("timeout_nsi_deploy")
220
221 # print(yaml.safe_dump(nsi))
222 try:
223 self._apiResource = '/netslice_instances_content'
224 self._apiBase = '{}{}{}'.format(self._apiName,
225 self._apiVersion, self._apiResource)
226 headers = self._client._headers
227 headers['Content-Type'] = 'application/yaml'
228 http_header = ['{}: {}'.format(key,val)
229 for (key,val) in list(headers.items())]
230 self._http.set_http_header(http_header)
231 http_code, resp = self._http.post_cmd(endpoint=self._apiBase,
232 postfields_dict=nsi)
233 #print('HTTP CODE: {}'.format(http_code))
234 #print('RESP: {}'.format(resp))
235 #if http_code in (200, 201, 202, 204):
236 if resp:
237 resp = json.loads(resp)
238 if not resp or 'id' not in resp:
239 raise ClientException('unexpected response from server - {} '.format(
240 resp))
241 if wait:
242 # Wait for status for NSI instance creation
243 self._wait(resp.get('nsilcmop_id'))
244 print(resp['id'])
245 #else:
246 # msg = ""
247 # if resp:
248 # try:
249 # msg = json.loads(resp)
250 # except ValueError:
251 # msg = resp
252 # raise ClientException(msg)
253 except ClientException as exc:
254 message="failed to create nsi: {} nst: {}\nerror:\n{}".format(
255 nsi_name,
256 nst_name,
257 str(exc))
258 raise ClientException(message)
259
260 def list_op(self, name, filter=None):
261 """Returns the list of operations of a NSI
262 """
263 self._logger.debug("")
264 nsi = self.get(name)
265 try:
266 self._apiResource = '/nsi_lcm_op_occs'
267 self._apiBase = '{}{}{}'.format(self._apiName,
268 self._apiVersion, self._apiResource)
269 filter_string = ''
270 if filter:
271 filter_string = '&{}'.format(filter)
272 http_code, resp = self._http.get2_cmd('{}?netsliceInstanceId={}'.format(
273 self._apiBase, nsi['_id'],
274 filter_string) )
275 #print('HTTP CODE: {}'.format(http_code))
276 #print('RESP: {}'.format(resp))
277 #if http_code == 200:
278 if resp:
279 resp = json.loads(resp)
280 return resp
281 else:
282 raise ClientException('unexpected response from server')
283 #else:
284 # msg = ""
285 # if resp:
286 # try:
287 # resp = json.loads(resp)
288 # msg = resp['detail']
289 # except ValueError:
290 # msg = resp
291 # raise ClientException(msg)
292 except ClientException as exc:
293 message="failed to get operation list of NSI {}:\nerror:\n{}".format(
294 name,
295 str(exc))
296 raise ClientException(message)
297
298 def get_op(self, operationId):
299 """Returns the status of an operation
300 """
301 self._logger.debug("")
302 self._client.get_token()
303 try:
304 self._apiResource = '/nsi_lcm_op_occs'
305 self._apiBase = '{}{}{}'.format(self._apiName,
306 self._apiVersion, self._apiResource)
307 http_code, resp = self._http.get2_cmd('{}/{}'.format(self._apiBase, operationId))
308 #print('HTTP CODE: {}'.format(http_code))
309 #print('RESP: {}'.format(resp))
310 #if http_code == 200:
311 if resp:
312 resp = json.loads(resp)
313 return resp
314 else:
315 raise ClientException('unexpected response from server')
316 #else:
317 # msg = ""
318 # if resp:
319 # try:
320 # resp = json.loads(resp)
321 # msg = resp['detail']
322 # except ValueError:
323 # msg = resp
324 # raise ClientException(msg)
325 except ClientException as exc:
326 message="failed to get status of operation {}:\nerror:\n{}".format(
327 operationId,
328 str(exc))
329 raise ClientException(message)
330
331 def exec_op(self, name, op_name, op_data=None):
332 """Executes an operation on a NSI
333 """
334 self._logger.debug("")
335 nsi = self.get(name)
336 try:
337 self._apiResource = '/netslice_instances'
338 self._apiBase = '{}{}{}'.format(self._apiName,
339 self._apiVersion, self._apiResource)
340 endpoint = '{}/{}/{}'.format(self._apiBase, nsi['_id'], op_name)
341 #print('OP_NAME: {}'.format(op_name))
342 #print('OP_DATA: {}'.format(json.dumps(op_data)))
343 http_code, resp = self._http.post_cmd(endpoint=endpoint, postfields_dict=op_data)
344 #print('HTTP CODE: {}'.format(http_code))
345 #print('RESP: {}'.format(resp))
346 #if http_code in (200, 201, 202, 204):
347 if resp:
348 resp = json.loads(resp)
349 if not resp or 'id' not in resp:
350 raise ClientException('unexpected response from server - {}'.format(
351 resp))
352 print(resp['id'])
353 #else:
354 # msg = ""
355 # if resp:
356 # try:
357 # msg = json.loads(resp)
358 # except ValueError:
359 # msg = resp
360 # raise ClientException(msg)
361 except ClientException as exc:
362 message="failed to exec operation {}:\nerror:\n{}".format(
363 name,
364 str(exc))
365 raise ClientException(message)
366