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