Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / subnets.py @ 1709a768

History | View | Annotate | Download (10.6 kB)

1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
import ipaddr
35
from logging import getLogger
36
from functools import wraps
37

    
38
from django.conf import settings
39
from django.db import transaction
40
from django.db.models import Q
41

    
42
from snf_django.lib import api
43
from snf_django.lib.api import faults
44

    
45
from synnefo.db.models import Subnet, Network, IPPoolTable
46

    
47
log = getLogger(__name__)
48

    
49

    
50
def subnet_command(action):
51
    def decorator(func):
52
        @wraps(func)
53
        @transaction.commit_on_success()
54
        def wrapper(subnet, *args, **kwargs):
55
            return func(subnet, *args, **kwargs)
56
        return wrapper
57
    return decorator
58

    
59

    
60
def list_subnets(user_id):
61
    """List all subnets of a user"""
62
    log.debug('list_subnets %s', user_id)
63

    
64
    query = (((Q(network__userid=user_id) & Q(network__public=False)) |
65
              Q(network__public=True)) & Q(deleted=False))
66
    user_subnets = Subnet.objects.filter(query)
67
    return user_subnets
68

    
69

    
70
@transaction.commit_on_success
71
def create_subnet(*args, **kwargs):
72
    return _create_subnet(*args, **kwargs)
73

    
74

    
75
def _create_subnet(network_id, user_id, cidr, name, ipversion=4, gateway=None,
76
                   dhcp=True, slaac=True, dns_nameservers=None,
77
                   allocation_pools=None, host_routes=None):
78
    """Create a subnet
79

80
    network_id and the desired cidr are mandatory, everything else is optional
81

82
    """
83
    try:
84
        network = Network.objects.get(id=network_id)
85
    except Network.DoesNotExist:
86
        raise api.faults.ItemNotFound("No network found with that id")
87

    
88
    if user_id != network.userid:
89
        raise api.faults.Unauthorized("Unauthorized operation")
90

    
91
    if ipversion not in [4, 6]:
92
        raise api.faults.BadRequest("Malformed IP version type")
93

    
94
    check_number_of_subnets(network, ipversion)
95

    
96
    if network.backend_networks.exists():
97
        raise api.faults.BadRequest("Cannot create subnet in network %s, VMs"
98
                                    " are already connected to this network" %
99
                                    network_id)
100

    
101
    try:
102
        cidr_ip = ipaddr.IPNetwork(cidr)
103
    except ValueError:
104
        raise api.faults.BadRequest("Malformed CIDR")
105

    
106
    if ipversion == 6:
107
        validate_subnet_params(subnet6=cidr, gateway6=gateway)
108
    else:
109
        validate_subnet_params(subnet=cidr, gateway=gateway)
110

    
111
    name = check_name_length(name)
112
    sub = Subnet.objects.create(name=name, network=network, cidr=cidr,
113
                                ipversion=ipversion, gateway=gateway,
114
                                dhcp=dhcp, host_routes=host_routes,
115
                                dns_nameservers=dns_nameservers)
116

    
117
    gateway_ip = ipaddr.IPAddress(gateway) if gateway else None
118

    
119
    if allocation_pools is not None:
120
        if ipversion == 6:
121
            raise api.faults.Conflict("Can't allocate an IP Pool in IPv6")
122
    elif ipversion == 4:
123
        # Check if the gateway is the first IP of the subnet, or the last. In
124
        # that case create a single ip pool.
125
        if gateway_ip:
126
            if int(gateway_ip) - int(cidr_ip) == 1:
127
                allocation_pools = [(gateway_ip + 1, cidr_ip.broadcast - 1)]
128
            elif int(cidr_ip.broadcast) - int(gateway_ip) == 1:
129
                allocation_pools = [(cidr_ip.network + 1, gateway_ip - 1)]
130
            else:
131
                # If the gateway isn't the first available ip, create two
132
                # different ip pools adjacent to said ip
133
                allocation_pools = [(cidr_ip.network + 1, gateway_ip - 1),
134
                                    (gateway_ip + 1, cidr_ip.broadcast - 1)]
135
        else:
136
            allocation_pools = [(cidr_ip.network + 1, cidr_ip.broadcast - 1)]
137

    
138
    if allocation_pools:
139
        # Validate the allocation pools
140
        validate_pools(allocation_pools, cidr_ip, gateway_ip)
141
        create_ip_pools(allocation_pools, cidr_ip, sub)
142

    
143
    return sub
144

    
145

    
146
def get_subnet(sub_id):
147
    """Show info of a specific subnet"""
148
    log.debug('get_subnet %s', sub_id)
149
    try:
150
        subnet = Subnet.objects.get(id=sub_id)
151
    except Subnet.DoesNotExist:
152
        raise api.faults.ItemNotFound("Subnet not found")
153

    
154
    return subnet
155

    
156

    
157
def delete_subnet():
158
    """Delete a subnet, raises BadRequest
159
    A subnet is deleted ONLY when the network that it belongs to is deleted
160

161
    """
162
    raise api.faults.BadRequest("Deletion of a subnet is not supported")
163

    
164

    
165
@transaction.commit_on_success
166
def update_subnet(sub_id, name, user_id):
167
    """Update the fields of a subnet
168
    Only the name can be updated
169

170
    """
171
    log.info('Update subnet %s, name %s' % (sub_id, name))
172

    
173
    try:
