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