Revision 3c96580c

b/snf-cyclades-app/synnefo/logic/ips.py
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("Can not allocate IP. Network %s is being"
48
                              " deleted" % network.id)
49
    ip_pools = IPPoolTable.objects.select_for_update()\
50
        .filter(subnet__network=network)
51
    try:
52
        return allocate_ip_from_pools(ip_pools, userid, address=address,
53
                                      floating_ip=floating_ip)
54
    except pools.EmptyPool:
55
        raise faults.Conflict("No more IP addresses available on network %s"
56
                              % network.id)
57
    except pools.ValueNotAvailable:
58
        raise faults.Conflict("IP address %s is already used." % address)
59
    except pools.InvalidValue:
60
        raise faults.BadRequest("Address %s does not belong to network %s" %
61
                                (address, network.id))
62

  
63

  
64
def allocate_public_ip(userid, floating_ip=False, backend=None):
65
    """Try to allocate a public or floating IP address.
66

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

  
73
    """
74

  
75
    ip_pool_rows = IPPoolTable.objects.select_for_update()\
76
        .prefetch_related("subnet__network")\
77
        .filter(subnet__deleted=False)\
78
        .filter(subnet__network__deleted=False)\
79
        .filter(subnet__network__public=True)\
80
        .filter(subnet__network__drained=False)
81
    if floating_ip:
82
        ip_pool_rows = ip_pool_rows\
83
            .filter(subnet__network__floating_ip_pool=True)
84
    if backend is not None:
85
        ip_pool_rows = ip_pool_rows\
86
            .filter(subnet__network__backend_networks__backend=backend)
87

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

  
107

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

  
121
        # Allocate the floating IP
122
        floating_ip = allocate_ip(network, userid, address=address,
123
                                  floating_ip=True)
124

  
125
    # Issue commission (quotas)
126
    quotas.issue_and_accept_commission(floating_ip)
127
    transaction.commit()
128

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

  
131
    return floating_ip
132

  
133

  
134
@transaction.commit_on_success
135
def delete_floating_ip(floating_ip):
136
    if floating_ip.nic:
137
        # This is safe, you also need for_update to attach floating IP to
138
        # instance.
139
        msg = "Floating IP '%s' is attached to instance." % floating_ip.id
140
        raise faults.Conflict(msg)
141

  
142
    # Return the address of the floating IP back to pool
143
    floating_ip.release_address()
144
    # And mark the floating IP as deleted
145
    floating_ip.deleted = True
146
    floating_ip.save()
147
    # Release quota for floating IP
148
    quotas.issue_and_accept_commission(floating_ip, delete=True)
149
    transaction.commit()
150
    # Delete the floating IP from DB
151
    log.info("Deleted floating IP '%s' of user '%s", floating_ip,
152
             floating_ip.userid)
153
    floating_ip.delete()

Also available in: Unified diff