Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (7 kB)

1
import logging
2

    
3
from snf_django.lib.api import faults
4
from django.db import transaction
5
from synnefo import quotas
6
from synnefo.db import pools
7
from synnefo.db.models import (IPPoolTable, IPAddress)
8
log = logging.getLogger(__name__)
9

    
10

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

14
    This function takes as argument a number of PoolTable objects and tries to
15
    allocate a value from them. If all pools are empty EmptyPool is raised.
16
    If an address is specified and does not belong to any of the pools,
17
    InvalidValue is raised.
18

19
    """
20
    for pool_row in pool_rows:
21
        pool = pool_row.pool
22
        try:
23
            value = pool.get(value=address)
24
            pool.save()
25
            subnet = pool_row.subnet
26
            ipaddress = IPAddress.objects.create(subnet=subnet,
27
                                                 network=subnet.network,
28
                                                 userid=userid,
29
                                                 address=value,
30
                                                 floating_ip=floating_ip)
31
            return ipaddress
32
        except pools.EmptyPool:
33
            pass
34
        except pools.InvalidValue:
35
            pass
36
    if address is None:
37
        raise pools.EmptyPool("No more IP addresses available on pools %s" %
38
                              pool_rows)
39
    else:
40
        raise pools.InvalidValue("Address %s does not belong to pools %s" %
41
                                 (address, pool_rows))
42

    
43

    
44
def allocate_ip(network, userid, address=None, floating_ip=False):
45
    """Try to allocate an IP from networks IP pools."""
46
    if network.action == "DESTROY":
47
        raise faults.Conflict("Cannot allocate IP. Network %s is being"
48
                              " deleted" % network.id)
49
    elif network.drained:
50
        raise faults.Conflict("Can not allocate IP while network '%s' is in"
51
                              " 'SNF:DRAINED' status" % network.id)
52

    
53
    ip_pools = IPPoolTable.objects.select_for_update()\
54
        .filter(subnet__network=network)
55
    try:
56
        return allocate_ip_from_pools(ip_pools, userid, address=address,
57
                                      floating_ip=floating_ip)
58
    except pools.EmptyPool:
59
        raise faults.Conflict("No more IP addresses available on network %s"
60
                              % network.id)
61
    except pools.ValueNotAvailable:
62
        raise faults.Conflict("IP address %s is already used." % address)
63
    except pools.InvalidValue:
64
        raise faults.BadRequest("Address %s does not belong to network %s" %
65
                                (address, network.id))
66

    
67

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

71
    Try to allocate a a public IPv4 address from one of the available networks.
72
    If 'floating_ip' is set, only networks which are floating IP pools will be
73
    used and the IPAddress that will be created will be marked as a floating
74
    IP. If 'backend' is set, only the networks that exist in this backend will
75
    be used.
76

77
    """
78

    
79
    ip_pool_rows = IPPoolTable.objects.select_for_update()\
80
        .prefetch_related("subnet__network")\
81
        .filter(subnet__deleted=False)\
82
        .filter(subnet__network__deleted=False)\
83
        .filter(subnet__network__public=True)\
84
        .filter(subnet__network__drained=False)
85
    if networks is not None:
86
        ip_pool_rows = ip_pool_rows.filter(subnet__network__in=networks)
87
    if floating_ip:
88
        ip_pool_rows = ip_pool_rows\
89
            .filter(subnet__network__floating_ip_pool=True)
90
    if backend is not None:
91
        ip_pool_rows = ip_pool_rows\
92
            .filter(subnet__network__backend_networks__backend=backend)
93

    
94
    try:
95
        return allocate_ip_from_pools(ip_pool_rows, userid,
96
                                      floating_ip=floating_ip)
97
    except pools.EmptyPool:
98
        ip_type = "floating" if floating_ip else "public"
99
        log_msg = "Failed to allocate a %s IP. Reason:" % ip_type
100
        if ip_pool_rows:
101
            log_msg += " No network exists."
102
        else:
103
            log_msg += " All network are full."
104
        if backend is not None:
105
            log_msg += " Backend: %s" % backend
106
        log.error(log_msg)
107
        exception_msg = "Cannot allocate a %s IP address." % ip_type
108
        raise faults.Conflict(exception_msg)
109

    
110

    
111
@transaction.commit_on_success
112
def create_floating_ip(userid, network=None, address=None):
113
    if network is None:
114
        floating_ip = allocate_public_ip(userid, floating_ip=True)
115
    else:
116
        if not network.floating_ip_pool:
117
            msg = ("Cannot allocate floating IP. Network %s is"
118
                   " not a floating IP pool.")
119
            raise faults.Conflict(msg % network.id)
120
        if network.action == "DESTROY":
121
            msg = "Cannot allocate floating IP. Network %s is being deleted."
122
            raise faults.Conflict(msg % network.id)
123

    
124
        # Allocate the floating IP
125
        floating_ip = allocate_ip(network, userid, address=address,
126
                                  floating_ip=True)
127

    
128
    # Issue commission (quotas)
129
    quotas.issue_and_accept_commission(floating_ip)
130
    transaction.commit()
131

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

    
134
    return floating_ip
135

    
136

    
137
def get_free_floating_ip(userid, network=None):
138
    """Get one of the free available floating IPs of the user.
139

140
    Get one of the users floating IPs that is not connected to any port
141
    or server. If network is specified, the floating IP must be from
142
    that network.
143

144
    """
145
    floating_ips = IPAddress.objects\
146
                            .filter(userid=userid, deleted=False, nic=None)
147
    if network is not None:
148
        floating_ips = floating_ips.filter(network=network)
149

    
150
    for floating_ip in floating_ips:
151
        floating_ip = IPAddress.objects.select_for_update()\
152
                                       .get(id=floating_ip.id)
153
        if floating_ip.nic is None:
154
            return floating_ip
155

    
156
    msg = "Cannot allocate a floating IP for connecting new server to"
157
    if network is not None:
158
        msg += " network '%s'." % network.id
159
    else:
160
        msg += " a public network."
161
    msg += " Please create more floating IPs."
162
    raise faults.Conflict(msg)
163

    
164

    
165
@transaction.commit_on_success
166
def delete_floating_ip(floating_ip):
167
    if floating_ip.nic:
168
        # This is safe, you also need for_update to attach floating IP to
169
        # instance.
170
        msg = "Floating IP '%s' is attached to instance." % floating_ip.id
171
        raise faults.Conflict(msg)
172

    
173
    # Return the address of the floating IP back to pool
174
    floating_ip.release_address()
175
    # And mark the floating IP as deleted
176
    floating_ip.deleted = True
177
    floating_ip.save()
178
    # Release quota for floating IP
179
    quotas.issue_and_accept_commission(floating_ip, delete=True)
180
    transaction.commit()
181
    # Delete the floating IP from DB
182
    log.info("Deleted floating IP '%s' of user '%s", floating_ip,
183
             floating_ip.userid)
184
    floating_ip.delete()