Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / subnets.py @ 316787ab

History | View | Annotate | Download (9 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.append([cidr_ip.network + 1, gateway_ip - 1])
133
            allocation_pools.append([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):
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
    check_name_length(name)
174

    
175
    subnet.name = name
176
    subnet.save()
177

    
178
    return subnet
179

    
180

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

    
191

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

    
198

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

    
205

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

    
213

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

    
220

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

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

    
236

    
237
def validate_subpools(pool_list, cidr, gateway):
238
    """Validate IP Pools
239

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

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

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

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