Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / ips.py @ 5920f82c

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
import logging
35

    
36
from snf_django.lib.api import faults
37
from django.db import transaction
38
from synnefo import quotas
39
from synnefo.db import pools
40
from synnefo.db.models import (IPPoolTable, IPAddress, Network)
41
log = logging.getLogger(__name__)
42

    
43

    
44
def allocate_ip_from_pools(pool_rows, userid, address=None, floating_ip=False):
45
    """Try to allocate a value from a number of pools.
46

47
    This function takes as argument a number of PoolTable objects and tries to
48
    allocate a value from them. If all pools are empty EmptyPool is raised.
49
    If an address is specified and does not belong to any of the pools,
50
    InvalidValue is raised.
51

52
    """
53
    for pool_row in pool_rows:
54
        pool = pool_row.pool
55
        try:
56
            value = pool.get(value=address)
57
            pool.save()
58
            subnet = pool_row.subnet
59
            ipaddress = IPAddress.objects.create(subnet=subnet,
60
                                                 network=subnet.network,
61
                                                 userid=userid,
62
                                                 address=value,
63
                                                 floating_ip=floating_ip,
64
                                                 ipversion=4)
65
            return ipaddress
66
        except pools.EmptyPool:
67
            pass
68
        except pools.InvalidValue:
69
            pass
70
    if address is None:
71
        raise pools.EmptyPool("No more IP addresses available on pools %s" %
72
                              pool_rows)
73
    else:
74
        raise pools.InvalidValue("Address %s does not belong to pools %s" %
75
                                 (address, pool_rows))
76

    
77

    
78
def allocate_ip(network, userid, address=None, floating_ip=False):
79
    """Try to allocate an IP from networks IP pools."""
80
    if network.action == "DESTROY":
81
        raise faults.Conflict("Cannot allocate IP. Network %s is being"
82
                              " deleted" % network.id)
83
    elif network.drained:
84
        raise faults.Conflict("Can not allocate IP while network '%s' is in"
85
                              " 'SNF:DRAINED' status" % network.id)
86

    
87
    ip_pools = IPPoolTable.objects.select_for_update()\
88
        .filter(subnet__network=network).order_by('id')
89
    try:
90
        return allocate_ip_from_pools(ip_pools, userid, address=address,
91
                                      floating_ip=floating_ip)
92
    except pools.EmptyPool:
93
        raise faults.Conflict("No more IP addresses available on network %s"
94
                              % network.id)
95
    except pools.ValueNotAvailable:
96
        raise faults.Conflict("IP address %s is already used." % address)
97
    except pools.InvalidValue:
98
        raise faults.BadRequest("Address %s does not belong to network %s" %
99
                                (address, network.id))
100

    
101

    
102
def allocate_public_ip(userid, floating_ip=False, backend=None, networks=None):
103
    """Try to allocate a public or floating IP address.
104

105
    Try to allocate a a public IPv4 address from one of the available networks.
106
    If 'floating_ip' is set, only networks which are floating IP pools will be
107
    used and the IPAddress that will be created will be marked as a floating
108
    IP. If 'backend' is set, only the networks that exist in this backend will
109
    be used.
110

111
    """
112

    
113
    ip_pool_rows = IPPoolTable.objects.select_for_update()\
114
        .prefetch_related("subnet__network")\
115
        .filter(subnet__deleted=False)\
116
        .filter(subnet__network__deleted=False)\
117
        .filter(subnet__network__public=True)\
118
        .filter(subnet__network__drained=False)
119
    if networks is not None:
120
        ip_pool_rows = ip_pool_rows.filter(subnet__network__in=networks)
121
    if floating_ip:
122
        ip_pool_rows = ip_pool_rows\
123
            .filter(subnet__network__floating_ip_pool=True)
124
    if backend is not None:
125
        ip_pool_rows = ip_pool_rows\
126
            .filter(subnet__network__backend_networks__backend=backend)
127

    
128
    try:
