Revision ce55f211

b/snf-cyclades-app/synnefo/api/networks.py
1 1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
# 
2
#
3 3
# Redistribution and use in source and binary forms, with or
4 4
# without modification, are permitted provided that the following
5 5
# conditions are met:
6
# 
6
#
7 7
#   1. Redistributions of source code must retain the above
8 8
#      copyright notice, this list of conditions and the following
9 9
#      disclaimer.
10
# 
10
#
11 11
#   2. Redistributions in binary form must reproduce the above
12 12
#      copyright notice, this list of conditions and the following
13 13
#      disclaimer in the documentation and/or other materials
14 14
#      provided with the distribution.
15
# 
15
#
16 16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
......
25 25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 27
# POSSIBILITY OF SUCH DAMAGE.
28
# 
28
#
29 29
# The views and conclusions contained in the software and
30 30
# documentation are those of the authors and should not be
31 31
# interpreted as representing official policies, either expressed
......
34 34
from logging import getLogger
35 35

  
36 36
from django.conf.urls.defaults import patterns
37
from django.conf import settings
37 38
from django.db.models import Q
38 39
from django.http import HttpResponse
39 40
from django.template.loader import render_to_string
40 41
from django.utils import simplejson as json
41 42

  
42
from synnefo.api import util
43
from synnefo.api import faults, util
43 44
from synnefo.api.actions import network_actions
44 45
from synnefo.api.common import method_not_allowed
45 46
from synnefo.api.faults import BadRequest, OverLimit, Unauthorized
46
from synnefo.db.models import Network
47
from synnefo.db.models import Network, NetworkLink
47 48
from synnefo.logic import backend
48 49

  
49 50

  
......
106 107
    #                       unauthorized (401),
107 108
    #                       badRequest (400),
108 109
    #                       overLimit (413)
109
    
110

  
110 111
    log.debug('list_networks detail=%s', detail)
111 112
    since = util.isoparse(request.GET.get('changes-since'))
112 113
    user_networks = Network.objects.filter(
113 114
                                Q(userid=request.user_uniq) | Q(public=True))
114
    
115

  
115 116
    if since:
116 117
        user_networks = user_networks.filter(updated__gte=since)
117 118
        if not user_networks:
118 119
            return HttpResponse(status=304)
119 120
    else:
120 121
        user_networks = user_networks.filter(state='ACTIVE')
121
    
122

  
122 123
    networks = [network_to_dict(network, request.user_uniq, detail)
123 124
                for network in user_networks]
124
    
125

  
125 126
    if request.serialization == 'xml':
