diff options
author | andy <github@anarkystic.com> | 2010-08-19 12:28:45 +0200 |
---|---|---|
committer | andy <github@anarkystic.com> | 2010-08-19 12:28:45 +0200 |
commit | a92465922fb74ca2c9b392e1c1b7ed5b5e306a76 (patch) | |
tree | e75cb0fe8a28b30b8292bc3c2207fbcc46a10ef4 | |
parent | f996ec188776ffcae62bcafc1925653a1602880f (diff) | |
download | nova-a92465922fb74ca2c9b392e1c1b7ed5b5e306a76.tar.gz |
Data abstraction for compute service
-rw-r--r-- | nova/compute/service.py | 144 | ||||
-rw-r--r-- | nova/db/__init__.py | 3 | ||||
-rw-r--r-- | nova/db/api.py | 53 | ||||
-rw-r--r-- | nova/db/sqlalchemy/__init__.py | 0 | ||||
-rw-r--r-- | nova/db/sqlalchemy/api.py | 43 | ||||
-rw-r--r-- | nova/models.py | 6 | ||||
-rw-r--r-- | nova/utils.py | 33 |
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) + |