summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorMark Washenberger <mark.washenberger@rackspace.com>2011-03-10 11:18:05 -0500
committerMark Washenberger <mark.washenberger@rackspace.com>2011-03-10 11:18:05 -0500
commitb3951f4c2a2473814f0dd1a90f950bef418457ca (patch)
tree95282149647b927160914dd6c42b2f302ad2342e /nova
parent61756210fe55edcb2e04275fc2326b5d153651e6 (diff)
parentacbc0f597f6183fc856f82b07392ddd4a61393f7 (diff)
downloadnova-b3951f4c2a2473814f0dd1a90f950bef418457ca.tar.gz
nova-b3951f4c2a2473814f0dd1a90f950bef418457ca.tar.xz
nova-b3951f4c2a2473814f0dd1a90f950bef418457ca.zip
merge lp:nova
Diffstat (limited to 'nova')
-rw-r--r--nova/api/openstack/__init__.py6
-rw-r--r--nova/api/openstack/zones.py24
-rw-r--r--nova/flags.py4
-rw-r--r--nova/image/s3.py31
-rw-r--r--nova/network/linux_net.py44
-rw-r--r--nova/objectstore/image.py33
-rw-r--r--nova/scheduler/api.py49
-rw-r--r--nova/scheduler/manager.py10
-rw-r--r--nova/scheduler/zone_manager.py143
-rw-r--r--nova/tests/api/openstack/test_zones.py52
-rw-r--r--nova/tests/test_zones.py172
-rw-r--r--nova/virt/disk.py2
-rw-r--r--nova/virt/libvirt_conn.py2
13 files changed, 504 insertions, 68 deletions
diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py
index 197fcc619..ab9dbb780 100644
--- a/nova/api/openstack/__init__.py
+++ b/nova/api/openstack/__init__.py
@@ -77,8 +77,8 @@ class APIRouter(wsgi.Router):
server_members['pause'] = 'POST'
server_members['unpause'] = 'POST'
- server_members["diagnostics"] = "GET"
- server_members["actions"] = "GET"
+ server_members['diagnostics'] = 'GET'
+ server_members['actions'] = 'GET'
server_members['suspend'] = 'POST'
server_members['resume'] = 'POST'
server_members['rescue'] = 'POST'
@@ -87,7 +87,7 @@ class APIRouter(wsgi.Router):
server_members['inject_network_info'] = 'POST'
mapper.resource("zone", "zones", controller=zones.Controller(),
- collection={'detail': 'GET'})
+ collection={'detail': 'GET', 'info': 'GET'}),
mapper.resource("server", "servers", controller=servers.Controller(),
collection={'detail': 'GET'},
diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py
index cf6cd789f..8fe84275a 100644
--- a/nova/api/openstack/zones.py
+++ b/nova/api/openstack/zones.py
@@ -1,4 +1,4 @@
-# Copyright 2010 OpenStack LLC.
+# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -18,6 +18,7 @@ import common
from nova import flags
from nova import wsgi
from nova import db
+from nova.scheduler import api
FLAGS = flags.FLAGS
@@ -32,6 +33,10 @@ def _filter_keys(item, keys):
return dict((k, v) for k, v in item.iteritems() if k in keys)
+def _exclude_keys(item, keys):
+ return dict((k, v) for k, v in item.iteritems() if k not in keys)
+
+
def _scrub_zone(zone):
return _filter_keys(zone, ('id', 'api_url'))
@@ -41,19 +46,30 @@ class Controller(wsgi.Controller):
_serialization_metadata = {
'application/xml': {
"attributes": {
- "zone": ["id", "api_url"]}}}
+ "zone": ["id", "api_url", "name", "capabilities"]}}}
def index(self, req):
"""Return all zones in brief"""
- items = db.zone_get_all(req.environ['nova.context'])
+ # Ask the ZoneManager in the Scheduler for most recent data,
+ # or fall-back to the database ...
+ items = api.API().get_zone_list(req.environ['nova.context'])
+ if not items:
+ items = db.zone_get_all(req.environ['nova.context'])
+
items = common.limited(items, req)
- items = [_scrub_zone(item) for item in items]
+ items = [_exclude_keys(item, ['username', 'password'])
+ for item in items]
return dict(zones=items)
def detail(self, req):
"""Return all zones in detail"""
return self.index(req)
+ def info(self, req):
+ """Return name and capabilities for this zone."""
+ return dict(zone=dict(name=FLAGS.zone_name,
+ capabilities=FLAGS.zone_capabilities))
+
def show(self, req, id):
"""Return data about the given zone id"""
zone_id = int(id)
diff --git a/nova/flags.py b/nova/flags.py
index 62cbf30ca..9123e9ac7 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -356,3 +356,7 @@ DEFINE_string('host', socket.gethostname(),
DEFINE_string('node_availability_zone', 'nova',
'availability zone of this node')
+
+DEFINE_string('zone_name', 'nova', 'name of this zone')
+DEFINE_string('zone_capabilities', 'kypervisor:xenserver;os:linux',
+ 'Key/Value tags which represent capabilities of this zone')
diff --git a/nova/image/s3.py b/nova/image/s3.py
index bbc54c263..85a2c651c 100644
--- a/nova/image/s3.py
+++ b/nova/image/s3.py
@@ -236,25 +236,32 @@ class S3ImageService(service.BaseImageService):
@staticmethod
def _decrypt_image(encrypted_filename, encrypted_key, encrypted_iv,
cloud_private_key, decrypted_filename):
- key, err = utils.execute(
- 'openssl rsautl -decrypt -inkey %s' % cloud_private_key,
- process_input=encrypted_key,
- check_exit_code=False)
+ key, err = utils.execute('openssl',
+ 'rsautl',
+ '-decrypt',
+ '-inkey', '%s' % cloud_private_key,
+ process_input=encrypted_key,
+ check_exit_code=False)
if err:
raise exception.Error(_("Failed to decrypt private key: %s")
% err)
- iv, err = utils.execute(
- 'openssl rsautl -decrypt -inkey %s' % cloud_private_key,
- process_input=encrypted_iv,
- check_exit_code=False)
+ iv, err = utils.execute('openssl',
+ 'rsautl',
+ '-decrypt',
+ '-inkey', '%s' % cloud_private_key,
+ process_input=encrypted_iv,
+ check_exit_code=False)
if err:
raise exception.Error(_("Failed to decrypt initialization "
"vector: %s") % err)
- _out, err = utils.execute(
- 'openssl enc -d -aes-128-cbc -in %s -K %s -iv %s -out %s'
- % (encrypted_filename, key, iv, decrypted_filename),
- check_exit_code=False)
+ _out, err = utils.execute('openssl', 'enc',
+ '-d', '-aes-128-cbc',
+ '-in', '%s' % (encrypted_filename,),
+ '-K', '%s' % (key,),
+ '-iv', '%s' % (iv,),
+ '-out', '%s' % (decrypted_filename,),
+ check_exit_code=False)
if err:
raise exception.Error(_("Failed to decrypt image file "
"%(image_file)s: %(err)s") %
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index 228a4d9ea..c0bd76adf 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -216,7 +216,7 @@ def ensure_bridge(bridge, interface, net_attrs=None):
_execute('sudo', 'brctl', 'setfd', bridge, 0)
# _execute("sudo brctl setageing %s 10" % bridge)
_execute('sudo', 'brctl', 'stp', bridge, 'off')
- _execute('sudo', 'ip', 'link', 'set', bridge, up)
+ _execute('sudo', 'ip', 'link', 'set', bridge, 'up')
if net_attrs:
# NOTE(vish): The ip for dnsmasq has to be the first address on the
# bridge for it to respond to reqests properly
@@ -330,7 +330,7 @@ def update_dhcp(context, network_id):
env = {'FLAGFILE': FLAGS.dhcpbridge_flagfile,
'DNSMASQ_INTERFACE': network_ref['bridge']}
command = _dnsmasq_cmd(network_ref)
- _execute(command, addl_env=env)
+ _execute(*command, addl_env=env)
def update_ra(context, network_id):
@@ -370,7 +370,7 @@ interface %s
else:
LOG.debug(_("Pid %d is stale, relaunching radvd"), pid)
command = _ra_cmd(network_ref)
- _execute(command)
+ _execute(*command)
db.network_update(context, network_id,
{"ra_server":
utils.get_my_linklocal(network_ref['bridge'])})
@@ -424,30 +424,30 @@ def _remove_rule(chain, *cmd):
def _dnsmasq_cmd(net):
"""Builds dnsmasq command"""
- cmd = ['sudo -E dnsmasq',
- ' --strict-order',
- ' --bind-interfaces',
- ' --conf-file=',
- ' --domain=%s' % FLAGS.dhcp_domain,
- ' --pid-file=%s' % _dhcp_file(net['bridge'], 'pid'),
- ' --listen-address=%s' % net['gateway'],
- ' --except-interface=lo',
- ' --dhcp-range=%s,static,120s' % net['dhcp_start'],
- ' --dhcp-hostsfile=%s' % _dhcp_file(net['bridge'], 'conf'),
- ' --dhcp-script=%s' % FLAGS.dhcpbridge,
- ' --leasefile-ro']
+ cmd = ['sudo', '-E', 'dnsmasq',
+ '--strict-order',
+ '--bind-interfaces',
+ '--conf-file=',
+ '--domain=%s' % FLAGS.dhcp_domain,
+ '--pid-file=%s' % _dhcp_file(net['bridge'], 'pid'),
+ '--listen-address=%s' % net['gateway'],
+ '--except-interface=lo',
+ '--dhcp-range=%s,static,120s' % net['dhcp_start'],
+ '--dhcp-hostsfile=%s' % _dhcp_file(net['bridge'], 'conf'),
+ '--dhcp-script=%s' % FLAGS.dhcpbridge,
+ '--leasefile-ro']
if FLAGS.dns_server:
- cmd.append(' -h -R --server=%s' % FLAGS.dns_server)
- return ''.join(cmd)
+ cmd += ['-h', '-R', '--server=%s' % FLAGS.dns_server]
+ return cmd
def _ra_cmd(net):
"""Builds radvd command"""
- cmd = ['sudo -E radvd',
-# ' -u nobody',
- ' -C %s' % _ra_file(net['bridge'], 'conf'),
- ' -p %s' % _ra_file(net['bridge'], 'pid')]
- return ''.join(cmd)
+ cmd = ['sudo', '-E', 'radvd',
+# '-u', 'nobody',
+ '-C', '%s' % _ra_file(net['bridge'], 'conf'),
+ '-p', '%s' % _ra_file(net['bridge'], 'pid')]
+ return cmd
def _stop_dnsmasq(network):
diff --git a/nova/objectstore/image.py b/nova/objectstore/image.py
index 8013cbd9c..c90b5b54b 100644
--- a/nova/objectstore/image.py
+++ b/nova/objectstore/image.py
@@ -253,25 +253,34 @@ class Image(object):
@staticmethod
def decrypt_image(encrypted_filename, encrypted_key, encrypted_iv,
cloud_private_key, decrypted_filename):
- key, err = utils.execute(
- 'openssl rsautl -decrypt -inkey %s' % cloud_private_key,
- process_input=encrypted_key,
- check_exit_code=False)
+ key, err = utils.execute('openssl',
+ 'rsautl',
+ '-decrypt',
+ '-inkey', '%s' % cloud_private_key,
+ process_input=encrypted_key,
+ check_exit_code=False)
if err:
raise exception.Error(_("Failed to decrypt private key: %s")
% err)
- iv, err = utils.execute(
- 'openssl rsautl -decrypt -inkey %s' % cloud_private_key,
- process_input=encrypted_iv,
- check_exit_code=False)
+ iv, err = utils.execute('openssl',
+ 'rsautl',
+ '-decrypt',
+ '-inkey', '%s' % cloud_private_key,
+ process_input=encrypted_iv,
+ check_exit_code=False)
if err:
raise exception.Error(_("Failed to decrypt initialization "
"vector: %s") % err)
- _out, err = utils.execute(
- 'openssl enc -d -aes-128-cbc -in %s -K %s -iv %s -out %s'
- % (encrypted_filename, key, iv, decrypted_filename),
- check_exit_code=False)
+ _out, err = utils.execute('openssl',
+ 'enc',
+ '-d',
+ '-aes-128-cbc',
+ '-in', '%s' % (encrypted_filename,),
+ '-K', '%s' % (key,),
+ '-iv', '%s' % (iv,),
+ '-out', '%s' % (decrypted_filename,),
+ check_exit_code=False)
if err:
raise exception.Error(_("Failed to decrypt image file "
"%(image_file)s: %(err)s") %
diff --git a/nova/scheduler/api.py b/nova/scheduler/api.py
new file mode 100644
index 000000000..2405f1343
--- /dev/null
+++ b/nova/scheduler/api.py
@@ -0,0 +1,49 @@
+# Copyright (c) 2011 Openstack, LLC.
+# All Rights Reserved.
+#
+# 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 relating to schedulers.
+"""
+
+from nova import flags
+from nova import log as logging
+from nova import rpc
+
+FLAGS = flags.FLAGS
+LOG = logging.getLogger('nova.scheduler.api')
+
+
+class API(object):
+ """API for interacting with the scheduler."""
+
+ def _call_scheduler(self, method, context, params=None):
+ """Generic handler for RPC calls to the scheduler.
+
+ :param params: Optional dictionary of arguments to be passed to the
+ scheduler worker
+
+ :retval: Result returned by scheduler worker
+ """
+ if not params:
+ params = {}
+ queue = FLAGS.scheduler_topic
+ kwargs = {'method': method, 'args': params}
+ return rpc.call(context, queue, kwargs)
+
+ def get_zone_list(self, context):
+ items = self._call_scheduler('get_zone_list', context)
+ for item in items:
+ item['api_url'] = item['api_url'].replace('\\/', '/')
+ return items
diff --git a/nova/scheduler/manager.py b/nova/scheduler/manager.py
index e9b47512e..c94397210 100644
--- a/nova/scheduler/manager.py
+++ b/nova/scheduler/manager.py
@@ -29,6 +29,7 @@ from nova import log as logging
from nova import manager
from nova import rpc
from nova import utils
+from nova.scheduler import zone_manager
LOG = logging.getLogger('nova.scheduler.manager')
FLAGS = flags.FLAGS
@@ -43,12 +44,21 @@ class SchedulerManager(manager.Manager):
if not scheduler_driver:
scheduler_driver = FLAGS.scheduler_driver
self.driver = utils.import_object(scheduler_driver)
+ self.zone_manager = zone_manager.ZoneManager()
super(SchedulerManager, self).__init__(*args, **kwargs)
def __getattr__(self, key):
"""Converts all method calls to use the schedule method"""
return functools.partial(self._schedule, key)
+ def periodic_tasks(self, context=None):
+ """Poll child zones periodically to get status."""
+ self.zone_manager.ping(context)
+
+ def get_zone_list(self, context=None):
+ """Get a list of zones from the ZoneManager."""
+ return self.zone_manager.get_zone_list()
+
def _schedule(self, method, context, topic, *args, **kwargs):
"""Tries to call schedule_* method on the driver to retrieve host.
diff --git a/nova/scheduler/zone_manager.py b/nova/scheduler/zone_manager.py
new file mode 100644
index 000000000..edf9000cc
--- /dev/null
+++ b/nova/scheduler/zone_manager.py
@@ -0,0 +1,143 @@
+# Copyright (c) 2011 Openstack, LLC.
+# All Rights Reserved.
+#
+# 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.
+
+"""
+ZoneManager oversees all communications with child Zones.
+"""
+
+import novaclient
+import thread
+import traceback
+
+from datetime import datetime
+from eventlet import greenpool
+
+from nova import db
+from nova import flags
+from nova import log as logging
+
+FLAGS = flags.FLAGS
+flags.DEFINE_integer('zone_db_check_interval', 60,
+ 'Seconds between getting fresh zone info from db.')
+flags.DEFINE_integer('zone_failures_to_offline', 3,
+ 'Number of consecutive errors before marking zone offline')
+
+
+class ZoneState(object):
+ """Holds the state of all connected child zones."""
+ def __init__(self):
+ self.is_active = True
+ self.name = None
+ self.capabilities = None
+ self.attempt = 0
+ self.last_seen = datetime.min
+ self.last_exception = None
+ self.last_exception_time = None
+
+ def update_credentials(self, zone):
+ """Update zone credentials from db"""
+ self.zone_id = zone.id
+ self.api_url = zone.api_url
+ self.username = zone.username
+ self.password = zone.password
+
+ def update_metadata(self, zone_metadata):
+ """Update zone metadata after successful communications with
+ child zone."""
+ self.last_seen = datetime.now()
+ self.attempt = 0
+ self.name = zone_metadata["name"]
+ self.capabilities = zone_metadata["capabilities"]
+ self.is_active = True
+
+ def to_dict(self):
+ return dict(name=self.name, capabilities=self.capabilities,
+ is_active=self.is_active, api_url=self.api_url,
+ id=self.zone_id)
+
+ def log_error(self, exception):
+ """Something went wrong. Check to see if zone should be
+ marked as offline."""
+ self.last_exception = exception
+ self.last_exception_time = datetime.now()
+ api_url = self.api_url
+ logging.warning(_("'%(exception)s' error talking to "
+ "zone %(api_url)s") % locals())
+
+ max_errors = FLAGS.zone_failures_to_offline
+ self.attempt += 1
+ if self.attempt >= max_errors:
+ self.is_active = False
+ logging.error(_("No answer from zone %(api_url)s "
+ "after %(max_errors)d "
+ "attempts. Marking inactive.") % locals())
+
+
+def _call_novaclient(zone):
+ """Call novaclient. Broken out for testing purposes."""
+ client = novaclient.OpenStack(zone.username, zone.password, zone.api_url)
+ return client.zones.info()._info
+
+
+def _poll_zone(zone):
+ """Eventlet worker to poll a zone."""
+ logging.debug(_("Polling zone: %s") % zone.api_url)
+ try:
+ zone.update_metadata(_call_novaclient(zone))
+ except Exception, e:
+ zone.log_error(traceback.format_exc())
+
+
+class ZoneManager(object):
+ """Keeps the zone states updated."""
+ def __init__(self):
+ self.last_zone_db_check = datetime.min
+ self.zone_states = {}
+ self.green_pool = greenpool.GreenPool()
+
+ def get_zone_list(self):
+ """Return the list of zones we know about."""
+ return [zone.to_dict() for zone in self.zone_states.values()]
+
+ def _refresh_from_db(self, context):
+ """Make our zone state map match the db."""
+ # Add/update existing zones ...
+ zones = db.zone_get_all(context)
+ existing = self.zone_states.keys()
+ db_keys = []
+ for zone in zones:
+ db_keys.append(zone.id)
+ if zone.id not in existing:
+ self.zone_states[zone.id] = ZoneState()
+ self.zone_states[zone.id].update_credentials(zone)
+
+ # Cleanup zones removed from db ...
+ keys = self.zone_states.keys() # since we're deleting
+ for zone_id in keys:
+ if zone_id not in db_keys:
+ del self.zone_states[zone_id]
+
+ def _poll_zones(self, context):
+ """Try to connect to each child zone and get update."""
+ self.green_pool.imap(_poll_zone, self.zone_states.values())
+
+ def ping(self, context=None):
+ """Ping should be called periodically to update zone status."""
+ diff = datetime.now() - self.last_zone_db_check
+ if diff.seconds >= FLAGS.zone_db_check_interval:
+ logging.debug(_("Updating zone cache from db."))
+ self.last_zone_db_check = datetime.now()
+ self._refresh_from_db(context)
+ self._poll_zones(context)
diff --git a/nova/tests/api/openstack/test_zones.py b/nova/tests/api/openstack/test_zones.py
index d0da8eaaf..4f4fabf12 100644
--- a/nova/tests/api/openstack/test_zones.py
+++ b/nova/tests/api/openstack/test_zones.py
@@ -1,4 +1,4 @@
-# Copyright 2010 OpenStack LLC.
+# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -24,6 +24,7 @@ from nova import flags
from nova import test
from nova.api.openstack import zones
from nova.tests.api.openstack import fakes
+from nova.scheduler import api
FLAGS = flags.FLAGS
@@ -31,7 +32,7 @@ FLAGS.verbose = True
def zone_get(context, zone_id):
- return dict(id=1, api_url='http://foo.com', username='bob',
+ return dict(id=1, api_url='http://example.com', username='bob',
password='xxx')
@@ -42,7 +43,7 @@ def zone_create(context, values):
def zone_update(context, zone_id, values):
- zone = dict(id=zone_id, api_url='http://foo.com', username='bob',
+ zone = dict(id=zone_id, api_url='http://example.com', username='bob',
password='xxx')
zone.update(values)
return zone
@@ -52,12 +53,26 @@ def zone_delete(context, zone_id):
pass
-def zone_get_all(context):
+def zone_get_all_scheduler(*args):
return [
- dict(id=1, api_url='http://foo.com', username='bob',
+ dict(id=1, api_url='http://example.com', username='bob',
password='xxx'),
- dict(id=2, api_url='http://blah.com', username='alice',
- password='qwerty')]
+ dict(id=2, api_url='http://example.org', username='alice',
+ password='qwerty')
+ ]
+
+
+def zone_get_all_scheduler_empty(*args):
+ return []
+
+
+def zone_get_all_db(context):
+ return [
+ dict(id=1, api_url='http://example.com', username='bob',
+ password='xxx'),
+ dict(id=2, api_url='http://example.org', username='alice',
+ password='qwerty')
+ ]
class ZonesTest(test.TestCase):
@@ -74,7 +89,6 @@ class ZonesTest(test.TestCase):
FLAGS.allow_admin_api = True
self.stubs.Set(nova.db, 'zone_get', zone_get)
- self.stubs.Set(nova.db, 'zone_get_all', zone_get_all)
self.stubs.Set(nova.db, 'zone_update', zone_update)
self.stubs.Set(nova.db, 'zone_create', zone_create)
self.stubs.Set(nova.db, 'zone_delete', zone_delete)
@@ -84,7 +98,19 @@ class ZonesTest(test.TestCase):
FLAGS.allow_admin_api = self.allow_admin
super(ZonesTest, self).tearDown()
- def test_get_zone_list(self):
+ def test_get_zone_list_scheduler(self):
+ self.stubs.Set(api.API, '_call_scheduler', zone_get_all_scheduler)
+ req = webob.Request.blank('/v1.0/zones')
+ res = req.get_response(fakes.wsgi_app())
+ res_dict = json.loads(res.body)
+
+ self.assertEqual(res.status_int, 200)
+ self.assertEqual(len(res_dict['zones']), 2)
+
+ def test_get_zone_list_db(self):
+ self.stubs.Set(api.API, '_call_scheduler',
+ zone_get_all_scheduler_empty)
+ self.stubs.Set(nova.db, 'zone_get_all', zone_get_all_db)
req = webob.Request.blank('/v1.0/zones')
req.headers["Content-Type"] = "application/json"
res = req.get_response(fakes.wsgi_app())
@@ -101,7 +127,7 @@ class ZonesTest(test.TestCase):
self.assertEqual(res.status_int, 200)
res_dict = json.loads(res.body)
self.assertEqual(res_dict['zone']['id'], 1)
- self.assertEqual(res_dict['zone']['api_url'], 'http://foo.com')
+ self.assertEqual(res_dict['zone']['api_url'], 'http://example.com')
self.assertFalse('password' in res_dict['zone'])
def test_zone_delete(self):
@@ -112,7 +138,7 @@ class ZonesTest(test.TestCase):
self.assertEqual(res.status_int, 200)
def test_zone_create(self):
- body = dict(zone=dict(api_url='http://blah.zoo', username='fred',
+ body = dict(zone=dict(api_url='http://example.com', username='fred',
password='fubar'))
req = webob.Request.blank('/v1.0/zones')
req.headers["Content-Type"] = "application/json"
@@ -124,7 +150,7 @@ class ZonesTest(test.TestCase):
self.assertEqual(res.status_int, 200)
res_dict = json.loads(res.body)
self.assertEqual(res_dict['zone']['id'], 1)
- self.assertEqual(res_dict['zone']['api_url'], 'http://blah.zoo')
+ self.assertEqual(res_dict['zone']['api_url'], 'http://example.com')
self.assertFalse('username' in res_dict['zone'])
def test_zone_update(self):
@@ -139,5 +165,5 @@ class ZonesTest(test.TestCase):
self.assertEqual(res.status_int, 200)
res_dict = json.loads(res.body)
self.assertEqual(res_dict['zone']['id'], 1)
- self.assertEqual(res_dict['zone']['api_url'], 'http://foo.com')
+ self.assertEqual(res_dict['zone']['api_url'], 'http://example.com')
self.assertFalse('username' in res_dict['zone'])
diff --git a/nova/tests/test_zones.py b/nova/tests/test_zones.py
new file mode 100644
index 000000000..5a52a0506
--- /dev/null
+++ b/nova/tests/test_zones.py
@@ -0,0 +1,172 @@
+# Copyright 2010 United States Government as represented by the
+# All Rights Reserved.
+#
+# 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 ZoneManager
+"""
+
+import datetime
+import mox
+import novaclient
+
+from nova import context
+from nova import db
+from nova import flags
+from nova import service
+from nova import test
+from nova import rpc
+from nova import utils
+from nova.auth import manager as auth_manager
+from nova.scheduler import zone_manager
+
+FLAGS = flags.FLAGS
+
+
+class FakeZone:
+ """Represents a fake zone from the db"""
+ def __init__(self, *args, **kwargs):
+ for k, v in kwargs.iteritems():
+ setattr(self, k, v)
+
+
+def exploding_novaclient(zone):
+ """Used when we want to simulate a novaclient call failing."""
+ raise Exception("kaboom")
+
+
+class ZoneManagerTestCase(test.TestCase):
+ """Test case for zone manager"""
+ def test_ping(self):
+ zm = zone_manager.ZoneManager()
+ self.mox.StubOutWithMock(zm, '_refresh_from_db')
+ self.mox.StubOutWithMock(zm, '_poll_zones')
+ zm._refresh_from_db(mox.IgnoreArg())
+ zm._poll_zones(mox.IgnoreArg())
+
+ self.mox.ReplayAll()
+ zm.ping(None)
+ self.mox.VerifyAll()
+
+ def test_refresh_from_db_new(self):
+ zm = zone_manager.ZoneManager()
+
+ self.mox.StubOutWithMock(db, 'zone_get_all')
+ db.zone_get_all(mox.IgnoreArg()).AndReturn([
+ FakeZone(id=1, api_url='http://foo.com', username='user1',
+ password='pass1'),
+ ])
+
+ self.assertEquals(len(zm.zone_states), 0)
+
+ self.mox.ReplayAll()
+ zm._refresh_from_db(None)
+ self.mox.VerifyAll()
+
+ self.assertEquals(len(zm.zone_states), 1)
+ self.assertEquals(zm.zone_states[1].username, 'user1')
+
+ def test_refresh_from_db_replace_existing(self):
+ zm = zone_manager.ZoneManager()
+ zone_state = zone_manager.ZoneState()
+ zone_state.update_credentials(FakeZone(id=1, api_url='http://foo.com',
+ username='user1', password='pass1'))
+ zm.zone_states[1] = zone_state
+
+ self.mox.StubOutWithMock(db, 'zone_get_all')
+ db.zone_get_all(mox.IgnoreArg()).AndReturn([
+ FakeZone(id=1, api_url='http://foo.com', username='user2',
+ password='pass2'),
+ ])
+
+ self.assertEquals(len(zm.zone_states), 1)
+
+ self.mox.ReplayAll()
+ zm._refresh_from_db(None)
+ self.mox.VerifyAll()
+
+ self.assertEquals(len(zm.zone_states), 1)
+ self.assertEquals(zm.zone_states[1].username, 'user2')
+
+ def test_refresh_from_db_missing(self):
+ zm = zone_manager.ZoneManager()
+ zone_state = zone_manager.ZoneState()
+ zone_state.update_credentials(FakeZone(id=1, api_url='http://foo.com',
+ username='user1', password='pass1'))
+ zm.zone_states[1] = zone_state
+
+ self.mox.StubOutWithMock(db, 'zone_get_all')
+ db.zone_get_all(mox.IgnoreArg()).AndReturn([])
+
+ self.assertEquals(len(zm.zone_states), 1)
+
+ self.mox.ReplayAll()
+ zm._refresh_from_db(None)
+ self.mox.VerifyAll()
+
+ self.assertEquals(len(zm.zone_states), 0)
+
+ def test_refresh_from_db_add_and_delete(self):
+ zm = zone_manager.ZoneManager()
+ zone_state = zone_manager.ZoneState()
+ zone_state.update_credentials(FakeZone(id=1, api_url='http://foo.com',
+ username='user1', password='pass1'))
+ zm.zone_states[1] = zone_state
+
+ self.mox.StubOutWithMock(db, 'zone_get_all')
+
+ db.zone_get_all(mox.IgnoreArg()).AndReturn([
+ FakeZone(id=2, api_url='http://foo.com', username='user2',
+ password='pass2'),
+ ])
+ self.assertEquals(len(zm.zone_states), 1)
+
+ self.mox.ReplayAll()
+ zm._refresh_from_db(None)
+ self.mox.VerifyAll()
+
+ self.assertEquals(len(zm.zone_states), 1)
+ self.assertEquals(zm.zone_states[2].username, 'user2')
+
+ def test_poll_zone(self):
+ self.mox.StubOutWithMock(zone_manager, '_call_novaclient')
+ zone_manager._call_novaclient(mox.IgnoreArg()).AndReturn(
+ dict(name='zohan', capabilities='hairdresser'))
+
+ zone_state = zone_manager.ZoneState()
+ zone_state.update_credentials(FakeZone(id=2,
+ api_url='http://foo.com', username='user2',
+ password='pass2'))
+ zone_state.attempt = 1
+
+ self.mox.ReplayAll()
+ zone_manager._poll_zone(zone_state)
+ self.mox.VerifyAll()
+ self.assertEquals(zone_state.attempt, 0)
+ self.assertEquals(zone_state.name, 'zohan')
+
+ def test_poll_zone_fails(self):
+ self.stubs.Set(zone_manager, "_call_novaclient", exploding_novaclient)
+
+ zone_state = zone_manager.ZoneState()
+ zone_state.update_credentials(FakeZone(id=2,
+ api_url='http://foo.com', username='user2',
+ password='pass2'))
+ zone_state.attempt = FLAGS.zone_failures_to_offline - 1
+
+ self.mox.ReplayAll()
+ zone_manager._poll_zone(zone_state)
+ self.mox.VerifyAll()
+ self.assertEquals(zone_state.attempt, 3)
+ self.assertFalse(zone_state.is_active)
+ self.assertEquals(zone_state.name, None)
diff --git a/nova/virt/disk.py b/nova/virt/disk.py
index a54cda003..5d499c42c 100644
--- a/nova/virt/disk.py
+++ b/nova/virt/disk.py
@@ -51,7 +51,7 @@ def extend(image, size):
return
utils.execute('truncate', '-s', size, image)
# NOTE(vish): attempts to resize filesystem
- utils.execute('e2fsck', '-fp', mage, check_exit_code=False)
+ utils.execute('e2fsck', '-fp', image, check_exit_code=False)
utils.execute('resize2fs', image, check_exit_code=False)
diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py
index e25e4af4f..44b07213a 100644
--- a/nova/virt/libvirt_conn.py
+++ b/nova/virt/libvirt_conn.py
@@ -463,7 +463,7 @@ class LibvirtConnection(object):
console_log = os.path.join(FLAGS.instances_path, instance['name'],
'console.log')
- utils.execute('sudo', 'chown', s.getuid(), console_log)
+ utils.execute('sudo', 'chown', os.getuid(), console_log)
if FLAGS.libvirt_type == 'xen':
# Xen is special