summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorIsaku Yamahata <yamahata@valinux.co.jp>2011-05-27 11:10:24 +0900
committerIsaku Yamahata <yamahata@valinux.co.jp>2011-05-27 11:10:24 +0900
commitab938bf376efe7a93b54e4ca595d3102d04b0080 (patch)
tree622e133dd585d8686afe5955a6d6fd427929ece5 /nova
parent945d566c10877aeaf4d75bc26e161ba3a5f9c4b9 (diff)
compute: implement ec2 stop/start instances
This patch implements ec2 stop/start instances with block device mapping support.
Diffstat (limited to 'nova')
-rw-r--r--nova/api/ec2/cloud.py34
-rw-r--r--nova/compute/api.py39
-rw-r--r--nova/compute/manager.py48
-rw-r--r--nova/db/api.py5
-rw-r--r--nova/db/sqlalchemy/api.py18
-rw-r--r--nova/scheduler/simple.py8
6 files changed, 134 insertions, 18 deletions
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 56b958458..0989a4f40 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -903,33 +903,47 @@ class CloudController(object):
return self._format_run_instances(context,
instances[0]['reservation_id'])
+ def _do_instance(self, action, context, ec2_id):
+ instance_id = ec2utils.ec2_id_to_id(ec2_id)
+ action(context, instance_id=instance_id)
+
+ def _do_instances(self, action, context, instance_id):
+ for ec2_id in instance_id:
+ self._do_instance(action, context, ec2_id)
+
def terminate_instances(self, context, instance_id, **kwargs):
"""Terminate each instance in instance_id, which is a list of ec2 ids.
instance_id is a kwarg so its name cannot be modified."""
LOG.debug(_("Going to start terminating instances"))
- for ec2_id in instance_id:
- instance_id = ec2utils.ec2_id_to_id(ec2_id)
- self.compute_api.delete(context, instance_id=instance_id)
+ self._do_instances(self.compute_api.delete, context, instance_id)
return True
def reboot_instances(self, context, instance_id, **kwargs):
"""instance_id is a list of instance ids"""
LOG.audit(_("Reboot instance %r"), instance_id, context=context)
- for ec2_id in instance_id:
- instance_id = ec2utils.ec2_id_to_id(ec2_id)
- self.compute_api.reboot(context, instance_id=instance_id)
+ self._do_instances(self.compute_api.reboot, context, instance_id)
+ return True
+
+ def stop_instances(self, context, instance_id, **kwargs):
+ """Stop each instance in instace_id"""
+ LOG.debug(_("Going to stop instnces"))
+ self._do_instances(self.compute_api.stop, context, instance_id)
+ return True
+
+ def start_instances(self, context, instance_id, **kwargs):
+ """Start each instance in instace_id"""
+ LOG.debug(_("Going to start instnces"))
+ self._do_instances(self.compute_api.start, context, instance_id)
return True
def rescue_instance(self, context, instance_id, **kwargs):
"""This is an extension to the normal ec2_api"""
- instance_id = ec2utils.ec2_id_to_id(instance_id)
- self.compute_api.rescue(context, instance_id=instance_id)
+ self._do_instance(self.compute_api.rescue, contect, instnace_id)
return True
def unrescue_instance(self, context, instance_id, **kwargs):
"""This is an extension to the normal ec2_api"""
- instance_id = ec2utils.ec2_id_to_id(instance_id)
- self.compute_api.unrescue(context, instance_id=instance_id)
+ self._do_instance(self.compute_api.unrescue, context, instance_id)
return True
def update_instance(self, context, instance_id, **kwargs):
diff --git a/nova/compute/api.py b/nova/compute/api.py
index af2f23724..6b38e4eb5 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -426,6 +426,45 @@ class API(base.Base):
terminate_volumes(self.db, context, instance_id)
self.db.instance_destroy(context, instance_id)
+ @scheduler_api.reroute_compute("stop")
+ def stop(self, context, instance_id):
+ """Stop an instnace."""
+ LOG.debug(_("Going to try to stop %s"), instance_id)
+
+ instance = self._get_instance(context, instance_id, 'stopping')
+ if not _is_able_to_shutdown(instance, instance_id):
+ return
+
+ self.update(context,
+ instance['id'],
+ state_description='stopping',
+ state=power_state.NOSTATE,
+ terminated_at=datetime.datetime.utcnow())
+
+ host = instance['host']
+ if host:
+ self._cast_compute_message('stop_instance', context,
+ instance_id, host)
+
+ def start(self, context, instance_id):
+ """Start an instnace."""
+ LOG.debug(_("Going to try to start %s"), instance_id)
+ instance = self._get_instance(context, instance_id, 'starting')
+ if instance['state_description'] != 'stopped':
+ _state_description = instance['state_description']
+ LOG.warning(_("Instance %(instance_id)s is not "
+ "stopped(%(_state_description)s)") % locals())
+ return
+
+ # TODO(yamahata): injected_files isn't supported right now.
+ # It is used only for osapi. not for ec2 api.
+ # availability_zone isn't used by run_instance.
+ rpc.cast(context,
+ FLAGS.scheduler_topic,
+ {"method": "start_instance",
+ "args": {"topic": FLAGS.compute_topic,
+ "instance_id": instance_id}})
+
def get(self, context, instance_id):
"""Get a single instance with the given instance_id."""
rv = self.db.instance_get(context, instance_id)
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 86dac0ff2..5e8429e88 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -269,7 +269,7 @@ class ComputeManager(manager.SchedulerDependentManager):
return block_device_mapping
- def run_instance(self, context, instance_id, **kwargs):
+ def _run_instance(self, context, instance_id, **kwargs):
"""Launch a new instance with specified options."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
@@ -334,12 +334,24 @@ class ComputeManager(manager.SchedulerDependentManager):
self._update_state(context, instance_id)
@exception.wrap_exception
+ def run_instance(self, context, instance_id, **kwargs):
+ self._run_instance(context, instance_id, **kwargs)
+
+ @exception.wrap_exception
@checks_instance_lock
- def terminate_instance(self, context, instance_id):
- """Terminate an instance on this host."""
+ def start_instance(self, context, instance_id):
+ """Starting an instance on this host."""
+ # TODO(yamahata): injected_files isn't supported.
+ # Anyway OSAPI doesn't support stop/start yet
+ self._run_instance(context, instance_id)
+
+ def _shutdown_instance(self, context, instance_id, action_str):
+ """Shutdown an instance on this host."""
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
- LOG.audit(_("Terminating instance %s"), instance_id, context=context)
+ LOG.audit(_("%(action_str)s instance %(instance_id)s") %
+ {'action_str': action_str, 'instance_id': instance_id},
+ context=context)
fixed_ip = instance_ref.get('fixed_ip')
if not FLAGS.stub_network and fixed_ip:
@@ -375,18 +387,36 @@ class ComputeManager(manager.SchedulerDependentManager):
volumes = instance_ref.get('volumes') or []
for volume in volumes:
- self.detach_volume(context, instance_id, volume['id'])
- if instance_ref['state'] == power_state.SHUTOFF:
+ self._detach_volume(context, instance_id, volume['id'], False)
+
+ if (instance_ref['state'] == power_state.SHUTOFF and
+ instance_ref['state_description'] != 'stopped'):
self.db.instance_destroy(context, instance_id)
raise exception.Error(_('trying to destroy already destroyed'
' instance: %s') % instance_id)
self.driver.destroy(instance_ref)
+ if action_str == 'Terminating':
+ terminate_volumes(self.db, context, instance_id)
+
+ @exception.wrap_exception
+ @checks_instance_lock
+ def terminate_instance(self, context, instance_id):
+ """Terminate an instance on this host."""
+ self._shutdown_instance(context, instance_id, 'Terminating')
+
# TODO(ja): should we keep it in a terminated state for a bit?
self.db.instance_destroy(context, instance_id)
@exception.wrap_exception
@checks_instance_lock
+ def stop_instance(self, context, instance_id):
+ """Stopping an instance on this host."""
+ self._shutdown_instance(context, instance_id, 'Stopping')
+ # instance state will be updated to stopped by _poll_istance_states()
+
+ @exception.wrap_exception
+ @checks_instance_lock
def rebuild_instance(self, context, instance_id, image_id):
"""Destroy and re-make this instance.
@@ -1250,11 +1280,15 @@ class ComputeManager(manager.SchedulerDependentManager):
"State=%(db_state)s, so setting state to "
"shutoff.") % locals())
vm_state = power_state.SHUTOFF
+ if db_instance['state_description'] == 'stopping':
+ self.db.instance_stop(context, db_instance['id'])
+ continue
else:
vm_state = vm_instance.state
vms_not_found_in_db.remove(name)
- if db_instance['state_description'] == 'migrating':
+
+ if (db_instance['state_description'] in ['migrating', 'stopping']):
# A situation which db record exists, but no instance"
# sometimes occurs while live-migration at src compute,
# this case should be ignored.
diff --git a/nova/db/api.py b/nova/db/api.py
index 858c8eb10..7fb7f336f 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -414,6 +414,11 @@ def instance_destroy(context, instance_id):
return IMPL.instance_destroy(context, instance_id)
+def instance_stop(context, instance_id):
+ """Stop the instance or raise if it does not exist."""
+ return IMPL.instance_stop(context, instance_id)
+
+
def instance_get(context, instance_id):
"""Get an instance or raise if it does not exist."""
return IMPL.instance_get(context, instance_id)
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index e0904b3c6..be47e2c2e 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -831,6 +831,24 @@ def instance_destroy(context, instance_id):
'deleted_at': datetime.datetime.utcnow(),
'updated_at': literal_column('updated_at')})
+@require_context
+def instance_stop(context, instance_id):
+ session = get_session()
+ with session.begin():
+ from nova.compute import power_state
+ session.query(models.Instance).\
+ filter_by(id=instance_id).\
+ update({'host': None,
+ 'state': power_state.SHUTOFF,
+ 'state_description': 'stopped',
+ 'updated_at': literal_column('updated_at')})
+ session.query(models.SecurityGroupInstanceAssociation).\
+ filter_by(instance_id=instance_id).\
+ update({'updated_at': literal_column('updated_at')})
+ session.query(models.InstanceMetadata).\
+ filter_by(instance_id=instance_id).\
+ update({'updated_at': literal_column('updated_at')})
+
@require_context
def instance_get(context, instance_id, session=None):
diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py
index dd568d2c6..ccbc79a36 100644
--- a/nova/scheduler/simple.py
+++ b/nova/scheduler/simple.py
@@ -40,7 +40,7 @@ flags.DEFINE_integer("max_networks", 1000,
class SimpleScheduler(chance.ChanceScheduler):
"""Implements Naive Scheduler that tries to find least loaded host."""
- def schedule_run_instance(self, context, instance_id, *_args, **_kwargs):
+ def _schedule_instance(self, context, instance_id, *_args, **_kwargs):
"""Picks a host that is up and has the fewest running instances."""
instance_ref = db.instance_get(context, instance_id)
if (instance_ref['availability_zone']
@@ -76,6 +76,12 @@ class SimpleScheduler(chance.ChanceScheduler):
" for this request. Is the appropriate"
" service running?"))
+ def schedule_run_instance(self, context, instance_id, *_args, **_kwargs):
+ return self._schedule_instance(context, instance_id, *_args, **_kwargs)
+
+ def schedule_start_instance(self, context, instance_id, *_args, **_kwargs):
+ return self._schedule_instance(context, instance_id, *_args, **_kwargs)
+
def schedule_create_volume(self, context, volume_id, *_args, **_kwargs):
"""Picks a host that is up and has the fewest volumes."""
volume_ref = db.volume_get(context, volume_id)