126 127
        data = render_to_string('list_networks.xml', {
127 128
            'networks': networks,
......
144 145

  
145 146
    req = util.get_request_dict(request)
146 147
    log.debug('create_network %s', req)
147
    
148

  
148 149
    try:
149 150
        d = req['network']
150 151
        name = d['name']
151 152
    except (KeyError, ValueError):
152 153
        raise BadRequest('Malformed request.')
153
    
154
    network = backend.create_network(name, request.user_uniq)
155
    if not network:
156
        raise OverLimit('Network count limit exceeded for your account.')
157
    
154

  
155
    count = Network.objects.filter(userid=request.user_uniq,
156
                                          state='ACTIVE').count()
157

  
158
    # get user limit
159
    networks_limit_for_user = \
160
        settings.NETWORKS_USER_QUOTA.get(request.user_uniq,
161
                settings.MAX_NETWORKS_PER_USER)
162

  
163
    if count >= networks_limit_for_user:
164
        raise faults.OverLimit("Network count limit exceeded for your account.")
165

  
166
    try:
167
        network = backend.create_network(name, request.user_uniq)
168
    except NetworkLink.NotAvailable:
169
        raise faults.OverLimit('No networks available.')
170

  
158 171
    networkdict = network_to_dict(network, request.user_uniq)
159 172
    return render_network(request, networkdict, status=202)
160 173

  
......
168 181
    #                       badRequest (400),
169 182
    #                       itemNotFound (404),
170 183
    #                       overLimit (413)
171
    
184

  
172 185
    log.debug('get_network_details %s', network_id)
173 186
    net = util.get_network(network_id, request.user_uniq)
174 187
    netdict = network_to_dict(net, request.user_uniq)
......
188 201

  
189 202
    req = util.get_request_dict(request)
190 203
    log.debug('update_network_name %s', network_id)
191
    
204

  
192 205
    try:
193 206
        name = req['network']['name']
194 207
    except (TypeError, KeyError):
......
211 224
    #                       itemNotFound (404),
212 225
    #                       unauthorized (401),
213 226
    #                       overLimit (413)
214
    
227

  
215 228
    log.debug('delete_network %s', network_id)
216 229
    net = util.get_network(network_id, request.user_uniq)
217 230
    if net.public:
......
226 239
    log.debug('network_action %s %s', network_id, req)
227 240
    if len(req) != 1:
228 241
        raise BadRequest('Malformed request.')
229
    
242

  
230 243
    net = util.get_network(network_id, request.user_uniq)
231 244
    if net.public:
232 245
        raise Unauthorized('Can not modify the public network.')
233
    
246

  
234 247
    key = req.keys()[0]
235 248
    val = req[key]
236 249

  
b/snf-cyclades-app/synnefo/api/servers.py
1 1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
# 
2
#
3 3
# Redistribution and use in source and binary forms, with or
4 4
# without modification, are permitted provided that the following
5 5
# conditions are met:
6
# 
6
#
7 7
#   1. Redistributions of source code must retain the above
8 8
#      copyright notice, this list of conditions and the following
9 9
#      disclaimer.
10
# 
10
#
11 11
#   2. Redistributions in binary form must reproduce the above
12 12
#      copyright notice, this list of conditions and the following
13 13
#      disclaimer in the documentation and/or other materials
14 14
#      provided with the distribution.
15
# 
15
#
16 16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
......
25 25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 27
# POSSIBILITY OF SUCH DAMAGE.
28
# 
28
#
29 29
# The views and conclusions contained in the software and
30 30
# documentation are those of the authors and should not be
31 31
# interpreted as representing official policies, either expressed
......
130 130
        d['created'] = util.isoformat(vm.created)
131 131
        d['flavorRef'] = vm.flavor.id
132 132
        d['imageRef'] = vm.imageid
133
        
133

  
134 134
        metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
135 135
        if metadata:
136 136
            d['metadata'] = {'values': metadata}
......
159 159
    #                       unauthorized (401),
160 160
    #                       badRequest (400),
161 161
    #                       overLimit (413)
162
    
162

  
163 163
    log.debug('list_servers detail=%s', detail)
164 164
    user_vms = VirtualMachine.objects.filter(userid=request.user_uniq)
165 165
    since = util.isoparse(request.GET.get('changes-since'))
166
    
166

  
167 167
    if since:
168 168
        user_vms = user_vms.filter(updated__gte=since)
169 169
        if not user_vms:
170 170
            return HttpResponse(status=304)
171 171
    else:
172 172
        user_vms = user_vms.filter(deleted=False)
173
    
173

  
174 174
    servers = [vm_to_dict(server, detail) for server in user_vms]
175 175

  
176 176
    if request.serialization == 'xml':
......
197 197

  
198 198
    req = util.get_request_dict(request)
199 199
    log.debug('create_server %s', req)
200
    
200

  
201 201
    try:
202 202
        server = req['server']
203 203
        name = server['name']
......
209 209
        assert isinstance(personality, list)
210 210
    except (KeyError, AssertionError):
211 211
        raise faults.BadRequest("Malformed request")
212
    
212

  
213 213
    if len(personality) > settings.MAX_PERSONALITY:
214 214
        raise faults.OverLimit("Maximum number of personalities exceeded")
215
    
215

  
216 216
    for p in personality:
217 217
        # Verify that personalities are well-formed
218 218
        try:
......
228 228
                raise faults.OverLimit("Maximum size of personality exceeded")
229 229
        except AssertionError:
230 230
            raise faults.BadRequest("Malformed personality in request")
231
    
231

  
232 232
    image = {}
233 233
    img = util.get_image(image_id, request.user_uniq)
234 234
    properties = img.get('properties', {})
......
236 236
    image['format'] = img['disk_format']
237 237
    image['metadata'] = dict((key.upper(), val) \
238 238
                             for key, val in properties.items())
239
    
239

  
240 240
    flavor = util.get_flavor(flavor_id)
241 241
    password = util.random_password()
242
    
242

  
243 243
    count = VirtualMachine.objects.filter(userid=request.user_uniq,
244 244
                                          deleted=False).count()
245
    if count >= settings.MAX_VMS_PER_USER:
245

  
246
    # get user limit
247
    vms_limit_for_user = \
248
        settings.VMS_USER_QUOTA.get(request.user_uniq,
249
                settings.MAX_VMS_PER_USER)
250

  
251
    if count >= vms_limit_for_user:
246 252
        raise faults.OverLimit("Server count limit exceeded for your account.")
247
    
253

  
248 254
    # We must save the VM instance now, so that it gets a valid vm.backend_id.
249 255
    vm = VirtualMachine.objects.create(
250 256
        name=name,
251 257
        userid=request.user_uniq,
252 258
        imageid=image_id,
253 259
        flavor=flavor)
254
    
260

  
255 261
    try:
256 262
        create_instance(vm, flavor, image, password, personality)
257 263
    except GanetiApiError:
......
263 269
            meta_key=key,
264 270
            meta_value=val,
265 271
            vm=vm)
266
    
272

  
267 273
    log.info('User %s created vm with %s cpus, %s ram and %s storage',
268 274
             request.user_uniq, flavor.cpu, flavor.ram, flavor.disk)
269
    
275

  
270 276
    server = vm_to_dict(vm, detail=True)
271 277
    server['status'] = 'BUILD'
272 278
    server['adminPass'] = password
......
282 288
    #                       badRequest (400),
283 289
    #                       itemNotFound (404),
284 290
    #                       overLimit (413)
285
    
291

  
286 292
    log.debug('get_server_details %s', server_id)
287 293
    vm = util.get_vm(server_id, request.user_uniq)
288 294
    server = vm_to_dict(vm, detail=True)
......
303 309

  
304 310
    req = util.get_request_dict(request)
305 311
    log.debug('update_server_name %s %s', server_id, req)
306
    
312

  
307 313
    try:
308 314
        name = req['server']['name']
309 315
    except (TypeError, KeyError):
......
326 332
    #                       unauthorized (401),
327 333
    #                       buildInProgress (409),
328 334
    #                       overLimit (413)
329
    
335

  
330 336
    log.debug('delete_server %s', server_id)
331 337
    vm = util.get_vm(server_id, request.user_uniq)
332 338
    delete_instance(vm)
......
361 367
    #                       unauthorized (401),
362 368
    #                       badRequest (400),
363 369
    #                       overLimit (413)
364
    
370

  
365 371
    log.debug('list_addresses %s', server_id)
366 372
    vm = util.get_vm(server_id, request.user_uniq)
367 373
    addresses = [nic_to_dict(nic) for nic in vm.nics.all()]
368
    
374

  
369 375
    if request.serialization == 'xml':
370 376
        data = render_to_string('list_addresses.xml', {'addresses': addresses})
371 377
    else:
......
383 389
    #                       badRequest (400),
384 390
    #                       itemNotFound (404),
385 391
    #                       overLimit (413)
386
    
392

  
387 393
    log.debug('list_addresses_by_network %s %s', server_id, network_id)
388 394
    machine = util.get_vm(server_id, request.user_uniq)
389 395
    network = util.get_network(network_id, request.user_uniq)
390 396
    nic = util.get_nic(machine, network)
391 397
    address = nic_to_dict(nic)
392
    
398

  
393 399
    if request.serialization == 'xml':
394 400
        data = render_to_string('address.xml', {'address': address})
395 401
    else:
......
406 412
    #                       unauthorized (401),
407 413
    #                       badRequest (400),
408 414
    #                       overLimit (413)
409
    
415

  
410 416
    log.debug('list_server_metadata %s', server_id)
411 417
    vm = util.get_vm(server_id, request.user_uniq)
412 418
    metadata = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
......
423 429
    #                       buildInProgress (409),
424 430
    #                       badMediaType(415),
425 431
    #                       overLimit (413)
426
    
432

  
427 433
    req = util.get_request_dict(request)
428 434
    log.debug('update_server_metadata %s %s', server_id, req)
429 435
    vm = util.get_vm(server_id, request.user_uniq)
......
432 438
        assert isinstance(metadata, dict)
433 439
    except (KeyError, AssertionError):
434 440
        raise faults.BadRequest("Malformed request")
435
    
441

  
436 442
    for key, val in metadata.items():
437 443
        meta, created = vm.metadata.get_or_create(meta_key=key)
438 444
        meta.meta_value = val
439 445
        meta.save()
440
    
446

  
441 447
    vm.save()
442 448
    vm_meta = dict((m.meta_key, m.meta_value) for m in vm.metadata.all())
443 449
    return util.render_metadata(request, vm_meta, status=201)
......
452 458
    #                       itemNotFound (404),
453 459
    #                       badRequest (400),
454 460
    #                       overLimit (413)
455
    
461

  
456 462
    log.debug('get_server_metadata_item %s %s', server_id, key)
457 463
    vm = util.get_vm(server_id, request.user_uniq)
458 464
    meta = util.get_vm_meta(vm, key)
......
471 477
    #                       buildInProgress (409),
472 478
    #                       badMediaType(415),
473 479
    #                       overLimit (413)
474
    
480

  
475 481
    req = util.get_request_dict(request)
476 482
    log.debug('create_server_metadata_item %s %s %s', server_id, key, req)
477 483
    vm = util.get_vm(server_id, request.user_uniq)
......
482 488
        assert key in metadict
483 489
    except (KeyError, AssertionError):
484 490
        raise faults.BadRequest("Malformed request")
485
    
491

  
486 492
    meta, created = VirtualMachineMetadata.objects.get_or_create(
487 493
        meta_key=key,
488 494
        vm=vm)
489
    
495

  
490 496
    meta.meta_value = metadict[key]
491 497
    meta.save()
492 498
    vm.save()
......
505 511
    #                       buildInProgress (409),
506 512
    #                       badMediaType(415),
507 513
    #                       overLimit (413),
508
    
514

  
509 515
    log.debug('delete_server_metadata_item %s %s', server_id, key)
510 516
    vm = util.get_vm(server_id, request.user_uniq)
511 517
    meta = util.get_vm_meta(vm, key)
......
523 529
    #                       badRequest (400),
524 530
    #                       itemNotFound (404),
525 531
    #                       overLimit (413)
526
    
532

  
527 533
    log.debug('server_stats %s', server_id)
528 534
    vm = util.get_vm(server_id, request.user_uniq)
529 535
    #secret = util.encrypt(vm.backend_id)
530 536
    secret = vm.backend_id      # XXX disable backend id encryption
531
    
537

  
532 538
    stats = {
533 539
        'serverRef': vm.id,
534 540
        'refresh': settings.STATS_REFRESH_PERIOD,
......
536 542
        'cpuTimeSeries': settings.CPU_TIMESERIES_GRAPH_URL % secret,
537 543
        'netBar': settings.NET_BAR_GRAPH_URL % secret,
538 544
        'netTimeSeries': settings.NET_TIMESERIES_GRAPH_URL % secret}
539
    
545

  
540 546
    if request.serialization == 'xml':
541 547
        data = render_to_string('server_stats.xml', stats)
542 548
    else:
b/snf-cyclades-app/synnefo/api/tests.py
46 46
from synnefo.db.models import *
47 47
from synnefo.logic.utils import get_rsapi_state
48 48

  
49
import mock
49 50

  
50 51
class AaiClient(Client):
51 52
    def request(self, **request):
......
53 54
        return super(AaiClient, self).request(**request)
54 55

  
55 56

  
57
class TestQuota(TestCase):
58

  
59
    fixtures = ['users', 'flavors']
60
    astakos_response_user = 'test'
61

  
62
    def setUp(self):
63

  
64
        self.astakos_response_user = 'test'
65
        def get_user_mock(request, *Args, **kwargs):
66
            if request.META.get('HTTP_X_AUTH_TOKEN', None) == '0000':
67
                request.user_uniq = self.astakos_response_user
68
                request.user = {'uniq': self.astakos_response_user}
69

  
70
        def get_image_mock(request, *Args, **kwargs):
71
            return {'backend_id':'1234', 'location':'pithos://dummyimage',
72
                    'disk_format': 'plain'}
73

  
74
        self.rapi_patch = mock.patch('synnefo.logic.backend.rapi')
75
        self.rapi_mock = self.rapi_patch.start()
76

  
77
        self.pithos_patch = mock.patch('synnefo.api.util.get_image')
78
        self.pithos_mock = self.rapi_patch.start()
79

  
80
        # mock the astakos authentication function
81
        from synnefo.api import util
82
        util.get_user = get_user_mock
83
        util.get_image = get_image_mock
84

  
85
        settings.SKIP_SSH_VALIDATION = True
86
        self.client = AaiClient()
87
        self.user = 'test'
88

  
89
    def test_vms_quota(self):
90
        request = {
91
                    "server": {
92
                        "name": "new-server-test",
93
                        "userid": "test",
94
                        "imageRef": 1,
95
                        "flavorRef": 1,
96
                        "metadata": {
97
                            "My Server Name": "Apache1"
98
                        },
99
                        "personality": []
100
                    }
101
        }
102

  
103
        def create_server(for_user='test'):
104
            self.astakos_response_user = for_user
105
            return self.client.post('/api/v1.1/servers', json.dumps(request),
106
                content_type="application/json")
107

  
108
        def user_vms_count(user='test'):
109
            return VirtualMachine.objects.filter(userid=user).count()
110

  
111
        # admin sets max vms per user to 2
112
        settings.MAX_VMS_PER_USER = 2
113
        create_server()
114
        create_server()
115
        self.assertEqual(user_vms_count(), 2)
116

  
117
        # third creation fails
118
        resp = create_server()
119
        self.assertEqual(resp.status_code, 413)
120
        self.assertEqual(user_vms_count(), 2)
121

  
122
        # setting changed, no additional servers can get created
123
        settings.MAX_VMS_PER_USER = 1
124
        resp = create_server()
125
        self.assertEqual(resp.status_code, 413)
126
        self.assertEqual(user_vms_count(), 2)
127

  
128
        # admin wants test user to create 4 vms, now test user can create
129
        # one additional vm, but no more
130
        settings.VMS_USER_QUOTA = {'test':3}
131
        create_server()
132
        self.assertEqual(user_vms_count(), 3)
133
        resp = create_server()
134
        self.assertEqual(resp.status_code, 413)
135
        self.assertEqual(user_vms_count(), 3)
136
        # other users still apply to the global quota
137
        create_server("testuser2")
138
        self.assertEqual(user_vms_count("testuser2"), 1)
139
        resp = create_server("testuser2")
140
        self.assertEqual(resp.status_code, 413)
141
        self.assertEqual(user_vms_count("testuser2"), 1)
142

  
143

  
144
    def test_networks_quota(self):
145

  
146
        def create_network(for_user='test'):
147
            request = json.dumps({'network': {'name': 'user %s network' %
148
                for_user}})
149
            self.astakos_response_user = for_user
150
            return self.client.post('/api/v1.1/networks', request,
151
                content_type="application/json")
152

  
153
        def user_networks_count(user='test'):
154
            return Network.objects.filter(userid=user).count()
155

  
156
        settings.MAX_NETWORKS_PER_USER = 1
157
        create_network()
158
        self.assertEqual(user_networks_count(),1)
159
        resp = create_network()
160
        self.assertEqual(resp.status_code, 413)
161
        self.assertEqual(user_networks_count(), 1)
162

  
163
        settings.NETWORKS_USER_QUOTA = {'test':2}
164
        create_network()
165
        self.assertEqual(user_networks_count(),2)
166
        resp = create_network()
167
        self.assertEqual(resp.status_code, 413)
168
        self.assertEqual(user_networks_count(), 2)
169

  
170
        create_network("testuser2")
171
        self.assertEqual(user_networks_count("testuser2"),1)
172
        resp = create_network("testuser2")
173
        self.assertEqual(resp.status_code, 413)
174
        self.assertEqual(user_networks_count("testuser2"), 1)
175

  
176
        settings.GANETI_MAX_LINK_NUMBER = 3
177
        settings.NETWORKS_USER_QUOTA = {'test':10}
178
        resp = create_network()
179
        self.assertEqual(Network.objects.count(), 4)
180
        self.assertEqual('No networks available.' in resp.content, True)
181
        self.assertEqual(user_networks_count(), 2)
182

  
183

  
184

  
185

  
56 186
class APITestCase(TestCase):
57 187
    fixtures = ['users', 'api_test_data']
58 188
    test_server_id = 1001
b/snf-cyclades-app/synnefo/api/util.py
106 106

  
107 107
def random_password():
108 108
    """Generates a random password
109
    
109

  
110 110
    We generate a windows compliant password: it must contain at least
111 111
    one charachter from each of the groups: upper case, lower case, digits.
112 112
    """
113
    
113

  
114 114
    pool = lowercase + uppercase + digits
115 115
    lowerset = set(lowercase)
116 116
    upperset = set(uppercase)
117 117
    digitset = set(digits)
118 118
    length = 10
119
    
119

  
120 120
    password = ''.join(choice(pool) for i in range(length - 2))
121
    
121

  
122 122
    # Make sure the password is compliant
123 123
    chars = set(password)
124 124
    if not chars & lowerset:
......
127 127
        password += choice(uppercase)
128 128
    if not chars & digitset:
129 129
        password += choice(digits)
130
    
130

  
131 131
    # Pad if necessary to reach required length
132 132
    password += ''.join(choice(pool) for i in range(length - len(password)))
133
    
133

  
134 134
    return password
135 135

  
136 136

  
......
238 238

  
239 239
    if settings.TEST:
240 240
        response['Date'] = format_date_time(time())
241
    
241

  
242 242
    add_never_cache_headers(response)
243 243

  
244 244

  
b/snf-cyclades-app/synnefo/app_settings/default/api.py
58 58
# Maximum number of VMs a user is allowed to have
59 59
MAX_VMS_PER_USER = 3
60 60

  
61
# VMs user/quota map.
62
VMS_USER_QUOTA = {}
63

  
64
# Maximum number of networks a user is allowed to have
65
MAX_NETWORKS_PER_USER = 5
66

  
67
# Networks user/quota map.
68
NETWORKS_USER_QUOTA = {}
69

  
61 70
# URL templates for the stat graphs.
62 71
# The API implementation replaces '%s' with the encrypted backend id.
63 72
# FIXME: For now we do not encrypt the backend id.
b/snf-cyclades-app/synnefo/db/models.py
38 38
    disk_template = models.CharField('Disk template', max_length=32,
39 39
            default=settings.DEFAULT_GANETI_DISK_TEMPLATE)
40 40
    deleted = models.BooleanField('Deleted', default=False)
41
    
41

  
42 42
    class Meta:
43 43
        verbose_name = u'Virtual machine flavor'
44 44
        unique_together = ('cpu', 'ram', 'disk', 'disk_template')
45
    
45

  
46 46
    @property
47 47
    def name(self):
48 48
        """Returns flavor name (generated)"""
49 49
        return u'C%dR%dD%d' % (self.cpu, self.ram, self.disk)
50
    
50

  
51 51
    def __unicode__(self):
52 52
        return self.name
53 53

  
......
62 62
       ('REBOOT', 'Reboot VM'),
63 63
       ('DESTROY', 'Destroy VM')
64 64
    )
