Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / subnets.py @ 784a3f1e

History | View | Annotate | Download (10.7 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
from synnefo.logic import utils
45

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

    
48
log = getLogger(__name__)
49

    
50

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

    
60

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

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

    
70

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

    
75

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

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

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

    
89
    if network.deleted:
90
        raise api.faults.BadRequest("Network has been deleted")
91

    
92
    if user_id != network.userid:
93
        raise api.faults.Unauthorized("Unauthorized operation")
94

    
95
    if ipversion not in [4, 6]:
96
        raise api.faults.BadRequest("Malformed IP version type")
97

    
98
    check_number_of_subnets(network, ipversion)
99

    
100
    if network.backend_networks.exists():
101
        raise api.faults.BadRequest("Cannot create subnet in network %s, VMs"
102
                                    " are already connected to this network" %
103
                                    network_id)
104

    
105
    try:
106
        cidr_ip = ipaddr.IPNetwork(cidr)
107
    except ValueError:
108
        raise api.faults.BadRequest("Malformed CIDR")
109

    
110
    if ipversion == 6:
111
        validate_subnet_params(subnet6=cidr, gateway6=gateway)
112
    else:
113
        validate_subnet_params(subnet=cidr, gateway=gateway)
114

    
115
    name = utils.check_name_length(name, Subnet.SUBNET_NAME_LENGTH, "Subnet "
116
                                   "name is too long")
117
    sub = Subnet.objects.create(name=name, network=network, cidr=cidr,
118
                                ipversion=ipversion, gateway=gateway,
119
                                dhcp=dhcp, host_routes=host_routes,
120
                                dns_nameservers=dns_nameservers)
121

    
122
    gateway_ip = ipaddr.IPAddress(gateway) if gateway else None
123

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

    
143
    if allocation_pools:
144
        # Validate the allocation pools
145
        validate_pools(allocation_pools, cidr_ip, gateway_ip)
146
        create_ip_pools(allocation_pools, cidr_ip, sub)
147

    
148
    return sub
149

    
150

    
151
def get_subnet(sub_id):
152
    """Show info of a specific subnet"""
153
    log.debug('get_subnet %s', sub_id)
154
    try:
155
        subnet = Subnet.objects.get(id=sub_id)
156
    except Subnet.DoesNotExist:
157
        raise api.faults.ItemNotFound("Subnet not found")
158

    
159
    return subnet
160

    
161

    
162
def delete_subnet():
163
    """Delete a subnet, raises BadRequest
164
    A subnet is deleted ONLY when the network that it belongs to is deleted
165

166
    """
167
    raise api.faults.BadRequest("Deletion of a subnet is not supported")
168

    
169

    
170
@transaction.commit_on_success
171
def update_subnet(sub_id, name, user_id):
172
    """Update the fields of a subnet
173
    Only the name can be updated
174

175
    """
176
    log.info('Update subnet %s, name %s' % (sub_id, name))
177

    
178
    try:
179
        subnet = Subnet.objects.get(id=sub_id)
180
    except:
181
        raise api.faults.ItemNotFound("Subnet not found")
182

    
183
    if user_id != subnet.network.userid:
184
        raise api.faults.Unauthorized("Unauthorized operation")
185

    
186
    utils.check_name_length(name, Subnet.SUBNET_NAME_LENGTH, "Subnet name is "
187
                            " too long")
188

    
189
    subnet.name = name
190
    subnet.save()
191

    
192
    return subnet
193

    
194

    
195
#Utility functions
196
def create_ip_pools(pools, cidr, subnet):
197
    """Create IP Pools in the database"""
198
    return [_create_ip_pool(pool, cidr, subnet) for pool in pools]
199

    
200

    
201
def _create_ip_pool(pool, cidr, subnet):
202
    size = int(pool[1]) - int(pool[0]) + 1
203
    base = str(cidr)
204
    offset = int(pool[0]) - int(cidr.network)
205
    return IPPoolTable.objects.create(size=size, offset=offset,
206
                                      base=base, subnet=subnet)
207

    
208

    
209
def check_number_of_subnets(network, version):
210
    """Check if a user can add a subnet in a network"""
211
    if network.subnets.filter(ipversion=version):
212
        raise api.faults.BadRequest("Only one subnet of IPv4/IPv6 per "
213
                                    "network is allowed")
214

    
215

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

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

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

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

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

    
248

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

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

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

    
293

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

    
304
    return alloc