65aabe141207bdbc66b4f065e531335aef4c6008
[snaps.git] / snaps / config / security_group.py
1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 #                    and others.  All rights reserved.
3 #
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:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15 import enum
16
17 from snaps.openstack.utils import keystone_utils, neutron_utils
18
19
20 class SecurityGroupConfig(object):
21     """
22     Class representing a keypair configuration
23     """
24
25     def __init__(self, **kwargs):
26         """
27         Constructor
28         :param name: The security group's name (required)
29         :param description: The security group's description (optional)
30         :param project_name: The name of the project under which the security
31                              group will be created
32         :param rule_settings: a list of SecurityGroupRuleConfig objects
33         :return:
34         """
35         self.name = kwargs.get('name')
36         self.description = kwargs.get('description')
37         self.project_name = kwargs.get('project_name')
38         self.rule_settings = list()
39
40         rule_settings = kwargs.get('rules')
41         if not rule_settings:
42             rule_settings = kwargs.get('rule_settings')
43
44         if rule_settings:
45             for rule_setting in rule_settings:
46                 if isinstance(rule_setting, SecurityGroupRuleConfig):
47                     self.rule_settings.append(rule_setting)
48                 else:
49                     rule_setting['sec_grp_name'] = self.name
50                     self.rule_settings.append(SecurityGroupRuleConfig(
51                         **rule_setting))
52
53         if not self.name:
54             raise SecurityGroupConfigError('The attribute name is required')
55
56         for rule_setting in self.rule_settings:
57             if rule_setting.sec_grp_name != self.name:
58                 raise SecurityGroupConfigError(
59                     'Rule settings must correspond with the name of this '
60                     'security group')
61
62     def dict_for_neutron(self, keystone, project_id):
63         """
64         Returns a dictionary object representing this object.
65         This is meant to be converted into JSON designed for use by the Neutron
66         API
67
68         TODO - expand automated testing to exercise all parameters
69         :param keystone: the Keystone client
70         :param project_id: the default project ID
71         :return: the dictionary object
72         """
73         out = dict()
74
75         if self.name:
76             out['name'] = self.name
77         if self.description:
78             out['description'] = self.description
79         if self.project_name:
80             project = keystone_utils.get_project(
81                 keystone=keystone, project_name=self.project_name)
82             project_id = None
83             if project:
84                 project_id = project.id
85             if project_id:
86                 out['tenant_id'] = project_id
87             else:
88                 raise SecurityGroupConfigError(
89                     'Could not find project ID for project named - ' +
90                     self.project_name)
91         else:
92             out['tenant_id'] = project_id
93
94         return {'security_group': out}
95
96
97 class Direction(enum.Enum):
98     """
99     A rule's direction
100     """
101     ingress = 'ingress'
102     egress = 'egress'
103
104
105 class Protocol(enum.Enum):
106     """
107     A rule's protocol
108     """
109     ah = 51
110     dccp = 33
111     egp = 8
112     esp = 50
113     gre = 47
114     icmp = 1
115     icmpv6 = 58
116     igmp = 2
117     ipv6_encap = 41
118     ipv6_frag = 44
119     ipv6_icmp = 58
120     ipv6_nonxt = 59
121     ipv6_opts = 60
122     ipv6_route = 43
123     ospf = 89
124     pgm = 113
125     rsvp = 46
126     sctp = 132
127     tcp = 6
128     udp = 17
129     udplite = 136
130     vrrp = 112
131     any = 'any'
132     null = 'null'
133
134
135 class Ethertype(enum.Enum):
136     """
137     A rule's ethertype
138     """
139     IPv4 = 4
140     IPv6 = 6
141
142
143 class SecurityGroupConfigError(Exception):
144     """
145     Exception to be thrown when security group settings attributes are
146     invalid
147     """
148
149
150 class SecurityGroupRuleConfig(object):
151     """
152     Class representing a keypair configuration
153     """
154
155     def __init__(self, **kwargs):
156         """
157         Constructor - all parameters are optional
158         :param sec_grp_name: The security group's name on which to add the
159                              rule. (required)
160         :param description: The rule's description
161         :param direction: An enumeration of type
162                           create_security_group.RULE_DIRECTION (required)
163         :param remote_group_id: The group ID to associate with this rule
164                                 (this should be changed to group name once
165                                 snaps support Groups) (optional)
166         :param protocol: An enumeration of type
167                          create_security_group.RULE_PROTOCOL or a string value
168                          that will be mapped accordingly (optional)
169         :param ethertype: An enumeration of type
170                           create_security_group.RULE_ETHERTYPE (optional)
171         :param port_range_min: The minimum port number in the range that is
172                                matched by the security group rule. When the
173                                protocol is TCP or UDP, this value must be <=
174                                port_range_max.
175         :param port_range_max: The maximum port number in the range that is
176                                matched by the security group rule. When the
177                                protocol is TCP or UDP, this value must be <=
178                                port_range_max.
179         :param remote_ip_prefix: The remote IP prefix to associate with this
180                                  metering rule packet (optional)
181
182         TODO - Need to support the tenant...
183         """
184
185         self.description = kwargs.get('description')
186         self.sec_grp_name = kwargs.get('sec_grp_name')
187         self.remote_group_id = kwargs.get('remote_group_id')
188         self.direction = None
189         if kwargs.get('direction'):
190             self.direction = map_direction(kwargs['direction'])
191
192         self.protocol = None
193         if kwargs.get('protocol'):
194             self.protocol = map_protocol(kwargs['protocol'])
195         else:
196             self.protocol = Protocol.null
197
198         self.ethertype = None
199         if kwargs.get('ethertype'):
200             self.ethertype = map_ethertype(kwargs['ethertype'])
201
202         self.port_range_min = kwargs.get('port_range_min')
203         self.port_range_max = kwargs.get('port_range_max')
204         self.remote_ip_prefix = kwargs.get('remote_ip_prefix')
205
206         if not self.direction or not self.sec_grp_name:
207             raise SecurityGroupRuleConfigError(
208                 'direction and sec_grp_name are required')
209
210     def dict_for_neutron(self, neutron, project_id):
211         """
212         Returns a dictionary object representing this object.
213         This is meant to be converted into JSON designed for use by the Neutron
214         API
215         :param neutron: the neutron client for performing lookups
216         :param project_id: the ID of the project associated with the group
217         :return: the dictionary object
218         """
219         out = dict()
220
221         if self.description:
222             out['description'] = self.description
223         if self.direction:
224             out['direction'] = self.direction.name
225         if self.port_range_min:
226             out['port_range_min'] = self.port_range_min
227         if self.port_range_max:
228             out['port_range_max'] = self.port_range_max
229         if self.ethertype:
230             out['ethertype'] = self.ethertype.name
231         if self.protocol and self.protocol.value != 'null':
232             out['protocol'] = self.protocol.value
233         if self.sec_grp_name:
234             sec_grp = neutron_utils.get_security_group(
235                 neutron, sec_grp_name=self.sec_grp_name, project_id=project_id)
236             if sec_grp:
237                 out['security_group_id'] = sec_grp.id
238             else:
239                 raise SecurityGroupRuleConfigError(
240                     'Cannot locate security group with name - ' +
241                     self.sec_grp_name)
242         if self.remote_group_id:
243             out['remote_group_id'] = self.remote_group_id
244         if self.remote_ip_prefix:
245             out['remote_ip_prefix'] = self.remote_ip_prefix
246
247         return {'security_group_rule': out}
248
249     def rule_eq(self, rule):
250         """
251         Returns True if this setting created the rule
252         :param rule: the rule to evaluate
253         :return: T/F
254         """
255         if self.description is not None:
256             if rule.description is not None and rule.description != '':
257                 return False
258         elif self.description != rule.description:
259             if rule.description != '':
260                 return False
261
262         if self.direction.name != rule.direction:
263             return False
264
265         if self.ethertype and rule.ethertype:
266             if self.ethertype.name != rule.ethertype:
267                 return False
268
269         if self.port_range_min and rule.port_range_min:
270             if self.port_range_min != rule.port_range_min:
271                 return False
272
273         if self.port_range_max and rule.port_range_max:
274             if self.port_range_max != rule.port_range_max:
275                 return False
276
277         if self.protocol and rule.protocol:
278             if self.protocol.name != rule.protocol:
279                 return False
280
281         if self.remote_group_id and rule.remote_group_id:
282             if self.remote_group_id != rule.remote_group_id:
283                 return False
284
285         if self.remote_ip_prefix and rule.remote_ip_prefix:
286             if self.remote_ip_prefix != rule.remote_ip_prefix:
287                 return False
288
289         return True
290
291     def __eq__(self, other):
292         return (
293             self.description == other.description and
294             self.direction == other.direction and
295             self.port_range_min == other.port_range_min and
296             self.port_range_max == other.port_range_max and
297             self.ethertype == other.ethertype and
298             self.protocol == other.protocol and
299             self.sec_grp_name == other.sec_grp_name and
300             self.remote_group_id == other.remote_group_id and
301             self.remote_ip_prefix == other.remote_ip_prefix)
302
303     def __hash__(self):
304         return hash((self.sec_grp_name, self.description, self.direction,
305                      self.remote_group_id,
306                      self.protocol, self.ethertype, self.port_range_min,
307                      self.port_range_max, self.remote_ip_prefix))
308
309
310 def map_direction(direction):
311     """
312     Takes a the direction value maps it to the Direction enum. When None return
313     None
314     :param direction: the direction value
315     :return: the Direction enum object
316     :raise: Exception if value is invalid
317     """
318     if not direction:
319         return None
320     if isinstance(direction, Direction):
321         return direction
322     elif (isinstance(direction, str) or isinstance(direction, unicode)
323             or isinstance(direction, unicode)):
324         dir_str = str(direction)
325         if dir_str == 'egress':
326             return Direction.egress
327         elif dir_str == 'ingress':
328             return Direction.ingress
329         else:
330             raise SecurityGroupRuleConfigError(
331                 'Invalid Direction - ' + dir_str)
332     else:
333         return map_direction(direction.value)
334
335
336 def map_protocol(protocol):
337     """
338     Takes a the protocol value maps it to the Protocol enum. When None return
339     None
340     :param protocol: the protocol value
341     :return: the Protocol enum object
342     :raise: Exception if value is invalid
343     """
344     if not protocol:
345         return None
346     elif isinstance(protocol, Protocol):
347         return protocol
348     elif (isinstance(protocol, str) or isinstance(protocol, unicode)
349             or isinstance(protocol, int)):
350         for proto_enum in Protocol:
351             if proto_enum.name == protocol or proto_enum.value == protocol:
352                 if proto_enum == Protocol.any:
353                     return Protocol.null
354                 return proto_enum
355         raise SecurityGroupRuleConfigError(
356             'Invalid Protocol - ' + protocol)
357     else:
358         return map_protocol(protocol.value)
359
360
361 def map_ethertype(ethertype):
362     """
363     Takes a the ethertype value maps it to the Ethertype enum. When None return
364     None
365     :param ethertype: the ethertype value
366     :return: the Ethertype enum object
367     :raise: Exception if value is invalid
368     """
369     if not ethertype:
370         return None
371     elif isinstance(ethertype, Ethertype):
372         return ethertype
373     elif (isinstance(ethertype, str) or isinstance(ethertype, unicode)
374             or isinstance(ethertype, int)):
375         eth_str = str(ethertype)
376         if eth_str == 'IPv6' or eth_str == '6':
377             return Ethertype.IPv6
378         elif eth_str == 'IPv4' or eth_str == '4':
379             return Ethertype.IPv4
380         else:
381             raise SecurityGroupRuleConfigError(
382                 'Invalid Ethertype - ' + eth_str)
383     else:
384         return map_ethertype(ethertype.value)
385
386
387 class SecurityGroupRuleConfigError(Exception):
388     """
389     Exception to be thrown when security group rule settings attributes are
390     invalid
391     """