Revision bcd80cd9

b/snf-cyclades-app/synnefo/api/management/commands/server-create.py
31 31
# interpreted as representing official policies, either expressed
32 32
# or implied, of GRNET S.A.
33 33

  
34
import json
35 34
from optparse import make_option
36 35

  
37
from django.db import transaction
38 36
from django.core.management.base import BaseCommand, CommandError
39 37
from synnefo.management import common
40 38

  
41
from synnefo.db.models import VirtualMachine
42
from synnefo.logic.backend import create_instance
43
from synnefo.logic.backend_allocator import BackendAllocator
44 39
from synnefo.api import util
45
from synnefo.api.servers import server_created
46
from synnefo import quotas
40
from synnefo.api.servers import do_create_server
47 41

  
48 42
HELP_MSG = """
49 43

  
......
77 71
                    help="Password for the new server")
78 72
    )
79 73

  
80
    @transaction.commit_manually
81 74
    def handle(self, *args, **options):
82 75
        if args:
83 76
            raise CommandError("Command doesn't accept any arguments")
......
97 90
            raise CommandError("password is mandatory")
98 91
        if not flavor_id:
99 92
            raise CommandError("flavor-id is mandatory")
100

  
101
        # Get Flavor
102
        if flavor_id:
103
            flavor = common.get_flavor(flavor_id)
104

  
105
        if image_id:
106
            img = common.get_image(image_id, user_id)
107

  
108
            properties = img.get('properties', {})
109
            image = {}
110
            image['backend_id'] = img['location']
111
            image['format'] = img['disk_format']
112
            image['metadata'] = dict((key.upper(), val)
113
                                     for key, val in properties.items())
114
        else:
93
        if not image_id:
115 94
            raise CommandError("image-id is mandatory")
116 95

  
117
        # Fix flavor for archipelago
118
        disk_template, provider = util.get_flavor_provider(flavor)
119
        if provider:
120
            flavor.disk_template = disk_template
121
            flavor.disk_provider = provider
122
            flavor.disk_origin = None
123
            if provider == 'vlmc':
124
                flavor.disk_origin = image['checksum']
125
                image['backend_id'] = 'null'
126
        else:
127
            flavor.disk_provider = None
128

  
129
        try:
130
            # Get Backend
131
            if backend_id:
132
                backend = common.get_backend(backend_id)
133
            else:
134
                ballocator = BackendAllocator()
135
                backend = ballocator.allocate(user_id, flavor)
136
                if not backend:
137
                    raise CommandError("Can not allocate VM")
138

  
139
            # Get Public address
140
            (network, address) = util.allocate_public_address(backend)
141
            if address is None:
142
                raise CommandError("Can not allocate a public address."
143
                                   " No available public network.")
144
            nic = {'ip': address, 'network': network.backend_id}
145

  
146
            # Create the VM in DB
147
            vm = VirtualMachine.objects.create(name=name,
148
                                               backend=backend,
149
                                               userid=user_id,
150
                                               imageid=image_id,
151
                                               flavor=flavor)
152
            # dispatch server created signal
153
            server_created.send(sender=vm, created_vm_params={
154
                'img_id': image['backend_id'],
155
                'img_passwd': password,
156
                'img_format': str(image['format']),
157
                'img_personality': '[]',
158
                'img_properties': json.dumps(image['metadata']),
159
            })
160

  
161
            quotas.issue_and_accept_commission(vm)
162
        except:
163
            transaction.rollback()
164
            raise
165
        else:
166
            transaction.commit()
167

  
168
        try:
169
            # Create the instance in Backend
170
            jobID = create_instance(vm, nic, flavor, image)
96
        flavor = common.get_flavor(flavor_id)
97
        image = common.get_image(image_id, user_id)
98
        if backend_id:
99
            backend = common.get_backend(backend_id)
171 100

  
172
            vm.backendjobid = jobID
173
            vm.save()
174
            self.stdout.write("Creating VM %s with IP %s in Backend %s."
175
                              " JobID: %s\n" % (vm, address, backend, jobID))
176
        except:
177
            transaction.rollback()
178
            raise
179
        else:
180
            transaction.commit()
101
        do_create_server(user_id, name, password, flavor, image,
102
                         backend=backend)
b/snf-cyclades-app/synnefo/api/servers.py
245 245

  
246 246

  
247 247
@api.api_method(http_method='POST', user_required=True, logger=log)
248
# Use manual transactions. Backend and IP pool allocations need exclusive
249
# access (SELECT..FOR UPDATE). Running create_server with commit_on_success
250
# would result in backends and public networks to be locked until the job is
251
# sent to the Ganeti backend.
252
@transaction.commit_manually
253 248
def create_server(request):
254 249
    # Normal Response Code: 202
255 250
    # Error Response Codes: computeFault (400, 500),
......
260 255
    #                       badRequest (400),
261 256
    #                       serverCapacityUnavailable (503),
262 257
    #                       overLimit (413)
258
    req = utils.get_request_dict(request)
259
    log.info('create_server %s', req)
260
    user_id = request.user_uniq
261

  
263 262
    try:
264
        req = utils.get_request_dict(request)
265
        log.info('create_server %s', req)
266
        user_id = request.user_uniq
263
        server = req['server']
264
        name = server['name']
265
        metadata = server.get('metadata', {})
266
        assert isinstance(metadata, dict)
267
        image_id = server['imageRef']
268
        flavor_id = server['flavorRef']
269
        personality = server.get('personality', [])
270
        assert isinstance(personality, list)
271
    except (KeyError, AssertionError):
272
        raise faults.BadRequest("Malformed request")
273

  
274
    # Verify that personalities are well-formed
275
    util.verify_personality(personality)
276
    # Get image information
277
    image = util.get_image_dict(image_id, user_id)
278
    # Get flavor (ensure it is active)
279
    flavor = util.get_flavor(flavor_id, include_deleted=False)
280
    # Generate password
281
    password = util.random_password()
282

  
283
    vm = do_create_server(user_id, name, password, flavor, image,
284
                          metadata=metadata, personality=personality)
285

  
286
    server = vm_to_dict(vm, detail=True)
287
    server['status'] = 'BUILD'
288
    server['adminPass'] = password
289

  
290
    response = render_server(request, server, status=202)
291

  
292
    return response
293

  
267 294

  
295
@transaction.commit_manually
296
def do_create_server(userid, name, password, flavor, image, metadata={},
297
                  personality=[], network=None, backend=None):
298
    if backend is None:
299
        # Allocate backend to host the server. Commit after allocation to
300
        # release the locks hold by the backend allocator.
268 301
        try:
269
            server = req['server']
270
            name = server['name']
271
            metadata = server.get('metadata', {})
272
            assert isinstance(metadata, dict)
273
            image_id = server['imageRef']
274
            flavor_id = server['flavorRef']
275
            personality = server.get('personality', [])
276
            assert isinstance(personality, list)
277
        except (KeyError, AssertionError):
278
            raise faults.BadRequest("Malformed request")
279

  
280
        # Verify that personalities are well-formed
281
        util.verify_personality(personality)
282
        # Get image information
283
        image = util.get_image_dict(image_id, user_id)
284
        # Get flavor (ensure it is active)
285
        flavor = util.get_flavor(flavor_id, include_deleted=False)
286
        # Allocate VM to backend
287
        backend_allocator = BackendAllocator()
288
        backend = backend_allocator.allocate(request.user_uniq, flavor)
289

  
290
        if backend is None:
291
            log.error("No available backends for VM with flavor %s", flavor)
292
            raise faults.ServiceUnavailable("No available backends")
293
    except:
294
        transaction.rollback()
295
        raise
296
    else:
297
        transaction.commit()
302
            backend_allocator = BackendAllocator()
303
            backend = backend_allocator.allocate(userid, flavor)
304
            if backend is None:
305
                log.error("No available backend for VM with flavor %s", flavor)
306
                raise faults.ServiceUnavailable("No available backends")
307
        except:
308
            transaction.rollback()
309
            raise
310
        else:
311
            transaction.commit()
298 312

  
299 313
    # Fix flavor for archipelago
300
    password = util.random_password()
301 314
    disk_template, provider = util.get_flavor_provider(flavor)
302 315
    if provider:
303 316
        flavor.disk_template = disk_template
......
310 323
        flavor.disk_provider = None
311 324

  
312 325
    try:
313
        # Allocate IP from public network
314
        (network, address) = util.get_public_ip(backend)
315
        nic = {'ip': address, 'network': network.backend_id}
326
        if network is None:
327
            # Allocate IP from public network
328
            (network, address) = util.get_public_ip(backend)
329
            nic = {'ip': address, 'network': network.backend_id}
330
        else:
331
            address = util.get_network_free_address(network)
316 332

  
317 333
        # We must save the VM instance now, so that it gets a valid
318 334
        # vm.backend_vm_id.
319 335
        vm = VirtualMachine.objects.create(
320 336
            name=name,
321 337
            backend=backend,
322
            userid=user_id,
323
            imageid=image_id,
338
            userid=userid,
339
            imageid=image["id"],
324 340
            flavor=flavor,
325 341
            action="CREATE")
326 342

  
......
364 380
        vm.save()
365 381
        transaction.commit()
366 382
        log.info("User %s created VM %s, NIC %s, Backend %s, JobID %s",
367
                 user_id, vm, nic, backend, str(jobID))
383
                 userid, vm, nic, backend, str(jobID))
368 384
    except GanetiApiError as e:
369 385
        log.exception("Can not communicate to backend %s: %s.",
370 386
                      backend, e)
......
380 396
        transaction.rollback()
381 397
        raise
382 398

  
383
    server = vm_to_dict(vm, detail=True)
384
    server['status'] = 'BUILD'
385
    server['adminPass'] = password
386

  
387
    response = render_server(request, server, status=202)
388

  
389
    return response
399
    return vm
390 400

  
391 401

  
392 402
@api.api_method(http_method='GET', user_required=True, logger=log)
b/snf-cyclades-app/synnefo/api/test/servers.py
172 172
           if a valid request has been speficied."""
