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