summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorDan Smith <danms@us.ibm.com>2012-10-03 10:46:12 -0700
committerDan Smith <danms@us.ibm.com>2012-11-19 11:41:34 -0800
commit4ea757b8804eb55f8c49f6fcf0c1def7d7390fd8 (patch)
tree569277bc66ab6888df9048b0855c74603e2dd641 /nova
parent9fb877820befcf8eba7a260c0f604d1646e13462 (diff)
Add the beginnings of the nova-conductor service
This adds a new service called "nova-conductor" which will provide services for nova-compute, such as completing database updates and handling long-running tasks. Right now, this just adds a fraction of the database-related work so that we can proceed with incremental changes to nova-compute. For compatibility/flexibility, both LocalAPI and (RPC)API classes are provided. Users concerned with performance or compatibility who wish to maintain compute-node-local database accesses can set the [conductor] use_local=True, which will mimic the legacy behavior. DocImpact Change-Id: Ib81d879f40b7daceeffad5af0631c7db68168661
Diffstat (limited to 'nova')
-rw-r--r--nova/conductor/__init__.py25
-rw-r--r--nova/conductor/api.py61
-rw-r--r--nova/conductor/manager.py51
-rw-r--r--nova/conductor/rpcapi.py43
-rw-r--r--nova/tests/conductor/__init__.py0
-rw-r--r--nova/tests/conductor/test_conductor.py133
6 files changed, 313 insertions, 0 deletions
diff --git a/nova/conductor/__init__.py b/nova/conductor/__init__.py
new file mode 100644
index 000000000..036860dbf
--- /dev/null
+++ b/nova/conductor/__init__.py
@@ -0,0 +1,25 @@
+# Copyright 2012 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from nova.conductor import api as conductor_api
+import nova.config
+import nova.openstack.common.importutils
+
+
+def API(*args, **kwargs):
+ if nova.config.CONF.conductor.use_local:
+ api = conductor_api.LocalAPI
+ else:
+ api = conductor_api.API
+ return api(*args, **kwargs)
diff --git a/nova/conductor/api.py b/nova/conductor/api.py
new file mode 100644
index 000000000..acb412625
--- /dev/null
+++ b/nova/conductor/api.py
@@ -0,0 +1,61 @@
+# Copyright 2012 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Handles all requests to the conductor service"""
+
+from nova.conductor import manager
+from nova.conductor import rpcapi
+from nova import config
+from nova.openstack.common import cfg
+
+conductor_opts = [
+ cfg.BoolOpt('use_local',
+ default=False,
+ help='Perform nova-conductor operations locally'),
+ cfg.StrOpt('topic',
+ default='conductor',
+ help='the topic conductor nodes listen on'),
+ cfg.StrOpt('manager',
+ default='nova.conductor.manager.ConductorManager',
+ help='full class name for the Manager for conductor'),
+]
+conductor_group = cfg.OptGroup(name='conductor',
+ title='Conductor Options')
+CONF = config.CONF
+CONF.register_group(conductor_group)
+CONF.register_opts(conductor_opts, conductor_group)
+
+
+class LocalAPI(object):
+ """A local version of the conductor API that does database updates
+ locally instead of via RPC"""
+
+ def __init__(self):
+ self._manager = manager.ConductorManager()
+
+ def instance_update(self, context, instance_uuid, **updates):
+ """Perform an instance update in the database"""
+ return self._manager.instance_update(context, instance_uuid, updates)
+
+
+class API(object):
+ """Conductor API that does updates via RPC to the ConductorManager"""
+
+ def __init__(self):
+ self.conductor_rpcapi = rpcapi.ConductorAPI()
+
+ def instance_update(self, context, instance_uuid, **updates):
+ """Perform an instance update in the database"""
+ return self.conductor_rpcapi.instance_update(context, instance_uuid,
+ updates)
diff --git a/nova/conductor/manager.py b/nova/conductor/manager.py
new file mode 100644
index 000000000..3ffe82645
--- /dev/null
+++ b/nova/conductor/manager.py
@@ -0,0 +1,51 @@
+# Copyright 2012 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Handles database requests from other nova services"""
+
+from nova import manager
+from nova import notifications
+from nova.openstack.common import jsonutils
+from nova.openstack.common import log as logging
+
+
+LOG = logging.getLogger(__name__)
+
+allowed_updates = ['task_state', 'vm_state', 'expected_task_state',
+ 'power_state', 'access_ip_v4', 'access_ip_v6',
+ 'launched_at', 'terminated_at', 'host',
+ 'memory_mb', 'vcpus', 'root_gb', 'ephemeral_gb',
+ 'instance_type_id',
+ ]
+
+
+class ConductorManager(manager.SchedulerDependentManager):
+ """Mission: TBD"""
+
+ RPC_API_VERSION = '1.0'
+
+ def __init__(self, *args, **kwargs):
+ super(ConductorManager, self).__init__(service_name='conductor',
+ *args, **kwargs)
+
+ def instance_update(self, context, instance_uuid, updates):
+ for key in updates:
+ if key not in allowed_updates:
+ LOG.error(_("Instance update attempted for "
+ "'%(key)s' on %(instance_uuid)s") % locals())
+ raise KeyError("unexpected update keyword '%s'" % key)
+ old_ref, instance_ref = self.db.instance_update_and_get_original(
+ context, instance_uuid, updates)
+ notifications.send_update(context, old_ref, instance_ref)
+ return jsonutils.to_primitive(instance_ref)
diff --git a/nova/conductor/rpcapi.py b/nova/conductor/rpcapi.py
new file mode 100644
index 000000000..7a6508f12
--- /dev/null
+++ b/nova/conductor/rpcapi.py
@@ -0,0 +1,43 @@
+# Copyright 2012 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Client side of the conductor RPC API"""
+
+from nova import config
+import nova.openstack.common.rpc.proxy
+
+CONF = config.CONF
+
+
+class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy):
+ """Client side of the conductor RPC API
+
+ API version history:
+
+ 1.0 - Initial version.
+ """
+
+ BASE_RPC_API_VERSION = '1.0'
+
+ def __init__(self):
+ super(ConductorAPI, self).__init__(
+ topic=CONF.conductor.topic,
+ default_version=self.BASE_RPC_API_VERSION)
+
+ def instance_update(self, context, instance_uuid, updates):
+ return self.call(context,
+ self.make_msg('instance_update',
+ instance_uuid=instance_uuid,
+ updates=updates),
+ topic=self.topic)
diff --git a/nova/tests/conductor/__init__.py b/nova/tests/conductor/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/nova/tests/conductor/__init__.py
diff --git a/nova/tests/conductor/test_conductor.py b/nova/tests/conductor/test_conductor.py
new file mode 100644
index 000000000..fbf7d0325
--- /dev/null
+++ b/nova/tests/conductor/test_conductor.py
@@ -0,0 +1,133 @@
+# Copyright 2012 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Tests for the conductor service"""
+
+from nova.compute import instance_types
+from nova.compute import vm_states
+from nova import conductor
+from nova.conductor import api as conductor_api
+from nova.conductor import manager as conductor_manager
+from nova.conductor import rpcapi as conductor_rpcapi
+from nova import context
+from nova import db
+from nova import notifications
+from nova import test
+
+
+FAKE_IMAGE_REF = 'fake-image-ref'
+
+
+class BaseTestCase(test.TestCase):
+ def setUp(self):
+ super(BaseTestCase, self).setUp()
+ self.user_id = 'fake'
+ self.project_id = 'fake'
+ self.context = context.RequestContext(self.user_id,
+ self.project_id)
+
+ def _create_fake_instance(self, params=None, type_name='m1.tiny'):
+ if not params:
+ params = {}
+
+ inst = {}
+ inst['vm_state'] = vm_states.ACTIVE
+ inst['image_ref'] = FAKE_IMAGE_REF
+ inst['reservation_id'] = 'r-fakeres'
+ inst['launch_time'] = '10'
+ inst['user_id'] = self.user_id
+ inst['project_id'] = self.project_id
+ inst['host'] = 'fake_host'
+ type_id = instance_types.get_instance_type_by_name(type_name)['id']
+ inst['instance_type_id'] = type_id
+ inst['ami_launch_index'] = 0
+ inst['memory_mb'] = 0
+ inst['vcpus'] = 0
+ inst['root_gb'] = 0
+ inst['ephemeral_gb'] = 0
+ inst['architecture'] = 'x86_64'
+ inst['os_type'] = 'Linux'
+ inst.update(params)
+ return db.instance_create(self.context, inst)
+
+
+class ConductorTestCase(BaseTestCase):
+ """Conductor Manager Tests"""
+ def setUp(self):
+ super(ConductorTestCase, self).setUp()
+ self.conductor = conductor_manager.ConductorManager()
+ self.db = None
+
+ def _do_update(self, instance_uuid, **updates):
+ return self.conductor.instance_update(self.context, instance_uuid,
+ updates)
+
+ def test_instance_update(self):
+ instance = self._create_fake_instance()
+ new_inst = self._do_update(instance['uuid'],
+ vm_state=vm_states.STOPPED)
+ instance = db.instance_get_by_uuid(self.context, instance['uuid'])
+ self.assertEqual(instance['vm_state'], vm_states.STOPPED)
+ self.assertEqual(new_inst['vm_state'], instance['vm_state'])
+
+ def test_instance_update_invalid_key(self):
+ # NOTE(danms): the real DB API call ignores invalid keys
+ if self.db == None:
+ self.assertRaises(KeyError,
+ self._do_update, 'any-uuid', foobar=1)
+
+
+class ConductorRPCAPITestCase(ConductorTestCase):
+ """Conductor RPC API Tests"""
+ def setUp(self):
+ super(ConductorRPCAPITestCase, self).setUp()
+ self.conductor_service = self.start_service(
+ 'conductor', manager='nova.conductor.manager.ConductorManager')
+ self.conductor = conductor_rpcapi.ConductorAPI()
+
+
+class ConductorLocalAPITestCase(ConductorTestCase):
+ """Conductor LocalAPI Tests"""
+ def setUp(self):
+ super(ConductorLocalAPITestCase, self).setUp()
+ self.conductor = conductor_api.LocalAPI()
+ self.db = db
+
+ def _do_update(self, instance_uuid, **updates):
+ # NOTE(danms): the public API takes actual keyword arguments,
+ # so override the base class here to make the call correctly
+ return self.conductor.instance_update(self.context, instance_uuid,
+ **updates)
+
+
+class ConductorAPITestCase(ConductorLocalAPITestCase):
+ """Conductor API Tests"""
+ def setUp(self):
+ super(ConductorAPITestCase, self).setUp()
+ self.conductor_service = self.start_service(
+ 'conductor', manager='nova.conductor.manager.ConductorManager')
+ self.conductor = conductor_api.API()
+ self.db = None
+
+
+class ConductorImportTest(test.TestCase):
+ def test_import_conductor_local(self):
+ self.flags(use_local=True, group='conductor')
+ self.assertTrue(isinstance(conductor.API(),
+ conductor_api.LocalAPI))
+
+ def test_import_conductor_rpc(self):
+ self.flags(use_local=False, group='conductor')
+ self.assertTrue(isinstance(conductor.API(),
+ conductor_api.API))