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):
333 class Ethertype(enum.Enum):
341 class SecurityGroupSettingsError(Exception):
343 Exception to be thrown when security group settings attributes are
348 class SecurityGroupRuleSettings:
350 Class representing a keypair configuration
353 def __init__(self, **kwargs):
355 Constructor - all parameters are optional
356 :param sec_grp_name: The security group's name on which to add the
358 :param description: The rule's description
359 :param direction: An enumeration of type
360 create_security_group.RULE_DIRECTION (required)
361 :param remote_group_id: The group ID to associate with this rule
362 (this should be changed to group name once
363 snaps support Groups) (optional)
364 :param protocol: An enumeration of type
365 create_security_group.RULE_PROTOCOL or a string value
366 that will be mapped accordingly (optional)
367 :param ethertype: An enumeration of type
368 create_security_group.RULE_ETHERTYPE (optional)
369 :param port_range_min: The minimum port number in the range that is
370 matched by the security group rule. When the
371 protocol is TCP or UDP, this value must be <=
373 :param port_range_max: The maximum port number in the range that is
374 matched by the security group rule. When the
375 protocol is TCP or UDP, this value must be <=
377 :param remote_ip_prefix: The remote IP prefix to associate with this
378 metering rule packet (optional)
380 TODO - Need to support the tenant...
383 self.description = kwargs.get('description')
384 self.sec_grp_name = kwargs.get('sec_grp_name')
385 self.remote_group_id = kwargs.get('remote_group_id')
386 self.direction = None
387 if kwargs.get('direction'):
388 self.direction = map_direction(kwargs['direction'])
391 if kwargs.get('protocol'):
392 self.protocol = map_protocol(kwargs['protocol'])
394 self.protocol = Protocol.null
396 self.ethertype = None
397 if kwargs.get('ethertype'):
398 self.ethertype = map_ethertype(kwargs['ethertype'])
400 self.port_range_min = kwargs.get('port_range_min')
401 self.port_range_max = kwargs.get('port_range_max')
402 self.remote_ip_prefix = kwargs.get('remote_ip_prefix')
404 if not self.direction or not self.sec_grp_name:
405 raise SecurityGroupRuleSettingsError(
406 'direction and sec_grp_name are required')
408 def dict_for_neutron(self, neutron):
410 Returns a dictionary object representing this object.
411 This is meant to be converted into JSON designed for use by the Neutron
414 :param neutron: the neutron client for performing lookups
415 :return: the dictionary object
420 out['description'] = self.description
422 out['direction'] = self.direction.name
423 if self.port_range_min:
424 out['port_range_min'] = self.port_range_min
425 if self.port_range_max:
426 out['port_range_max'] = self.port_range_max
428 out['ethertype'] = self.ethertype.name
429 if self.protocol and self.protocol.value != 'null':
430 out['protocol'] = self.protocol.value
431 if self.sec_grp_name:
432 sec_grp = neutron_utils.get_security_group(
433 neutron, sec_grp_name=self.sec_grp_name)
435 out['security_group_id'] = sec_grp.id
437 raise SecurityGroupRuleSettingsError(
438 'Cannot locate security group with name - ' +
440 if self.remote_group_id:
441 out['remote_group_id'] = self.remote_group_id
442 if self.remote_ip_prefix:
443 out['remote_ip_prefix'] = self.remote_ip_prefix
445 return {'security_group_rule': out}
447 def rule_eq(self, rule):
449 Returns True if this setting created the rule
450 :param rule: the rule to evaluate
453 if self.description is not None:
454 if (rule.description is not None and
455 rule.description != ''):
457 elif self.description != rule.description:
458 if rule.description != '':
461 if self.direction.name != rule.direction:
464 if self.ethertype and rule.ethertype:
465 if self.ethertype.name != rule.ethertype:
468 if self.port_range_min and rule.port_range_min:
469 if self.port_range_min != rule.port_range_min:
472 if self.port_range_max and rule.port_range_max:
473 if self.port_range_max != rule.port_range_max:
476 if self.protocol and rule.protocol:
477 if self.protocol.name != rule.protocol:
480 if self.remote_group_id and rule.remote_group_id:
481 if self.remote_group_id != rule.remote_group_id:
484 if self.remote_ip_prefix and rule.remote_ip_prefix:
485 if self.remote_ip_prefix != rule.remote_ip_prefix:
490 def __eq__(self, other):
492 self.description == other.description and
493 self.direction == other.direction and
494 self.port_range_min == other.port_range_min and
495 self.port_range_max == other.port_range_max and
496 self.ethertype == other.ethertype and
497 self.protocol == other.protocol and
498 self.sec_grp_name == other.sec_grp_name and
499 self.remote_group_id == other.remote_group_id and
500 self.remote_ip_prefix == other.remote_ip_prefix)
503 return hash((self.sec_grp_name, self.description, self.direction,
504 self.remote_group_id,
505 self.protocol, self.ethertype, self.port_range_min,
506 self.port_range_max, self.remote_ip_prefix))
509 def map_direction(direction):
511 Takes a the direction value maps it to the Direction enum. When None return
513 :param direction: the direction value
514 :return: the Direction enum object
515 :raise: Exception if value is invalid
519 if isinstance(direction, Direction):
522 dir_str = str(direction)
523 if dir_str == 'egress':
524 return Direction.egress
525 elif dir_str == 'ingress':
526 return Direction.ingress
528 raise SecurityGroupRuleSettingsError(
529 'Invalid Direction - ' + dir_str)
532 def map_protocol(protocol):
534 Takes a the protocol value maps it to the Protocol enum. When None return
536 :param protocol: the protocol value
537 :return: the Protocol enum object
538 :raise: Exception if value is invalid
542 elif isinstance(protocol, Protocol):
545 for proto_enum in Protocol:
546 if proto_enum.name == protocol or proto_enum.value == protocol:
547 if proto_enum == Protocol.any:
550 raise SecurityGroupRuleSettingsError(
551 'Invalid Protocol - ' + protocol)
554 def map_ethertype(ethertype):
556 Takes a the ethertype value maps it to the Ethertype enum. When None return
558 :param ethertype: the ethertype value
559 :return: the Ethertype enum object
560 :raise: Exception if value is invalid
564 elif isinstance(ethertype, Ethertype):
567 eth_str = str(ethertype)
568 if eth_str == 'IPv6':
569 return Ethertype.IPv6
570 elif eth_str == 'IPv4':
571 return Ethertype.IPv4
573 raise SecurityGroupRuleSettingsError(
574 'Invalid Ethertype - ' + eth_str)
577 class SecurityGroupRuleSettingsError(Exception):
579 Exception to be thrown when security group rule settings attributes are