From 9cb6887b5eb8ebc71cb88dfb06953504cc80bf6b Mon Sep 17 00:00:00 2001 From: John Tran Date: Tue, 29 Mar 2011 13:44:38 -0700 Subject: [PATCH 1/9] initial unit test for describe images --- nova/tests/test_cloud.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 00803d0a..791498f8 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -216,6 +216,16 @@ class CloudTestCase(test.TestCase): db.service_destroy(self.context, comp1['id']) db.service_destroy(self.context, comp2['id']) + def test_describe_images(self): + def fake_detail(meh, context): + return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type':'machine'}}] + + self.stubs.Set(local.LocalImageService, 'detail', fake_detail) + result = self.cloud.describe_images(self.context) + result = result['imagesSet'][0] + self.assertEqual(result['imageId'], 'ami-00000001') + def test_console_output(self): instance_type = FLAGS.default_instance_type max_count = 1 From aa3acab0aefe4acfd0e51ad51aff077f14342f86 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Tue, 29 Mar 2011 15:37:32 -0700 Subject: [PATCH 2/9] conversion of properties should set owner as owner_id not owner --- bin/nova-manage | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/nova-manage b/bin/nova-manage index 25695482..6789efba 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -902,7 +902,7 @@ class ImageCommands(object): 'disk_format': disk_format, 'container_format': container_format, 'properties': {'image_state': 'available', - 'owner': owner, + 'owner_id': owner, 'type': image_type, 'architecture': architecture, 'image_location': 'local', @@ -980,7 +980,7 @@ class ImageCommands(object): 'is_public': True, 'name': old['imageId'], 'properties': {'image_state': old['imageState'], - 'owner': old['imageOwnerId'], + 'owner_id': old['imageOwnerId'], 'architecture': old['architecture'], 'type': old['type'], 'image_location': old['imageLocation'], From 3f8297f4f34453045ef426d0660699ff50052b80 Mon Sep 17 00:00:00 2001 From: Anthony Young Date: Tue, 29 Mar 2011 16:13:09 -0700 Subject: [PATCH 3/9] fix for lp742650 --- bin/nova-ajax-console-proxy | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/bin/nova-ajax-console-proxy b/bin/nova-ajax-console-proxy index 0342c620..d88f59e4 100755 --- a/bin/nova-ajax-console-proxy +++ b/bin/nova-ajax-console-proxy @@ -108,17 +108,17 @@ class AjaxConsoleProxy(object): return "Server Error" def register_listeners(self): - class Callback: - def __call__(self, data, message): - if data['method'] == 'authorize_ajax_console': - AjaxConsoleProxy.tokens[data['args']['token']] = \ - {'args': data['args'], 'last_activity': time.time()} + class TopicProxy(): + @staticmethod + def authorize_ajax_console(context, **kwargs): + AjaxConsoleProxy.tokens[kwargs['token']] = \ + {'args': kwargs, 'last_activity': time.time()} conn = rpc.Connection.instance(new=True) consumer = rpc.TopicAdapterConsumer( - connection=conn, - topic=FLAGS.ajax_console_proxy_topic) - consumer.register_callback(Callback()) + connection=conn, + proxy=TopicProxy, + topic=FLAGS.ajax_console_proxy_topic) def delete_expired_tokens(): now = time.time() @@ -130,8 +130,7 @@ class AjaxConsoleProxy(object): for k in to_delete: del AjaxConsoleProxy.tokens[k] - utils.LoopingCall(consumer.fetch, auto_ack=True, - enable_callbacks=True).start(0.1) + utils.LoopingCall(consumer.fetch, enable_callbacks=True).start(0.1) utils.LoopingCall(delete_expired_tokens).start(1) if __name__ == '__main__': From 097c20a8b4cc745d2edd247447355ada025b7443 Mon Sep 17 00:00:00 2001 From: John Tran Date: Tue, 29 Mar 2011 17:07:59 -0700 Subject: [PATCH 4/9] adding unit tests for describe_images --- Authors | 1 + nova/tests/test_cloud.py | 31 ++++++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Authors b/Authors index eccf38a4..48b91218 100644 --- a/Authors +++ b/Authors @@ -32,6 +32,7 @@ Jesse Andrews Joe Heck Joel Moore John Dewey +John Tran Jonathan Bryce Jordan Rinke Josh Durgin diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 791498f8..5cb96997 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -41,6 +41,7 @@ from nova.compute import power_state from nova.api.ec2 import cloud from nova.api.ec2 import ec2utils from nova.image import local +from nova.exception import NotFound FLAGS = flags.FLAGS @@ -71,7 +72,8 @@ class CloudTestCase(test.TestCase): host = self.network.get_network_host(self.context.elevated()) def fake_show(meh, context, id): - return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}} + return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, + 'type': 'machine'}} self.stubs.Set(local.LocalImageService, 'show', fake_show) self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show) @@ -217,14 +219,33 @@ class CloudTestCase(test.TestCase): db.service_destroy(self.context, comp2['id']) def test_describe_images(self): + describe_images = self.cloud.describe_images + def fake_detail(meh, context): return [{'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1, - 'type':'machine'}}] + 'type': 'machine'}}] + + def fake_show_none(meh, context, id): + raise NotFound self.stubs.Set(local.LocalImageService, 'detail', fake_detail) - result = self.cloud.describe_images(self.context) - result = result['imagesSet'][0] - self.assertEqual(result['imageId'], 'ami-00000001') + # list all + result1 = describe_images(self.context) + result1 = result1['imagesSet'][0] + self.assertEqual(result1['imageId'], 'ami-00000001') + # provided a valid image_id + result2 = describe_images(self.context, ['ami-00000001']) + self.assertEqual(1, len(result2['imagesSet'])) + # provide more than 1 valid image_id + result3 = describe_images(self.context, ['ami-00000001', + 'ami-00000002']) + self.assertEqual(2, len(result3['imagesSet'])) + # provide an non-existing image_id + self.stubs.UnsetAll() + self.stubs.Set(local.LocalImageService, 'show', fake_show_none) + self.stubs.Set(local.LocalImageService, 'show_by_name', fake_show_none) + self.assertRaises(NotFound, describe_images, + self.context, ['ami-fake']) def test_console_output(self): instance_type = FLAGS.default_instance_type From 6d4c6bfc06df25e63be1b746b096d51c74c4ef90 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 30 Mar 2011 08:29:17 -0700 Subject: [PATCH 5/9] queues properly reconnect if rabbitmq is restarted --- nova/rpc.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/nova/rpc.py b/nova/rpc.py index 388f78d6..be7cb3f3 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -74,7 +74,12 @@ class Connection(carrot_connection.BrokerConnection): """Recreates the connection instance This is necessary to recover from some network errors/disconnects""" - del cls._instance + try: + del cls._instance + except AttributeError, e: + # The _instance stuff is for testing purposes. Usually we don't use + # it. So don't freak out if it doesn't exist. + pass return cls.instance() @@ -125,10 +130,12 @@ class Consumer(messaging.Consumer): # NOTE(vish): This is catching all errors because we really don't # want exceptions to be logged 10 times a second if some # persistent failure occurs. - except Exception: # pylint: disable=W0703 + except Exception, e: # pylint: disable=W0703 if not self.failed_connection: - LOG.exception(_("Failed to fetch message from queue")) + LOG.exception(_("Failed to fetch message from queue: %s" % e)) self.failed_connection = True + else: + LOG.exception(_("Unhandled exception %s" % e)) def attach_to_eventlet(self): """Only needed for unit tests!""" From 33e4eb18cad59ca02666652957d4d48725868383 Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Wed, 30 Mar 2011 20:33:56 -0700 Subject: [PATCH 6/9] Removed adminclient and referred to pypi nova_adminclient module --- nova/adminclient.py | 473 -------------------------------------------- 1 file changed, 473 deletions(-) delete mode 100644 nova/adminclient.py diff --git a/nova/adminclient.py b/nova/adminclient.py deleted file mode 100644 index f570e12c..00000000 --- a/nova/adminclient.py +++ /dev/null @@ -1,473 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# 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. -""" -Nova User API client library. -""" - -import base64 -import boto -import boto.exception -import httplib -import re -import string - -from boto.ec2.regioninfo import RegionInfo - - -DEFAULT_CLC_URL = 'http://127.0.0.1:8773' -DEFAULT_REGION = 'nova' - - -class UserInfo(object): - """ - Information about a Nova user, as parsed through SAX. - - **Fields Include** - - * username - * accesskey - * secretkey - * file (optional) containing zip of X509 cert & rc file - - """ - - def __init__(self, connection=None, username=None, endpoint=None): - self.connection = connection - self.username = username - self.endpoint = endpoint - - def __repr__(self): - return 'UserInfo:%s' % self.username - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'username': - self.username = str(value) - elif name == 'file': - self.file = base64.b64decode(str(value)) - elif name == 'accesskey': - self.accesskey = str(value) - elif name == 'secretkey': - self.secretkey = str(value) - - -class UserRole(object): - """ - Information about a Nova user's role, as parsed through SAX. - - **Fields include** - - * role - - """ - - def __init__(self, connection=None): - self.connection = connection - self.role = None - - def __repr__(self): - return 'UserRole:%s' % self.role - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'role': - self.role = value - else: - setattr(self, name, str(value)) - - -class ProjectInfo(object): - """ - Information about a Nova project, as parsed through SAX. - - **Fields include** - - * projectname - * description - * projectManagerId - * memberIds - - """ - - def __init__(self, connection=None): - self.connection = connection - self.projectname = None - self.description = None - self.projectManagerId = None - self.memberIds = [] - - def __repr__(self): - return 'ProjectInfo:%s' % self.projectname - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'projectname': - self.projectname = value - elif name == 'description': - self.description = value - elif name == 'projectManagerId': - self.projectManagerId = value - elif name == 'memberId': - self.memberIds.append(value) - else: - setattr(self, name, str(value)) - - -class ProjectMember(object): - """ - Information about a Nova project member, as parsed through SAX. - - **Fields include** - - * memberId - - """ - - def __init__(self, connection=None): - self.connection = connection - self.memberId = None - - def __repr__(self): - return 'ProjectMember:%s' % self.memberId - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == 'member': - self.memberId = value - else: - setattr(self, name, str(value)) - - -class HostInfo(object): - """ - Information about a Nova Host, as parsed through SAX. - - **Fields Include** - - * Hostname - * Compute service status - * Volume service status - * Instance count - * Volume count - """ - - def __init__(self, connection=None): - self.connection = connection - self.hostname = None - self.compute = None - self.volume = None - self.instance_count = 0 - self.volume_count = 0 - - def __repr__(self): - return 'Host:%s' % self.hostname - - # this is needed by the sax parser, so ignore the ugly name - def startElement(self, name, attrs, connection): - return None - - # this is needed by the sax parser, so ignore the ugly name - def endElement(self, name, value, connection): - fixed_name = string.lower(re.sub(r'([A-Z])', r'_\1', name)) - setattr(self, fixed_name, value) - - -class Vpn(object): - """ - Information about a Vpn, as parsed through SAX - - **Fields Include** - - * instance_id - * project_id - * public_ip - * public_port - * created_at - * internal_ip - * state - """ - - def __init__(self, connection=None): - self.connection = connection - self.instance_id = None - self.project_id = None - - def __repr__(self): - return 'Vpn:%s:%s' % (self.project_id, self.instance_id) - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - fixed_name = string.lower(re.sub(r'([A-Z])', r'_\1', name)) - setattr(self, fixed_name, value) - - -class InstanceType(object): - """ - Information about a Nova instance type, as parsed through SAX. - - **Fields include** - - * name - * vcpus - * disk_gb - * memory_mb - * flavor_id - - """ - - def __init__(self, connection=None): - self.connection = connection - self.name = None - self.vcpus = None - self.disk_gb = None - self.memory_mb = None - self.flavor_id = None - - def __repr__(self): - return 'InstanceType:%s' % self.name - - def startElement(self, name, attrs, connection): - return None - - def endElement(self, name, value, connection): - if name == "memoryMb": - self.memory_mb = str(value) - elif name == "flavorId": - self.flavor_id = str(value) - elif name == "diskGb": - self.disk_gb = str(value) - else: - setattr(self, name, str(value)) - - -class NovaAdminClient(object): - - def __init__( - self, - clc_url=DEFAULT_CLC_URL, - region=DEFAULT_REGION, - access_key=None, - secret_key=None, - **kwargs): - parts = self.split_clc_url(clc_url) - - self.clc_url = clc_url - self.region = region - self.access = access_key - self.secret = secret_key - self.apiconn = boto.connect_ec2(aws_access_key_id=access_key, - aws_secret_access_key=secret_key, - is_secure=parts['is_secure'], - region=RegionInfo(None, - region, - parts['ip']), - port=parts['port'], - path='/services/Admin', - **kwargs) - self.apiconn.APIVersion = 'nova' - - def connection_for(self, username, project, clc_url=None, region=None, - **kwargs): - """Returns a boto ec2 connection for the given username.""" - if not clc_url: - clc_url = self.clc_url - if not region: - region = self.region - parts = self.split_clc_url(clc_url) - user = self.get_user(username) - access_key = '%s:%s' % (user.accesskey, project) - return boto.connect_ec2(aws_access_key_id=access_key, - aws_secret_access_key=user.secretkey, - is_secure=parts['is_secure'], - region=RegionInfo(None, - self.region, - parts['ip']), - port=parts['port'], - path='/services/Cloud', - **kwargs) - - def split_clc_url(self, clc_url): - """Splits a cloud controller endpoint url.""" - parts = httplib.urlsplit(clc_url) - is_secure = parts.scheme == 'https' - ip, port = parts.netloc.split(':') - return {'ip': ip, 'port': int(port), 'is_secure': is_secure} - - def get_users(self): - """Grabs the list of all users.""" - return self.apiconn.get_list('DescribeUsers', {}, [('item', UserInfo)]) - - def get_user(self, name): - """Grab a single user by name.""" - user = self.apiconn.get_object('DescribeUser', - {'Name': name}, - UserInfo) - if user.username != None: - return user - - def has_user(self, username): - """Determine if user exists.""" - return self.get_user(username) != None - - def create_user(self, username): - """Creates a new user, returning the userinfo object with - access/secret.""" - return self.apiconn.get_object('RegisterUser', {'Name': username}, - UserInfo) - - def delete_user(self, username): - """Deletes a user.""" - return self.apiconn.get_object('DeregisterUser', {'Name': username}, - UserInfo) - - def get_roles(self, project_roles=True): - """Returns a list of available roles.""" - return self.apiconn.get_list('DescribeRoles', - {'ProjectRoles': project_roles}, - [('item', UserRole)]) - - def get_user_roles(self, user, project=None): - """Returns a list of roles for the given user. - - Omitting project will return any global roles that the user has. - Specifying project will return only project specific roles. - - """ - params = {'User': user} - if project: - params['Project'] = project - return self.apiconn.get_list('DescribeUserRoles', - params, - [('item', UserRole)]) - - def add_user_role(self, user, role, project=None): - """Add a role to a user either globally or for a specific project.""" - return self.modify_user_role(user, role, project=project, - operation='add') - - def remove_user_role(self, user, role, project=None): - """Remove a role from a user either globally or for a specific - project.""" - return self.modify_user_role(user, role, project=project, - operation='remove') - - def modify_user_role(self, user, role, project=None, operation='add', - **kwargs): - """Add or remove a role for a user and project.""" - params = {'User': user, - 'Role': role, - 'Project': project, - 'Operation': operation} - return self.apiconn.get_status('ModifyUserRole', params) - - def get_projects(self, user=None): - """Returns a list of all projects.""" - if user: - params = {'User': user} - else: - params = {} - return self.apiconn.get_list('DescribeProjects', - params, - [('item', ProjectInfo)]) - - def get_project(self, name): - """Returns a single project with the specified name.""" - project = self.apiconn.get_object('DescribeProject', - {'Name': name}, - ProjectInfo) - - if project.projectname != None: - return project - - def create_project(self, projectname, manager_user, description=None, - member_users=None): - """Creates a new project.""" - params = {'Name': projectname, - 'ManagerUser': manager_user, - 'Description': description, - 'MemberUsers': member_users} - return self.apiconn.get_object('RegisterProject', params, ProjectInfo) - - def modify_project(self, projectname, manager_user=None, description=None): - """Modifies an existing project.""" - params = {'Name': projectname, - 'ManagerUser': manager_user, - 'Description': description} - return self.apiconn.get_status('ModifyProject', params) - - def delete_project(self, projectname): - """Permanently deletes the specified project.""" - return self.apiconn.get_object('DeregisterProject', - {'Name': projectname}, - ProjectInfo) - - def get_project_members(self, name): - """Returns a list of members of a project.""" - return self.apiconn.get_list('DescribeProjectMembers', - {'Name': name}, - [('item', ProjectMember)]) - - def add_project_member(self, user, project): - """Adds a user to a project.""" - return self.modify_project_member(user, project, operation='add') - - def remove_project_member(self, user, project): - """Removes a user from a project.""" - return self.modify_project_member(user, project, operation='remove') - - def modify_project_member(self, user, project, operation='add'): - """Adds or removes a user from a project.""" - params = {'User': user, - 'Project': project, - 'Operation': operation} - return self.apiconn.get_status('ModifyProjectMember', params) - - def get_zip(self, user, project): - """Returns the content of a zip file containing novarc and access - credentials.""" - params = {'Name': user, 'Project': project} - zip = self.apiconn.get_object('GenerateX509ForUser', params, UserInfo) - return zip.file - - def start_vpn(self, project): - """ - Starts the vpn for a user - """ - return self.apiconn.get_object('StartVpn', {'Project': project}, Vpn) - - def get_vpns(self): - """Return a list of vpn with project name""" - return self.apiconn.get_list('DescribeVpns', {}, [('item', Vpn)]) - - def get_hosts(self): - return self.apiconn.get_list('DescribeHosts', {}, [('item', HostInfo)]) - - def get_instance_types(self): - """Grabs the list of all users.""" - return self.apiconn.get_list('DescribeInstanceTypes', {}, - [('item', InstanceType)]) From 13b3d587ba1144465a6b066bdfb4f5b2e1988483 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 31 Mar 2011 14:29:16 -0500 Subject: [PATCH 8/9] Review feedback --- nova/scheduler/simple.py | 12 +++++++++--- nova/scheduler/zone.py | 5 ++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/nova/scheduler/simple.py b/nova/scheduler/simple.py index 0191ceb3..dd568d2c 100644 --- a/nova/scheduler/simple.py +++ b/nova/scheduler/simple.py @@ -72,7 +72,9 @@ class SimpleScheduler(chance.ChanceScheduler): {'host': service['host'], 'scheduled_at': now}) return service['host'] - raise driver.NoValidHost(_("No hosts found")) + raise driver.NoValidHost(_("Scheduler was unable to locate a host" + " for this request. Is the appropriate" + " service running?")) def schedule_create_volume(self, context, volume_id, *_args, **_kwargs): """Picks a host that is up and has the fewest volumes.""" @@ -107,7 +109,9 @@ class SimpleScheduler(chance.ChanceScheduler): {'host': service['host'], 'scheduled_at': now}) return service['host'] - raise driver.NoValidHost(_("No hosts found")) + raise driver.NoValidHost(_("Scheduler was unable to locate a host" + " for this request. Is the appropriate" + " service running?")) def schedule_set_network_host(self, context, *_args, **_kwargs): """Picks a host that is up and has the fewest networks.""" @@ -119,4 +123,6 @@ class SimpleScheduler(chance.ChanceScheduler): raise driver.NoValidHost(_("All hosts have too many networks")) if self.service_is_up(service): return service['host'] - raise driver.NoValidHost(_("No hosts found")) + raise driver.NoValidHost(_("Scheduler was unable to locate a host" + " for this request. Is the appropriate" + " service running?")) diff --git a/nova/scheduler/zone.py b/nova/scheduler/zone.py index 49786cd3..44d5a166 100644 --- a/nova/scheduler/zone.py +++ b/nova/scheduler/zone.py @@ -52,5 +52,8 @@ class ZoneScheduler(driver.Scheduler): zone = _kwargs.get('availability_zone') hosts = self.hosts_up_with_zone(context, topic, zone) if not hosts: - raise driver.NoValidHost(_("No hosts found")) + raise driver.NoValidHost(_("Scheduler was unable to locate a host" + " for this request. Is the appropriate" + " service running?")) + return hosts[int(random.random() * len(hosts))] From 4b12fd2088df27b4f7138ef20915f0e6fcbba842 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 31 Mar 2011 12:31:35 -0700 Subject: [PATCH 9/9] removes excessive logging on rabbitmq failure --- nova/rpc.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/rpc.py b/nova/rpc.py index be7cb3f3..b610cdf9 100644 --- a/nova/rpc.py +++ b/nova/rpc.py @@ -134,8 +134,6 @@ class Consumer(messaging.Consumer): if not self.failed_connection: LOG.exception(_("Failed to fetch message from queue: %s" % e)) self.failed_connection = True - else: - LOG.exception(_("Unhandled exception %s" % e)) def attach_to_eventlet(self): """Only needed for unit tests!"""