Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / neutron / models.py @ d6b24130

History | View | Annotate | Download (10.4 kB)

1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or without
4
# modification, are permitted provided that the following conditions
5
# are met:
6
#
7
#   1. Redistributions of source code must retain the above copyright
8
#      notice, this list of conditions and the following disclaimer.
9
#
10
#  2. Redistributions in binary form must reproduce the above copyright
11
#     notice, this list of conditions and the following disclaimer in the
12
#     documentation and/or other materials provided with the distribution.
13
#
14
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24
# SUCH DAMAGE.
25
#
26
# The views and conclusions contained in the software and documentation are
27
# those of the authors and should not be interpreted as representing official
28
# policies, either expressed or implied, of GRNET S.A.
29

    
30
import datetime
31

    
32
from copy import deepcopy
33
from django.conf import settings
34
from django.db import models
35
from django.db import IntegrityError
36

    
37
from contextlib import contextmanager
38
from hashlib import sha1
39
from snf_django.lib.api import faults
40
from django.conf import settings as snf_settings
41

    
42
from synnefo.db.managers import ForUpdateManager, ProtectedDeleteManager
43
from synnefo.db import pools
44

    
45
from synnefo.db.models import VirtualMachine, QuotaHolderSerial
46

    
47
from synnefo.logic.rapi_pool import (get_rapi_client,
48
                                     put_rapi_client)
49

    
50
import logging
51
log = logging.getLogger(__name__)
52

    
53

    
54

    
55
class Network(models.Model):
56
    OPER_STATES = (
57
        ('PENDING', 'Pending'),  # Unused because of lazy networks
58
        ('ACTIVE', 'Active'),
59
        ('DELETED', 'Deleted'),
60
        ('ERROR', 'Error')
61
    )
62

    
63
    ACTIONS = (
64
        ('CREATE', 'Create Network'),
65
        ('DESTROY', 'Destroy Network'),
66
        ('ADD', 'Add server to Network'),
67
        ('REMOVE', 'Remove server from Network'),
68
    )
69

    
70
    RSAPI_STATE_FROM_OPER_STATE = {
71
        'PENDING': 'PENDING',
72
        'ACTIVE': 'ACTIVE',
73
        'DELETED': 'DELETED',
74
        'ERROR': 'ERROR'
75
    }
76

    
77
    FLAVORS = {
78
        'CUSTOM': {
79
            'mode': 'bridged',
80
            'link': settings.DEFAULT_BRIDGE,
81
            'mac_prefix': settings.DEFAULT_MAC_PREFIX,
82
            'tags': None,
83
            'desc': "Basic flavor used for a bridged network",
84
        },
85
        'IP_LESS_ROUTED': {
86
            'mode': 'routed',
87
            'link': settings.DEFAULT_ROUTING_TABLE,
88
            'mac_prefix': settings.DEFAULT_MAC_PREFIX,
89
            'tags': 'ip-less-routed',
90
            'desc': "Flavor used for an IP-less routed network using"
91
                    " Proxy ARP",
92
        },
93
        'MAC_FILTERED': {
94
            'mode': 'bridged',
95
            'link': settings.DEFAULT_MAC_FILTERED_BRIDGE,
96
            'mac_prefix': 'pool',
97
            'tags': 'private-filtered',
98
            'desc': "Flavor used for bridged networks that offer isolation"
99
                    " via filtering packets based on their src "
100
                    " MAC (ebtables)",
101
        },
102
        'PHYSICAL_VLAN': {
103
            'mode': 'bridged',
104
            'link': 'pool',
105
            'mac_prefix': settings.DEFAULT_MAC_PREFIX,
106
            'tags': 'physical-vlan',
107
            'desc': "Flavor used for bridged network that offer isolation"
108
                    " via dedicated physical vlan",
109
        },
110
    }
111

    
112
    name = models.CharField('Network Name', max_length=128)
113
    userid = models.CharField('User ID of the owner', max_length=128,
114
                              null=True, db_index=True)
115
    flavor = models.CharField('Flavor', max_length=32, null=False)
116
    mode = models.CharField('Network Mode', max_length=16, null=True)
117
    link = models.CharField('Network Link', max_length=32, null=True)
118
    mac_prefix = models.CharField('MAC Prefix', max_length=32, null=False)
119
    tags = models.CharField('Network Tags', max_length=128, null=True)
120
    public = models.BooleanField(default=False, db_index=True)
121
    created = models.DateTimeField(auto_now_add=True)
122
    updated = models.DateTimeField(auto_now=True)
123
    deleted = models.BooleanField('Deleted', default=False, db_index=True)
124
    state = models.CharField(choices=OPER_STATES, max_length=32,
125
                             default='PENDING')
126
    machines = models.ManyToManyField(VirtualMachine,
127
                                      through='NetworkInterface', related_name='neutron_machines')
128
    action = models.CharField(choices=ACTIONS, max_length=32, null=True,
129
                              default=None)
130
    drained = models.BooleanField("Drained", default=False, null=False) # this is the opposite of admin_state_up
131
    floating_ip_pool = models.BooleanField('Floating IP Pool', null=False,
132
                                           default=False)
133
    #serial = models.ForeignKey(QuotaHolderSerial, related_name='network',
