CAL and SDNAL cleanup for failure cases in accounts
[osm/SO.git] / rwlaunchpad / plugins / rwvns / vala / rwsdn_openstack / rwsdn_openstack.py
1
2 #
3 # Copyright 2016 RIFT.IO Inc
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain 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,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17
18 import logging
19
20 import gi
21 gi.require_version('RwSdn', '1.0')
22
23 import rift.rwcal.openstack as openstack_drv
24 from rift.rwcal.openstack import session as sess_drv
25 from rift.rwcal.openstack import keystone as ks_drv
26 from rift.rwcal.openstack import neutron as nt_drv
27 from rift.rwcal.openstack import portchain as port_drv
28
29
30
31 import rw_status
32 import rift.cal.rwcal_status as rwcal_status
33 import rwlogger
34 import neutronclient.common.exceptions as NeutronException
35 import keystoneclient.exceptions as KeystoneExceptions
36
37
38 from gi.repository import (
39 GObject,
40 RwSdn, # Vala package
41 RwsdnalYang,
42 RwTypes)
43
44 rwstatus_exception_map = {IndexError: RwTypes.RwStatus.NOTFOUND,
45 KeyError: RwTypes.RwStatus.NOTFOUND,
46 NotImplementedError: RwTypes.RwStatus.NOT_IMPLEMENTED, }
47
48 rwstatus = rw_status.rwstatus_from_exc_map(rwstatus_exception_map)
49 rwcalstatus = rwcal_status.rwcalstatus_from_exc_map(rwstatus_exception_map)
50
51
52 class OpenstackSdnOperationFailure(Exception):
53 pass
54
55 class UninitializedPluginError(Exception):
56 pass
57
58 class OpenstackL2PortChainingDriver(object):
59 """
60 Driver for openstack keystone and neutron
61 """
62 def __init__(self, logger = None, **kwargs):
63 """
64 OpenstackDriver Driver constructor
65 Arguments:
66 logger: (instance of logging.Logger)
67 kwargs: A dictionary of
68 {
69 username (string) : Username for project/tenant.
70 password (string) : Password
71 auth_url (string) : Keystone Authentication URL.
72 project (string) : Openstack project name
73 cert_validate (boolean, optional) : In case of SSL/TLS connection if certificate validation is required or not.
74 user_domain : Domain name for user
75 project_domain : Domain name for project
76 region : Region name
77 }
78 """
79
80 if logger is None:
81 self.log = logging.getLogger('rwsdn.openstack.driver')
82 self.log.setLevel(logging.DEBUG)
83 else:
84 self.log = logger
85
86 args = dict(auth_url = kwargs['auth_url'],
87 username = kwargs['username'],
88 password = kwargs['password'],
89 project_name = kwargs['project'],
90 project_domain_name = kwargs['project_domain'] if 'project_domain' in kwargs else None,
91 user_domain_name = kwargs['user_domain'] if 'user_domain' in kwargs else None,)
92
93 self.auth_url = kwargs['auth_url']
94 cert_validate = kwargs['cert_validate'] if 'cert_validate' in kwargs else False
95 region = kwargs['region_name'] if 'region_name' in kwargs else False
96
97 discover = ks_drv.KeystoneVersionDiscover(kwargs['auth_url'], logger = self.log)
98 (major, minor) = discover.get_version()
99
100 self.sess_drv = sess_drv.SessionDriver(auth_method = 'password',
101 version = str(major),
102 cert_validate = cert_validate,
103 logger = self.log,
104 **args)
105
106 self.neutron_drv = nt_drv.NeutronDriver(self.sess_drv,
107 region_name = region,
108 logger = self.log)
109
110 self.portchain_drv = port_drv.L2PortChainDriver(self.sess_drv,
111 self.neutron_drv.neutron_endpoint,
112 logger = self.log)
113
114 def validate_account_creds(self):
115 status = RwsdnalYang.SdnConnectionStatus()
116 try:
117 self.sess_drv.invalidate_auth_token()
118 self.sess_drv.auth_token
119 except KeystoneExceptions.Unauthorized as e:
120 self.log.error("Invalid credentials given for SDN account ")
121 status.status = "failure"
122 status.details = "Invalid Credentials: %s" % str(e)
123
124 except KeystoneExceptions.AuthorizationFailure as e:
125 self.log.error("Bad authentication URL given for SDN account. Given auth url: %s",
126 self.auth_url)
127 status.status = "failure"
128 status.details = "Invalid auth url: %s" % str(e)
129
130 except NeutronException.NotFound as e:
131 status.status = "failure"
132 status.details = "Neutron exception %s" % str(e)
133
134 except openstack_drv.ValidationError as e:
135 self.log.error("RwcalOpenstackPlugin: OpenstackDriver credential validation failed. Exception: %s", str(e))
136 status.status = "failure"
137 status.details = "Invalid Credentials: %s" % str(e)
138
139 except Exception as e:
140 msg = "RwsdnOpenstackPlugin: OpenstackDriver connection failed. Exception: %s" %(str(e))
141 self.log.error(msg)
142 status.status = "failure"
143 status.details = msg
144
145 else:
146 status.status = "success"
147 status.details = "Connection was successful"
148
149 return status
150
151 def delete_port_chain(self, port_chain_id):
152 "Delete port chain"
153 try:
154 result = self.portchain_drv.get_port_chain(port_chain_id)
155 port_chain = result.json()
156 self.log.debug("Port chain result is %s", port_chain)
157 port_pair_groups = port_chain["port_chain"]["port_pair_groups"]
158 self.portchain_drv.delete_port_chain(port_chain_id)
159
160 # Get port pairs and delete port pair groups
161 port_pairs = list()
162 self.log.debug("Port pair groups during delete is %s", port_pair_groups)
163 for port_pair_group_id in port_pair_groups:
164 result = self.portchain_drv.get_port_pair_group(port_pair_group_id)
165 port_pair_group = result.json()
166 self.log.debug("Port pair group result is %s", port_pair_group)
167 port_pairs.extend(port_pair_group["port_pair_group"]["port_pairs"])
168 self.portchain_drv.delete_port_pair_group(port_pair_group_id)
169
170 self.log.debug("Port pairs during delete is %s", port_pairs)
171
172 for port_pair_id in port_pairs:
173 self.portchain_drv.delete_port_pair(port_pair_id)
174 pass
175 except Exception as e:
176 self.log.error("Error while delete port chain with id %s, exception %s", port_chain_id, str(e))
177
178 def update_port_chain(self, port_chain_id, flow_classifier_list):
179 result = self.portchain_drv.get_port_chain(port_chain_id)
180 result.raise_for_status()
181 port_chain = result.json()['port_chain']
182 new_flow_classifier_list = list()
183 if port_chain and port_chain['flow_classifiers']:
184 new_flow_classifier_list.extend(port_chain['flow_classifiers'])
185 new_flow_classifier_list.extend(flow_classifier_list)
186 port_chain_id = self.portchain_drv.update_port_chain(port_chain['id'], flow_classifiers=new_flow_classifier_list)
187 return port_chain_id
188
189 def create_flow_classifer(self, classifier_name, classifier_dict):
190 "Create flow classifier"
191 flow_classifier_id = self.portchain_drv.create_flow_classifier(classifier_name, classifier_dict)
192 return flow_classifier_id
193
194 def delete_flow_classifier(self, classifier_id):
195 "Create flow classifier"
196 try:
197 self.portchain_drv.delete_flow_classifier(classifier_id)
198 except Exception as e:
199 self.log.error("Error while deleting flow classifier with id %s, exception %s", classifier_id, str(e))
200
201 def get_port_chain_list(self):
202 result = self.portchain_drv.get_port_chain_list()
203 port_chain_list = result.json()
204 if 'port_chains' in port_chain_list:
205 return port_chain_list['port_chains']
206
207
208 class RwsdnAccountDriver(object):
209 """
210 Container class per sdn account
211 """
212 def __init__(self, logger, **kwargs):
213 self.log = logger
214 try:
215 self._driver = OpenstackL2PortChainingDriver(logger = self.log, **kwargs)
216 except (KeystoneExceptions.Unauthorized, KeystoneExceptions.AuthorizationFailure,
217 NeutronException.NotFound) as e:
218 raise
219 except Exception as e:
220 self.log.error("RwsdnOpenstackPlugin: OpenstackL2PortChainingDriver init failed. Exception: %s" %(str(e)))
221 raise
222
223 @property
224 def driver(self):
225 return self._driver
226
227
228 class SdnOpenstackPlugin(GObject.Object, RwSdn.Topology):
229 instance_num = 1
230
231 def __init__(self):
232 GObject.Object.__init__(self)
233 self.log = logging.getLogger('rwsdn.openstack.%s' % SdnOpenstackPlugin.instance_num)
234 self.log.setLevel(logging.DEBUG)
235
236 self._rwlog_handler = None
237 self._account_drivers = dict()
238 SdnOpenstackPlugin.instance_num += 1
239
240 def _use_driver(self, account):
241 if self._rwlog_handler is None:
242 raise UninitializedPluginError("Must call init() in SDN plugin before use.")
243
244 if account.name not in self._account_drivers:
245 self.log.debug("Creating SDN OpenstackDriver")
246 kwargs = dict(username = account.openstack.key,
247 password = account.openstack.secret,
248 auth_url = account.openstack.auth_url,
249 project = account.openstack.tenant,
250 cert_validate = account.openstack.cert_validate,
251 user_domain = account.openstack.user_domain,
252 project_domain = account.openstack.project_domain,
253 region = account.openstack.region)
254 drv = RwsdnAccountDriver(self.log, **kwargs)
255 self._account_drivers[account.name] = drv
256 return drv.driver
257 else:
258 return self._account_drivers[account.name].driver
259
260 @rwstatus
261 def do_init(self, rwlog_ctx):
262 self._rwlog_handler = rwlogger.RwLogger(
263 category="rw-cal-log",
264 subcategory="openstack",
265 log_hdl=rwlog_ctx,
266 )
267 self.log.addHandler(self._rwlog_handler)
268 self.log.propagate = False
269
270 @rwstatus(ret_on_failure=[None])
271 def do_validate_sdn_creds(self, account):
272 """
273 Validates the sdn account credentials for the specified account.
274 Performs an access to the resources using Keystone API. If creds
275 are not valid, returns an error code & reason string
276
277 @param account - a SDN account
278
279 Returns:
280 Validation Code and Details String
281 """
282 status = RwsdnalYang.SdnConnectionStatus()
283 try:
284 drv = self._use_driver(account)
285 drv.validate_account_creds()
286
287 except openstack_drv.ValidationError as e:
288 self.log.error("SdnOpenstackPlugin: OpenstackDriver credential validation failed. Exception: %s", str(e))
289 status.status = "failure"
290 status.details = "Invalid Credentials: %s" % str(e)
291
292 except Exception as e:
293 msg = "SdnOpenstackPlugin: OpenstackDriver connection failed. Exception: %s" %(str(e))
294 self.log.error(msg)
295 status.status = "failure"
296 status.details = msg
297
298 else:
299 status.status = "success"
300 status.details = "Connection was successful"
301
302 return status
303
304 @rwstatus(ret_on_failure=[""])
305 def do_create_vnffg_chain(self, account, vnffg):
306 """
307 Creates Service Function chain in ODL
308
309 @param account - a SDN account
310
311 """
312 self.log.debug('Received Create VNFFG chain for account {}, chain {}'.format(account, vnffg))
313 drv = self._use_driver(account)
314 port_list = list()
315 vnf_chain_list = sorted(vnffg.vnf_chain_path, key = lambda x: x.order)
316 prev_vm_id = None
317 for path in vnf_chain_list:
318 if prev_vm_id and path.vnfr_ids[0].vdu_list[0].vm_id == prev_vm_id:
319 prev_entry = port_list.pop()
320 port_list.append((prev_entry[0], path.vnfr_ids[0].vdu_list[0].port_id))
321 prev_vm_id = None
322 else:
323 prev_vm_id = path.vnfr_ids[0].vdu_list[0].vm_id
324 port_list.append((path.vnfr_ids[0].vdu_list[0].port_id, path.vnfr_ids[0].vdu_list[0].port_id))
325 vnffg_id = drv.create_port_chain(vnffg.name, port_list)
326 return vnffg_id
327
328 @rwstatus
329 def do_terminate_vnffg_chain(self, account, vnffg_id):
330 """
331 Terminate Service Function chain in ODL
332
333 @param account - a SDN account
334 """
335 self.log.debug('Received terminate VNFFG chain for id %s ', vnffg_id)
336 drv = self._use_driver(account)
337 drv.delete_port_chain(vnffg_id)
338
339 @rwstatus(ret_on_failure=[None])
340 def do_create_vnffg_classifier(self, account, vnffg_classifier):
341 """
342 Add VNFFG Classifier
343
344 @param account - a SDN account
345 """
346 self.log.debug('Received Create VNFFG classifier for account {}, classifier {}'.format(account, vnffg_classifier))
347 protocol_map = {1: 'ICMP', 6: 'TCP', 17: 'UDP'}
348 flow_classifier_list = list()
349 drv = self._use_driver(account)
350 for rule in vnffg_classifier.match_attributes:
351 classifier_name = vnffg_classifier.name + '_' + rule.name
352 flow_dict = {}
353 for field, value in rule.as_dict().items():
354 if field == 'ip_proto':
355 flow_dict['protocol'] = protocol_map.get(value, None)
356 elif field == 'source_ip_address':
357 flow_dict['source_ip_prefix'] = value
358 elif field == 'destination_ip_address':
359 flow_dict['destination_ip_prefix'] = value
360 elif field == 'source_port':
361 flow_dict['source_port_range_min'] = value
362 flow_dict['source_port_range_max'] = value
363 elif field == 'destination_port':
364 flow_dict['destination_port_range_min'] = value
365 flow_dict['destination_port_range_max'] = value
366 if vnffg_classifier.has_field('port_id'):
367 flow_dict['logical_source_port'] = vnffg_classifier.port_id
368 flow_classifier_id = drv.create_flow_classifer(classifier_name, flow_dict)
369 flow_classifier_list.append(flow_classifier_id)
370 drv.update_port_chain(vnffg_classifier.rsp_id, flow_classifier_list)
371 return flow_classifier_list
372
373 @rwstatus(ret_on_failure=[None])
374 def do_terminate_vnffg_classifier(self, account, vnffg_classifier_list):
375 """
376 Add VNFFG Classifier
377
378 @param account - a SDN account
379 """
380 self.log.debug('Received terminate VNFFG classifier for id %s ', vnffg_classifier_list)
381 drv = self._use_driver(account)
382 for classifier_id in vnffg_classifier_list:
383 drv.delete_flow_classifier(classifier_id)
384
385 @rwstatus(ret_on_failure=[None])
386 def do_get_vnffg_rendered_paths(self, account):
387 """
388 Get Rendered Service Path List (SFC)
389
390 @param account - a SDN account
391 """
392 self.log.debug('Received get VNFFG rendered path for account %s ', account)
393 vnffg_rsps = RwsdnalYang.VNFFGRenderedPaths()
394 drv = self._use_driver(account)
395 port_chain_list = drv.get_port_chain_list()
396 for port_chain in port_chain_list:
397 #rsp = vnffg_rsps.vnffg_rendered_path.add()
398 #rsp.name = port_chain['name']
399 pass
400 return vnffg_rsps
401
402