65
    
65

  
66 66
    # The internal operating state of a VM
67 67
    OPER_STATES = (
68 68
        ('BUILD', 'Queued for creation'),
......
71 71
        ('STARTED', 'Started'),
72 72
        ('DESTROYED', 'Destroyed')
73 73
    )
74
    
74

  
75 75
    # The list of possible operations on the backend
76 76
    BACKEND_OPCODES = (
77 77
        ('OP_INSTANCE_CREATE', 'Create Instance'),
......
93 93
        ('OP_INSTANCE_RECREATE_DISKS', 'Recreate Disks'),
94 94
        ('OP_INSTANCE_FAILOVER', 'Failover Instance')
95 95
    )
96
    
96

  
97 97
    # A backend job may be in one of the following possible states
98 98
    BACKEND_STATUSES = (
99 99
        ('queued', 'request queued'),
......
149 149
    suspended = models.BooleanField('Administratively Suspended',
150 150
                                    default=False)
151 151

  
152
    # VM State 
152
    # VM State
153 153
    # The following fields are volatile data, in the sense
154 154
    # that they need not be persistent in the DB, but rather
155 155
    # get generated at runtime by quering Ganeti and applying
156 156
    # updates received from Ganeti.
157
    
157

  
158 158
    # In the future they could be moved to a separate caching layer
159 159
    # and removed from the database.
160 160
    # [vkoukis] after discussion with [faidon].
......
188 188
            self._action = action
189 189
         def __str__(self):
190 190
            return repr(str(self._action))
191
    
191

  
192 192
    class DeletedError(Exception):
193 193
        pass
194
    
194

  
195 195
    class BuildingError(Exception):
196 196
        pass
197
    
197

  
198 198
    def __init__(self, *args, **kw):
199 199
        """Initialize state for just created VM instances."""
200 200
        super(VirtualMachine, self).__init__(*args, **kw)
201 201
        # This gets called BEFORE an instance gets save()d for
202 202
        # the first time.
203
        if not self.pk: 
203
        if not self.pk:
204 204
            self.action = None
205 205
            self.backendjobid = None
206 206
            self.backendjobstatus = None
207 207
            self.backendopcode = None
208 208
            self.backendlogmsg = None
209 209
            self.operstate = 'BUILD'
210
    
210

  
211 211
    @property
212 212
    def backend_id(self):
213 213
        """Returns the backend id for this VM by prepending backend-prefix."""
214 214
        if not self.id:
215 215
            raise VirtualMachine.InvalidBackendIdError("self.id is None")
216 216
        return '%s%s' % (settings.BACKEND_PREFIX_ID, self.id)
217
    
217

  
218 218
    class Meta:
219 219
        verbose_name = u'Virtual machine instance'
220 220
        get_latest_by = 'created'
221
    
221

  
222 222
    def __unicode__(self):
223 223
        return self.name
224 224

  
......
227 227
    meta_key = models.CharField(max_length=50)
228 228
    meta_value = models.CharField(max_length=500)
229 229
    vm = models.ForeignKey(VirtualMachine, related_name='metadata')
230
    
230

  
231 231
    class Meta:
232 232
        unique_together = (('meta_key', 'vm'),)
233 233
        verbose_name = u'Key-value pair of metadata for a VM.'
234
    
234

  
235 235
    def __unicode__(self):
236 236
        return u'%s: %s' % (self.meta_key, self.meta_value)
237 237

  
......
241 241
        ('ACTIVE', 'Active'),
242 242
        ('DELETED', 'Deleted')
243 243
    )
