Statistics
| Branch: | Tag: | Revision:

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

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_NAME_LENGTH = 128
218

    
219
    subnet_id = models.CharField('ID of the subnet', max_length=128,
220
                                 null=True, db_index=True, primary_key=True)
221
    network = models.ForeignKey('Network')
222

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

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

    
240

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

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

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

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