17
17
# under the License.
20
Handles all API requests relating to instances (guest vms).
20
Handles all requests relating to instances (guest vms).
27
27
from nova import db
28
28
from nova import exception
29
29
from nova import flags
30
from nova import log as logging
31
from nova import network
30
32
from nova import quota
31
33
from nova import rpc
32
34
from nova import utils
35
from nova import volume
33
36
from nova.compute import instance_types
34
37
from nova.db import base
36
39
FLAGS = flags.FLAGS
39
def generate_default_hostname(internal_id):
40
LOG = logging.getLogger('nova.compute.api')
43
def generate_default_hostname(instance_id):
40
44
"""Default function to generate a hostname given an instance reference."""
41
return str(internal_id)
44
class ComputeAPI(base.Base):
45
return str(instance_id)
45
49
"""API for interacting with the compute manager."""
47
def __init__(self, network_manager=None, image_service=None, **kwargs):
48
if not network_manager:
49
network_manager = utils.import_object(FLAGS.network_manager)
50
self.network_manager = network_manager
51
def __init__(self, image_service=None, network_api=None,
52
volume_api=None, hostname_factory=generate_default_hostname,
51
54
if not image_service:
52
55
image_service = utils.import_object(FLAGS.image_service)
53
56
self.image_service = image_service
54
super(ComputeAPI, self).__init__(**kwargs)
56
def create_instances(self, context, instance_type, image_id, min_count=1,
57
max_count=1, kernel_id=None, ramdisk_id=None,
58
display_name='', description='', key_name=None,
59
key_data=None, security_group='default',
60
generate_hostname=generate_default_hostname):
61
"""Create the number of instances requested if quote and
58
network_api = network.API()
59
self.network_api = network_api
61
volume_api = volume.API()
62
self.volume_api = volume_api
63
self.hostname_factory = hostname_factory
64
super(API, self).__init__(**kwargs)
66
def get_network_topic(self, context, instance_id):
67
"""Get the network topic for an instance."""
69
instance = self.get(context, instance_id)
70
except exception.NotFound as e:
71
LOG.warning(_("Instance %d was not found in get_network_topic"),
75
host = instance['host']
77
raise exception.Error(_("Instance %d has no host") % instance_id)
78
topic = self.db.queue_get_for(context, FLAGS.compute_topic, host)
79
return rpc.call(context,
81
{"method": "get_network_topic", "args": {'fake': 1}})
83
def create(self, context, instance_type,
84
image_id, kernel_id=None, ramdisk_id=None,
85
min_count=1, max_count=1,
86
display_name='', display_description='',
87
key_name=None, key_data=None, security_group='default',
88
availability_zone=None, user_data=None):
89
"""Create the number of instances requested if quota and
62
90
other arguments check out ok."""
64
num_instances = quota.allowed_instances(context, max_count,
92
type_data = instance_types.INSTANCE_TYPES[instance_type]
93
num_instances = quota.allowed_instances(context, max_count, type_data)
66
94
if num_instances < min_count:
67
logging.warn("Quota exceeeded for %s, tried to run %s instances",
68
context.project_id, min_count)
69
raise quota.QuotaError("Instance quota exceeded. You can only "
70
"run %s more instances of this type." %
95
LOG.warn(_("Quota exceeeded for %s, tried to run %s instances"),
96
context.project_id, min_count)
97
raise quota.QuotaError(_("Instance quota exceeded. You can only "
98
"run %s more instances of this type.") %
71
99
num_instances, "InstanceLimitExceeded")
73
101
is_vpn = image_id == FLAGS.vpn_image_id
75
103
image = self.image_service.show(context, image_id)
76
104
if kernel_id is None:
77
kernel_id = image.get('kernelId', FLAGS.default_kernel)
105
kernel_id = image.get('kernelId', None)
78
106
if ramdisk_id is None:
79
ramdisk_id = image.get('ramdiskId', FLAGS.default_ramdisk)
81
# Make sure we have access to kernel and ramdisk
82
self.image_service.show(context, kernel_id)
83
self.image_service.show(context, ramdisk_id)
107
ramdisk_id = image.get('ramdiskId', None)
108
# No kernel and ramdisk for raw images
109
if kernel_id == str(FLAGS.null_kernel):
112
LOG.debug(_("Creating a raw instance"))
113
# Make sure we have access to kernel and ramdisk (if not raw)
114
logging.debug("Using Kernel=%s, Ramdisk=%s" %
115
(kernel_id, ramdisk_id))
117
self.image_service.show(context, kernel_id)
119
self.image_service.show(context, ramdisk_id)
85
121
if security_group is None:
86
122
security_group = ['default']
199
return self.db.instance_update(context, instance_id, kwargs)
286
rv = self.db.instance_update(context, instance_id, kwargs)
287
return dict(rv.iteritems())
201
def delete_instance(self, context, instance_id):
202
logging.debug("Going to try and terminate %d" % instance_id)
289
def delete(self, context, instance_id):
290
LOG.debug(_("Going to try to terminate %s"), instance_id)
204
instance = self.db.instance_get_by_internal_id(context,
292
instance = self.get(context, instance_id)
206
293
except exception.NotFound as e:
207
logging.warning("Instance %d was not found during terminate",
294
LOG.warning(_("Instance %d was not found during terminate"),
211
298
if (instance['state_description'] == 'terminating'):
212
logging.warning("Instance %d is already being terminated",
299
LOG.warning(_("Instance %d is already being terminated"),
216
self.update_instance(context,
218
state_description='terminating',
220
terminated_at=datetime.datetime.utcnow())
222
# FIXME(ja): where should network deallocate occur?
223
address = self.db.instance_get_floating_address(context,
226
logging.debug("Disassociating address %s" % address)
227
# NOTE(vish): Right now we don't really care if the ip is
228
# disassociated. We may need to worry about
229
# checking this later. Perhaps in the scheduler?
231
self._get_network_topic(context),
232
{"method": "disassociate_floating_ip",
233
"args": {"floating_address": address}})
235
address = self.db.instance_get_fixed_address(context, instance['id'])
237
logging.debug("Deallocating address %s" % address)
238
# NOTE(vish): Currently, nothing needs to be done on the
239
# network node until release. If this changes,
240
# we will need to cast here.
241
self.network_manager.deallocate_fixed_ip(context.elevated(),
305
state_description='terminating',
307
terminated_at=datetime.datetime.utcnow())
244
309
host = instance['host']
247
self.db.queue_get_for(context, FLAGS.compute_topic, host),
248
{"method": "terminate_instance",
249
"args": {"instance_id": instance['id']}})
311
self._cast_compute_message('terminate_instance', context,
251
self.db.instance_destroy(context, instance['id'])
253
def get_instances(self, context, project_id=None):
254
"""Get all instances, possibly filtered by project ID or
255
user ID. If there is no filter and the context is an admin,
256
it will retreive all instances in the system."""
314
self.db.instance_destroy(context, instance_id)
316
def get(self, context, instance_id):
317
"""Get a single instance with the given ID."""
318
rv = self.db.instance_get_by_id(context, instance_id)
319
return dict(rv.iteritems())
321
def get_all(self, context, project_id=None, reservation_id=None,
323
"""Get all instances, possibly filtered by one of the
324
given parameters. If there is no filter and the context is
325
an admin, it will retreive all instances in the system."""
326
if reservation_id is not None:
327
return self.db.instance_get_all_by_reservation(context,
329
if fixed_ip is not None:
330
return self.db.fixed_ip_get_instance(context, fixed_ip)
257
331
if project_id or not context.is_admin:
258
332
if not context.project:
259
333
return self.db.instance_get_all_by_user(context,
261
335
if project_id is None:
262
336
project_id = context.project_id
263
return self.db.instance_get_all_by_project(context, project_id)
337
return self.db.instance_get_all_by_project(context,
264
339
return self.db.instance_get_all(context)
266
def get_instance(self, context, instance_id):
267
return self.db.instance_get_by_internal_id(context, instance_id)
341
def _cast_compute_message(self, method, context, instance_id, host=None,
343
"""Generic handler for RPC casts to compute.
345
:param params: Optional dictionary of arguments to be passed to the
353
instance = self.get(context, instance_id)
354
host = instance['host']
355
queue = self.db.queue_get_for(context, FLAGS.compute_topic, host)
356
params['instance_id'] = instance_id
357
kwargs = {'method': method, 'args': params}
358
rpc.cast(context, queue, kwargs)
360
def _call_compute_message(self, method, context, instance_id, host=None,
362
"""Generic handler for RPC calls to compute.
364
:param params: Optional dictionary of arguments to be passed to the
367
:retval: Result returned by compute worker
372
instance = self.get(context, instance_id)
373
host = instance["host"]
374
queue = self.db.queue_get_for(context, FLAGS.compute_topic, host)
375
params['instance_id'] = instance_id
376
kwargs = {'method': method, 'args': params}
377
return rpc.call(context, queue, kwargs)
379
def snapshot(self, context, instance_id, name):
380
"""Snapshot the given instance.
382
:retval: A dict containing image metadata
384
data = {'name': name, 'is_public': False}
385
image_meta = self.image_service.create(context, data)
386
params = {'image_id': image_meta['id']}
387
self._cast_compute_message('snapshot_instance', context, instance_id,
269
391
def reboot(self, context, instance_id):
270
392
"""Reboot the given instance."""
271
instance = self.db.instance_get_by_internal_id(context, instance_id)
272
host = instance['host']
274
self.db.queue_get_for(context, FLAGS.compute_topic, host),
275
{"method": "reboot_instance",
276
"args": {"instance_id": instance['id']}})
393
self._cast_compute_message('reboot_instance', context, instance_id)
395
def pause(self, context, instance_id):
396
"""Pause the given instance."""
397
self._cast_compute_message('pause_instance', context, instance_id)
399
def unpause(self, context, instance_id):
400
"""Unpause the given instance."""
401
self._cast_compute_message('unpause_instance', context, instance_id)
403
def get_diagnostics(self, context, instance_id):
404
"""Retrieve diagnostics for the given instance."""
405
return self._call_compute_message(
410
def get_actions(self, context, instance_id):
411
"""Retrieve actions for the given instance."""
412
return self.db.instance_get_actions(context, instance_id)
414
def suspend(self, context, instance_id):
415
"""suspend the instance with instance_id"""
416
self._cast_compute_message('suspend_instance', context, instance_id)
418
def resume(self, context, instance_id):
419
"""resume the instance with instance_id"""
420
self._cast_compute_message('resume_instance', context, instance_id)
278
422
def rescue(self, context, instance_id):
279
423
"""Rescue the given instance."""
280
instance = self.db.instance_get_by_internal_id(context, instance_id)
281
host = instance['host']
283
self.db.queue_get_for(context, FLAGS.compute_topic, host),
284
{"method": "rescue_instance",
285
"args": {"instance_id": instance['id']}})
424
self._cast_compute_message('rescue_instance', context, instance_id)
287
426
def unrescue(self, context, instance_id):
288
427
"""Unrescue the given instance."""
289
instance = self.db.instance_get_by_internal_id(context, instance_id)
290
host = instance['host']
292
self.db.queue_get_for(context, FLAGS.compute_topic, host),
293
{"method": "unrescue_instance",
294
"args": {"instance_id": instance['id']}})
296
def _get_network_topic(self, context):
297
"""Retrieves the network host for a project"""
298
network_ref = self.network_manager.get_network(context)
299
host = network_ref['host']
301
host = rpc.call(context,
303
{"method": "set_network_host",
304
"args": {"network_id": network_ref['id']}})
305
return self.db.queue_get_for(context, FLAGS.network_topic, host)
428
self._cast_compute_message('unrescue_instance', context, instance_id)
430
def set_admin_password(self, context, instance_id):
431
"""Set the root/admin password for the given instance."""
432
self._cast_compute_message('set_admin_password', context, instance_id)
434
def get_ajax_console(self, context, instance_id):
435
"""Get a url to an AJAX Console"""
436
instance = self.get(context, instance_id)
437
output = self._call_compute_message('get_ajax_console',
440
rpc.cast(context, '%s' % FLAGS.ajax_console_proxy_topic,
441
{'method': 'authorize_ajax_console',
442
'args': {'token': output['token'], 'host': output['host'],
443
'port': output['port']}})
444
return {'url': '%s?token=%s' % (FLAGS.ajax_console_proxy_url,
447
def get_console_output(self, context, instance_id):
448
"""Get console output for an an instance"""
449
return self._call_compute_message('get_console_output',
453
def lock(self, context, instance_id):
454
"""lock the instance with instance_id"""
455
self._cast_compute_message('lock_instance', context, instance_id)
457
def unlock(self, context, instance_id):
458
"""unlock the instance with instance_id"""
459
self._cast_compute_message('unlock_instance', context, instance_id)
461
def get_lock(self, context, instance_id):
462
"""return the boolean state of (instance with instance_id)'s lock"""
463
instance = self.get(context, instance_id)
464
return instance['locked']
466
def attach_volume(self, context, instance_id, volume_id, device):
467
if not re.match("^/dev/[a-z]d[a-z]+$", device):
468
raise exception.ApiError(_("Invalid device specified: %s. "
469
"Example device: /dev/vdb") % device)
470
self.volume_api.check_attach(context, volume_id)
471
instance = self.get(context, instance_id)
472
host = instance['host']
474
self.db.queue_get_for(context, FLAGS.compute_topic, host),
475
{"method": "attach_volume",
476
"args": {"volume_id": volume_id,
477
"instance_id": instance_id,
478
"mountpoint": device}})
480
def detach_volume(self, context, volume_id):
481
instance = self.db.volume_get_instance(context.elevated(), volume_id)
483
raise exception.ApiError(_("Volume isn't attached to anything!"))
484
self.volume_api.check_detach(context, volume_id)
485
host = instance['host']
487
self.db.queue_get_for(context, FLAGS.compute_topic, host),
488
{"method": "detach_volume",
489
"args": {"instance_id": instance['id'],
490
"volume_id": volume_id}})
493
def associate_floating_ip(self, context, instance_id, address):
494
instance = self.get(context, instance_id)
495
self.network_api.associate_floating_ip(context, address,
496
instance['fixed_ip'])