129
        return allocate_ip_from_pools(ip_pool_rows, userid,
130
                                      floating_ip=floating_ip)
131
    except pools.EmptyPool:
132
        ip_type = "floating" if floating_ip else "public"
133
        log_msg = "Failed to allocate a %s IP. Reason:" % ip_type
134
        if ip_pool_rows:
135
            log_msg += " No network exists."
136
        else:
137
            log_msg += " All network are full."
138
        if backend is not None:
139
            log_msg += " Backend: %s" % backend
140
        log.error(log_msg)
141
        exception_msg = "Cannot allocate a %s IP address." % ip_type
142
        raise faults.Conflict(exception_msg)
143

    
144

    
145
@transaction.commit_on_success
146
def create_floating_ip(userid, network=None, address=None):
147
    if network is None:
148
        floating_ip = allocate_public_ip(userid, floating_ip=True)
149
    else:
150
        if not network.floating_ip_pool:
151
            msg = ("Cannot allocate floating IP. Network %s is"
152
                   " not a floating IP pool.")
153
            raise faults.Conflict(msg % network.id)
154
        if network.action == "DESTROY":
155
            msg = "Cannot allocate floating IP. Network %s is being deleted."
156
            raise faults.Conflict(msg % network.id)
157

    
158
        # Allocate the floating IP
159
        floating_ip = allocate_ip(network, userid, address=address,
160
                                  floating_ip=True)
161

    
162
    # Issue commission (quotas)
163
    quotas.issue_and_accept_commission(floating_ip)
164
    transaction.commit()
165

    
166
    log.info("Created floating IP '%s' for user IP '%s'", floating_ip, userid)
167

    
168
    return floating_ip
169

    
170

    
171
def get_free_floating_ip(userid, network=None):
172
    """Get one of the free available floating IPs of the user.
173

174
    Get one of the users floating IPs that is not connected to any port
175
    or server. If network is specified, the floating IP must be from
176
    that network.
177

178
    """
179
    floating_ips = IPAddress.objects\
180
                            .filter(userid=userid, deleted=False, nic=None,
181
                                    floating_ip=True)
182
    if network is not None:
183
        floating_ips = floating_ips.filter(network=network)
184

    
185
    for floating_ip in floating_ips:
186
        floating_ip = IPAddress.objects.select_for_update()\
187
                                       .get(id=floating_ip.id)
188
        if floating_ip.nic is None:
189
            return floating_ip
190

    
191
    msg = "Cannot find an unused floating IP to connect server to"
192
    if network is not None:
193
        msg += " network '%s'." % network.id
194
    else:
195
        msg += " a public network."
196
    msg += " Please create a floating IP."
197
    raise faults.Conflict(msg)
198

    
199

    
200
@transaction.commit_on_success
201
def delete_floating_ip(floating_ip):
202
    if floating_ip.nic:
203
        # This is safe, you also need for_update to attach floating IP to
204
        # instance.
205
        server = floating_ip.nic.machine
206
        if server is None:
207
            msg = ("Floating IP '%s' is used by port '%s'" %
208
                   (floating_ip.id, floating_ip.nic_id))
209
        else:
210
            msg = ("Floating IP '%s' is used by server '%s'" %
211
                   (floating_ip.id, floating_ip.nic.machine_id))
212
        raise faults.Conflict(msg)
213

    
214
    # Lock network to prevent deadlock
215
    Network.objects.select_for_update().get(id=floating_ip.network_id)
216

    
217
    # Return the address of the floating IP back to pool
218
    floating_ip.release_address()
219
    # And mark the floating IP as deleted
220
    floating_ip.deleted = True
221
    floating_ip.save()
222
    # Release quota for floating IP
223
    quotas.issue_and_accept_commission(floating_ip, action="DESTROY")
224
    transaction.commit()
225
    # Delete the floating IP from DB
226
    log.info("Deleted floating IP '%s' of user '%s", floating_ip,
227
             floating_ip.userid)
228
    floating_ip.delete()