1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 # and others. All rights reserved.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
18 from neutronclient.common.exceptions import NotFound, Conflict
20 from snaps.openstack.openstack_creator import OpenStackNetworkObject
21 from snaps.openstack.utils import keystone_utils
22 from snaps.openstack.utils import neutron_utils
24 __author__ = 'spisarski'
26 logger = logging.getLogger('OpenStackSecurityGroup')
29 class OpenStackSecurityGroup(OpenStackNetworkObject):
31 Class responsible for managing a Security Group in OpenStack
34 def __init__(self, os_creds, sec_grp_settings):
36 Constructor - all parameters are required
37 :param os_creds: The credentials to connect with OpenStack
38 :param sec_grp_settings: The settings used to create a security group
40 super(self.__class__, self).__init__(os_creds)
42 self.sec_grp_settings = sec_grp_settings
44 # Attributes instantiated on create()
45 self.__security_group = None
47 # dict where the rule settings object is the key
52 Loads existing security group.
53 :return: the security group domain object
55 super(self.__class__, self).initialize()
57 self.__security_group = neutron_utils.get_security_group(
58 self._neutron, sec_grp_settings=self.sec_grp_settings)
59 if self.__security_group:
61 existing_rules = neutron_utils.get_rules_by_security_group(
62 self._neutron, self.__security_group)
64 for existing_rule in existing_rules:
66 rule_setting = self.__get_setting_from_rule(existing_rule)
67 self.__rules[rule_setting] = existing_rule
69 self.__security_group = neutron_utils.get_security_group_by_id(
70 self._neutron, self.__security_group.id)
72 return self.__security_group
76 Responsible for creating the security group.
77 :return: the security group domain object
81 if not self.__security_group:
83 'Creating security group %s...' % self.sec_grp_settings.name)
85 keystone = keystone_utils.keystone_client(self._os_creds)
86 self.__security_group = neutron_utils.create_security_group(
87 self._neutron, keystone,
88 self.sec_grp_settings)
90 # Get the rules added for free
91 auto_rules = neutron_utils.get_rules_by_security_group(
92 self._neutron, self.__security_group)
95 for auto_rule in auto_rules:
96 auto_rule_setting = self.__generate_rule_setting(auto_rule)
97 self.__rules[auto_rule_setting] = auto_rule
100 # Create the custom rules
101 for sec_grp_rule_setting in self.sec_grp_settings.rule_settings:
103 custom_rule = neutron_utils.create_security_group_rule(
104 self._neutron, sec_grp_rule_setting)
105 self.__rules[sec_grp_rule_setting] = custom_rule
106 except Conflict as e:
107 logger.warn('Unable to create rule due to conflict - %s',
110 # Refresh security group object to reflect the new rules added
111 self.__security_group = neutron_utils.get_security_group(
112 self._neutron, sec_grp_settings=self.sec_grp_settings)
114 return self.__security_group
116 def __generate_rule_setting(self, rule):
118 Creates a SecurityGroupRuleSettings object for a given rule
119 :param rule: the rule from which to create the
120 SecurityGroupRuleSettings object
121 :return: the newly instantiated SecurityGroupRuleSettings object
123 sec_grp = neutron_utils.get_security_group_by_id(
124 self._neutron, rule.security_group_id)
126 setting = SecurityGroupRuleSettings(
127 description=rule.description,
128 direction=rule.direction,
129 ethertype=rule.ethertype,
130 port_range_min=rule.port_range_min,
131 port_range_max=rule.port_range_max,
132 protocol=rule.protocol,
133 remote_group_id=rule.remote_group_id,
134 remote_ip_prefix=rule.remote_ip_prefix,
135 sec_grp_name=sec_grp.name)
140 Removes and deletes the rules then the security group.
142 for setting, rule in self.__rules.items():
144 neutron_utils.delete_security_group_rule(self._neutron, rule)
145 except NotFound as e:
146 logger.warning('Rule not found, cannot delete - ' + str(e))
148 self.__rules = dict()
150 if self.__security_group:
152 neutron_utils.delete_security_group(self._neutron,
153 self.__security_group)
154 except NotFound as e:
156 'Security Group not found, cannot delete - ' + str(e))
158 self.__security_group = None
160 def get_security_group(self):
162 Returns the OpenStack security group object
165 return self.__security_group
169 Returns the associated rules
174 def add_rule(self, rule_setting):
176 Adds a rule to this security group
177 :param rule_setting: the rule configuration
179 rule_setting.sec_grp_name = self.sec_grp_settings.name
180 new_rule = neutron_utils.create_security_group_rule(self._neutron,
182 self.__rules[rule_setting] = new_rule
183 self.sec_grp_settings.rule_settings.append(rule_setting)
185 def remove_rule(self, rule_id=None, rule_setting=None):
187 Removes a rule to this security group by id, name, or rule_setting
189 :param rule_id: the rule's id
190 :param rule_setting: the rule's setting object
192 rule_to_remove = None
193 if rule_id or rule_setting:
195 rule_to_remove = neutron_utils.get_rule_by_id(
196 self._neutron, self.__security_group, rule_id)
198 rule_to_remove = self.__rules.get(rule_setting)
201 neutron_utils.delete_security_group_rule(self._neutron,
203 rule_setting = self.__get_setting_from_rule(rule_to_remove)
205 self.__rules.pop(rule_setting)
207 logger.warning('Rule setting is None, cannot remove rule')
209 def __get_setting_from_rule(self, rule):
211 Returns the associated RuleSetting object for a given rule
212 :param rule: the Rule object
213 :return: the associated RuleSetting object or None
215 for rule_setting in self.sec_grp_settings.rule_settings:
216 if rule_setting.rule_eq(rule):
221 class SecurityGroupSettings:
223 Class representing a keypair configuration
226 def __init__(self, **kwargs):
229 :param name: The security group's name (required)
230 :param description: The security group's description (optional)
231 :param project_name: The name of the project under which the security
232 group will be created
233 :param rule_settings: a list of SecurityGroupRuleSettings objects
236 self.name = kwargs.get('name')
237 self.description = kwargs.get('description')
238 self.project_name = kwargs.get('project_name')
239 self.rule_settings = list()
241 rule_settings = kwargs.get('rules')
242 if not rule_settings:
243 rule_settings = kwargs.get('rule_settings')
246 for rule_setting in rule_settings:
247 if isinstance(rule_setting, SecurityGroupRuleSettings):
248 self.rule_settings.append(rule_setting)
250 rule_setting['sec_grp_name'] = self.name
251 self.rule_settings.append(SecurityGroupRuleSettings(
255 raise SecurityGroupSettingsError('The attribute name is required')
257 for rule_setting in self.rule_settings:
258 if rule_setting.sec_grp_name is not self.name:
259 raise SecurityGroupSettingsError(
260 'Rule settings must correspond with the name of this '
263 def dict_for_neutron(self, keystone):
265 Returns a dictionary object representing this object.
266 This is meant to be converted into JSON designed for use by the Neutron
269 TODO - expand automated testing to exercise all parameters
270 :param keystone: the Keystone client
271 :return: the dictionary object
276 out['name'] = self.name
278 out['description'] = self.description
279 if self.project_name:
280 project = keystone_utils.get_project(
281 keystone=keystone, project_name=self.project_name)
284 project_id = project.id
286 out['tenant_id'] = project_id
288 raise SecurityGroupSettingsError(
289 'Could not find project ID for project named - ' +
292 return {'security_group': out}
295 class Direction(enum.Enum):
303 class Protocol(enum.Enum):
313 class Ethertype(enum.Enum):
321 class SecurityGroupSettingsError(Exception):
323 Exception to be thrown when security group settings attributes are
328 class SecurityGroupRuleSettings:
330 Class representing a keypair configuration
333 def __init__(self, **kwargs):
335 Constructor - all parameters are optional
336 :param sec_grp_name: The security group's name on which to add the
338 :param description: The rule's description
339 :param direction: An enumeration of type
340 create_security_group.RULE_DIRECTION (required)
341 :param remote_group_id: The group ID to associate with this rule
342 (this should be changed to group name once
343 snaps support Groups) (optional)
344 :param protocol: An enumeration of type
345 create_security_group.RULE_PROTOCOL or a string value
346 that will be mapped accordingly (optional)
347 :param ethertype: An enumeration of type
348 create_security_group.RULE_ETHERTYPE (optional)
349 :param port_range_min: The minimum port number in the range that is
350 matched by the security group rule. When the
351 protocol is TCP or UDP, this value must be <=
353 :param port_range_max: The maximum port number in the range that is
354 matched by the security group rule. When the
355 protocol is TCP or UDP, this value must be <=
357 :param remote_ip_prefix: The remote IP prefix to associate with this
358 metering rule packet (optional)
360 TODO - Need to support the tenant...
363 self.description = kwargs.get('description')
364 self.sec_grp_name = kwargs.get('sec_grp_name')
365 self.remote_group_id = kwargs.get('remote_group_id')
366 self.direction = None
367 if kwargs.get('direction'):
368 self.direction = map_direction(kwargs['direction'])
371 if kwargs.get('protocol'):
372 self.protocol = map_protocol(kwargs['protocol'])
374 self.protocol = Protocol.null
376 self.ethertype = None
377 if kwargs.get('ethertype'):
378 self.ethertype = map_ethertype(kwargs['ethertype'])
380 self.port_range_min = kwargs.get('port_range_min')
381 self.port_range_max = kwargs.get('port_range_max')
382 self.remote_ip_prefix = kwargs.get('remote_ip_prefix')
384 if not self.direction or not self.sec_grp_name:
385 raise SecurityGroupRuleSettingsError(
386 'direction and sec_grp_name are required')
388 def dict_for_neutron(self, neutron):
390 Returns a dictionary object representing this object.
391 This is meant to be converted into JSON designed for use by the Neutron
394 :param neutron: the neutron client for performing lookups
395 :return: the dictionary object
400 out['description'] = self.description
402 out['direction'] = self.direction.name
403 if self.port_range_min:
404 out['port_range_min'] = self.port_range_min
405 if self.port_range_max:
406 out['port_range_max'] = self.port_range_max
408 out['ethertype'] = self.ethertype.name
409 if self.protocol and self.protocol.name != 'null':
410 out['protocol'] = self.protocol.name
411 if self.sec_grp_name:
412 sec_grp = neutron_utils.get_security_group(
413 neutron, sec_grp_name=self.sec_grp_name)
415 out['security_group_id'] = sec_grp.id
417 raise SecurityGroupRuleSettingsError(
418 'Cannot locate security group with name - ' +
420 if self.remote_group_id:
421 out['remote_group_id'] = self.remote_group_id
422 if self.remote_ip_prefix:
423 out['remote_ip_prefix'] = self.remote_ip_prefix
425 return {'security_group_rule': out}
427 def rule_eq(self, rule):
429 Returns True if this setting created the rule
430 :param rule: the rule to evaluate
433 if self.description is not None:
434 if (rule.description is not None and
435 rule.description != ''):
437 elif self.description != rule.description:
438 if rule.description != '':
441 if self.direction.name != rule.direction:
444 if self.ethertype and rule.ethertype:
445 if self.ethertype.name != rule.ethertype:
448 if self.port_range_min and rule.port_range_min:
449 if self.port_range_min != rule.port_range_min:
452 if self.port_range_max and rule.port_range_max:
453 if self.port_range_max != rule.port_range_max:
456 if self.protocol and rule.protocol:
457 if self.protocol.name != rule.protocol:
460 if self.remote_group_id and rule.remote_group_id:
461 if self.remote_group_id != rule.remote_group_id:
464 if self.remote_ip_prefix and rule.remote_ip_prefix:
465 if self.remote_ip_prefix != rule.remote_ip_prefix:
470 def __eq__(self, other):
472 self.description == other.description and
473 self.direction == other.direction and
474 self.port_range_min == other.port_range_min and
475 self.port_range_max == other.port_range_max and
476 self.ethertype == other.ethertype and
477 self.protocol == other.protocol and
478 self.sec_grp_name == other.sec_grp_name and
479 self.remote_group_id == other.remote_group_id and
480 self.remote_ip_prefix == other.remote_ip_prefix)
483 return hash((self.sec_grp_name, self.description, self.direction,
484 self.remote_group_id,
485 self.protocol, self.ethertype, self.port_range_min,
486 self.port_range_max, self.remote_ip_prefix))
489 def map_direction(direction):
491 Takes a the direction value maps it to the Direction enum. When None return
493 :param direction: the direction value
494 :return: the Direction enum object
495 :raise: Exception if value is invalid
499 if isinstance(direction, Direction):
502 dir_str = str(direction)
503 if dir_str == 'egress':
504 return Direction.egress
505 elif dir_str == 'ingress':
506 return Direction.ingress
508 raise SecurityGroupRuleSettingsError(
509 'Invalid Direction - ' + dir_str)
512 def map_protocol(protocol):
514 Takes a the protocol value maps it to the Protocol enum. When None return
516 :param protocol: the protocol value
517 :return: the Protocol enum object
518 :raise: Exception if value is invalid
522 elif isinstance(protocol, Protocol):
525 proto_str = str(protocol)
526 if proto_str == 'icmp':
528 elif proto_str == 'tcp':
530 elif proto_str == 'udp':
532 elif proto_str == 'null':
535 raise SecurityGroupRuleSettingsError(
536 'Invalid Protocol - ' + proto_str)
539 def map_ethertype(ethertype):
541 Takes a the ethertype value maps it to the Ethertype enum. When None return
543 :param ethertype: the ethertype value
544 :return: the Ethertype enum object
545 :raise: Exception if value is invalid
549 elif isinstance(ethertype, Ethertype):
552 eth_str = str(ethertype)
553 if eth_str == 'IPv6':
554 return Ethertype.IPv6
555 elif eth_str == 'IPv4':
556 return Ethertype.IPv4
558 raise SecurityGroupRuleSettingsError(
559 'Invalid Ethertype - ' + eth_str)
562 class SecurityGroupRuleSettingsError(Exception):
564 Exception to be thrown when security group rule settings attributes are