174
        subnet = Subnet.objects.get(id=sub_id)
175
    except:
176
        raise api.faults.ItemNotFound("Subnet not found")
177

    
178
    if user_id != subnet.network.userid:
179
        raise api.faults.Unauthorized("Unauthorized operation")
180

    
181
    check_name_length(name)
182

    
183
    subnet.name = name
184
    subnet.save()
185

    
186
    return subnet
187

    
188

    
189
#Utility functions
190
def create_ip_pools(pools, cidr, subnet):
191
    """Create IP Pools in the database"""
192
    return [_create_ip_pool(pool, cidr, subnet) for pool in pools]
193

    
194

    
195
def _create_ip_pool(pool, cidr, subnet):
196
    size = int(pool[1]) - int(pool[0]) + 1
197
    base = str(cidr)
198
    offset = int(pool[0]) - int(cidr.network)
199
    return IPPoolTable.objects.create(size=size, offset=offset,
200
                                      base=base, subnet=subnet)
201

    
202

    
203
def check_number_of_subnets(network, version):
204
    """Check if a user can add a subnet in a network"""
205
    if network.subnets.filter(ipversion=version):
206
        raise api.faults.BadRequest("Only one subnet of IPv4/IPv6 per "
207
                                    "network is allowed")
208

    
209

    
210
def check_name_length(name):
211
    """Check if the length of a name is within acceptable value"""
212
    if len(str(name)) > Subnet.SUBNET_NAME_LENGTH:
213
        raise api.faults.BadRequest("Subnet name too long")
214
    return name
215

    
216

    
217
def validate_pools(pool_list, cidr, gateway):
218
    """Validate IP Pools
219

220
    Validate the given IP pools are inside the cidr range
221
    Validate there are no overlaps in the given pools
222
    Finally, validate the gateway isn't in the given ip pools
223
    Input must be a list containing a sublist with start/end ranges as
224
    ipaddr.IPAddress items eg.,
225
    [[IPv4Address('192.168.42.11'), IPv4Address('192.168.42.15')],
226
     [IPv4Address('192.168.42.30'), IPv4Address('192.168.42.60')]]
227

228
    """
229
    if pool_list[0][0] <= cidr.network:
230
        raise api.faults.Conflict("IP Pool out of bounds")
231
    elif pool_list[-1][1] >= cidr.broadcast:
232
        raise api.faults.Conflict("IP Pool out of bounds")
233

    
234
    for start, end in pool_list:
235
        if start > end:
236
            raise api.faults.Conflict("Invalid IP pool range")
237
        # Raise BadRequest if gateway is inside the pool range
238
        if gateway:
239
            if not (gateway < start or gateway > end):
240
                raise api.faults.Conflict("Gateway cannot be in pool range")
241

    
242
    # Check if there is a conflict between the IP Pool ranges
243
    end = cidr.network
244
    for pool in pool_list:
245
        if end >= pool[0]:
246
            raise api.faults.Conflict("IP Pool range conflict")
247
        end = pool[1]
248

    
249

    
250
def validate_subnet_params(subnet=None, gateway=None, subnet6=None,
251
                           gateway6=None):
252
    if subnet:
253
        try:
254
            # Use strict option to not all subnets with host bits set
255
            network = ipaddr.IPv4Network(subnet, strict=True)
256
        except ValueError:
257
            raise faults.BadRequest("Invalid network IPv4 subnet")
258

    
259
        # Check that network size is allowed!
260
        prefixlen = network.prefixlen
261
        if prefixlen > 29 or prefixlen <= settings.MAX_CIDR_BLOCK:
262
            raise faults.OverLimit(
263
                message="Unsupported network size",
264
                details="Netmask must be in range: (%s, 29]" %
265
                settings.MAX_CIDR_BLOCK)
266
        if gateway:  # Check that gateway belongs to network
267
            try:
268
                gateway = ipaddr.IPv4Address(gateway)
269
            except ValueError:
270
                raise faults.BadRequest("Invalid network IPv4 gateway")
271
            if not gateway in network:
272
                raise faults.BadRequest("Invalid network IPv4 gateway")
273

    
274
    if subnet6:
275
        try:
276
            # Use strict option to not all subnets with host bits set
277
            network6 = ipaddr.IPv6Network(subnet6, strict=True)
278
        except ValueError:
279
            raise faults.BadRequest("Invalid network IPv6 subnet")
280
        # Check that network6 is an /64 subnet, because this is imposed by
281
        # 'mac2eui64' utiity.
282
        if network6.prefixlen != 64:
283
            msg = ("Unsupported IPv6 subnet size. Network netmask must be"
284
                   " /64")
285
            raise faults.BadRequest(msg)
286
        if gateway6:
287
            try:
288
                gateway6 = ipaddr.IPv6Address(gateway6)
289
            except ValueError:
290
                raise faults.BadRequest("Invalid network IPv6 gateway")
291
            if not gateway6 in network6:
292
                raise faults.BadRequest("Invalid network IPv6 gateway")
293

    
294

    
295
def parse_allocation_pools(allocation_pools):
296
    alloc = list()
297
    for pool in allocation_pools:
298
        try:
299
            start, end = pool.split(',')
300
            alloc.append([ipaddr.IPv4Address(start),
301
                          ipaddr.IPv4Address(end)])
302
        except ValueError:
303
            raise faults.BadRequest("Malformed IPv4 address")
304

    
305
    return alloc