244
    
244

  
245 245
    name = models.CharField(max_length=255)
246 246
    created = models.DateTimeField(auto_now_add=True)
247 247
    updated = models.DateTimeField(auto_now=True)
......
252 252
    link = models.ForeignKey('NetworkLink', related_name='+')
253 253
    machines = models.ManyToManyField(VirtualMachine,
254 254
                                      through='NetworkInterface')
255
    
255

  
256 256
    def __unicode__(self):
257 257
        return self.name
258 258

  
......
263 263
        ('DISABLED', 'Disabled'),
264 264
        ('PROTECTED', 'Protected')
265 265
    )
266
    
266

  
267 267
    machine = models.ForeignKey(VirtualMachine, related_name='nics')
268 268
    network = models.ForeignKey(Network, related_name='nics')
269 269
    created = models.DateTimeField(auto_now_add=True)
......
274 274
    ipv6 = models.CharField(max_length=100, null=True)
275 275
    firewall_profile = models.CharField(choices=FIREWALL_PROFILES,
276 276
                                        max_length=30, null=True)
277
    
277

  
278 278
    def __unicode__(self):
279 279
        return '%s@%s' % (self.machine.name, self.network.name)
280 280

  
......
284 284
    index = models.IntegerField()
285 285
    name = models.CharField(max_length=255)
