Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (9.5 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, project=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
    if project is None:
163
        project = userid
164
    floating_ip.project = project
165
    floating_ip.save()
166
    # Issue commission (quotas)
167
    quotas.issue_and_accept_commission(floating_ip)
168
    transaction.commit()
169

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

    
172
    return floating_ip
173

    
174

    
175
def get_free_floating_ip(userid, network=None):
176
    """Get one of the free available floating IPs of the user.
177

178
    Get one of the users floating IPs that is not connected to any port
179
    or server. If network is specified, the floating IP must be from
180
    that network.
181

182
    """
183
    floating_ips = IPAddress.objects\
184
                            .filter(userid=userid, deleted=False, nic=None,
185
                                    floating_ip=True)
186
    if network is not None:
187
        floating_ips = floating_ips.filter(network=network)
188

    
189
    for floating_ip in floating_ips:
190
        floating_ip = IPAddress.objects.select_for_update()\
191
                                       .get(id=floating_ip.id)
192
        if floating_ip.nic is None:
193
            return floating_ip
194

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

    
203

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

    
218
    # Lock network to prevent deadlock
219
    Network.objects.select_for_update().get(id=floating_ip.network_id)
220

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

    
234

    
235
@transaction.commit_on_success
236
def reassign_floating_ip(floating_ip, project):
237
    action_fields = {"to_project": project,
238
                     "from_project": floating_ip.project}
239
    floating_ip.project = project
240
    floating_ip.save()
241
    quotas.issue_and_accept_commission(floating_ip, action="REASSIGN",
242
                                       action_fields=action_fields)