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