286 286
    available = models.BooleanField(default=True)
287
    
287

  
288 288
    def __unicode__(self):
289 289
        return self.name
290

  
291
    class NotAvailable(Exception):
292
        pass
293

  
b/snf-cyclades-app/synnefo/logic/backend.py
221 221
def create_instance(vm, flavor, image, password, personality):
222 222
    """`image` is a dictionary which should contain the keys:
223 223
            'backend_id', 'format' and 'metadata'
224
        
224

  
225 225
        metadata value should be a dictionary.
226 226
    """
227 227
    nic = {'ip': 'pool', 'mode': 'routed', 'link': settings.GANETI_PUBLIC_LINK}
......
268 268
        'img_format': image['format']}
269 269
    if personality:
270 270
        kw['osparams']['img_personality'] = json.dumps(personality)
271
    
271

  
272 272
    kw['osparams']['img_properties'] = json.dumps(image['metadata'])
273
    
273

  
274 274
    # Defined in settings.GANETI_CREATEINSTANCE_KWARGS
275 275
    # kw['hvparams'] = dict(serial_console=False)
276 276

  
......
356 356
    except IndexError:
357 357
        link = create_network_link()
358 358
        if not link:
359
            return None
359
            raise NetworkLink.NotAvailable
360 360

  
361 361
    network = Network.objects.create(
362 362
        name=name,

Also available in: Unified diff