summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandy <github@anarkystic.com>2010-08-19 12:28:45 +0200
committerandy <github@anarkystic.com>2010-08-19 12:28:45 +0200
commita92465922fb74ca2c9b392e1c1b7ed5b5e306a76 (patch)
treee75cb0fe8a28b30b8292bc3c2207fbcc46a10ef4
parentf996ec188776ffcae62bcafc1925653a1602880f (diff)
downloadnova-a92465922fb74ca2c9b392e1c1b7ed5b5e306a76.tar.gz
Data abstraction for compute service
-rw-r--r--nova/compute/service.py144
-rw-r--r--nova/db/__init__.py3
-rw-r--r--nova/db/api.py53
-rw-r--r--nova/db/sqlalchemy/__init__.py0
-rw-r--r--nova/db/sqlalchemy/api.py43
-rw-r--r--nova/models.py6
-rw-r--r--nova/utils.py33
7 files changed, 211 insertions, 71 deletions
diff --git a/nova/compute/service.py b/nova/compute/service.py
index 3909c8245b..7a2cb277d5 100644
--- a/nova/compute/service.py
+++ b/nova/compute/service.py
@@ -30,6 +30,7 @@ import os
from twisted.internet import defer
+from nova import db
from nova import exception
from nova import flags
from nova import process
@@ -44,7 +45,7 @@ from nova.volume import service as volume_service
FLAGS = flags.FLAGS
flags.DEFINE_string('instances_path', utils.abspath('../instances'),
- 'where instances are stored on disk')
+ 'where instances are stored on disk')
class ComputeService(service.Service):
@@ -52,109 +53,107 @@ class ComputeService(service.Service):
Manages the running instances.
"""
def __init__(self):
- """ load configuration options for this node and connect to the hypervisor"""
+ """Load configuration options and connect to the hypervisor."""
super(ComputeService, self).__init__()
self._instances = {}
self._conn = virt_connection.get_connection()
- # TODO(joshua): This needs to ensure system state, specifically: modprobe aoe
+ # TODO(joshua): This needs to ensure system state, specifically
+ # modprobe aoe
def noop(self):
- """ simple test of an AMQP message call """
+ """Simple test of an AMQP message call."""
return defer.succeed('PONG')
- def update_state(self, instance_id):
- inst = models.Instance.find(instance_id)
+ def update_state(self, instance_id, context):
# FIXME(ja): include other fields from state?
- inst.state = self._conn.get_info(inst.name)['state']
- inst.save()
-
- @exception.wrap_exception
- def adopt_instances(self):
- """ if there are instances already running, adopt them """
- return defer.succeed(0)
- instance_names = self._conn.list_instances()
- for name in instance_names:
- try:
- new_inst = Instance.fromName(self._conn, name)
- new_inst.update_state()
- except:
- pass
- return defer.succeed(len(self._instances))
+ instance_ref = db.instance_get(context, instance_id)
+ state = self._conn.get_info(instance_ref.name)['state']
+ db.instance_state(context, instance_id, state)
@defer.inlineCallbacks
@exception.wrap_exception
- def run_instance(self, instance_id, **_kwargs):
- """ launch a new instance with specified options """
- inst = models.Instance.find(instance_id)
- if inst.name in self._conn.list_instances():
+ def run_instance(self, instance_id, context=None, **_kwargs):
+ """Launch a new instance with specified options."""
+ instance_ref = db.instance_get(context, instance_id)
+ if instance_ref['name'] in self._conn.list_instances():
raise exception.Error("Instance has already been created")
logging.debug("Starting instance %s..." % (instance_id))
- inst = models.Instance.find(instance_id)
+
# NOTE(vish): passing network type allows us to express the
# network without making a call to network to find
# out which type of network to setup
- network_service.setup_compute_network(inst.project_id)
- inst.node_name = FLAGS.node_name
- inst.save()
+ network_service.setup_compute_network(instance_ref['project_id'])
+ db.instance_update(context, instance_id, {'node_name': FLAGS.node_name})
# TODO(vish) check to make sure the availability zone matches
- inst.set_state(power_state.NOSTATE, 'spawning')
+ db.instance_state(context, instance_id, power_state.NOSTATE, 'spawning')
try:
- yield self._conn.spawn(inst)
+ yield self._conn.spawn(instance_ref)
except:
- logging.exception("Failed to spawn instance %s" % inst.name)
- inst.set_state(power_state.SHUTDOWN)
+ logging.exception("Failed to spawn instance %s" %
+ instance_ref['name'])
+ db.instance_state(context, instance_id, power_state.SHUTDOWN)
- self.update_state(instance_id)
+ self.update_state(instance_id, context)
@defer.inlineCallbacks
@exception.wrap_exception
- def terminate_instance(self, instance_id):
- """ terminate an instance on this machine """
+ def terminate_instance(self, instance_id, context=None):
+ """Terminate an instance on this machine."""
logging.debug("Got told to terminate instance %s" % instance_id)
- inst = models.Instance.find(instance_id)
+ instance_ref = db.instance_get(context, instance_id)
- if inst.state == power_state.SHUTOFF:
- # self.datamodel.destroy() FIXME: RE-ADD ?????
+ if instance_ref['state'] == power_state.SHUTOFF:
+ # self.datamodel.destroy() FIXME: RE-ADD?
raise exception.Error('trying to destroy already destroyed'
' instance: %s' % instance_id)
- inst.set_state(power_state.NOSTATE, 'shutting_down')
- yield self._conn.destroy(inst)
+ db.instance_state(
+ context, instance_id, power_state.NOSTATE, 'shutting_down')
+ yield self._conn.destroy(instance_ref)
+
# FIXME(ja): should we keep it in a terminated state for a bit?
- inst.delete()
+ db.instance_destroy(context, instance_id)
@defer.inlineCallbacks
@exception.wrap_exception
- def reboot_instance(self, instance_id):
- """ reboot an instance on this server
- KVM doesn't support reboot, so we terminate and restart """
- self.update_state(instance_id)
- instance = models.Instance.find(instance_id)
+ def reboot_instance(self, instance_id, context=None):
+ """Reboot an instance on this server.
+
+ KVM doesn't support reboot, so we terminate and restart.
+
+ """
+ self.update_state(instance_id, context)
+ instance_ref = db.instance_get(context, instance_id)
# FIXME(ja): this is only checking the model state - not state on disk?
- if instance.state != power_state.RUNNING:
+ if instance_ref['state'] != power_state.RUNNING:
raise exception.Error(
'trying to reboot a non-running'
- 'instance: %s (state: %s excepted: %s)' % (instance.name, instance.state, power_state.RUNNING))
+ 'instance: %s (state: %s excepted: %s)' %
+ (instance_ref['name'],
+ instance_ref['state'],
+ power_state.RUNNING))
- logging.debug('rebooting instance %s' % instance.name)
- instance.set_state(power_state.NOSTATE, 'rebooting')
- yield self._conn.reboot(instance)
- self.update_state(instance_id)
+ logging.debug('rebooting instance %s' % instance_ref['name'])
+ db.instance_state(
+ context, instance_id, power_state.NOSTATE, 'rebooting')
+ yield self._conn.reboot(instance_ref)
+ self.update_state(instance_id, context)
@exception.wrap_exception
- def get_console_output(self, instance_id):
- """ send the console output for an instance """
+ def get_console_output(self, instance_id, context=None):
+ """Send the console output for an instance."""
# FIXME: Abstract this for Xen
logging.debug("Getting console output for %s" % (instance_id))
- inst = models.Instance.find(instance_id)
+ instance_ref = db.instance_get(context, instance_id)
if FLAGS.connection_type == 'libvirt':
- fname = os.path.abspath(
- os.path.join(FLAGS.instances_path, inst.name, 'console.log'))
+ fname = os.path.abspath(os.path.join(FLAGS.instances_path,
+ instance_ref['name'],
+ 'console.log'))
with open(fname, 'r') as f:
output = f.read()
else:
@@ -169,32 +168,35 @@ class ComputeService(service.Service):
@defer.inlineCallbacks
@exception.wrap_exception
- def attach_volume(self, instance_id = None,
- volume_id = None, mountpoint = None):
- volume = volume_service.get_volume(volume_id)
+ def attach_volume(self, instance_id=None, volume_id=None, mountpoint=None,
+ context=None):
+ """Attach a volume to an instance."""
+ # TODO(termie): check that instance_id exists
+ volume_ref = volume_get(context, volume_id)
yield self._init_aoe()
yield process.simple_execute(
"sudo virsh attach-disk %s /dev/etherd/%s %s" %
(instance_id,
volume['aoe_device'],
mountpoint.rpartition('/dev/')[2]))
- volume.finish_attach()
+ volume_attached(context, volume_id)
defer.returnValue(True)
@defer.inlineCallbacks
- def _init_aoe(self):
- yield process.simple_execute("sudo aoe-discover")
- yield process.simple_execute("sudo aoe-stat")
-
- @defer.inlineCallbacks
@exception.wrap_exception
- def detach_volume(self, instance_id, volume_id):
- """ detach a volume from an instance """
+ def detach_volume(self, instance_id, volume_id, context=None):
+ """Detach a volume from an instance."""
# despite the documentation, virsh detach-disk just wants the device
# name without the leading /dev/
- volume = volume_service.get_volume(volume_id)
+ # TODO(termie): check that instance_id exists
+ volume_ref = volume_get(context, volume_id)
target = volume['mountpoint'].rpartition('/dev/')[2]
yield process.simple_execute(
"sudo virsh detach-disk %s %s " % (instance_id, target))
- volume.finish_detach()
+ volume_detached(context, volume_id)
defer.returnValue(True)
+
+ @defer.inlineCallbacks
+ def _init_aoe(self):
+ yield process.simple_execute("sudo aoe-discover")
+ yield process.simple_execute("sudo aoe-stat")
diff --git a/nova/db/__init__.py b/nova/db/__init__.py
new file mode 100644
index 0000000000..2d893cb361
--- /dev/null
+++ b/nova/db/__init__.py
@@ -0,0 +1,3 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+from nova.db.api import *
diff --git a/nova/db/api.py b/nova/db/api.py
new file mode 100644
index 0000000000..c1b2dee0dc
--- /dev/null
+++ b/nova/db/api.py
@@ -0,0 +1,53 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+from nova import flags
+from nova import utils
+
+
+FLAGS = flags.FLAGS
+flags.DEFINE_string('db_backend', 'sqlalchemy',
+ 'The backend to use for db')
+
+
+_impl = utils.LazyPluggable(FLAGS['db_backend'],
+ sqlalchemy='nova.db.sqlalchemy.api')
+
+
+def instance_destroy(context, instance_id):
+ """Destroy the instance or raise if it does not exist."""
+ return _impl.instance_destroy(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)
+
+
+def instance_state(context, instance_id, state, description=None):
+ """Set the state of an instance."""
+ return _impl.instance_state(context, instance_id, state, description)
+
+
+def instance_update(context, instance_id, new_values):
+ """Set the given properties on an instance and update it.
+
+ Raises if instance does not exist.
+
+ """
+ return _impl.instance_update(context, instance_id, new_values)
+
+
+def volume_get(context, volume_id):
+ """Get a volume or raise if it does not exist."""
+ return _impl.volume_get(context, volume_id)
+
+
+def volume_attached(context, volume_id):
+ """Ensure that a volume is set as attached."""
+ return _impl.volume_attached(context, volume_id)
+
+
+def volume_detached(context, volume_id):
+ """Ensure that a volume is set as detached."""
+ return _impl.volume_detached(context, volume_id)
+
diff --git a/nova/db/sqlalchemy/__init__.py b/nova/db/sqlalchemy/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/nova/db/sqlalchemy/__init__.py
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
new file mode 100644
index 0000000000..6d9f5fe5fe
--- /dev/null
+++ b/nova/db/sqlalchemy/api.py
@@ -0,0 +1,43 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+from nova import models
+
+
+def instance_destroy(context, instance_id):
+ instance_ref = instance_get(context, instance_id)
+ instance_ref.delete()
+
+
+def instance_get(context, instance_id):
+ return models.Instance.find(instance_id)
+
+
+def instance_state(context, instance_id, state, description=None):
+ instance_ref = instance_get(context, instance_id)
+ instance_ref.set_state(state, description)
+
+
+def instance_update(context, instance_id, properties):
+ instance_ref = instance_get(context, instance_id)
+ for k, v in properties.iteritems():
+ instance_ref[k] = v
+ instance_ref.save()
+
+
+def volume_get(context, volume_id):
+ return models.Volume.find(volume_id)
+
+
+def volume_attached(context, volume_id):
+ volume_ref = volume_get(context, volume_id)
+ volume_ref['attach_status'] = 'attached'
+ volume_ref.save()
+
+
+def volume_detached(context, volume_id):
+ volume_ref = volume_get(context, volume_id)
+ volume_ref['instance_id'] = None
+ volume_ref['mountpoint'] = None
+ volume_ref['status'] = 'available'
+ volume_ref['attach_status'] = 'detached'
+ volume_ref.save()
diff --git a/nova/models.py b/nova/models.py
index d0b66d9b7c..ea529713ca 100644
--- a/nova/models.py
+++ b/nova/models.py
@@ -100,6 +100,12 @@ class NovaBase(object):
session = NovaBase.get_session()
session.refresh(self)
+ def __setitem__(self, key, value):
+ setattr(self, key, value)
+
+ def __getitem__(self, key):
+ return getattr(self, key)
+
class Image(Base, NovaBase):
__tablename__ = 'images'
diff --git a/nova/utils.py b/nova/utils.py
index e826f9b714..9e12a5301b 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -142,3 +142,36 @@ def isotime(at=None):
def parse_isotime(timestr):
return datetime.datetime.strptime(timestr, TIME_FORMAT)
+
+
+
+class LazyPluggable(object):
+ """A pluggable backend loaded lazily based on some value."""
+
+ def __init__(self, pivot, **backends):
+ self.__backends = backends
+ self.__pivot = pivot
+ self.__backend = None
+
+ def __get_backend(self):
+ if not self.__backend:
+ backend_name = self.__pivot.value
+ if backend_name not in self.__backends:
+ raise exception.Error('Invalid backend: %s' % backend_name)
+
+ backend = self.__backends[backend_name]
+ if type(backend) == type(tuple()):
+ name = backend[0]
+ fromlist = backend[1]
+ else:
+ name = backend
+ fromlist = backend
+
+ self.__backend = __import__(name, None, None, fromlist)
+ logging.error('backend %s', self.__backend)
+ return self.__backend
+
+ def __getattr__(self, key):
+ backend = self.__get_backend()
+ return getattr(backend, key)
+