173 173
        mimage.return_value = {'location': 'pithos://foo',
174 174
                               'checksum': '1234',
175
                               "id": 1,
176
                               "name": "test_image",
175 177
                               'disk_format': 'diskdump'}
176 178
        mrapi().CreateInstance.return_value = 12
177 179
        flavor = mfactory.FlavorFactory()
b/snf-cyclades-app/synnefo/api/util.py
158 158
    image = {}
159 159
    img = get_image(image_id, user_id)
160 160
    properties = img.get('properties', {})
161
    image["id"] = img["id"]
162
    image["name"] = img["name"]
161 163
    image['backend_id'] = img['location']
162 164
    image['format'] = img['disk_format']
163 165
    image['metadata'] = dict((key.upper(), val)
b/snf-cyclades-app/synnefo/management/common.py
35 35
from synnefo.db.models import Backend, VirtualMachine, Network, Flavor
36 36

  
37 37
from snf_django.lib.api import faults
38
from synnefo.api.util import get_image as backend_get_image
39
from synnefo.api.util import validate_network_params
38
from synnefo.api import util
40 39
from synnefo.logic.rapi import GanetiApiError, GanetiRapiClient
41 40
from synnefo.logic.utils import (id_from_instance_name,
42 41
                                 id_from_network_name)
......
59 58
    gateway6 = options['gateway6']
60 59

  
61 60
    try:
62
        validate_network_params(subnet, gateway)
61
        util.validate_network_params(subnet, gateway)
63 62
    except (faults.BadRequest, faults.OverLimit) as e:
64 63
        raise CommandError(e)
65 64

  
......
81 80
def get_image(image_id, user_id):
82 81
    if image_id:
83 82
        try:
84
            return backend_get_image(image_id, user_id)
83
            return util.get_image_dict(image_id, user_id)
85 84
        except faults.ItemNotFound:
86 85
            raise CommandError("Image with ID %s not found."
87 86
                               " Use snf-manage image-list to find"

Also available in: Unified diff