Merge "Add missing licences in requirements.txt"
[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 keystone: the keystone client for performing lookups
214         :param project_name: the name of the project associated with the group
215         :return: the dictionary object
216         """
217         out = dict()
218
219         if self.description:
220             out['description'] = self.description
221         if self.direction:
222             out['direction'] = self.direction.name
223         if self.port_range_min:
224             out['port_range_min'] = self.port_range_min
225         if self.port_range_max:
226             out['port_range_max'] = self.port_range_max
227         if self.ethertype:
228             out['ethertype'] = self.ethertype.name
229         if self.protocol and self.protocol.value != 'null':
230             out['protocol'] = self.protocol.value
231         if self.sec_grp_name:
232             sec_grp = neutron_utils.get_security_group(
233                 neutron, keystone, sec_grp_name=self.sec_grp_name,
234                 project_name=project_name)
235             if sec_grp:
236                 out['security_group_id'] = sec_grp.id
237             else:
238                 raise SecurityGroupRuleConfigError(
239                     'Cannot locate security group with name - ' +
240                     self.sec_grp_name)
241         if self.remote_group_id:
242             out['remote_group_id'] = self.remote_group_id
243         if self.remote_ip_prefix:
244             out['remote_ip_prefix'] = self.remote_ip_prefix
245
246         return {'security_group_rule': out}
247
248     def rule_eq(self, rule):
249         """
250         Returns True if this setting created the rule
251         :param rule: the rule to evaluate
252         :return: T/F
253         """
254         if self.description is not None:
255             if rule.description is not None and rule.description != '':
256                 return False
257         elif self.description != rule.description:
258             if rule.description != '':
259                 return False
260
261         if self.direction.name != rule.direction:
262             return False
263
264         if self.ethertype and rule.ethertype:
265             if self.ethertype.name != rule.ethertype:
266                 return False
267
268         if self.port_range_min and rule.port_range_min:
269             if self.port_range_min != rule.port_range_min:
270                 return False
271
272         if self.port_range_max and rule.port_range_max:
273             if self.port_range_max != rule.port_range_max:
274                 return False
275
276         if self.protocol and rule.protocol:
277             if self.protocol.name != rule.protocol:
278                 return False
279
280         if self.remote_group_id and rule.remote_group_id:
281             if self.remote_group_id != rule.remote_group_id:
282                 return False
283
284         if self.remote_ip_prefix and rule.remote_ip_prefix:
285             if self.remote_ip_prefix != rule.remote_ip_prefix:
286                 return False
287
288         return True
289
290     def __eq__(self, other):
291         return (
292             self.description == other.description and
293             self.direction == other.direction and
294             self.port_range_min == other.port_range_min and
295             self.port_range_max == other.port_range_max and
296             self.ethertype == other.ethertype and
297             self.protocol == other.protocol and
298             self.sec_grp_name == other.sec_grp_name and
299             self.remote_group_id == other.remote_group_id and
300             self.remote_ip_prefix == other.remote_ip_prefix)
301
302     def __hash__(self):
303         return hash((self.sec_grp_name, self.description, self.direction,
304                      self.remote_group_id,
305                      self.protocol, self.ethertype, self.port_range_min,
306                      self.port_range_max, self.remote_ip_prefix))
307
308
309 def map_direction(direction):
310     """
311     Takes a the direction value maps it to the Direction enum. When None return
312     None
313     :param direction: the direction value
314     :return: the Direction enum object
315     :raise: Exception if value is invalid
316     """
317     if not direction or 'None' == str(direction):
318         return None
319     if isinstance(direction, enum.Enum):
320         if direction.__class__.__name__ == 'Direction':
321             return direction
322         else:
323             raise SecurityGroupRuleConfigError(
324                 'Invalid class - ' + direction.__class__.__name__)
325     elif isinstance(direction, str):
326         dir_str = str(direction)
327         if dir_str == 'egress':
328             return Direction.egress
329         elif dir_str == 'ingress':
330             return Direction.ingress
331         else:
332             raise SecurityGroupRuleConfigError(
333                 'Invalid Direction - ' + dir_str)
334     else:
335         return map_direction(str(direction))
336
337
338 def map_protocol(protocol):
339     """
340     Takes a the protocol value maps it to the Protocol enum. When None return
341     None
342     :param protocol: the protocol value
343     :return: the Protocol enum object
344     :raise: Exception if value is invalid
345     """
346     if not protocol:
347         return None
348     elif isinstance(protocol, enum.Enum):
349         if protocol.__class__.__name__ == 'Protocol':
350             return protocol
351         else:
352             raise SecurityGroupRuleConfigError(
353                 'Invalid class - ' + protocol.__class__.__name__)
354     elif isinstance(protocol, str) or isinstance(protocol, int):
355         for proto_enum in Protocol:
356             if proto_enum.name == protocol or proto_enum.value == protocol:
357                 if proto_enum == Protocol.any:
358                     return Protocol.null
359                 return proto_enum
360         raise SecurityGroupRuleConfigError(
361             'Invalid Protocol - ' + str(protocol))
362     else:
363         return map_protocol(str(protocol))
364
365
366 def map_ethertype(ethertype):
367     """
368     Takes a the ethertype value maps it to the Ethertype enum. When None return
369     None
370     :param ethertype: the ethertype value
371     :return: the Ethertype enum object
372     :raise: Exception if value is invalid
373     """
374     if not ethertype or 'None' == str(ethertype):
375         return None
376     elif isinstance(ethertype, enum.Enum):
377         if ethertype.__class__.__name__ == 'Ethertype':
378             return ethertype
379         else:
380             raise SecurityGroupRuleConfigError(
381                 'Invalid class - ' + ethertype.__class__.__name__)
382     elif isinstance(ethertype, str) or isinstance(ethertype, int):
383         eth_str = str(ethertype)
384         if eth_str == 'IPv6' or eth_str == '6':
385             return Ethertype.IPv6
386         elif eth_str == 'IPv4' or eth_str == '4':
387             return Ethertype.IPv4
388         else:
389             raise SecurityGroupRuleConfigError(
390                 'Invalid Ethertype - ' + eth_str)
391     else:
392         return map_ethertype(str(ethertype))
393
394
395 class SecurityGroupRuleConfigError(Exception):
396     """
397     Exception to be thrown when security group rule settings attributes are
398     invalid
399     """