Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (10.3 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
    network = models.ForeignKey('Network')
220
    name = models.CharField('Network Name', max_length=SUBNET_NAME_LENGTH,
221
                            null=True)
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
    #pool = models.OneToOneField('IPPoolTable', related_name='network',
230
    #                            default=lambda: IPPoolTable.objects.create(
231
    #                                                        available_map='',
232
    #                                                        reserved_map='',
233
    #                                                        size=0),
234
    #                           null=True)
235

    
236
    def __unicode__(self):
237
        return "<Subnet %s>" % str(self.id)
238

    
239

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

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

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

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