Set lock_path correctly.
[openstack-build/neutron-build.git] / neutron / extensions / securitygroup.py
1 # Copyright (c) 2012 OpenStack Foundation.
2 # All rights reserved.
3 #
4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
5 #    not use this file except in compliance with the License. You may obtain
6 #    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, WITHOUT
12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 #    License for the specific language governing permissions and limitations
14 #    under the License.
15
16 import abc
17 import netaddr
18
19 from oslo_config import cfg
20 from oslo_utils import uuidutils
21 import six
22
23 from neutron._i18n import _
24 from neutron.api import extensions
25 from neutron.api.v2 import attributes as attr
26 from neutron.api.v2 import base
27 from neutron.common import constants as const
28 from neutron.common import exceptions as nexception
29 from neutron import manager
30 from neutron.quota import resource_registry
31
32
33 # Security group Exceptions
34 class SecurityGroupInvalidPortRange(nexception.InvalidInput):
35     message = _("For TCP/UDP protocols, port_range_min must be "
36                 "<= port_range_max")
37
38
39 class SecurityGroupInvalidPortValue(nexception.InvalidInput):
40     message = _("Invalid value for port %(port)s")
41
42
43 class SecurityGroupInvalidIcmpValue(nexception.InvalidInput):
44     message = _("Invalid value for ICMP %(field)s (%(attr)s) "
45                 "%(value)s. It must be 0 to 255.")
46
47
48 class SecurityGroupEthertypeConflictWithProtocol(nexception.InvalidInput):
49     message = _("Invalid ethertype %(ethertype)s for protocol "
50                 "%(protocol)s.")
51
52
53 class SecurityGroupMissingIcmpType(nexception.InvalidInput):
54     message = _("ICMP code (port-range-max) %(value)s is provided"
55                 " but ICMP type (port-range-min) is missing.")
56
57
58 class SecurityGroupInUse(nexception.InUse):
59     message = _("Security Group %(id)s %(reason)s.")
60
61     def __init__(self, **kwargs):
62         if 'reason' not in kwargs:
63             kwargs['reason'] = _("in use")
64         super(SecurityGroupInUse, self).__init__(**kwargs)
65
66
67 class SecurityGroupCannotRemoveDefault(nexception.InUse):
68     message = _("Insufficient rights for removing default security group.")
69
70
71 class SecurityGroupCannotUpdateDefault(nexception.InUse):
72     message = _("Updating default security group not allowed.")
73
74
75 class SecurityGroupDefaultAlreadyExists(nexception.InUse):
76     message = _("Default security group already exists.")
77
78
79 class SecurityGroupRuleInvalidProtocol(nexception.InvalidInput):
80     message = _("Security group rule protocol %(protocol)s not supported. "
81                 "Only protocol values %(values)s and integer representations "
82                 "[0 to 255] are supported.")
83
84
85 class SecurityGroupRulesNotSingleTenant(nexception.InvalidInput):
86     message = _("Multiple tenant_ids in bulk security group rule create"
87                 " not allowed")
88
89
90 class SecurityGroupRemoteGroupAndRemoteIpPrefix(nexception.InvalidInput):
91     message = _("Only remote_ip_prefix or remote_group_id may "
92                 "be provided.")
93
94
95 class SecurityGroupProtocolRequiredWithPorts(nexception.InvalidInput):
96     message = _("Must also specifiy protocol if port range is given.")
97
98
99 class SecurityGroupNotSingleGroupRules(nexception.InvalidInput):
100     message = _("Only allowed to update rules for "
101                 "one security profile at a time")
102
103
104 class SecurityGroupNotFound(nexception.NotFound):
105     message = _("Security group %(id)s does not exist")
106
107
108 class SecurityGroupRuleNotFound(nexception.NotFound):
109     message = _("Security group rule %(id)s does not exist")
110
111
112 class DuplicateSecurityGroupRuleInPost(nexception.InUse):
113     message = _("Duplicate Security Group Rule in POST.")
114
115
116 class SecurityGroupRuleExists(nexception.InUse):
117     message = _("Security group rule already exists. Rule id is %(id)s.")
118
119
120 class SecurityGroupRuleInUse(nexception.InUse):
121     message = _("Security Group Rule %(id)s %(reason)s.")
122
123     def __init__(self, **kwargs):
124         if 'reason' not in kwargs:
125             kwargs['reason'] = _("in use")
126         super(SecurityGroupRuleInUse, self).__init__(**kwargs)
127
128
129 class SecurityGroupRuleParameterConflict(nexception.InvalidInput):
130     message = _("Conflicting value ethertype %(ethertype)s for CIDR %(cidr)s")
131
132
133 class SecurityGroupConflict(nexception.Conflict):
134     message = _("Error %(reason)s while attempting the operation.")
135
136
137 class SecurityGroupRuleInvalidEtherType(nexception.InvalidInput):
138     message = _("Security group rule for ethertype '%(ethertype)s' not "
139                 "supported. Allowed values are %(values)s.")
140
141
142 def convert_protocol(value):
143     if value is None:
144         return
145     try:
146         val = int(value)
147         if val >= 0 and val <= 255:
148             # Set value of protocol number to string due to bug 1381379,
149             # PostgreSQL fails when it tries to compare integer with string,
150             # that exists in db.
151             return str(value)
152         raise SecurityGroupRuleInvalidProtocol(
153             protocol=value, values=sg_supported_protocols)
154     except (ValueError, TypeError):
155         if value.lower() in sg_supported_protocols:
156             return value.lower()
157         raise SecurityGroupRuleInvalidProtocol(
158             protocol=value, values=sg_supported_protocols)
159     except AttributeError:
160         raise SecurityGroupRuleInvalidProtocol(
161             protocol=value, values=sg_supported_protocols)
162
163
164 def convert_ethertype_to_case_insensitive(value):
165     if isinstance(value, six.string_types):
166         for ethertype in sg_supported_ethertypes:
167             if ethertype.lower() == value.lower():
168                 return ethertype
169     raise SecurityGroupRuleInvalidEtherType(
170         ethertype=value, values=sg_supported_ethertypes)
171
172
173 def convert_validate_port_value(port):
174     if port is None:
175         return port
176     try:
177         val = int(port)
178     except (ValueError, TypeError):
179         raise SecurityGroupInvalidPortValue(port=port)
180
181     if val >= 0 and val <= 65535:
182         return val
183     else:
184         raise SecurityGroupInvalidPortValue(port=port)
185
186
187 def convert_to_uuid_list_or_none(value_list):
188     if value_list is None:
189         return
190     for sg_id in value_list:
191         if not uuidutils.is_uuid_like(sg_id):
192             msg = _("'%s' is not an integer or uuid") % sg_id
193             raise nexception.InvalidInput(error_message=msg)
194     return value_list
195
196
197 def convert_ip_prefix_to_cidr(ip_prefix):
198     if not ip_prefix:
199         return
200     try:
201         cidr = netaddr.IPNetwork(ip_prefix)
202         return str(cidr)
203     except (ValueError, TypeError, netaddr.AddrFormatError):
204         raise nexception.InvalidCIDR(input=ip_prefix)
205
206
207 def _validate_name_not_default(data, valid_values=None):
208     if data.lower() == "default":
209         raise SecurityGroupDefaultAlreadyExists()
210
211
212 attr.validators['type:name_not_default'] = _validate_name_not_default
213
214 sg_supported_protocols = [None, const.PROTO_NAME_TCP, const.PROTO_NAME_UDP,
215                           const.PROTO_NAME_ICMP, const.PROTO_NAME_ICMP_V6]
216 sg_supported_ethertypes = ['IPv4', 'IPv6']
217
218 # Attribute Map
219 RESOURCE_ATTRIBUTE_MAP = {
220     'security_groups': {
221         'id': {'allow_post': False, 'allow_put': False,
222                'validate': {'type:uuid': None},
223                'is_visible': True,
224                'primary_key': True},
225         'name': {'allow_post': True, 'allow_put': True,
226                  'is_visible': True, 'default': '',
227                  'validate': {'type:name_not_default': attr.NAME_MAX_LEN}},
228         'description': {'allow_post': True, 'allow_put': True,
229                         'validate': {'type:string': attr.DESCRIPTION_MAX_LEN},
230                         'is_visible': True, 'default': ''},
231         'tenant_id': {'allow_post': True, 'allow_put': False,
232                       'required_by_policy': True,
233                       'validate': {'type:string': attr.TENANT_ID_MAX_LEN},
234                       'is_visible': True},
235         'security_group_rules': {'allow_post': False, 'allow_put': False,
236                                  'is_visible': True},
237     },
238     'security_group_rules': {
239         'id': {'allow_post': False, 'allow_put': False,
240                'validate': {'type:uuid': None},
241                'is_visible': True,
242                'primary_key': True},
243         'security_group_id': {'allow_post': True, 'allow_put': False,
244                               'is_visible': True, 'required_by_policy': True},
245         'remote_group_id': {'allow_post': True, 'allow_put': False,
246                             'default': None, 'is_visible': True},
247         'direction': {'allow_post': True, 'allow_put': False,
248                       'is_visible': True,
249                       'validate': {'type:values': ['ingress', 'egress']}},
250         'protocol': {'allow_post': True, 'allow_put': False,
251                      'is_visible': True, 'default': None,
252                      'convert_to': convert_protocol},
253         'port_range_min': {'allow_post': True, 'allow_put': False,
254                            'convert_to': convert_validate_port_value,
255                            'default': None, 'is_visible': True},
256         'port_range_max': {'allow_post': True, 'allow_put': False,
257                            'convert_to': convert_validate_port_value,
258                            'default': None, 'is_visible': True},
259         'ethertype': {'allow_post': True, 'allow_put': False,
260                       'is_visible': True, 'default': 'IPv4',
261                       'convert_to': convert_ethertype_to_case_insensitive,
262                       'validate': {'type:values': sg_supported_ethertypes}},
263         'remote_ip_prefix': {'allow_post': True, 'allow_put': False,
264                              'default': None, 'is_visible': True,
265                              'convert_to': convert_ip_prefix_to_cidr},
266         'tenant_id': {'allow_post': True, 'allow_put': False,
267                       'required_by_policy': True,
268                       'validate': {'type:string': attr.TENANT_ID_MAX_LEN},
269                       'is_visible': True},
270     }
271 }
272
273
274 SECURITYGROUPS = 'security_groups'
275 EXTENDED_ATTRIBUTES_2_0 = {
276     'ports': {SECURITYGROUPS: {'allow_post': True,
277                                'allow_put': True,
278                                'is_visible': True,
279                                'convert_to': convert_to_uuid_list_or_none,
280                                'default': attr.ATTR_NOT_SPECIFIED}}}
281 security_group_quota_opts = [
282     cfg.IntOpt('quota_security_group',
283                default=10,
284                help=_('Number of security groups allowed per tenant. '
285                       'A negative value means unlimited.')),
286     cfg.IntOpt('quota_security_group_rule',
287                default=100,
288                help=_('Number of security rules allowed per tenant. '
289                       'A negative value means unlimited.')),
290 ]
291 cfg.CONF.register_opts(security_group_quota_opts, 'QUOTAS')
292
293
294 class Securitygroup(extensions.ExtensionDescriptor):
295     """Security group extension."""
296
297     @classmethod
298     def get_name(cls):
299         return "security-group"
300
301     @classmethod
302     def get_alias(cls):
303         return "security-group"
304
305     @classmethod
306     def get_description(cls):
307         return "The security groups extension."
308
309     @classmethod
310     def get_updated(cls):
311         return "2012-10-05T10:00:00-00:00"
312
313     @classmethod
314     def get_resources(cls):
315         """Returns Ext Resources."""
316         my_plurals = [(key, key[:-1]) for key in RESOURCE_ATTRIBUTE_MAP.keys()]
317         attr.PLURALS.update(dict(my_plurals))
318         exts = []
319         plugin = manager.NeutronManager.get_plugin()
320         for resource_name in ['security_group', 'security_group_rule']:
321             collection_name = resource_name.replace('_', '-') + "s"
322             params = RESOURCE_ATTRIBUTE_MAP.get(resource_name + "s", dict())
323             resource_registry.register_resource_by_name(resource_name)
324             controller = base.create_resource(collection_name,
325                                               resource_name,
326                                               plugin, params, allow_bulk=True,
327                                               allow_pagination=True,
328                                               allow_sorting=True)
329
330             ex = extensions.ResourceExtension(collection_name,
331                                               controller,
332                                               attr_map=params)
333             exts.append(ex)
334
335         return exts
336
337     def get_extended_resources(self, version):
338         if version == "2.0":
339             return dict(list(EXTENDED_ATTRIBUTES_2_0.items()) +
340                         list(RESOURCE_ATTRIBUTE_MAP.items()))
341         else:
342             return {}
343
344
345 @six.add_metaclass(abc.ABCMeta)
346 class SecurityGroupPluginBase(object):
347
348     @abc.abstractmethod
349     def create_security_group(self, context, security_group):
350         pass
351
352     @abc.abstractmethod
353     def update_security_group(self, context, id, security_group):
354         pass
355
356     @abc.abstractmethod
357     def delete_security_group(self, context, id):
358         pass
359
360     @abc.abstractmethod
361     def get_security_groups(self, context, filters=None, fields=None,
362                             sorts=None, limit=None, marker=None,
363                             page_reverse=False):
364         pass
365
366     @abc.abstractmethod
367     def get_security_group(self, context, id, fields=None):
368         pass
369
370     @abc.abstractmethod
371     def create_security_group_rule(self, context, security_group_rule):
372         pass
373
374     @abc.abstractmethod
375     def delete_security_group_rule(self, context, id):
376         pass
377
378     @abc.abstractmethod
379     def get_security_group_rules(self, context, filters=None, fields=None,
380                                  sorts=None, limit=None, marker=None,
381                                  page_reverse=False):
382         pass
383
384     @abc.abstractmethod
385     def get_security_group_rule(self, context, id, fields=None):
386         pass