Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / subnets.py @ 8646e606

History | View | Annotate | Download (9.1 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
from logging import getLogger
35
from snf_django.lib import api
36
from snf_django.lib.api import faults
37
from django.db import transaction
38

    
39
from django.conf.urls import patterns
40
from django.http import HttpResponse
41
from django.utils import simplejson as json
42

    
43
from snf_django.lib.api import utils
44
from synnefo.db.models import Subnet, Network, IPPoolTable
45
from synnefo.logic import networks
46

    
47
from ipaddr import IPv4Address, IPAddress, IPNetwork
48

    
49
log = getLogger(__name__)
50

    
51

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

    
61

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

    
66
    user_subnets = Subnet.objects.filter(network__userid=user_id)
67
    return user_subnets
68

    
69

    
70
@transaction.commit_on_success
71
def create_subnet(network_id, cidr, name, ipversion, gateway, dhcp, slac,
72
                  dns_nameservers, allocation_pools, host_routes, user_id):
73
    """Create a subnet
74
    network_id and the desired cidr are mandatory, everything else is optional
75

76
    """
77
    try:
78
        network = Network.objects.get(id=network_id)
79
    except Network.DoesNotExist:
80
        raise api.faults.ItemNotFound("No networks found with that id")
81

    
82
    if user_id != network.userid:
83
        raise api.faults.Unauthorized("Unauthorized operation")
84

    
85
    if ipversion not in [4, 6]:
86
        raise api.faults.BadRequest("Malformed IP version type")
87

    
88
    check_number_of_subnets(network, ipversion)
89

    
90
    # Returns the first available IP in the subnet
91
    try:
92
        cidr_ip = IPNetwork(cidr)
93
    except ValueError:
94
        raise api.faults.BadRequest("Malformed CIDR")
95
    potential_gateway = str(IPNetwork(cidr).network + 1)
96

    
97
    if gateway is "":
98
        gateway = potential_gateway
99

    
100
    if ipversion == 6:
101
        networks.validate_network_params(None, None, cidr, gateway)
102
        if slac is not None:
103
            dhcp = check_boolean_value(slac, "enable_slac")
104
        else:
105
            dhcp = check_boolean_value(dhcp, "dhcp")
106
    else:
107
        networks.validate_network_params(cidr, gateway)
108
        dhcp = check_boolean_value(dhcp, "dhcp")
109

    
110
    name = check_name_length(name)
111

    
112
    gateway_ip = IPAddress(gateway)
113

    
114
    sub = Subnet.objects.create(name=name, network=network, cidr=cidr,
115
                                ipversion=ipversion, gateway=gateway,
116
                                dhcp=dhcp, host_routes=host_routes,
117
                                dns_nameservers=dns_nameservers)
118

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

    
135
    if allocation_pools:
136
        create_ip_pools(allocation_pools, cidr_ip, sub)
137

    
138
    return sub
139

    
140

    
141
def get_subnet(sub_id):
142
    """Show info of a specific subnet"""
143
    log.debug('get_subnet %s', sub_id)
144
    try:
145
        subnet = Subnet.objects.get(id=sub_id)
146
    except Subnet.DoesNotExist:
147
        raise api.faults.ItemNotFound("Subnet not found")
148

    
149
    return subnet
150

    
151

    
152
def delete_subnet():
153
    """Delete a subnet, raises BadRequest
154
    A subnet is deleted ONLY when the network that it belongs to is deleted
155

156
    """
157
    raise api.faults.BadRequest("Deletion of a subnet is not supported")
158

    
159

    
160
@transaction.commit_on_success
161
def update_subnet(sub_id, name, user_id):
162
    """Update the fields of a subnet
163
    Only the name can be updated
164

165
    """
166
    log.info('Update subnet %s, name %s' % (sub_id, name))
167

    
168
    try:
169
        subnet = Subnet.objects.get(id=sub_id)
170
    except:
171
        raise api.faults.ItemNotFound("Subnet not found")
172

    
173
    if user_id != subnet.network.userid:
174
        raise api.faults.Unauthorized("Unauthorized operation")
175

    
176
    check_name_length(name)
177

    
178
    subnet.name = name
179
    subnet.save()
180

    
181
    return subnet
182

    
183

    
184
#Utility functions
185
def create_ip_pools(pools, cidr, subnet):
186
    """Create IP Pools in the database"""
187
    for pool in pools:
188
        size = int(pool[1]) - int(pool[0]) + 1
189
        base = str(cidr)
190
        offset = int(pool[0]) - int(cidr.network)
191
        ip_pool = IPPoolTable.objects.create(size=size, offset=offset,
192
                                             base=base, subnet=subnet)
193

    
194

    
195
def check_empty_lists(value):
196
    """Check if value is Null/None, in which case we return an empty list"""
197
    if value is None:
198
        return []
199
    return value
200

    
201

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

    
208

    
209
def check_boolean_value(value, key):
210
    """Check if dhcp value is in acceptable values"""
211
    if value not in [True, False]:
212
        raise api.faults.BadRequest("Malformed request, %s must "
213
                                    "be True or False" % key)
214
    return value
215

    
216

    
217
def check_name_length(name):
218
    """Check if the length of a name is within acceptable value"""
219
    if len(str(name)) > Subnet.SUBNET_NAME_LENGTH:
220
        raise api.faults.BadRequest("Subnet name too long")
221
    return name
222

    
223

    
224
def get_subnet_fromdb(subnet_id, user_id, for_update=False):
225
    """Return a Subnet instance or raise ItemNotFound.
226
    This is the same as util.get_network
227

228
    """
229
    try:
230
        subnet_id = int(subnet_id)
231
        if for_update:
232
            return Subnet.objects.select_for_update().get(id=subnet_id,
233
                                                          network__userid=
234
                                                          user_id)
235
        return Subnet.objects.get(id=subnet_id, network__userid=user_id)
236
    except (ValueError, Subnet.DoesNotExist):
237
        raise api.faults.ItemNotFound('Subnet not found')
238

    
239

    
240
def validate_subpools(pool_list, cidr, gateway):
241
    """Validate IP Pools
242

243
    Validate the given IP pools are inside the cidr range
244
    Validate there are no overlaps in the given pools
245
    Finally, validate the gateway isn't in the given ip pools
246
    Input must be a list containing a sublist with start/end ranges as
247
    ipaddr.IPAddress items eg.,
248
    [[IPv4Address('192.168.42.11'), IPv4Address('192.168.42.15')],
249
     [IPv4Address('192.168.42.30'), IPv4Address('192.168.42.60')]]
250

251
    """
252
    if pool_list[0][0] <= cidr.network:
253
        raise api.faults.Conflict("IP Pool out of bounds")
254
    elif pool_list[-1][1] >= cidr.broadcast:
255
        raise api.faults.Conflict("IP Pool out of bounds")
256

    
257
    for start, end in pool_list:
258
        if start > end:
259
            raise api.faults.Conflict("Invalid IP pool range")
260
        # Raise BadRequest if gateway is inside the pool range
261
        if not (gateway < start or gateway > end):
262
            raise api.faults.Conflict("Gateway cannot be in pool range")
263

    
264
    # Check if there is a conflict between the IP Poll ranges
265
    end = cidr.network
266
    for pool in pool_list:
267
        if end >= pool[0]:
268
            raise api.faults.Conflict("IP Pool range conflict")
269
        end = pool[1]