Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (8.6 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
            return ipaddress
65
        except pools.EmptyPool:
66
            pass
67
        except pools.InvalidValue:
68
            pass
69
    if address is None:
70
        raise pools.EmptyPool("No more IP addresses available on pools %s" %
71
                              pool_rows)
72
    else:
73
        raise pools.InvalidValue("Address %s does not belong to pools %s" %
74
                                 (address, pool_rows))
75

    
76

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

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

    
100

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

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

110
    """
111

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

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

    
143

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

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

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

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

    
167
    return floating_ip
168

    
169

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

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

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

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

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

    
198

    
199
@transaction.commit_on_success
200
def delete_floating_ip(floating_ip):
201
    if floating_ip.nic:
202
        # This is safe, you also need for_update to attach floating IP to
203
        # instance.
204
        msg = "Floating IP '%s' is attached to instance." % floating_ip.id
205
        raise faults.Conflict(msg)
206

    
207
    # Lock network to prevent deadlock
208
    Network.objects.select_for_update().get(id=floating_ip.network_id)
209

    
210
    # Return the address of the floating IP back to pool
211
    floating_ip.release_address()
212
    # And mark the floating IP as deleted
213
    floating_ip.deleted = True
214
    floating_ip.save()
215
    # Release quota for floating IP
216
    quotas.issue_and_accept_commission(floating_ip, action="DESTROY")
217
    transaction.commit()
218
    # Delete the floating IP from DB
219
    log.info("Deleted floating IP '%s' of user '%s", floating_ip,
220
             floating_ip.userid)
221
    floating_ip.delete()