134
    #                           null=True)
135

    
136
    objects = ForUpdateManager()
137

    
138
    def __unicode__(self):
139
        return "<Network: %s>" % str(self.id)
140

    
141
    @property
142
    def backend_id(self):
143
        """Return the backend id by prepending backend-prefix."""
144
        if not self.id:
145
            raise Network.InvalidBackendIdError("self.id is None")
146
        return "%snet-%s" % (settings.BACKEND_PREFIX_ID, str(self.id))
147

    
148
    @property
149
    def backend_tag(self):
150
        """Return the network tag to be used in backend
151

152
        """
153
        if self.tags:
154
            return self.tags.split(',')
155
        else:
156
            return []
157

    
158
    def create_backend_network(self, backend=None):
159
        """Create corresponding BackendNetwork entries."""
160

    
161
        backends = [backend] if backend else\
162
            Backend.objects.filter(offline=False)
163
        for backend in backends:
164
            backend_exists =\
165
                BackendNetwork.objects.filter(backend=backend, network=self)\
166
                                      .exists()
167
            if not backend_exists:
168
                BackendNetwork.objects.create(backend=backend, network=self)
169

    
170
    def get_pool(self, with_lock=True):
171
        if not self.pool_id:
172
            self.pool = IPPoolTable.objects.create(available_map='',
173
                                                   reserved_map='',
174
                                                   size=0)
175
            self.save()
176
        objects = IPPoolTable.objects
177
        if with_lock:
178
            objects = objects.select_for_update()
179
        return objects.get(id=self.pool_id).pool
180

    
181
    def reserve_address(self, address):
182
        pool = self.get_pool()
183
        pool.reserve(address)
184
        pool.save()
185

    
186
    def release_address(self, address):
187
        pool = self.get_pool()
188
        pool.put(address)
189
        pool.save()
190

    
191
    class InvalidBackendIdError(Exception):
192
        def __init__(self, value):
193
            self.value = value
194

    
195
        def __str__(self):
196
            return repr(self.value)
197

    
198
    class InvalidBackendMsgError(Exception):
199
        def __init__(self, opcode, status):
200
            self.opcode = opcode
201
            self.status = status
202

    
203
        def __str__(self):
204
            return repr('<opcode: %s, status: %s>'
205
                        % (self.opcode, self.status))
206

    
207
    class InvalidActionError(Exception):
208
        def __init__(self, action):
209
            self._action = action
210

    
211
        def __str__(self):
212
            return repr(str(self._action))
213

    
214
class Subnet(models.Model):
215

    
216
    name = models.CharField('Network Name', max_length=128)
217
    subnet_id = models.CharField('ID of the subnet', max_length=128,
218
                              null=True, db_index=True)
219
    network_id = models.ForeignKey('Network')
220
    # subnet will be null for IPv6 only networks
221
    subnet = models.CharField('Subnet', max_length=32, null=True)
222
    # subnet6 will be null for IPv4 only networks
223
    subnet6 = models.CharField('IPv6 Subnet', max_length=64, null=True)
224
    gateway = models.CharField('Gateway', max_length=32, null=True)
225
    gateway6 = models.CharField('IPv6 Gateway', max_length=64, null=True)
226
    dhcp = models.BooleanField('DHCP', default=True)
227
    #pool = models.OneToOneField('IPPoolTable', related_name='network',
228
    #                            default=lambda: IPPoolTable.objects.create(
229
    #                                                        available_map='',
230
    #                                                        reserved_map='',
231
    #                                                        size=0),
232
    #                           null=True)
233

    
234
class NetworkInterface(models.Model):
235
    STATES = (
236
        ("ACTIVE", "Active"),
237
        ("BUILDING", "Building"),
238
    )
239

    
240
    name = models.CharField('Network Name', max_length=128)
241
    machine = models.ForeignKey(VirtualMachine, related_name='neutron_nics')
242
    network = models.ForeignKey(Network, related_name='neutron_nics')
243
    created = models.DateTimeField(auto_now_add=True)
244
    updated = models.DateTimeField(auto_now=True)
245
    index = models.IntegerField(null=True)
246
    mac = models.CharField(max_length=32, null=True, unique=True)
247
    ipv4 = models.CharField(max_length=15, null=True)
248
    ipv6 = models.CharField(max_length=100, null=True)
249
    #firewall_profile = models.CharField(choices=FIREWALL_PROFILES,
250
    #                                    max_length=30, null=True)
251
    dirty = models.BooleanField(default=False)
252
    state = models.CharField(max_length=32, null=False, default="ACTIVE",
253
                             choices=STATES)
254
    admin_state_up = models.BooleanField(default=False, db_index=True)
255

    
256
    def __unicode__(self):
257
        return "<%s:vm:%s network:%s ipv4:%s ipv6:%s>" % \
258
            (self.index, self.machine_id, self.network_id, self.ipv4,
259
             self.ipv6)
260

    
261
    @property
262
    def is_floating_ip(self):
263
        network = self.network
264
        if self.ipv4 and network.floating_ip_pool:
265
            return network.floating_ips.filter(machine=self.machine,
266
                                               ipv4=self.ipv4,
267
                                               deleted=False).exists()