Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (10.5 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
class Network(models.Model):
55
    OPER_STATES = (
56
        ('PENDING', 'Pending'),  # Unused because of lazy networks
57
        ('ACTIVE', 'Active'),
58
        ('DELETED', 'Deleted'),
59
        ('ERROR', 'Error')
60
    )
61

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

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

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

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

    
137
    objects = ForUpdateManager()
138

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
215

    
216
class Subnet(models.Model):
217
    subnet_id = models.CharField('ID of the subnet', max_length=128,
218
                                 null=True, db_index=True, primary_key=True)
219
    network = models.ForeignKey('Network')
220

    
221
    name = models.CharField('Network Name', max_length=128)
222
    ipversion = models.IntegerField('IP Version', default=4)
223
    cidr = models.CharField('Subnet', max_length=32, null=True)
224
    gateway = models.CharField('Gateway', max_length=32, null=True)
225
    dhcp = models.BooleanField('DHCP', default=True)
226

    
227
    # Synnefo related fields
228
    # subnet6 will be null for IPv4 only networks
229
    subnet6 = models.CharField('IPv6 Subnet', max_length=64, null=True)
230
    gateway6 = models.CharField('IPv6 Gateway', max_length=64, null=True)
231
    #pool = models.OneToOneField('IPPoolTable', related_name='network',
232
    #                            default=lambda: IPPoolTable.objects.create(
233
    #                                                        available_map='',
234
    #                                                        reserved_map='',
235
    #                                                        size=0),
236
    #                           null=True)
237

    
238

    
239
class NetworkInterface(models.Model):
240
    STATES = (
241
        ("ACTIVE", "Active"),
242
        ("BUILDING", "Building"),
243
    )
244

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

    
261
    def __unicode__(self):
262
        return "<%s:vm:%s network:%s ipv4:%s ipv6:%s>" % \
263
            (self.index, self.machine_id, self.network_id, self.ipv4,
264
             self.ipv6)
265

    
266
    @property
267
    def is_floating_ip(self):
268
        network = self.network
269
        if self.ipv4 and network.floating_ip_pool:
270
            return network.floating_ips.filter(machine=self.machine,
271
                                               ipv4=self.ipv4,
272
                                               deleted=False).exists()