From b2557962b5a365d78346fef727b2fcee75fe3270 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Thu, 30 Dec 2010 21:27:44 +0000 Subject: Changing DN creation to do searches for entries. This change adds additional interoperability (as many directory servers and LDAP admins use cn, or another attribute, as the naming attribute). DN creation will incur a slight performance penalty for doing so, as DNs must be searched for now. User and project creation skip this performance penalty, as there is no need to search for an entry that is being created. --- nova/auth/ldapdriver.py | 92 ++++++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 39 deletions(-) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index 7616ff112..e139be926 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -117,8 +117,7 @@ class LdapDriver(object): def get_project(self, pid): """Retrieve project by id""" - dn = 'cn=%s,%s' % (pid, - FLAGS.ldap_project_subtree) + dn = self.__project_to_dn(pid) attr = self.__find_object(dn, LdapDriver.project_pattern) return self.__to_project(attr) @@ -226,7 +225,8 @@ class LdapDriver(object): ('description', [description]), (LdapDriver.project_attribute, [manager_dn]), ('member', members)] - self.conn.add_s('cn=%s,%s' % (name, FLAGS.ldap_project_subtree), attr) + dn = self.__project_to_dn(name, search=False) + self.conn.add_s(dn, attr) return self.__to_project(dict(attr)) def modify_project(self, project_id, manager_uid=None, description=None): @@ -244,23 +244,22 @@ class LdapDriver(object): manager_dn)) if description: attr.append((self.ldap.MOD_REPLACE, 'description', description)) - self.conn.modify_s('cn=%s,%s' % (project_id, - FLAGS.ldap_project_subtree), - attr) + dn = self.__project_to_dn(project_id) + self.conn.modify_s(dn, attr) def add_to_project(self, uid, project_id): """Add user to project""" - dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) + dn = self.__project_to_dn(project_id) return self.__add_to_group(uid, dn) def remove_from_project(self, uid, project_id): """Remove user from project""" - dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) + dn = self.__project_to_dn(project_id) return self.__remove_from_group(uid, dn) def is_in_project(self, uid, project_id): """Check if user is in project""" - dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) + dn = self.__project_to_dn(project_id) return self.__is_in_group(uid, dn) def has_role(self, uid, role, project_id=None): @@ -300,7 +299,7 @@ class LdapDriver(object): roles.append(role) return roles else: - project_dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) + project_dn = self.__project_to_dn(project_id) query = ('(&(&(objectclass=groupOfNames)(!%s))(member=%s))' % (LdapDriver.project_pattern, self.__uid_to_dn(uid))) roles = self.__find_objects(project_dn, query) @@ -333,7 +332,7 @@ class LdapDriver(object): def delete_project(self, project_id): """Delete a project""" - project_dn = 'cn=%s,%s' % (project_id, FLAGS.ldap_project_subtree) + project_dn = self.__project_to_dn(project_id) self.__delete_roles(project_dn) self.__delete_group(project_dn) @@ -365,9 +364,10 @@ class LdapDriver(object): def __get_ldap_user(self, uid): """Retrieve LDAP user entry by id""" - attr = self.__find_object(self.__uid_to_dn(uid), - '(objectclass=novaUser)') - return attr + dn = FLAGS.ldap_user_subtree + query = ('(&(%s=%s)(objectclass=novaUser))' % + (FLAGS.ldap_user_id_attribute, uid)) + return self.__find_object(dn, query) def __find_object(self, dn, query=None, scope=None): """Find an object by dn and query""" @@ -418,15 +418,13 @@ class LdapDriver(object): query = '(objectclass=groupOfNames)' return self.__find_object(dn, query) is not None - @staticmethod - def __role_to_dn(role, project_id=None): + def __role_to_dn(self, role, project_id=None): """Convert role to corresponding dn""" if project_id is None: return FLAGS.__getitem__("ldap_%s" % role).value else: - return 'cn=%s,cn=%s,%s' % (role, - project_id, - FLAGS.ldap_project_subtree) + project_dn = self.__project_to_dn(project_id) + return 'cn=%s,%s' % (role, project_dn) def __create_group(self, group_dn, name, uid, description, member_uids=None): @@ -532,6 +530,42 @@ class LdapDriver(object): for role_dn in self.__find_role_dns(project_dn): self.__delete_group(role_dn) + def __to_project(self, attr): + """Convert ldap attributes to Project object""" + if attr is None: + return None + member_dns = attr.get('member', []) + return { + 'id': attr['cn'][0], + 'name': attr['cn'][0], + 'project_manager_id': + self.__dn_to_uid(attr[LdapDriver.project_attribute][0]), + 'description': attr.get('description', [None])[0], + 'member_ids': [self.__dn_to_uid(x) for x in member_dns]} + + def __uid_to_dn(self, uid, search=True): + """Convert uid to dn""" + # By default return a generated DN + userdn = (FLAGS.ldap_user_id_attribute + '=%s,%s' + % (uid, FLAGS.ldap_user_subtree)) + if search: + query = ('%s=%s' % (FLAGS.ldap_user_id_attribute, uid)) + user = self.__find_objects(FLAGS.ldap_user_subtree, query) + if len(user) > 0: + userdn = user['dn'] + return userdn + + def __project_to_dn(self, pid, search=True): + """Convert pid to dn""" + # By default return a generated DN + projectdn = ('cn=%s,%s' % (pid, FLAGS.ldap_project_subtree)) + if search: + query = ('(&(cn=%s)%s))' % (pid, LdapDriver.project_pattern)) + project = self.__find_objects(FLAGS.ldap_project_subtree, query) + if len(project) > 0: + projectdn = project['dn'] + return projectdn + @staticmethod def __to_user(attr): """Convert ldap attributes to User object""" @@ -548,31 +582,11 @@ class LdapDriver(object): else: return None - def __to_project(self, attr): - """Convert ldap attributes to Project object""" - if attr is None: - return None - member_dns = attr.get('member', []) - return { - 'id': attr['cn'][0], - 'name': attr['cn'][0], - 'project_manager_id': - self.__dn_to_uid(attr[LdapDriver.project_attribute][0]), - 'description': attr.get('description', [None])[0], - 'member_ids': [self.__dn_to_uid(x) for x in member_dns]} - @staticmethod def __dn_to_uid(dn): """Convert user dn to uid""" return dn.split(',')[0].split('=')[1] - @staticmethod - def __uid_to_dn(uid): - """Convert uid to dn""" - return (FLAGS.ldap_user_id_attribute + '=%s,%s' - % (uid, FLAGS.ldap_user_subtree)) - - class FakeLdapDriver(LdapDriver): """Fake Ldap Auth driver""" -- cgit From 58940b1d62456543f0ebf4dca96055556624927e Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Thu, 30 Dec 2010 23:44:49 +0000 Subject: * Fix bad query in __project_to_dn * use __find_dns instead of __find_objects in __uid_to_dn and __project_to_dn --- nova/auth/ldapdriver.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index e139be926..3f6045986 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -550,9 +550,9 @@ class LdapDriver(object): % (uid, FLAGS.ldap_user_subtree)) if search: query = ('%s=%s' % (FLAGS.ldap_user_id_attribute, uid)) - user = self.__find_objects(FLAGS.ldap_user_subtree, query) + user = self.__find_dns(FLAGS.ldap_user_subtree, query) if len(user) > 0: - userdn = user['dn'] + userdn = user[0] return userdn def __project_to_dn(self, pid, search=True): @@ -560,10 +560,10 @@ class LdapDriver(object): # By default return a generated DN projectdn = ('cn=%s,%s' % (pid, FLAGS.ldap_project_subtree)) if search: - query = ('(&(cn=%s)%s))' % (pid, LdapDriver.project_pattern)) - project = self.__find_objects(FLAGS.ldap_project_subtree, query) + query = ('(&(cn=%s)%s)' % (pid, LdapDriver.project_pattern)) + project = self.__find_dns(FLAGS.ldap_project_subtree, query) if len(project) > 0: - projectdn = project['dn'] + projectdn = project[0] return projectdn @staticmethod -- cgit From e80e8d95c136526167dcb8fc92fb3a584c6b0027 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Thu, 30 Dec 2010 23:48:27 +0000 Subject: PEP8 fix --- nova/auth/ldapdriver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/auth/ldapdriver.py b/nova/auth/ldapdriver.py index 3f6045986..466c8c04b 100644 --- a/nova/auth/ldapdriver.py +++ b/nova/auth/ldapdriver.py @@ -587,6 +587,7 @@ class LdapDriver(object): """Convert user dn to uid""" return dn.split(',')[0].split('=')[1] + class FakeLdapDriver(LdapDriver): """Fake Ldap Auth driver""" -- cgit From f4a2d86519434f934bd6c90ba401b08875420d19 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 3 Jan 2011 05:17:42 -0800 Subject: temp --- bin/nova-combined | 4 ++-- nova/api/__init__.py | 3 +-- nova/api/openstack/__init__.py | 6 +++++- nova/api/openstack/backup_schedules.py | 9 +++++++-- nova/api/openstack/common.py | 16 ++++++++++++++++ nova/api/openstack/images.py | 19 ++++++++++++++++++- nova/api/openstack/servers.py | 27 ++++++++++++++++++++++++--- nova/api/openstack/sharedipgroups.py | 10 +++++++--- nova/compute/api.py | 1 + nova/virt/xenapi/vm_utils.py | 3 ++- 10 files changed, 83 insertions(+), 15 deletions(-) diff --git a/bin/nova-combined b/bin/nova-combined index 53322f1a0..4de17915f 100755 --- a/bin/nova-combined +++ b/bin/nova-combined @@ -56,11 +56,11 @@ if __name__ == '__main__': compute = service.Service.create(binary='nova-compute') network = service.Service.create(binary='nova-network') - volume = service.Service.create(binary='nova-volume') + #volume = service.Service.create(binary='nova-volume') scheduler = service.Service.create(binary='nova-scheduler') #objectstore = service.Service.create(binary='nova-objectstore') - service.serve(compute, network, volume, scheduler) + service.serve(compute, network, scheduler) server = wsgi.Server() server.start(api.API('os'), FLAGS.osapi_port, host=FLAGS.osapi_host) diff --git a/nova/api/__init__.py b/nova/api/__init__.py index 26fed847b..7a783df86 100644 --- a/nova/api/__init__.py +++ b/nova/api/__init__.py @@ -59,8 +59,7 @@ class API(wsgi.Router): mapper.connect("/", controller=self.osapi_versions, conditions=osapi_subdomain) - mapper.connect("/v1.0/{path_info:.*}", controller=openstack.API(), - conditions=osapi_subdomain) + mapper.connect("/v1.0/{path_info:.*}", controller=openstack.API()) mapper.connect("/", controller=self.ec2api_versions, conditions=ec2api_subdomain) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index a1430caed..8a8d90354 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -51,6 +51,10 @@ flags.DEFINE_string('os_api_ratelimiting', 'nova.api.openstack.ratelimiting.RateLimitingMiddleware', 'Default ratelimiting implementation for the Openstack API') +flags.DEFINE_string('os_krm_mapping_file', + 'krm_mapping.json', + 'Location of Flavor/OS -> Kernel/Ramdisk/Machine JSON mapping file.') + flags.DEFINE_bool('allow_admin_api', False, 'When True, this API service will accept admin operations.') @@ -109,7 +113,7 @@ class APIRouter(wsgi.Router): collection={'detail': 'GET'}) mapper.resource("flavor", "flavors", controller=flavors.Controller(), collection={'detail': 'GET'}) - mapper.resource("sharedipgroup", "sharedipgroups", + mapper.resource("shared_ip_group", "shared_ip_groups", controller=sharedipgroups.Controller()) super(APIRouter, self).__init__(mapper) diff --git a/nova/api/openstack/backup_schedules.py b/nova/api/openstack/backup_schedules.py index fcc07bdd3..29a7df4cb 100644 --- a/nova/api/openstack/backup_schedules.py +++ b/nova/api/openstack/backup_schedules.py @@ -15,7 +15,9 @@ # License for the specific language governing permissions and limitations # under the License. +import logging import time + from webob import exc from nova import wsgi @@ -41,13 +43,16 @@ class Controller(wsgi.Controller): def index(self, req, server_id): """ Returns the list of backup schedules for a given instance """ + logging.debug("INDEX") return _translate_keys({}) def create(self, req, server_id): """ No actual update method required, since the existing API allows both create and update through a POST """ - return faults.Fault(exc.HTTPNotFound()) + logging.debug("CREATE") + return faults.Fault(exc.HTTPNotImplemented()) def delete(self, req, server_id, id): """ Deletes an existing backup schedule """ - return faults.Fault(exc.HTTPNotFound()) + logging.debug("DELETE") + return faults.Fault(exc.HTTPNotImplemented()) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index ac0572c96..907adfcbc 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -15,6 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +from nova import exception + def limited(items, req): """Return a slice of items according to requested offset and limit. @@ -34,3 +36,17 @@ def limited(items, req): limit = min(1000, limit) range_end = offset + limit return items[offset:range_end] + + +def get_image_id_from_image_hash(image_service, context, image_hash): + try: + items = image_service.detail(context) + except NotImplementedError: + items = image_service.index(context) + for image in items: + image_id = image['imageId'] + if abs(hash(image_id)) == int(image_hash): + return image_id + raise exception.NotFound(image_hash) + + diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 867ee5a7e..52dc65b08 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -15,6 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +import logging + from webob import exc from nova import flags @@ -30,6 +32,9 @@ from nova.compute import api as compute_api FLAGS = flags.FLAGS +logging.basicConfig(filename='api.log', level=logging.DEBUG) + + def _translate_keys(item): """ Maps key names to Rackspace-like attributes for return @@ -113,6 +118,11 @@ class Controller(wsgi.Controller): items = self._service.detail(req.environ['nova.context']) except NotImplementedError: items = self._service.index(req.environ['nova.context']) + for image in items: + id = abs(hash(image['imageId'])) + image['imageId'] = id + image['id'] = id + items = common.limited(items, req) items = [_translate_keys(item) for item in items] items = [_translate_status(item) for item in items] @@ -120,7 +130,14 @@ class Controller(wsgi.Controller): def show(self, req, id): """Return data about the given image id""" - return dict(image=self._service.show(req.environ['nova.context'], id)) + image_id = common.get_image_id_from_image_hash(self._service, + req.environ['nova.context'], id) + + image = self._service.show(req.environ['nova.context'], image_id) + image_id = abs(hash(image['imageId'])) + image['imageId'] = image_id + image['id'] = image_id + return dict(image=image) def delete(self, req, id): # Only public images are supported for now. diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index c5cbe21ef..a1565184c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -15,13 +15,16 @@ # License for the specific language governing permissions and limitations # under the License. +import json import logging import traceback from webob import exc from nova import exception +from nova import flags from nova import wsgi +from nova import utils from nova.api.openstack import common from nova.api.openstack import faults from nova.auth import manager as auth_manager @@ -31,8 +34,7 @@ from nova.compute import power_state import nova.api.openstack -LOG = logging.getLogger('server') -LOG.setLevel(logging.DEBUG) +FLAGS = flags.FLAGS def _translate_detail_keys(inst): @@ -81,6 +83,7 @@ class Controller(wsgi.Controller): def __init__(self): self.compute_api = compute_api.ComputeAPI() + self._image_service = utils.import_object(FLAGS.image_service) super(Controller, self).__init__() def index(self, req): @@ -120,6 +123,18 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPNotFound()) return exc.HTTPAccepted() + def _get_kernel_ramdisk_from_image(self, image_id): + mapping_filename = FLAGS.os_krm_mapping_file + + with open(mapping_filename) as f: + mapping = json.load(f) + if mapping.has_key(image_id): + return mapping[image_id] + + raise exception.NotFound( + _("No entry for image '%s' in mapping file '%s'") % + (image_id, mapping_filename)) + def create(self, req): """ Creates a new server for a given user """ env = self._deserialize(req.body, req) @@ -128,10 +143,15 @@ class Controller(wsgi.Controller): key_pair = auth_manager.AuthManager.get_key_pairs( req.environ['nova.context'])[0] + image_id = common.get_image_id_from_image_hash(self._image_service, + req.environ['nova.context'], env['server']['imageId']) + kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image(image_id) instances = self.compute_api.create_instances( req.environ['nova.context'], instance_types.get_by_flavor_id(env['server']['flavorId']), - env['server']['imageId'], + image_id, + kernel_id = kernel_id, + ramdisk_id = ramdisk_id, display_name=env['server']['name'], description=env['server']['name'], key_name=key_pair['name'], @@ -163,6 +183,7 @@ class Controller(wsgi.Controller): """ Multi-purpose method used to reboot, rebuild, and resize a server """ input_dict = self._deserialize(req.body, req) + logging.debug(_("Compute.api::action %s"), input_dict) try: reboot_type = input_dict['reboot']['type'] except Exception: diff --git a/nova/api/openstack/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py index 845f5bead..65595c8ff 100644 --- a/nova/api/openstack/sharedipgroups.py +++ b/nova/api/openstack/sharedipgroups.py @@ -15,6 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +import logging + from webob import exc from nova import wsgi @@ -29,7 +31,7 @@ def _translate_keys(inst): def _translate_detail_keys(inst): """ Coerces a shared IP group instance into proper dictionary format with correctly mapped attributes """ - return dict(sharedIpGroup=inst) + return dict(sharedIpGroups=inst) class Controller(wsgi.Controller): @@ -46,6 +48,8 @@ class Controller(wsgi.Controller): def show(self, req, id): """ Shows in-depth information on a specific Shared IP Group """ + if id == 'detail': + return _translate_detail_keys({}) return _translate_keys({}) def update(self, req, id): @@ -54,7 +58,7 @@ class Controller(wsgi.Controller): def delete(self, req, id): """ Deletes a Shared IP Group """ - raise faults.Fault(exc.HTTPNotFound()) + raise faults.Fault(exc.HTTPNotImplemented()) def detail(self, req, id): """ Returns a complete list of Shared IP Groups """ @@ -62,4 +66,4 @@ class Controller(wsgi.Controller): def create(self, req): """ Creates a new Shared IP group """ - raise faults.Fault(exc.HTTPNotFound()) + raise faults.Fault(exc.HTTPNotImplemented()) diff --git a/nova/compute/api.py b/nova/compute/api.py index 07c69bd31..be188190d 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -102,6 +102,7 @@ class ComputeAPI(base.Base): ramdisk_id = None logging.debug("Creating a raw instance") # Make sure we have access to kernel and ramdisk (if not raw) + logging.debug("KERNEL=%s, RAMDISK=%s" % (kernel_id, ramdisk_id)) if kernel_id: self.image_service.show(context, kernel_id) if ramdisk_id: diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 9d1b51848..8d344d84c 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -360,7 +360,8 @@ class VMHelper(HelperBase): if i >= 3 and i <= 11: ref = node.childNodes # Name and Value - diags[ref[0].firstChild.data] = ref[6].firstChild.data + if len(ref) > 6: + diags[ref[0].firstChild.data] = ref[6].firstChild.data return diags except cls.XenAPI.Failure as e: return {"Unable to retrieve diagnostics": e} -- cgit From 16e5c095619d6ea3fd493ecf3e349d8bad0c3eae Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 3 Jan 2011 17:06:50 -0800 Subject: added cloudserver vars to novarc template --- nova/auth/manager.py | 14 +++++++++----- nova/auth/novarc.template | 4 ++++ nova/flags.py | 2 ++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/nova/auth/manager.py b/nova/auth/manager.py index d3e266952..8806d9ba2 100644 --- a/nova/auth/manager.py +++ b/nova/auth/manager.py @@ -645,8 +645,7 @@ class AuthManager(object): else: regions = {'nova': FLAGS.cc_host} for region, host in regions.iteritems(): - rc = self.__generate_rc(user.access, - user.secret, + rc = self.__generate_rc(user, pid, use_dmz, host) @@ -686,7 +685,7 @@ class AuthManager(object): return self.__generate_rc(user.access, user.secret, pid, use_dmz) @staticmethod - def __generate_rc(access, secret, pid, use_dmz=True, host=None): + def __generate_rc(user, pid, use_dmz=True, host=None): """Generate rc file for user""" if use_dmz: cc_host = FLAGS.cc_dmz @@ -699,14 +698,19 @@ class AuthManager(object): s3_host = host cc_host = host rc = open(FLAGS.credentials_template).read() - rc = rc % {'access': access, + rc = rc % {'access': user.access, 'project': pid, - 'secret': secret, + 'secret': user.secret, 'ec2': '%s://%s:%s%s' % (FLAGS.ec2_prefix, cc_host, FLAGS.cc_port, FLAGS.ec2_suffix), 's3': 'http://%s:%s' % (s3_host, FLAGS.s3_port), + 'os': '%s://%s:%s%s' % (FLAGS.os_prefix, + cc_host, + FLAGS.cc_port, + FLAGS.os_suffix), + 'user': user.name, 'nova': FLAGS.ca_file, 'cert': FLAGS.credential_cert_file, 'key': FLAGS.credential_key_file} diff --git a/nova/auth/novarc.template b/nova/auth/novarc.template index 1b8ecb173..c53a4acdc 100644 --- a/nova/auth/novarc.template +++ b/nova/auth/novarc.template @@ -10,3 +10,7 @@ export NOVA_CERT=${NOVA_KEY_DIR}/%(nova)s export EUCALYPTUS_CERT=${NOVA_CERT} # euca-bundle-image seems to require this set alias ec2-bundle-image="ec2-bundle-image --cert ${EC2_CERT} --privatekey ${EC2_PRIVATE_KEY} --user 42 --ec2cert ${NOVA_CERT}" alias ec2-upload-bundle="ec2-upload-bundle -a ${EC2_ACCESS_KEY} -s ${EC2_SECRET_KEY} --url ${S3_URL} --ec2cert ${NOVA_CERT}" +export CLOUD_SERVERS_API_KEY="%(access)s" +export CLOUD_SERVERS_USERNAME="%(user)s" +export CLOUD_SERVERS_URL="%(os)s" + diff --git a/nova/flags.py b/nova/flags.py index 4b7334927..13e152feb 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -236,10 +236,12 @@ DEFINE_integer('rabbit_retry_interval', 10, 'rabbit connection retry interval') DEFINE_integer('rabbit_max_retries', 12, 'rabbit connection attempts') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') DEFINE_string('ec2_prefix', 'http', 'prefix for ec2') +DEFINE_string('os_prefix', 'http', 'prefix for openstack') DEFINE_string('cc_host', utils.get_my_ip(), 'ip of api server') DEFINE_string('cc_dmz', utils.get_my_ip(), 'internal ip of api server') DEFINE_integer('cc_port', 8773, 'cloud controller port') DEFINE_string('ec2_suffix', '/services/Cloud', 'suffix for ec2') +DEFINE_string('os_suffix', '/v1.0/', 'suffix for openstack') DEFINE_string('default_project', 'openstack', 'default project for openstack') DEFINE_string('default_image', 'ami-11111', -- cgit From 1097e32645dad68b89507d6ac7704c1db626723b Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 3 Jan 2011 19:10:33 -0800 Subject: dabo fix to update for password reset --- nova/compute/api.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index edc8c0b4a..fa9925627 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -204,7 +204,7 @@ class ComputeAPI(base.Base): """Updates the instance in the datastore. :param context: The security context - :param instance_id: ID of the instance to update + :param instance_id: internal_id of the instance to update :param kwargs: All additional keyword args are treated as data fields of the instance to be updated @@ -212,7 +212,8 @@ class ComputeAPI(base.Base): :retval None """ - return self.db.instance_update(context, instance_id, kwargs) + instance = self.get_instance(context, instance_id) + return self.db.instance_update(context, instance.id, kwargs) def delete_instance(self, context, instance_id): logging.debug("Going to try and terminate %d" % instance_id) -- cgit From 09b1844c012504e5b14d05e6178e35dfeb0c6579 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 3 Jan 2011 19:28:54 -0800 Subject: krm_mapping.json sample file added --- krm_mapping.json.sample | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 krm_mapping.json.sample diff --git a/krm_mapping.json.sample b/krm_mapping.json.sample new file mode 100644 index 000000000..046013803 --- /dev/null +++ b/krm_mapping.json.sample @@ -0,0 +1,3 @@ +{ + "machine" : ["kernel", "ramdisk"], +} -- cgit From 75618ce6379cb01b9f78ddb7c2f26501b838ca71 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 3 Jan 2011 19:32:25 -0800 Subject: dabo fix to update for password reset v2 --- nova/api/openstack/servers.py | 6 +++--- nova/compute/api.py | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 35c3324ab..a5de62230 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -176,9 +176,9 @@ class Controller(wsgi.Controller): try: ctxt = req.environ['nova.context'] - self.compute_api.update_instance(ctxt, - id, - **update_dict) + # The ID passed in is actually the internal_id of the + # instance, not the value of the id column in the DB. + self.compute_api.update_instance(ctxt, instance.id, **update_dict) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) return exc.HTTPNoContent() diff --git a/nova/compute/api.py b/nova/compute/api.py index fa9925627..edc8c0b4a 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -204,7 +204,7 @@ class ComputeAPI(base.Base): """Updates the instance in the datastore. :param context: The security context - :param instance_id: internal_id of the instance to update + :param instance_id: ID of the instance to update :param kwargs: All additional keyword args are treated as data fields of the instance to be updated @@ -212,8 +212,7 @@ class ComputeAPI(base.Base): :retval None """ - instance = self.get_instance(context, instance_id) - return self.db.instance_update(context, instance.id, kwargs) + return self.db.instance_update(context, instance_id, kwargs) def delete_instance(self, context, instance_id): logging.debug("Going to try and terminate %d" % instance_id) -- cgit From 53127ca97729fd60a51588dea397dda3a9e80b3b Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 5 Jan 2011 03:10:49 -0400 Subject: tests fixed up --- nova/tests/api/openstack/test_images.py | 2 ++ nova/tests/api/openstack/test_servers.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 0f274bd15..bde24a5e8 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -173,6 +173,7 @@ class ImageControllerWithGlanceServiceTest(unittest.TestCase): IMAGE_FIXTURES = [ {'id': '23g2ogk23k4hhkk4k42l', + 'imageId': '23g2ogk23k4hhkk4k42l', 'name': 'public image #1', 'created_at': str(datetime.datetime.utcnow()), 'updated_at': str(datetime.datetime.utcnow()), @@ -182,6 +183,7 @@ class ImageControllerWithGlanceServiceTest(unittest.TestCase): 'status': 'available', 'image_type': 'kernel'}, {'id': 'slkduhfas73kkaskgdas', + 'imageId': 'slkduhfas73kkaskgdas', 'name': 'public image #2', 'created_at': str(datetime.datetime.utcnow()), 'updated_at': str(datetime.datetime.utcnow()), diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 70ff714e6..e7c358aad 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -140,6 +140,12 @@ class ServersTest(unittest.TestCase): def queue_get_for(context, *args): return 'network_topic' + def kernel_ramdisk_mapping(*args, **kwargs): + return (1, 1) + + def image_id_from_hash(*args, **kwargs): + return 2 + self.stubs.Set(nova.db.api, 'project_get_network', project_get_network) self.stubs.Set(nova.db.api, 'instance_create', instance_create) self.stubs.Set(nova.rpc, 'cast', fake_method) @@ -149,6 +155,10 @@ class ServersTest(unittest.TestCase): self.stubs.Set(nova.db.api, 'queue_get_for', queue_get_for) self.stubs.Set(nova.network.manager.VlanManager, 'allocate_fixed_ip', fake_method) + self.stubs.Set(nova.api.openstack.servers.Controller, + "_get_kernel_ramdisk_from_image", kernel_ramdisk_mapping) + self.stubs.Set(nova.api.openstack.common, + "get_image_id_from_image_hash", image_id_from_hash) body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, -- cgit From e774f2cd7206b5ae632a42c1eda7330858b1613c Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 5 Jan 2011 03:51:21 -0400 Subject: pep8 --- nova/api/openstack/common.py | 4 ++-- nova/api/openstack/images.py | 2 +- nova/api/openstack/servers.py | 8 ++++---- nova/compute/api.py | 3 ++- nova/tests/api/openstack/test_servers.py | 6 +++--- nova/virt/xenapi/vm_utils.py | 3 ++- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index e2c7ed7ba..037ed47a0 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -47,8 +47,8 @@ def get_image_id_from_image_hash(image_service, context, image_hash): """ # FIX(sandy): This is terribly inefficient. It pulls all images - # from objectstore in order to find the match. ObjectStore - # should have a numeric counterpart to the string ID. + # from objectstore in order to find the match. ObjectStore + # should have a numeric counterpart to the string ID. try: items = image_service.detail(context) except NotImplementedError: diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 4da3b943b..42ffd22a5 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -124,7 +124,7 @@ class Controller(wsgi.Controller): items = self._service.index(req.environ['nova.context']) for image in items: _convert_image_id_to_hash(image) - + items = common.limited(items, req) items = [_translate_keys(item) for item in items] items = [_translate_status(item) for item in items] diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index a5de62230..024de0072 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -132,11 +132,11 @@ class Controller(wsgi.Controller): with open(mapping_filename) as f: mapping = json.load(f) - if mapping.has_key(image_id): + if image_id in mapping: return mapping[image_id] raise exception.NotFound( - _("No entry for image '%s' in mapping file '%s'") % + _("No entry for image '%s' in mapping file '%s'") % (image_id, mapping_filename)) def create(self, req): @@ -154,8 +154,8 @@ class Controller(wsgi.Controller): req.environ['nova.context'], instance_types.get_by_flavor_id(env['server']['flavorId']), image_id, - kernel_id = kernel_id, - ramdisk_id = ramdisk_id, + kernel_id=kernel_id, + ramdisk_id=ramdisk_id, display_name=env['server']['name'], description=env['server']['name'], key_name=key_pair['name'], diff --git a/nova/compute/api.py b/nova/compute/api.py index edc8c0b4a..f9595bde5 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -102,7 +102,8 @@ class ComputeAPI(base.Base): ramdisk_id = None logging.debug("Creating a raw instance") # Make sure we have access to kernel and ramdisk (if not raw) - logging.debug("Using Kernel=%s, Ramdisk=%s" % (kernel_id, ramdisk_id)) + logging.debug("Using Kernel=%s, Ramdisk=%s" % + (kernel_id, ramdisk_id)) if kernel_id: self.image_service.show(context, kernel_id) if ramdisk_id: diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index e7c358aad..9e642e927 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -145,7 +145,7 @@ class ServersTest(unittest.TestCase): def image_id_from_hash(*args, **kwargs): return 2 - + self.stubs.Set(nova.db.api, 'project_get_network', project_get_network) self.stubs.Set(nova.db.api, 'instance_create', instance_create) self.stubs.Set(nova.rpc, 'cast', fake_method) @@ -155,9 +155,9 @@ class ServersTest(unittest.TestCase): self.stubs.Set(nova.db.api, 'queue_get_for', queue_get_for) self.stubs.Set(nova.network.manager.VlanManager, 'allocate_fixed_ip', fake_method) - self.stubs.Set(nova.api.openstack.servers.Controller, + self.stubs.Set(nova.api.openstack.servers.Controller, "_get_kernel_ramdisk_from_image", kernel_ramdisk_mapping) - self.stubs.Set(nova.api.openstack.common, + self.stubs.Set(nova.api.openstack.common, "get_image_id_from_image_hash", image_id_from_hash) body = dict(server=dict( diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 8d344d84c..b91c37fb0 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -361,7 +361,8 @@ class VMHelper(HelperBase): ref = node.childNodes # Name and Value if len(ref) > 6: - diags[ref[0].firstChild.data] = ref[6].firstChild.data + diags[ref[0].firstChild.data] = \ + ref[6].firstChild.data return diags except cls.XenAPI.Failure as e: return {"Unable to retrieve diagnostics": e} -- cgit From cd2eb79121d987afcb0c8d33b861b8c723db696b Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 5 Jan 2011 08:10:08 -0400 Subject: fixed json syntax error --- krm_mapping.json.sample | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/krm_mapping.json.sample b/krm_mapping.json.sample index 046013803..1ecfba635 100644 --- a/krm_mapping.json.sample +++ b/krm_mapping.json.sample @@ -1,3 +1,3 @@ { - "machine" : ["kernel", "ramdisk"], + "machine" : ["kernel", "ramdisk"] } -- cgit From 8434ba0b13cc1b7e46be64ace3bee300de882aa0 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 5 Jan 2011 09:23:19 -0400 Subject: Changed Paused power state from Error to Paused --- nova/api/openstack/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 024de0072..52f93a3d2 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -50,7 +50,7 @@ def _translate_detail_keys(inst): power_state.RUNNING: 'active', power_state.BLOCKED: 'active', power_state.SUSPENDED: 'suspended', - power_state.PAUSED: 'error', + power_state.PAUSED: 'paused', power_state.SHUTDOWN: 'active', power_state.SHUTOFF: 'active', power_state.CRASHED: 'error'} -- cgit From 732d1946b5de78ec5e5ad8ac13b7d02c5fd90d10 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 5 Jan 2011 09:40:19 -0400 Subject: self.XENAPI... --- nova/virt/xenapi/vmops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 76f31635a..67c95a068 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -215,7 +215,7 @@ class VMOps(object): ret = None try: ret = self._session.wait_for_task(instance_id, task) - except XenAPI.Failure, exc: + except self.XenAPI.Failure, exc: logging.warn(exc) callback(ret) -- cgit From b437a98738c7a564205d1b27e36b844cd54445d1 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Wed, 5 Jan 2011 14:16:14 -0600 Subject: add in xs-console worker and tests. --- Authors | 1 + bin/nova-combined | 5 +- bin/nova-console | 44 ++++++++++ nova/compute/manager.py | 5 ++ nova/console/__init__.py | 11 +++ nova/console/driver.py | 59 +++++++++++++ nova/console/fake.py | 59 +++++++++++++ nova/console/manager.py | 130 +++++++++++++++++++++++++++ nova/console/xvp.conf.template | 16 ++++ nova/console/xvp.py | 193 +++++++++++++++++++++++++++++++++++++++++ nova/db/api.py | 41 +++++++++ nova/db/sqlalchemy/api.py | 81 +++++++++++++++++ nova/db/sqlalchemy/models.py | 23 ++++- nova/flags.py | 3 + nova/tests/test_console.py | 134 ++++++++++++++++++++++++++++ nova/virt/fake.py | 5 ++ nova/virt/libvirt_conn.py | 8 ++ nova/virt/xenapi_conn.py | 7 ++ 18 files changed, 822 insertions(+), 3 deletions(-) create mode 100755 bin/nova-console create mode 100644 nova/console/__init__.py create mode 100644 nova/console/driver.py create mode 100644 nova/console/fake.py create mode 100644 nova/console/manager.py create mode 100644 nova/console/xvp.conf.template create mode 100644 nova/console/xvp.py create mode 100644 nova/tests/test_console.py diff --git a/Authors b/Authors index 963cd6704..03dcf149c 100644 --- a/Authors +++ b/Authors @@ -22,6 +22,7 @@ Joshua McKenty Justin Santa Barbara Matt Dietz Michael Gundlach +Monsyne Dragon Monty Taylor Paul Voccio Rick Clark diff --git a/bin/nova-combined b/bin/nova-combined index 53322f1a0..a1d2cdc66 100755 --- a/bin/nova-combined +++ b/bin/nova-combined @@ -56,11 +56,12 @@ if __name__ == '__main__': compute = service.Service.create(binary='nova-compute') network = service.Service.create(binary='nova-network') - volume = service.Service.create(binary='nova-volume') +# volume = service.Service.create(binary='nova-volume') scheduler = service.Service.create(binary='nova-scheduler') #objectstore = service.Service.create(binary='nova-objectstore') - service.serve(compute, network, volume, scheduler) +# service.serve(compute, network, volume, scheduler) + service.serve(compute, network, scheduler) server = wsgi.Server() server.start(api.API('os'), FLAGS.osapi_port, host=FLAGS.osapi_host) diff --git a/bin/nova-console b/bin/nova-console new file mode 100755 index 000000000..802cc80b6 --- /dev/null +++ b/bin/nova-console @@ -0,0 +1,44 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 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. + +"""Starter script for Nova Console Proxy.""" + +import eventlet +eventlet.monkey_patch() + +import gettext +import os +import sys + +# If ../nova/__init__.py exists, add ../ to Python search path, so that +# it will override what happens to be installed in /usr/(local/)lib/python... +possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), + os.pardir, + os.pardir)) +if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): + sys.path.insert(0, possible_topdir) + +gettext.install('nova', unicode=1) + +from nova import service +from nova import utils + +if __name__ == '__main__': + utils.default_flagfile() + service.serve() + service.wait() diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 70b175e7c..295e75eca 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -99,6 +99,11 @@ class ComputeManager(manager.Manager): FLAGS.network_topic, host) + + def get_console_pool_info(self, context, console_type): + return self.driver.get_console_pool_info(console_type) + + @exception.wrap_exception def refresh_security_group(self, context, security_group_id, **_kwargs): """This call passes stright through to the virtualization driver.""" diff --git a/nova/console/__init__.py b/nova/console/__init__.py new file mode 100644 index 000000000..adce8621e --- /dev/null +++ b/nova/console/__init__.py @@ -0,0 +1,11 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +""" +:mod:`nova.console` -- Console Prxy to set up VM console access (i.e. with xvp) +===================================================== + +.. automodule:: nova.console + :platform: Unix + :synopsis: Wrapper around console proxies such as xvp to set up multitenant VM console access +.. moduleauthor:: Monsyne Dragon +""" diff --git a/nova/console/driver.py b/nova/console/driver.py new file mode 100644 index 000000000..b92765b34 --- /dev/null +++ b/nova/console/driver.py @@ -0,0 +1,59 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 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. + +""" +ConsoleProxy base class that all ConsoleProxies should inherit from +""" + +from nova import exception + + +class ConsoleProxy(object): + """The base class for all ConsoleProxy driver classes.""" + + @property + def console_type(self): + raise NotImplementedError("Must specify type in subclass") + + def setup_console(self, context, console): + """Sets up actual proxies""" + raise NotImplementedError("Must implement setup in subclass") + + def teardown_console(self, context, console): + """Tears down actual proxies""" + raise NotImplementedError("Must implement teardown in subclass") + + def init_host(self): + """Start up any config'ed consoles on start""" + pass + + def generate_password(self, length=8): + """Returns random console password""" + return os.urandom(length*2).encode('base64')[:length] + + def get_port(self, context): + """get available port for consoles that need one""" + return None + + def fix_pool_password(self, password): + """Trim password to length, and any other massaging""" + return password + + def fix_console_password(self, password): + """Trim password to length, and any other massaging""" + return password + diff --git a/nova/console/fake.py b/nova/console/fake.py new file mode 100644 index 000000000..4a9f1244c --- /dev/null +++ b/nova/console/fake.py @@ -0,0 +1,59 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 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. + +""" +Fake ConsoleProxy driver for tests. +""" + +from nova import exception +from nova.console import driver + +class FakeConsoleProxy(driver.ConsoleProxy): + """Fake ConsoleProxy driver.""" + + @property + def console_type(self): + return "fake" + + def setup_console(self, context, console): + """Sets up actual proxies""" + pass + + def teardown_console(self, context, console): + """Tears down actual proxies""" + pass + + def init_host(self): + """Start up any config'ed consoles on start""" + pass + + def generate_password(self, length=8): + """Returns random console password""" + return "fakepass" + + def get_port(self, context): + """get available port for consoles that need one""" + return 5999 + + def fix_pool_password(self, password): + """Trim password to length, and any other massaging""" + return password + + def fix_console_password(self, password): + """Trim password to length, and any other massaging""" + return password + diff --git a/nova/console/manager.py b/nova/console/manager.py new file mode 100644 index 000000000..93c6fabce --- /dev/null +++ b/nova/console/manager.py @@ -0,0 +1,130 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 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. + +""" +Console Proxy Service +""" + +import logging +import functools + +from nova import exception +from nova import flags +from nova import manager +from nova import rpc +from nova import utils + +FLAGS = flags.FLAGS +flags.DEFINE_string('console_driver', + 'nova.console.xvp.XVPConsoleProxy', + 'Driver to use for the console proxy') +flags.DEFINE_boolean('stub_compute', False, + 'Stub calls to compute worker for tests') + +class ConsoleProxyManager(manager.Manager): + + """ Sets up and tears down any proxy connections needed for accessing + instance consoles securely""" + + def __init__(self, console_driver=None, *args, **kwargs): + if not console_driver: + console_driver = FLAGS.console_driver + self.driver = utils.import_object(console_driver) + super(ConsoleProxyManager, self).__init__(*args, **kwargs) + self.driver.host = self.host + + def init_host(self): + self.driver.init_host() + + @exception.wrap_exception + def add_console(self, context, instance_id, password = None, + port = None, **kwargs): + instance = self.db.instance_get(context, instance_id) + host = instance['host'] + name = instance['name'] + pool = self.get_pool_for_instance_host(context, host) + try: + console = self.db.console_get_by_pool_instance(context, + pool['id'], + instance_id) + except exception.NotFound: + logging.debug("Adding console") + if not password: + password = self.driver.generate_password() + if not port: + port = self.driver.get_port(context) + console_data = {'instance_name' : name, + 'instance_id' : instance_id, + 'password' : password, + 'pool_id' : pool['id']} + if port: + console_data['port'] = port + console = self.db.console_create(context, console_data) + self.driver.setup_console(context, console) + return console['id'] + + @exception.wrap_exception + def remove_console(self, context, instance_id, **_kwargs): + instance = self.db.instance_get(context, instance_id) + host = instance['host'] + pool = self.get_pool_for_instance_host(context, host) + try: + console = self.db.console_get_by_pool_instance(context, + pool['id'], + instance_id) + except exception.NotFound: + logging.debug(_('Tried to remove non-existant console in pool ' + '%(pool_id)s for instance %(instance_id)s.' % + {'instance_id' : instance_id, + 'pool_id' : pool['id']})) + return + self.db.console_delete(context, console['id']) + self.driver.teardown_console(context, console) + + + def get_pool_for_instance_host(self, context, instance_host): + context = context.elevated() + console_type = self.driver.console_type + try: + pool = self.db.console_pool_get_by_host_type(context, + instance_host, + self.host, + console_type) + except exception.NotFound: + #NOTE(mdragon): Right now, the only place this info exists is the + # compute worker's flagfile, at least for + # xenserver. Thus we ned to ask. + if FLAGS.stub_compute: + pool_info = {'address' : '127.0.0.1', + 'username' : 'test', + 'password' : '1234pass'} + else: + pool_info = rpc.call(context, + self.db.queue_get_for(context, + FLAGS.compute_topic, + instance_host), + {"method": "get_console_pool_info", + "args": {"console_type": console_type}}) + pool_info['password'] = self.driver.fix_pool_password( + pool_info['password']) + pool_info['host'] = self.host + pool_info['console_type'] = self.driver.console_type + pool_info['compute_host'] = instance_host + pool = self.db.console_pool_create(context, pool_info) + return pool + + diff --git a/nova/console/xvp.conf.template b/nova/console/xvp.conf.template new file mode 100644 index 000000000..695ddbe96 --- /dev/null +++ b/nova/console/xvp.conf.template @@ -0,0 +1,16 @@ +# One time password use with time window +OTP ALLOW IPCHECK HTTP 60 +#if $multiplex_port +MULTIPLEX $multiplex_port +#end if + +#for $pool in $pools +POOL $pool.address + DOMAIN $pool.address + MANAGER root $pool.password + HOST $pool.address + VM - dummy 0123456789ABCDEF + #for $console in $pool.consoles + VM #if $multiplex_port then '-' else $console.port # $console.instance_name $pass_encode($console.password) + #end for +#end for diff --git a/nova/console/xvp.py b/nova/console/xvp.py new file mode 100644 index 000000000..62ad3b2bb --- /dev/null +++ b/nova/console/xvp.py @@ -0,0 +1,193 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 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. + +""" +XVP (Xenserver VNC Proxy) driver. +""" + +import fcntl +import logging +import os +import signal +import subprocess + +from Cheetah.Template import Template + +from nova import context +from nova import db +from nova import exception +from nova import flags +from nova import utils +from nova.console import driver + +flags.DEFINE_string('console_xvp_conf_template', + utils.abspath('console/xvp.conf.template'), + 'XVP conf template') +flags.DEFINE_string('console_xvp_conf', + '/etc/xvp.conf', + 'generated XVP conf file') +flags.DEFINE_string('console_xvp_pid', + '/var/run/xvp.pid', + 'XVP master process pid file') +flags.DEFINE_string('console_xvp_log', + '/var/log/xvp.log', + 'XVP log file') +flags.DEFINE_integer('console_xvp_multiplex_port', + 5900, + "port for XVP to multiplex VNC connections on") +FLAGS = flags.FLAGS + +class XVPConsoleProxy(driver.ConsoleProxy): + """Sets up XVP config, and manages xvp daemon""" + + def __init__(self): + self.xvpconf_template = open(FLAGS.console_xvp_conf_template).read() + self.host = FLAGS.host #default, set by manager. + super(XVPConsoleProxy, self).__init__() + + @property + def console_type(self): + return "vnc+xvp" + + def get_port(self, context): + """get available port for consoles that need one""" + #TODO(mdragon): implement port selection for non multiplex ports, + # we are not using that, but someone else may want + # it. + return FLAGS.console_xvp_multiplex_port + + def setup_console(self, context, console): + """Sets up actual proxies""" + self._rebuild_xvp_conf(context.elevated()) + + def teardown_console(self, context, console): + """Tears down actual proxies""" + self._rebuild_xvp_conf(context.elevated()) + + def init_host(self): + """Start up any config'ed consoles on start""" + ctxt = context.get_admin_context() + self._rebuild_xvp_conf(ctxt) + + def fix_pool_password(self, password): + """Trim password to length, and encode""" + return self._xvp_encrypt(password, is_pool_password=True) + + def fix_console_password(self, password): + """Trim password to length, and encode""" + return self._xvp_encrypt(password) + + def _rebuild_xvp_conf(self, context): + logging.debug("Rebuilding xvp conf") + pools = [ pool for pool in + db.console_pool_get_all_by_host_type(context, self.host, + self.console_type) + if pool['consoles']] + if not pools: + logging.debug("No console pools!") + self._xvp_stop() + return + conf_data = {'multiplex_port': FLAGS.console_xvp_multiplex_port, + 'pools': pools, + 'pass_encode' : self.fix_console_password } + config = str(Template(self.xvpconf_template, searchList=[conf_data])) + self._write_conf(config) + self._xvp_restart() + + def _write_conf(self, config): + logging.debug('Re-wrote %s' % FLAGS.console_xvp_conf) + with open(FLAGS.console_xvp_conf, 'w') as cfile: + cfile.write(config) + + def _xvp_stop(self): + logging.debug("Stopping xvp") + pid = self._xvp_pid() + if not pid: + return + try: + os.kill(pid,signal.SIGTERM) + except OSError: + #if it's already not running, no problem. + pass + + def _xvp_start(self): + if self._xvp_check_running(): + return + logging.debug("Starting xvp") + try: + utils.execute('xvp -p %s -c %s -l %s' % + (FLAGS.console_xvp_pid, + FLAGS.console_xvp_conf, + FLAGS.console_xvp_log)) + except exception.ProcessExecutionError, err: + logging.error("Error starting xvp: %s" % err) + + def _xvp_restart(self): + logging.debug("Restarting xvp") + if not self._xvp_check_running(): + logging.debug("xvp not running...") + self._xvp_start() + else: + pid = self._xvp_pid() + os.kill(pid, signal.SIGUSR1) + + def _xvp_pid(self): + try: + with open(FLAGS.console_xvp_pid, 'r') as pidfile: + pid = int(pidfile.read()) + except IOError: + return None + except ValueError: + return None + return pid + + def _xvp_check_running(self): + pid = self._xvp_pid() + if not pid: + return False + try: + os.kill(pid,0) + except OSError: + return False + return True + + def _xvp_encrypt(self, password, is_pool_password=False): + """Call xvp to obfuscate passwords for config file. + + Args: + - password: the password to encode, max 8 char for vm passwords, + and 16 chars for pool passwords. passwords will + be trimmed to max len before encoding. + - is_pool_password: True if this this is the XenServer api password + False if it's a VM console password + (xvp uses different keys and max lengths for pool passwords) + + Note that xvp's obfuscation should not be considered 'real' encryption. + It simply DES encrypts the passwords with static keys plainly viewable + in the xvp source code.""" + maxlen = 8 + flag = '-e' + if is_pool_password: + maxlen = 16 + flag = '-x' + #xvp will blow up on passwords that are too long (mdragon) + password = password[:maxlen] + out, err = utils.execute('xvp %s' % flag, process_input=password) + #p = subprocess.Popen(['xvp', flag], stdin=subprocess.PIPE, stdout=subprocess.PIPE) + #out,err = p.communicate(password) + return out.strip() + diff --git a/nova/db/api.py b/nova/db/api.py index fde3f0852..af9856cb6 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -884,3 +884,44 @@ def host_get_networks(context, host): """ return IMPL.host_get_networks(context, host) + +################## + +def console_pool_create(context, values): + """Create console pool.""" + return IMPL.console_pool_create(context, values) + +def console_pool_get(context, pool_id): + """Get a console pool.""" + return IMPL.console_pool_get(context, pool_id) + + +def console_pool_get_by_host_type(context, compute_host, proxy_host, + console_type): + """Fetch a console pool for a given proxy host, compute host, and type.""" + return IMPL.console_pool_get_by_host_type(context, + compute_host, + proxy_host, + console_type) + + +def console_pool_get_all_by_host_type(context, host, console_type): + """Fetch all pools for given proxy host and type.""" + return IMPL.console_pool_get_all_by_host_type(context, + host, + console_type) + + +def console_create(context,values): + """Create a console.""" + return IMPL.console_create(context, values) + +def console_delete(context, console_id): + """Delete a console.""" + return IMPL.console_delete(context, console_id) + +def console_get_by_pool_instance(context, pool_id, instance_id): + """Get console entry for a given instance and pool.""" + return IMPL.console_get_by_pool_instance(context, pool_id, instance_id) + + diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 7e945e4cb..25a3922c7 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1863,3 +1863,84 @@ def host_get_networks(context, host): filter_by(deleted=False).\ filter_by(host=host).\ all() + + +################## + + +def console_pool_create(context, values): + pool = models.ConsolePool() + pool.update(values) + pool.save() + return pool + + +def console_pool_get(context, pool_id): + session = get_session() + result = session.query(models.ConsolePool).\ + filter_by(deleted=False).\ + filter_by(id=pool_id).\ + first() + if not result: + raise exception.NotFound(_("No console pool with id %(pool_id)s") % {'pool_id': pool_id}) + + return result + +def console_pool_get_by_host_type(context, compute_host, host, + console_type): + session = get_session() + result = session.query(models.ConsolePool).\ + filter_by(host=host).\ + filter_by(console_type=console_type).\ + filter_by(compute_host=compute_host).\ + filter_by(deleted=False).\ + options(joinedload('consoles')).\ + first() + if not result: + raise exception.NotFound(_('No console pool of type %(type)s ' + 'for compute host %(compute_host)s ' + 'on proxy host %(host)s') % + {'type' : console_type, + 'compute_host' : compute_host, + 'host' : host}) + return result + + +def console_pool_get_all_by_host_type(context, host, console_type): + session = get_session() + return session.query(models.ConsolePool).\ + filter_by(host=host).\ + filter_by(console_type=console_type).\ + filter_by(deleted=False).\ + options(joinedload('consoles')).\ + all() + + +def console_create(context, values): + console = models.Console() + console.update(values) + console.save() + return console + +def console_delete(context, console_id): + session = get_session() + with session.begin(): + # consoles are meant to be transient. (mdragon) + session.execute('delete from consoles ' + 'where id=:id', {'id': console_id}) + +def console_get_by_pool_instance(context, pool_id, instance_id): + session = get_session() + result = session.query(models.Console).\ + filter_by(pool_id=pool_id).\ + filter_by(instance_id=instance_id).\ + options(joinedload('pool')).\ + first() + if not result: + raise exception.NotFound(_('No console for instance %(instance_id)s ' + 'in pool %(pool_id)s') % + {'instance_id': instance_id, + 'pool_id': pool_id}) + return result + + diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 693db8d23..e7f2d427e 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -553,6 +553,27 @@ class FloatingIp(BASE, NovaBase): project_id = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) +class ConsolePool(BASE, NovaBase): + """Represents pool of consoles on the same physical node.""" + __tablename__ = 'console_pools' + id = Column(Integer, primary_key=True) + address = Column(String(255)) + username = Column(String(255)) + password = Column(String(255)) + console_type = Column(String(255)) + host = Column(String(255)) + compute_host = Column(String(255)) + +class Console(BASE, NovaBase): + """Represents a console session for an instance.""" + __tablename__ = 'consoles' + id = Column(Integer, primary_key=True) + instance_name = Column(String(255)) + instance_id = Column(Integer) + password = Column(String(255)) + port = Column(Integer,nullable=True) + pool_id = Column(Integer, ForeignKey('console_pools.id')) + pool = relationship(ConsolePool, backref=backref('consoles')) def register_models(): """Register Models and create metadata. @@ -565,7 +586,7 @@ def register_models(): Volume, ExportDevice, IscsiTarget, FixedIp, FloatingIp, Network, SecurityGroup, SecurityGroupIngressRule, SecurityGroupInstanceAssociation, AuthToken, User, - Project, Certificate) # , Image, Host + Project, Certificate, ConsolePool, Console) # , Image, Host engine = create_engine(FLAGS.sql_connection, echo=False) for model in models: model.metadata.create_all(engine) diff --git a/nova/flags.py b/nova/flags.py index 76a98d35a..447cc6c6c 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -216,6 +216,7 @@ DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', utils.get_my_ip(), 's3 host (for infrastructure)') DEFINE_string('s3_dmz', utils.get_my_ip(), 's3 dmz ip (for instances)') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') +DEFINE_string('console_topic', 'console', 'the topic console proxy nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') DEFINE_string('volume_topic', 'volume', 'the topic volume nodes listen on') @@ -263,6 +264,8 @@ DEFINE_string('sql_connection', DEFINE_string('compute_manager', 'nova.compute.manager.ComputeManager', 'Manager for compute') +DEFINE_string('console_manager', 'nova.console.manager.ConsoleProxyManager', + 'Manager for console proxy') DEFINE_string('network_manager', 'nova.network.manager.VlanManager', 'Manager for network') DEFINE_string('volume_manager', 'nova.volume.manager.VolumeManager', diff --git a/nova/tests/test_console.py b/nova/tests/test_console.py new file mode 100644 index 000000000..9f06a1771 --- /dev/null +++ b/nova/tests/test_console.py @@ -0,0 +1,134 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Openstack, LLC. +# 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. + +""" +Tests For Console proxy. +""" + +import datetime +import logging + +from nova import context +from nova import db +from nova import exception +from nova import flags +from nova import test +from nova import utils +from nova.auth import manager +from nova.console import manager as console_manager + +FLAGS = flags.FLAGS + + +class ConsoleTestCase(test.TestCase): + """Test case for console proxy""" + def setUp(self): + logging.getLogger().setLevel(logging.DEBUG) + super(ConsoleTestCase, self).setUp() + self.flags(console_driver='nova.console.fake.FakeConsoleProxy', + stub_compute=True) + self.console = utils.import_object(FLAGS.console_manager) + self.manager = manager.AuthManager() + self.user = self.manager.create_user('fake', 'fake', 'fake') + self.project = self.manager.create_project('fake', 'fake', 'fake') + self.context = context.get_admin_context() + self.host = 'test_compute_host' + + def tearDown(self): + self.manager.delete_user(self.user) + self.manager.delete_project(self.project) + super(ConsoleTestCase, self).tearDown() + + def _create_instance(self): + """Create a test instance""" + inst = {} + #inst['host'] = self.host + #inst['name'] = 'instance-1234' + inst['image_id'] = 'ami-test' + inst['reservation_id'] = 'r-fakeres' + inst['launch_time'] = '10' + inst['user_id'] = self.user.id + inst['project_id'] = self.project.id + inst['instance_type'] = 'm1.tiny' + inst['mac_address'] = utils.generate_mac() + inst['ami_launch_index'] = 0 + return db.instance_create(self.context, inst)['id'] + + def test_get_pool_for_instance_host(self): + pool = self.console.get_pool_for_instance_host(self.context, self.host) + self.assertEqual(pool['compute_host'], self.host) + + def test_get_pool_creates_new_pool_if_needed(self): + self.assertRaises(exception.NotFound, + db.console_pool_get_by_host_type, + self.context, + self.host, + self.console.host, + self.console.driver.console_type) + pool = self.console.get_pool_for_instance_host(self.context, + self.host) + pool2 = db.console_pool_get_by_host_type(self.context, + self.host, + self.console.host, + self.console.driver.console_type) + self.assertEqual(pool['id'], pool2['id']) + + def test_get_pool_does_not_create_new_pool_if_exists(self): + pool_info = {'address' : '127.0.0.1', + 'username' : 'test', + 'password' : '1234pass', + 'host' : self.console.host, + 'console_type' : self.console.driver.console_type, + 'compute_host' : 'sometesthostname' } + new_pool = db.console_pool_create(self.context, pool_info) + pool = self.console.get_pool_for_instance_host(self.context, + 'sometesthostname') + self.assertEqual(pool['id'], new_pool['id']) + + def test_add_console(self): + instance_id = self._create_instance() + self.console.add_console(self.context, instance_id) + instance = db.instance_get(self.context, instance_id) + pool = db.console_pool_get_by_host_type(self.context, + instance['host'], + self.console.host, + self.console.driver.console_type) + + console_instances = [con['instance_id'] for con in pool.consoles] + self.assert_(instance_id in console_instances) + + def test_add_console_does_not_duplicate(self): + instance_id = self._create_instance() + cons1 = self.console.add_console(self.context, instance_id) + cons2 = self.console.add_console(self.context, instance_id) + self.assertEqual(cons1,cons2) + + def test_remove_console(self): + instance_id = self._create_instance() + self.console.add_console(self.context, instance_id) + self.console.remove_console(self.context, instance_id) + + instance = db.instance_get(self.context, instance_id) + pool = db.console_pool_get_by_host_type(self.context, + instance['host'], + self.console.host, + self.console.driver.console_type) + + console_instances = [con['instance_id'] for con in pool.consoles] + self.assert_(instance_id not in console_instances) + diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 706888b0d..acabb8034 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -272,6 +272,11 @@ class FakeConnection(object): def get_console_output(self, instance): return 'FAKE CONSOLE OUTPUT' + def get_console_pool_info(self, console_type): + return {'address' : '127.0.0.1', + 'username' : 'fakeuser', + 'password' : 'fakepassword'} + class FakeInstance(object): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 65cf65098..51353147f 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -671,6 +671,14 @@ class LibvirtConnection(object): fw = NWFilterFirewall(self._conn) fw.ensure_security_group_filter(security_group_id) + def get_console_pool_info(self, console_type): + #TODO(mdragon): console proxy should be implemented for libvirt, + # in case someone wants to use it with kvm or + # such. For now return fake data. + return {'address' : '127.0.0.1', + 'username' : 'fakeuser', + 'password' : 'fakepassword'} + class NWFilterFirewall(object): """ diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 7f03d6c2b..abad0a08a 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -52,6 +52,7 @@ reactor thread if the VM.get_by_name_label or VM.get_record calls block. import logging import sys +import urlparse import xmlrpclib from eventlet import event @@ -177,6 +178,12 @@ class XenAPIConnection(object): """Detach volume storage to VM instance""" return self._volumeops.detach_volume(instance_name, mountpoint) + def get_console_pool_info(self, console_type): + xs_url = urlparse.urlparse(FLAGS.xenapi_connection_url) + return {'address' : xs_url.netloc, + 'username' : FLAGS.xenapi_connection_username, + 'password' : FLAGS.xenapi_connection_password} + class XenAPISession(object): """The session to invoke XenAPI SDK calls""" -- cgit From f21f078113fc81c1dcee4f3a077bd555c0cf85f6 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Wed, 5 Jan 2011 19:45:46 -0600 Subject: Fix a bunch of pep8 stuff --- nova/api/openstack/consoles.py | 5 ----- nova/compute/manager.py | 2 -- nova/console/__init__.py | 7 ++++--- nova/console/api.py | 2 +- nova/console/driver.py | 3 +-- nova/console/fake.py | 2 +- nova/console/manager.py | 26 ++++++++++++-------------- nova/console/xvp.py | 18 ++++++++---------- nova/db/api.py | 11 ++++++++--- nova/db/sqlalchemy/api.py | 22 +++++++++++++--------- nova/db/sqlalchemy/models.py | 5 ++++- nova/flags.py | 3 ++- nova/tests/test_console.py | 19 +++++++++---------- nova/virt/fake.py | 6 +++--- nova/virt/libvirt_conn.py | 8 ++++---- nova/virt/xenapi_conn.py | 6 +++--- 16 files changed, 73 insertions(+), 72 deletions(-) diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index bf3403655..49eefe09d 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -85,8 +85,3 @@ class Controller(wsgi.Controller): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) return exc.HTTPAccepted() - -# def detail(self, req, id): -# """ Returns a complete list of consoles for this instance""" -# return _translate_detail_keys({}) - diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 3e73c351c..403b46b2a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -112,11 +112,9 @@ class ComputeManager(manager.Manager): FLAGS.network_topic, host) - def get_console_pool_info(self, context, console_type): return self.driver.get_console_pool_info(console_type) - @exception.wrap_exception def refresh_security_group(self, context, security_group_id, **_kwargs): """This call passes stright through to the virtualization driver.""" diff --git a/nova/console/__init__.py b/nova/console/__init__.py index adce8621e..491df075b 100644 --- a/nova/console/__init__.py +++ b/nova/console/__init__.py @@ -1,11 +1,12 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 """ -:mod:`nova.console` -- Console Prxy to set up VM console access (i.e. with xvp) +:mod:`nova.console` -- Console Prxy to set up VM console access (i.e. with xvp) ===================================================== .. automodule:: nova.console :platform: Unix - :synopsis: Wrapper around console proxies such as xvp to set up multitenant VM console access -.. moduleauthor:: Monsyne Dragon + :synopsis: Wrapper around console proxies such as xvp to set up + multitenant VM console access +.. moduleauthor:: Monsyne Dragon """ diff --git a/nova/console/api.py b/nova/console/api.py index 78bfe636b..54317e3b3 100644 --- a/nova/console/api.py +++ b/nova/console/api.py @@ -29,6 +29,7 @@ from nova import rpc FLAGS = flags.FLAGS + class ConsoleAPI(base.Base): """API for spining up or down console proxy connections""" @@ -70,7 +71,6 @@ class ConsoleAPI(base.Base): {"method": "add_console", "args": {"instance_id": instance['id']}}) - def _get_console_topic(self, context, instance_host): topic = self.db.queue_get_for(context, FLAGS.compute_topic, diff --git a/nova/console/driver.py b/nova/console/driver.py index b92765b34..d2dafb1f3 100644 --- a/nova/console/driver.py +++ b/nova/console/driver.py @@ -43,7 +43,7 @@ class ConsoleProxy(object): def generate_password(self, length=8): """Returns random console password""" - return os.urandom(length*2).encode('base64')[:length] + return os.urandom(length * 2).encode('base64')[:length] def get_port(self, context): """get available port for consoles that need one""" @@ -56,4 +56,3 @@ class ConsoleProxy(object): def fix_console_password(self, password): """Trim password to length, and any other massaging""" return password - diff --git a/nova/console/fake.py b/nova/console/fake.py index 4a9f1244c..46782bdb3 100644 --- a/nova/console/fake.py +++ b/nova/console/fake.py @@ -22,6 +22,7 @@ Fake ConsoleProxy driver for tests. from nova import exception from nova.console import driver + class FakeConsoleProxy(driver.ConsoleProxy): """Fake ConsoleProxy driver.""" @@ -56,4 +57,3 @@ class FakeConsoleProxy(driver.ConsoleProxy): def fix_console_password(self, password): """Trim password to length, and any other massaging""" return password - diff --git a/nova/console/manager.py b/nova/console/manager.py index e3cbdae0e..b743e55b5 100644 --- a/nova/console/manager.py +++ b/nova/console/manager.py @@ -35,9 +35,10 @@ flags.DEFINE_string('console_driver', flags.DEFINE_boolean('stub_compute', False, 'Stub calls to compute worker for tests') + class ConsoleProxyManager(manager.Manager): - """ Sets up and tears down any proxy connections needed for accessing + """ Sets up and tears down any proxy connections needed for accessing instance consoles securely""" def __init__(self, console_driver=None, *args, **kwargs): @@ -51,8 +52,8 @@ class ConsoleProxyManager(manager.Manager): self.driver.init_host() @exception.wrap_exception - def add_console(self, context, instance_id, password = None, - port = None, **kwargs): + def add_console(self, context, instance_id, password=None, + port=None, **kwargs): instance = self.db.instance_get(context, instance_id) host = instance['host'] name = instance['name'] @@ -67,10 +68,10 @@ class ConsoleProxyManager(manager.Manager): password = self.driver.generate_password() if not port: port = self.driver.get_port(context) - console_data = {'instance_name' : name, - 'instance_id' : instance_id, - 'password' : password, - 'pool_id' : pool['id']} + console_data = {'instance_name': name, + 'instance_id': instance_id, + 'password': password, + 'pool_id': pool['id']} if port: console_data['port'] = port console = self.db.console_create(context, console_data) @@ -84,12 +85,11 @@ class ConsoleProxyManager(manager.Manager): except exception.NotFound: logging.debug(_('Tried to remove non-existant console ' '%(console_id)s.') % - {'console_id' : console_id}) + {'console_id': console_id}) return self.db.console_delete(context, console_id) self.driver.teardown_console(context, console) - def get_pool_for_instance_host(self, context, instance_host): context = context.elevated() console_type = self.driver.console_type @@ -103,9 +103,9 @@ class ConsoleProxyManager(manager.Manager): # compute worker's flagfile, at least for # xenserver. Thus we ned to ask. if FLAGS.stub_compute: - pool_info = {'address' : '127.0.0.1', - 'username' : 'test', - 'password' : '1234pass'} + pool_info = {'address': '127.0.0.1', + 'username': 'test', + 'password': '1234pass'} else: pool_info = rpc.call(context, self.db.queue_get_for(context, @@ -120,5 +120,3 @@ class ConsoleProxyManager(manager.Manager): pool_info['compute_host'] = instance_host pool = self.db.console_pool_create(context, pool_info) return pool - - diff --git a/nova/console/xvp.py b/nova/console/xvp.py index 62ad3b2bb..161b5ce20 100644 --- a/nova/console/xvp.py +++ b/nova/console/xvp.py @@ -51,12 +51,13 @@ flags.DEFINE_integer('console_xvp_multiplex_port', "port for XVP to multiplex VNC connections on") FLAGS = flags.FLAGS + class XVPConsoleProxy(driver.ConsoleProxy): """Sets up XVP config, and manages xvp daemon""" def __init__(self): self.xvpconf_template = open(FLAGS.console_xvp_conf_template).read() - self.host = FLAGS.host #default, set by manager. + self.host = FLAGS.host # default, set by manager. super(XVPConsoleProxy, self).__init__() @property @@ -93,8 +94,8 @@ class XVPConsoleProxy(driver.ConsoleProxy): def _rebuild_xvp_conf(self, context): logging.debug("Rebuilding xvp conf") - pools = [ pool for pool in - db.console_pool_get_all_by_host_type(context, self.host, + pools = [pool for pool in + db.console_pool_get_all_by_host_type(context, self.host, self.console_type) if pool['consoles']] if not pools: @@ -103,7 +104,7 @@ class XVPConsoleProxy(driver.ConsoleProxy): return conf_data = {'multiplex_port': FLAGS.console_xvp_multiplex_port, 'pools': pools, - 'pass_encode' : self.fix_console_password } + 'pass_encode': self.fix_console_password} config = str(Template(self.xvpconf_template, searchList=[conf_data])) self._write_conf(config) self._xvp_restart() @@ -119,7 +120,7 @@ class XVPConsoleProxy(driver.ConsoleProxy): if not pid: return try: - os.kill(pid,signal.SIGTERM) + os.kill(pid, signal.SIGTERM) except OSError: #if it's already not running, no problem. pass @@ -129,7 +130,7 @@ class XVPConsoleProxy(driver.ConsoleProxy): return logging.debug("Starting xvp") try: - utils.execute('xvp -p %s -c %s -l %s' % + utils.execute('xvp -p %s -c %s -l %s' % (FLAGS.console_xvp_pid, FLAGS.console_xvp_conf, FLAGS.console_xvp_log)) @@ -160,7 +161,7 @@ class XVPConsoleProxy(driver.ConsoleProxy): if not pid: return False try: - os.kill(pid,0) + os.kill(pid, 0) except OSError: return False return True @@ -187,7 +188,4 @@ class XVPConsoleProxy(driver.ConsoleProxy): #xvp will blow up on passwords that are too long (mdragon) password = password[:maxlen] out, err = utils.execute('xvp %s' % flag, process_input=password) - #p = subprocess.Popen(['xvp', flag], stdin=subprocess.PIPE, stdout=subprocess.PIPE) - #out,err = p.communicate(password) return out.strip() - diff --git a/nova/db/api.py b/nova/db/api.py index 15b87520b..2c53fbeef 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -895,12 +895,15 @@ def host_get_networks(context, host): """ return IMPL.host_get_networks(context, host) + ################## + def console_pool_create(context, values): """Create console pool.""" return IMPL.console_pool_create(context, values) + def console_pool_get(context, pool_id): """Get a console pool.""" return IMPL.console_pool_get(context, pool_id) @@ -922,24 +925,26 @@ def console_pool_get_all_by_host_type(context, host, console_type): console_type) -def console_create(context,values): +def console_create(context, values): """Create a console.""" return IMPL.console_create(context, values) + def console_delete(context, console_id): """Delete a console.""" return IMPL.console_delete(context, console_id) + def console_get_by_pool_instance(context, pool_id, instance_id): """Get console entry for a given instance and pool.""" return IMPL.console_get_by_pool_instance(context, pool_id, instance_id) + def console_get_all_by_instance(context, instance_id): """Get consoles for a given instance.""" return IMPL.console_get_all_by_instance(context, instance_id) + def console_get(context, console_id, instance_id=None): """Get a specific console (possibly on a given instance).""" return IMPL.console_get(context, console_id, instance_id) - - diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 77dfe4a28..7b70566eb 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1835,10 +1835,12 @@ def console_pool_get(context, pool_id): filter_by(id=pool_id).\ first() if not result: - raise exception.NotFound(_("No console pool with id %(pool_id)s") % {'pool_id': pool_id}) + raise exception.NotFound(_("No console pool with id %(pool_id)s") % + {'pool_id': pool_id}) return result + def console_pool_get_by_host_type(context, compute_host, host, console_type): session = get_session() @@ -1853,9 +1855,9 @@ def console_pool_get_by_host_type(context, compute_host, host, raise exception.NotFound(_('No console pool of type %(type)s ' 'for compute host %(compute_host)s ' 'on proxy host %(host)s') % - {'type' : console_type, - 'compute_host' : compute_host, - 'host' : host}) + {'type': console_type, + 'compute_host': compute_host, + 'host': host}) return result @@ -1875,13 +1877,15 @@ def console_create(context, values): console.save() return console + def console_delete(context, console_id): session = get_session() with session.begin(): - # consoles are meant to be transient. (mdragon) + # consoles are meant to be transient. (mdragon) session.execute('delete from consoles ' 'where id=:id', {'id': console_id}) + def console_get_by_pool_instance(context, pool_id, instance_id): session = get_session() result = session.query(models.Console).\ @@ -1891,11 +1895,12 @@ def console_get_by_pool_instance(context, pool_id, instance_id): first() if not result: raise exception.NotFound(_('No console for instance %(instance_id)s ' - 'in pool %(pool_id)s') % + 'in pool %(pool_id)s') % {'instance_id': instance_id, 'pool_id': pool_id}) return result + def console_get_all_by_instance(context, instance_id): session = get_session() results = session.query(models.Console).\ @@ -1904,6 +1909,7 @@ def console_get_all_by_instance(context, instance_id): all() return results + def console_get(context, console_id, instance_id=None): session = get_session() query = session.query(models.Console).\ @@ -1914,7 +1920,5 @@ def console_get(context, console_id, instance_id=None): if not result: idesc = _(" on instance %(instance_id)s") if instance_id else "" raise exception.NotFound(_("No console with id %(instance)s") % - {'instance' : idesc}) + {'instance': idesc}) return result - - diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 58cf21b0d..dd75927d0 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -537,6 +537,7 @@ class FloatingIp(BASE, NovaBase): project_id = Column(String(255)) host = Column(String(255)) # , ForeignKey('hosts.id')) + class ConsolePool(BASE, NovaBase): """Represents pool of consoles on the same physical node.""" __tablename__ = 'console_pools' @@ -548,6 +549,7 @@ class ConsolePool(BASE, NovaBase): host = Column(String(255)) compute_host = Column(String(255)) + class Console(BASE, NovaBase): """Represents a console session for an instance.""" __tablename__ = 'consoles' @@ -555,10 +557,11 @@ class Console(BASE, NovaBase): instance_name = Column(String(255)) instance_id = Column(Integer) password = Column(String(255)) - port = Column(Integer,nullable=True) + port = Column(Integer, nullable=True) pool_id = Column(Integer, ForeignKey('console_pools.id')) pool = relationship(ConsolePool, backref=backref('consoles')) + def register_models(): """Register Models and create metadata. diff --git a/nova/flags.py b/nova/flags.py index 58ba4d16d..f3a19c0c8 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -218,7 +218,8 @@ DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', utils.get_my_ip(), 's3 host (for infrastructure)') DEFINE_string('s3_dmz', utils.get_my_ip(), 's3 dmz ip (for instances)') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') -DEFINE_string('console_topic', 'console', 'the topic console proxy nodes listen on') +DEFINE_string('console_topic', 'console', + 'the topic console proxy nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') DEFINE_string('volume_topic', 'volume', 'the topic volume nodes listen on') diff --git a/nova/tests/test_console.py b/nova/tests/test_console.py index b23b1499b..31b5ca79c 100644 --- a/nova/tests/test_console.py +++ b/nova/tests/test_console.py @@ -89,12 +89,12 @@ class ConsoleTestCase(test.TestCase): self.assertEqual(pool['id'], pool2['id']) def test_get_pool_does_not_create_new_pool_if_exists(self): - pool_info = {'address' : '127.0.0.1', - 'username' : 'test', - 'password' : '1234pass', - 'host' : self.console.host, - 'console_type' : self.console.driver.console_type, - 'compute_host' : 'sometesthostname' } + pool_info = {'address': '127.0.0.1', + 'username': 'test', + 'password': '1234pass', + 'host': self.console.host, + 'console_type': self.console.driver.console_type, + 'compute_host': 'sometesthostname'} new_pool = db.console_pool_create(self.context, pool_info) pool = self.console.get_pool_for_instance_host(self.context, 'sometesthostname') @@ -107,16 +107,16 @@ class ConsoleTestCase(test.TestCase): pool = db.console_pool_get_by_host_type(self.context, instance['host'], self.console.host, - self.console.driver.console_type) + self.console.driver.console_type) - console_instances = [con['instance_id'] for con in pool.consoles] + console_instances = [con['instance_id'] for con in pool.consoles] self.assert_(instance_id in console_instances) def test_add_console_does_not_duplicate(self): instance_id = self._create_instance() cons1 = self.console.add_console(self.context, instance_id) cons2 = self.console.add_console(self.context, instance_id) - self.assertEqual(cons1,cons2) + self.assertEqual(cons1, cons2) def test_remove_console(self): instance_id = self._create_instance() @@ -127,4 +127,3 @@ class ConsoleTestCase(test.TestCase): db.console_get, self.context, console_id) - diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 13490b12e..849261f07 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -290,9 +290,9 @@ class FakeConnection(object): return 'FAKE CONSOLE OUTPUT' def get_console_pool_info(self, console_type): - return {'address' : '127.0.0.1', - 'username' : 'fakeuser', - 'password' : 'fakepassword'} + return {'address': '127.0.0.1', + 'username': 'fakeuser', + 'password': 'fakepassword'} class FakeInstance(object): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index ded1004cd..0e3b6dff6 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -697,10 +697,10 @@ class LibvirtConnection(object): def get_console_pool_info(self, console_type): #TODO(mdragon): console proxy should be implemented for libvirt, # in case someone wants to use it with kvm or - # such. For now return fake data. - return {'address' : '127.0.0.1', - 'username' : 'fakeuser', - 'password' : 'fakepassword'} + # such. For now return fake data. + return {'address': '127.0.0.1', + 'username': 'fakeuser', + 'password': 'fakepassword'} class NWFilterFirewall(object): diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index c702059f7..86efb6b07 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -189,9 +189,9 @@ class XenAPIConnection(object): def get_console_pool_info(self, console_type): xs_url = urlparse.urlparse(FLAGS.xenapi_connection_url) - return {'address' : xs_url.netloc, - 'username' : FLAGS.xenapi_connection_username, - 'password' : FLAGS.xenapi_connection_password} + return {'address': xs_url.netloc, + 'username': FLAGS.xenapi_connection_username, + 'password': FLAGS.xenapi_connection_password} class XenAPISession(object): -- cgit From 324d8fdf284bd5109e34692049256722d731b572 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 6 Jan 2011 01:51:05 +0000 Subject: allow smoketests to find nova package and add security rules --- smoketests/admin_smoketests.py | 9 ++++- smoketests/user_smoketests.py | 87 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/smoketests/admin_smoketests.py b/smoketests/admin_smoketests.py index 50bb3fa2e..1ef1c1425 100644 --- a/smoketests/admin_smoketests.py +++ b/smoketests/admin_smoketests.py @@ -19,10 +19,17 @@ import os import random import sys -import time import unittest import zipfile +# If ../nova/__init__.py exists, add ../ to Python search path, so that +# it will override what happens to be installed in /usr/(local/)lib/python... +possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), + os.pardir, + os.pardir)) +if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): + sys.path.insert(0, possible_topdir) + from nova import adminclient from smoketests import flags from smoketests import base diff --git a/smoketests/user_smoketests.py b/smoketests/user_smoketests.py index d29e3aea3..578c0722e 100644 --- a/smoketests/user_smoketests.py +++ b/smoketests/user_smoketests.py @@ -24,6 +24,14 @@ import sys import time import unittest +# If ../nova/__init__.py exists, add ../ to Python search path, so that +# it will override what happens to be installed in /usr/(local/)lib/python... +possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), + os.pardir, + os.pardir)) +if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): + sys.path.insert(0, possible_topdir) + from smoketests import flags from smoketests import base @@ -40,6 +48,7 @@ flags.DEFINE_string('bundle_image', 'openwrt-x86-ext2.image', TEST_PREFIX = 'test%s' % int (random.random()*1000000) TEST_BUCKET = '%s_bucket' % TEST_PREFIX TEST_KEY = '%s_key' % TEST_PREFIX +TEST_GROUP = '%s_group' % TEST_PREFIX TEST_DATA = {} @@ -137,7 +146,7 @@ class InstanceTests(UserSmokeTestCase): self.data['instance_id'] = reservation.instances[0].id def test_003_instance_runs_within_60_seconds(self): - reservations = self.conn.get_all_instances([data['instance_id']]) + reservations = self.conn.get_all_instances([self.data['instance_id']]) instance = reservations[0].instances[0] # allow 60 seconds to exit pending with IP for x in xrange(60): @@ -207,7 +216,7 @@ class InstanceTests(UserSmokeTestCase): def test_999_tearDown(self): self.delete_key_pair(self.conn, TEST_KEY) if self.data.has_key('instance_id'): - self.conn.terminate_instances([data['instance_id']]) + self.conn.terminate_instances([self.data['instance_id']]) class VolumeTests(UserSmokeTestCase): @@ -319,8 +328,80 @@ class VolumeTests(UserSmokeTestCase): self.conn.delete_key_pair(TEST_KEY) +class SecurityGroupTests(UserSmokeTestCase): + + def __public_instance_is_accessible(self): + id_url = "latest/meta-data/instance-id" + options = "-s --max-time 1" + command = "curl %s %s/%s" % (options, self.data['public_ip'], id_url) + instance_id = commands.getoutput(command).strip() + if not instance_id: + return False + if instance_id != self.data['instance_id']: + raise Exception("Wrong instance id") + return True + + def test_001_can_create_security_group(self): + self.conn.create_security_group(TEST_GROUP, description='test') + + groups = self.conn.get_all_security_groups() + self.assertTrue(TEST_GROUP in [group.name for group in groups]) + + def test_002_can_launch_instance_in_security_group(self): + self.create_key_pair(self.conn, TEST_KEY) + reservation = self.conn.run_instances(FLAGS.test_image, + key_name=TEST_KEY, + security_groups=[TEST_GROUP], + instance_type='m1.tiny') + + self.data['instance_id'] = reservation.instances[0].id + + def test_003_can_authorize_security_group_ingress(self): + self.assertTrue(self.conn.authorize_security_group(TEST_GROUP, + ip_protocol='tcp', + from_port=80, + to_port=80)) + + def test_004_can_access_instance_over_public_ip(self): + result = self.conn.allocate_address() + self.assertTrue(hasattr(result, 'public_ip')) + self.data['public_ip'] = result.public_ip + + result = self.conn.associate_address(self.data['instance_id'], + self.data['public_ip']) + start_time = time.time() + while not self.__public_instance_is_accessible(): + # 1 minute to launch + if time.time() - start_time > 60: + raise Exception("Timeout") + time.sleep(1) + + def test_005_can_revoke_security_group_ingress(self): + self.assertTrue(self.conn.revoke_security_group(TEST_GROUP, + ip_protocol='tcp', + from_port=80, + to_port=80)) + start_time = time.time() + while self.__public_instance_is_accessible(): + # 1 minute to teardown + if time.time() - start_time > 60: + raise Exception("Timeout") + time.sleep(1) + + + def test_999_tearDown(self): + self.conn.delete_key_pair(TEST_KEY) + self.conn.delete_security_group(TEST_GROUP) + groups = self.conn.get_all_security_groups() + self.assertFalse(TEST_GROUP in [group.name for group in groups]) + self.conn.terminate_instances([self.data['instance_id']]) + self.assertTrue(self.conn.release_address(self.data['public_ip'])) + + if __name__ == "__main__": suites = {'image': unittest.makeSuite(ImageTests), 'instance': unittest.makeSuite(InstanceTests), - 'volume': unittest.makeSuite(VolumeTests)} + 'security_group': unittest.makeSuite(SecurityGroupTests), + 'volume': unittest.makeSuite(VolumeTests) + } sys.exit(base.run_tests(suites)) -- cgit From f9fa25f9a873c1e4831c342689f7b5adc8f41013 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Wed, 5 Jan 2011 20:14:36 -0600 Subject: add in separate public hostname for console hosts. flesh out console api data. --- nova/api/openstack/consoles.py | 17 +++++++++++++---- nova/console/manager.py | 7 ++++++- nova/db/sqlalchemy/models.py | 1 + 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index 49eefe09d..e108bab86 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -23,15 +23,24 @@ from nova.console import api as console_api from nova.api.openstack import faults -def _translate_keys(inst): +def _translate_keys(cons): """Coerces a console instance into proper dictionary format """ - return dict(console=inst) + pool = cons['pool'] + info = {'id': cons['id'], + 'console_type': pool['console_type']} + return dict(console=info) -def _translate_detail_keys(inst): +def _translate_detail_keys(cons): """Coerces a console instance into proper dictionary format with correctly mapped attributes """ - return dict(console=inst) + pool = cons['pool'] + info = {'id': cons['id'], + 'console_type': pool['console_type'], + 'password': cons['password'], + 'port': cons['port'], + 'host': pool['public_hostname']} + return dict(console=info) class Controller(wsgi.Controller): diff --git a/nova/console/manager.py b/nova/console/manager.py index b743e55b5..c55ca8e8f 100644 --- a/nova/console/manager.py +++ b/nova/console/manager.py @@ -19,8 +19,9 @@ Console Proxy Service """ -import logging import functools +import logging +import socket from nova import exception from nova import flags @@ -34,6 +35,9 @@ flags.DEFINE_string('console_driver', 'Driver to use for the console proxy') flags.DEFINE_boolean('stub_compute', False, 'Stub calls to compute worker for tests') +flags.DEFINE_string('console_public_hostname', + socket.gethostname(), + 'Publicly visable name for this console host') class ConsoleProxyManager(manager.Manager): @@ -116,6 +120,7 @@ class ConsoleProxyManager(manager.Manager): pool_info['password'] = self.driver.fix_pool_password( pool_info['password']) pool_info['host'] = self.host + pool_info['public_hostname'] = FLAGS.console_public_hostname pool_info['console_type'] = self.driver.console_type pool_info['compute_host'] = instance_host pool = self.db.console_pool_create(context, pool_info) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index dd75927d0..0c55ee75e 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -546,6 +546,7 @@ class ConsolePool(BASE, NovaBase): username = Column(String(255)) password = Column(String(255)) console_type = Column(String(255)) + public_hostname = Column(String(255)) host = Column(String(255)) compute_host = Column(String(255)) -- cgit -- cgit From b55940e8e3d977960ff60f4cb7cff4b6ea2e8fb8 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Wed, 5 Jan 2011 22:11:05 -0600 Subject: fix some glitches due to someone removing instanc.internal_id (not that I mind) remove accidental change to nova-combined script --- bin/nova-combined | 5 ++--- nova/console/api.py | 23 +++++++++-------------- nova/console/driver.py | 2 ++ nova/db/sqlalchemy/api.py | 6 +++--- 4 files changed, 16 insertions(+), 20 deletions(-) diff --git a/bin/nova-combined b/bin/nova-combined index a1d2cdc66..53322f1a0 100755 --- a/bin/nova-combined +++ b/bin/nova-combined @@ -56,12 +56,11 @@ if __name__ == '__main__': compute = service.Service.create(binary='nova-compute') network = service.Service.create(binary='nova-network') -# volume = service.Service.create(binary='nova-volume') + volume = service.Service.create(binary='nova-volume') scheduler = service.Service.create(binary='nova-scheduler') #objectstore = service.Service.create(binary='nova-objectstore') -# service.serve(compute, network, volume, scheduler) - service.serve(compute, network, scheduler) + service.serve(compute, network, volume, scheduler) server = wsgi.Server() server.start(api.API('os'), FLAGS.osapi_port, host=FLAGS.osapi_host) diff --git a/nova/console/api.py b/nova/console/api.py index 54317e3b3..93e28ad64 100644 --- a/nova/console/api.py +++ b/nova/console/api.py @@ -36,20 +36,16 @@ class ConsoleAPI(base.Base): def __init__(self, **kwargs): super(ConsoleAPI, self).__init__(**kwargs) - def get_consoles(self, context, instance_internal_id): - instance = self.db.instance_get_by_internal_id(context, - instance_internal_id) - return self.db.console_get_all_by_instance(context, instance['id']) + def get_consoles(self, context, instance_id): + return self.db.console_get_all_by_instance(context, instance_id) - def get_console(self, context, instance_internal_id, console_id): - return self.db.console_get(context, console_id, instance_internal_id) + def get_console(self, context, instance_id, console_id): + return self.db.console_get(context, console_id, instance_id) - def delete_console(self, context, instance_internal_id, console_id): - instance = self.db.instance_get_by_internal_id(context, - instance_internal_id) + def delete_console(self, context, instance_id, console_id): console = self.db.console_get(context, console_id, - instance['id']) + instance_id) pool = console['pool'] rpc.cast(context, self.db.queue_get_for(context, @@ -58,9 +54,8 @@ class ConsoleAPI(base.Base): {"method": "remove_console", "args": {"console_id": console['id']}}) - def create_console(self, context, instance_internal_id): - instance = self.db.instance_get_by_internal_id(context, - instance_internal_id) + def create_console(self, context, instance_id): + instance = self.db.instance_get(context, instance_id) #NOTE(mdragon): If we wanted to return this the console info # here, as we would need to do a call. # They can just do an index later to fetch @@ -69,7 +64,7 @@ class ConsoleAPI(base.Base): rpc.cast(context, self._get_console_topic(context, instance['host']), {"method": "add_console", - "args": {"instance_id": instance['id']}}) + "args": {"instance_id": instance_id}}) def _get_console_topic(self, context, instance_host): topic = self.db.queue_get_for(context, diff --git a/nova/console/driver.py b/nova/console/driver.py index d2dafb1f3..c4cf5e5e9 100644 --- a/nova/console/driver.py +++ b/nova/console/driver.py @@ -19,6 +19,8 @@ ConsoleProxy base class that all ConsoleProxies should inherit from """ +import os + from nova import exception diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 7b70566eb..6f4d068f1 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1918,7 +1918,7 @@ def console_get(context, console_id, instance_id=None): query = query.filter_by(instance_id=instance_id) result = query.options(joinedload('pool')).first() if not result: - idesc = _(" on instance %(instance_id)s") if instance_id else "" - raise exception.NotFound(_("No console with id %(instance)s") % - {'instance': idesc}) + idesc = (_("on instance %s") % instance_id) if instance_id else "" + raise exception.NotFound(_("No console with id %(console_id)s %(instance)s") % + {'instance': idesc, 'console_id': console_id}) return result -- cgit From 114577d4f4ed1ca173dadf47d4bf3a5a05c449a2 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 6 Jan 2011 03:05:59 -0400 Subject: Fixed display_name on create_instance --- nova/compute/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 7ab7eb6ad..252f88003 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -171,7 +171,7 @@ class API(base.Base): # Set sane defaults if not specified updates = dict(hostname=generate_hostname(instance_id)) - if 'display_name' not in instance: + if not hasattr(instance, 'display_name'): updates['display_name'] = "Server %s" % instance_id instance = self.update(context, instance_id, **updates) -- cgit From 457e19826cfdb7f8f324180e42d8df79da48cfc6 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 6 Jan 2011 04:55:16 -0400 Subject: renamed sharedipgroups to shared_ip_groups and fixed tests for display_name --- nova/api/openstack/__init__.py | 4 +- nova/api/openstack/shared_ip_groups.py | 69 +++++++++++++++++++++++ nova/api/openstack/sharedipgroups.py | 69 ----------------------- nova/compute/api.py | 3 +- nova/tests/api/openstack/test_shared_ip_groups.py | 39 +++++++++++++ nova/tests/api/openstack/test_sharedipgroups.py | 39 ------------- 6 files changed, 112 insertions(+), 111 deletions(-) create mode 100644 nova/api/openstack/shared_ip_groups.py delete mode 100644 nova/api/openstack/sharedipgroups.py create mode 100644 nova/tests/api/openstack/test_shared_ip_groups.py delete mode 100644 nova/tests/api/openstack/test_sharedipgroups.py diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 0b54c7233..33eac001a 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -39,7 +39,7 @@ from nova.api.openstack import flavors from nova.api.openstack import images from nova.api.openstack import ratelimiting from nova.api.openstack import servers -from nova.api.openstack import sharedipgroups +from nova.api.openstack import shared_ip_groups FLAGS = flags.FLAGS @@ -114,7 +114,7 @@ class APIRouter(wsgi.Router): mapper.resource("flavor", "flavors", controller=flavors.Controller(), collection={'detail': 'GET'}) mapper.resource("shared_ip_group", "shared_ip_groups", - controller=sharedipgroups.Controller()) + controller=shared_ip_groups.Controller()) super(APIRouter, self).__init__(mapper) diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py new file mode 100644 index 000000000..65595c8ff --- /dev/null +++ b/nova/api/openstack/shared_ip_groups.py @@ -0,0 +1,69 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 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. + +import logging + +from webob import exc + +from nova import wsgi +from nova.api.openstack import faults + + +def _translate_keys(inst): + """ Coerces a shared IP group instance into proper dictionary format """ + return dict(sharedIpGroup=inst) + + +def _translate_detail_keys(inst): + """ Coerces a shared IP group instance into proper dictionary format with + correctly mapped attributes """ + return dict(sharedIpGroups=inst) + + +class Controller(wsgi.Controller): + """ The Shared IP Groups Controller for the Openstack API """ + + _serialization_metadata = { + 'application/xml': { + 'attributes': { + 'sharedIpGroup': []}}} + + def index(self, req): + """ Returns a list of Shared IP Groups for the user """ + return dict(sharedIpGroups=[]) + + def show(self, req, id): + """ Shows in-depth information on a specific Shared IP Group """ + if id == 'detail': + return _translate_detail_keys({}) + return _translate_keys({}) + + def update(self, req, id): + """ You can't update a Shared IP Group """ + raise faults.Fault(exc.HTTPNotImplemented()) + + def delete(self, req, id): + """ Deletes a Shared IP Group """ + raise faults.Fault(exc.HTTPNotImplemented()) + + def detail(self, req, id): + """ Returns a complete list of Shared IP Groups """ + return _translate_detail_keys({}) + + def create(self, req): + """ Creates a new Shared IP group """ + raise faults.Fault(exc.HTTPNotImplemented()) diff --git a/nova/api/openstack/sharedipgroups.py b/nova/api/openstack/sharedipgroups.py deleted file mode 100644 index 65595c8ff..000000000 --- a/nova/api/openstack/sharedipgroups.py +++ /dev/null @@ -1,69 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 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. - -import logging - -from webob import exc - -from nova import wsgi -from nova.api.openstack import faults - - -def _translate_keys(inst): - """ Coerces a shared IP group instance into proper dictionary format """ - return dict(sharedIpGroup=inst) - - -def _translate_detail_keys(inst): - """ Coerces a shared IP group instance into proper dictionary format with - correctly mapped attributes """ - return dict(sharedIpGroups=inst) - - -class Controller(wsgi.Controller): - """ The Shared IP Groups Controller for the Openstack API """ - - _serialization_metadata = { - 'application/xml': { - 'attributes': { - 'sharedIpGroup': []}}} - - def index(self, req): - """ Returns a list of Shared IP Groups for the user """ - return dict(sharedIpGroups=[]) - - def show(self, req, id): - """ Shows in-depth information on a specific Shared IP Group """ - if id == 'detail': - return _translate_detail_keys({}) - return _translate_keys({}) - - def update(self, req, id): - """ You can't update a Shared IP Group """ - raise faults.Fault(exc.HTTPNotImplemented()) - - def delete(self, req, id): - """ Deletes a Shared IP Group """ - raise faults.Fault(exc.HTTPNotImplemented()) - - def detail(self, req, id): - """ Returns a complete list of Shared IP Groups """ - return _translate_detail_keys({}) - - def create(self, req): - """ Creates a new Shared IP group """ - raise faults.Fault(exc.HTTPNotImplemented()) diff --git a/nova/compute/api.py b/nova/compute/api.py index 252f88003..912c7d93f 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -171,7 +171,8 @@ class API(base.Base): # Set sane defaults if not specified updates = dict(hostname=generate_hostname(instance_id)) - if not hasattr(instance, 'display_name'): + if (not hasattr(instance, 'display_name')) or \ + instance.display_name == None: updates['display_name'] = "Server %s" % instance_id instance = self.update(context, instance_id, **updates) diff --git a/nova/tests/api/openstack/test_shared_ip_groups.py b/nova/tests/api/openstack/test_shared_ip_groups.py new file mode 100644 index 000000000..c2fc3a203 --- /dev/null +++ b/nova/tests/api/openstack/test_shared_ip_groups.py @@ -0,0 +1,39 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 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. + +import unittest + +import stubout + +from nova.api.openstack import shared_ip_groups + + +class SharedIpGroupsTest(unittest.TestCase): + def setUp(self): + self.stubs = stubout.StubOutForTesting() + + def tearDown(self): + self.stubs.UnsetAll() + + def test_get_shared_ip_groups(self): + pass + + def test_create_shared_ip_group(self): + pass + + def test_delete_shared_ip_group(self): + pass diff --git a/nova/tests/api/openstack/test_sharedipgroups.py b/nova/tests/api/openstack/test_sharedipgroups.py deleted file mode 100644 index d199951d8..000000000 --- a/nova/tests/api/openstack/test_sharedipgroups.py +++ /dev/null @@ -1,39 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 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. - -import unittest - -import stubout - -from nova.api.openstack import sharedipgroups - - -class SharedIpGroupsTest(unittest.TestCase): - def setUp(self): - self.stubs = stubout.StubOutForTesting() - - def tearDown(self): - self.stubs.UnsetAll() - - def test_get_shared_ip_groups(self): - pass - - def test_create_shared_ip_group(self): - pass - - def test_delete_shared_ip_group(self): - pass -- cgit From 1ce25cab7f1818aababb18d60959f44602f2e17c Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Thu, 6 Jan 2011 13:19:58 -0600 Subject: pep8 fix --- nova/db/sqlalchemy/api.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 6f4d068f1..c3dee4329 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1919,6 +1919,8 @@ def console_get(context, console_id, instance_id=None): result = query.options(joinedload('pool')).first() if not result: idesc = (_("on instance %s") % instance_id) if instance_id else "" - raise exception.NotFound(_("No console with id %(console_id)s %(instance)s") % - {'instance': idesc, 'console_id': console_id}) + raise exception.NotFound(_("No console with id %(console_id)s" + " %(instance)s") % + {'instance': idesc, + 'console_id': console_id}) return result -- cgit From b2d6bb841857599096467470ec704e6696317829 Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Fri, 7 Jan 2011 19:04:22 -0600 Subject: change API classname to match the way other API's are done. --- nova/api/openstack/consoles.py | 4 ++-- nova/console/__init__.py | 1 + nova/console/api.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/consoles.py b/nova/api/openstack/consoles.py index e108bab86..9ebdbe710 100644 --- a/nova/api/openstack/consoles.py +++ b/nova/api/openstack/consoles.py @@ -17,9 +17,9 @@ from webob import exc +from nova import console from nova import exception from nova import wsgi -from nova.console import api as console_api from nova.api.openstack import faults @@ -52,7 +52,7 @@ class Controller(wsgi.Controller): 'console': []}}} def __init__(self): - self.console_api = console_api.ConsoleAPI() + self.console_api = console.API() super(Controller, self).__init__() def index(self, req, server_id): diff --git a/nova/console/__init__.py b/nova/console/__init__.py index 491df075b..dfc72cd61 100644 --- a/nova/console/__init__.py +++ b/nova/console/__init__.py @@ -10,3 +10,4 @@ multitenant VM console access .. moduleauthor:: Monsyne Dragon """ +from nova.console.api import API diff --git a/nova/console/api.py b/nova/console/api.py index 93e28ad64..3850d2c44 100644 --- a/nova/console/api.py +++ b/nova/console/api.py @@ -30,11 +30,11 @@ from nova import rpc FLAGS = flags.FLAGS -class ConsoleAPI(base.Base): +class API(base.Base): """API for spining up or down console proxy connections""" def __init__(self, **kwargs): - super(ConsoleAPI, self).__init__(**kwargs) + super(API, self).__init__(**kwargs) def get_consoles(self, context, instance_id): return self.db.console_get_all_by_instance(context, instance_id) -- cgit From 3b4582b5db905a6dcadda31be27c9f340d7fe5cf Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Sun, 9 Jan 2011 18:08:54 -0800 Subject: Moved get_my_ip into flags because that is the only thing it is being used for and use it to set a new flag called my_ip --- nova/flags.py | 26 +++++++++++++++++++------- nova/network/linux_net.py | 2 +- nova/network/manager.py | 2 +- nova/tests/api/openstack/fakes.py | 2 +- nova/utils.py | 13 ------------- 5 files changed, 22 insertions(+), 23 deletions(-) diff --git a/nova/flags.py b/nova/flags.py index f5c2d4233..0e6d3176c 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -200,10 +200,22 @@ def DECLARE(name, module_string, flag_values=FLAGS): "%s not defined by %s" % (name, module_string)) +def _get_my_ip(): + """Returns the actual ip of the local machine.""" + try: + csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + csock.connect(('8.8.8.8', 80)) + (addr, port) = csock.getsockname() + csock.close() + return addr + except socket.gaierror as ex: + return "127.0.0.1" + + # __GLOBAL FLAGS ONLY__ # Define any app-specific flags in their own files, docs at: -# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#39 - +# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#a9 +DEFINE_string('my_ip', _get_my_ip(), 'host ip address') DEFINE_list('region_list', [], 'list of region=url pairs separated by commas') @@ -211,10 +223,10 @@ DEFINE_string('connection_type', 'libvirt', 'libvirt, xenapi or fake') DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID') DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key') DEFINE_integer('glance_port', 9292, 'glance port') -DEFINE_string('glance_host', '127.0.0.1', 'glance host') +DEFINE_string('glance_host', '$my_ip', 'glance host') DEFINE_integer('s3_port', 3333, 's3 port') -DEFINE_string('s3_host', '127.0.0.1', 's3 host (for infrastructure)') -DEFINE_string('s3_dmz', '127.0.0.1', 's3 dmz ip (for instances)') +DEFINE_string('s3_host', '$my_ip', 's3 host (for infrastructure)') +DEFINE_string('s3_dmz', '$my_ip', 's3 dmz ip (for instances)') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') @@ -234,8 +246,8 @@ DEFINE_integer('rabbit_retry_interval', 10, 'rabbit connection retry interval') DEFINE_integer('rabbit_max_retries', 12, 'rabbit connection attempts') DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') DEFINE_string('ec2_prefix', 'http', 'prefix for ec2') -DEFINE_string('cc_host', '127.0.0.1', 'ip of api server') -DEFINE_string('cc_dmz', '127.0.0.1', 'internal ip of api server') +DEFINE_string('cc_host', '$my_ip', 'ip of api server') +DEFINE_string('cc_dmz', '$my_ip', 'internal ip of api server') DEFINE_integer('cc_port', 8773, 'cloud controller port') DEFINE_string('ec2_suffix', '/services/Cloud', 'suffix for ec2') diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index c525d5dc8..a0648ca25 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -46,7 +46,7 @@ flags.DEFINE_string('vlan_interface', 'eth0', 'network device for vlans') flags.DEFINE_string('dhcpbridge', _bin_file('nova-dhcpbridge'), 'location of nova-dhcpbridge') -flags.DEFINE_string('routing_source_ip', utils.get_my_ip(), +flags.DEFINE_string('routing_source_ip', '$my_ip', 'Public IP of network host') flags.DEFINE_bool('use_nova_chains', False, 'use the nova_ routing chains instead of default') diff --git a/nova/network/manager.py b/nova/network/manager.py index fd286f210..c75ecc671 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -74,7 +74,7 @@ flags.DEFINE_string('flat_network_dhcp_start', '10.0.0.2', 'Dhcp start for FlatDhcp') flags.DEFINE_integer('vlan_start', 100, 'First VLAN for private networks') flags.DEFINE_integer('num_networks', 1000, 'Number of networks to support') -flags.DEFINE_string('vpn_ip', utils.get_my_ip(), +flags.DEFINE_string('vpn_ip', '$my_ip', 'Public IP for the cloudpipe VPN servers') flags.DEFINE_integer('vpn_start', 1000, 'First Vpn port for private networks') flags.DEFINE_integer('network_size', 256, diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 291a0e468..194304e79 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -107,7 +107,7 @@ def stub_out_rate_limiting(stubs): def stub_out_networking(stubs): def get_my_ip(): return '127.0.0.1' - stubs.Set(nova.utils, 'get_my_ip', get_my_ip) + stubs.Set(nova.flags, '_get_my_ip', get_my_ip) def stub_out_compute_api_snapshot(stubs): diff --git a/nova/utils.py b/nova/utils.py index cc632b835..aadbec532 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -195,19 +195,6 @@ def last_octet(address): return int(address.split(".")[-1]) -def get_my_ip(): - """Returns the actual ip of the local machine.""" - try: - csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - csock.connect(('8.8.8.8', 80)) - (addr, port) = csock.getsockname() - csock.close() - return addr - except socket.gaierror as ex: - LOG.warn(_("Couldn't get IP, using 127.0.0.1 %s"), ex) - return "127.0.0.1" - - def utcnow(): """Overridable version of datetime.datetime.utcnow.""" if utcnow.override_time: -- cgit From d09511edeef2a8f6dc866ea3011bd8cc4632ac38 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 10 Jan 2011 04:20:11 -0400 Subject: Fixed xenapi_conn wait_for_task to properly terminate LoopingCall on exception --- nova/tests/xenapi/stubs.py | 24 ++++++++++++++++++++++++ nova/virt/xenapi_conn.py | 15 +++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index 55f751f11..d7a9a5f3e 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -41,9 +41,33 @@ def stubout_instance_snapshot(stubs): rv = done.wait() return rv + def fake_loop(self): + pass + stubs.Set(xenapi_conn.XenAPISession, 'wait_for_task', fake_wait_for_task) + stubs.Set(xenapi_conn.XenAPISession, 'stop_loop', fake_loop) + + from nova.virt.xenapi.fake import create_vdi + name_label = "instance-%s" % instance_id + #TODO: create fake SR record + sr_ref = "fakesr" + vdi_ref = create_vdi(name_label=name_label, read_only=False, + sr_ref=sr_ref, sharable=False) + vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) + vdi_uuid = vdi_rec['uuid'] + return vdi_uuid + + stubs.Set(vm_utils.VMHelper, 'fetch_image', fake_fetch_image) + + def fake_parse_xmlrpc_value(val): + return val + + stubs.Set(xenapi_conn, '_parse_xmlrpc_value', fake_parse_xmlrpc_value) + + def fake_wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, + original_parent_uuid): from nova.virt.xenapi.fake import create_vdi name_label = "instance-%s" % instance_id #TODO: create fake SR record diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 3aaaf09ed..ad32e890d 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -198,6 +198,7 @@ class XenAPISession(object): self.XenAPI = self.get_imported_xenapi() self._session = self._create_session(url) self._session.login_with_password(user, pw) + self.loop = None def get_imported_xenapi(self): """Stubout point. This can be replaced with a mock xenapi module.""" @@ -234,14 +235,19 @@ class XenAPISession(object): def wait_for_task(self, id, task): """Return the result of the given task. The task is polled - until it completes.""" + until it completes. Not re-entrant.""" done = event.Event() - loop = utils.LoopingCall(self._poll_task, id, task, done) - loop.start(FLAGS.xenapi_task_poll_interval, now=True) + self.loop = utils.LoopingCall(self._poll_task, id, task, done) + self.loop.start(FLAGS.xenapi_task_poll_interval, now=True) rv = done.wait() - loop.stop() + self.loop.stop() return rv + def stop_loop(self): + # Had to break this call out to support unit tests. + if self.loop: + self.loop.stop() + def _create_session(self, url): """Stubout point. This can be replaced with a mock session.""" return self.XenAPI.Session(url) @@ -278,6 +284,7 @@ class XenAPISession(object): except self.XenAPI.Failure, exc: LOG.warn(exc) done.send_exception(*sys.exc_info()) + self.stop_loop() def _unwrap_plugin_exceptions(self, func, *args, **kwargs): """Parse exception details""" -- cgit From f9d1a59fc10425b0a9b82edca857c771e6be9809 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 10 Jan 2011 04:40:57 -0400 Subject: Fixed xenapi_conn wait_for_task to properly terminate LoopingCall on exception --- nova/virt/xenapi_conn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index ad32e890d..f4293ef3b 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -244,7 +244,7 @@ class XenAPISession(object): return rv def stop_loop(self): - # Had to break this call out to support unit tests. + #NOTE(sandy-walsh) Had to break this call out to support unit tests. if self.loop: self.loop.stop() -- cgit From 72e9f0819837da68c52f5604e83385037fdcdfb2 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 10 Jan 2011 05:12:48 -0400 Subject: Fixed xenapi_conn wait_for_task to properly terminate LoopingCall on exception --- nova/tests/xenapi/stubs.py | 2 +- nova/virt/xenapi_conn.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index d7a9a5f3e..292bd9ba9 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -47,7 +47,7 @@ def stubout_instance_snapshot(stubs): stubs.Set(xenapi_conn.XenAPISession, 'wait_for_task', fake_wait_for_task) - stubs.Set(xenapi_conn.XenAPISession, 'stop_loop', fake_loop) + stubs.Set(xenapi_conn.XenAPISession, '_stop_loop', fake_loop) from nova.virt.xenapi.fake import create_vdi name_label = "instance-%s" % instance_id diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index f4293ef3b..b8ab9245f 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -243,7 +243,8 @@ class XenAPISession(object): self.loop.stop() return rv - def stop_loop(self): + def _stop_loop(self): + """Stop polling for task to finish.""" #NOTE(sandy-walsh) Had to break this call out to support unit tests. if self.loop: self.loop.stop() @@ -284,7 +285,7 @@ class XenAPISession(object): except self.XenAPI.Failure, exc: LOG.warn(exc) done.send_exception(*sys.exc_info()) - self.stop_loop() + self._stop_loop() def _unwrap_plugin_exceptions(self, func, *args, **kwargs): """Parse exception details""" -- cgit From 20a326c1724fe74b196db5695a0ac2ecf040c0d8 Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Mon, 10 Jan 2011 15:02:29 +0000 Subject: Adding modify option for projects --- bin/nova-manage | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/nova-manage b/bin/nova-manage index 40f540e5b..a81e407e9 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -333,6 +333,11 @@ class ProjectCommands(object): arguments: name project_manager [description]""" self.manager.create_project(name, project_manager, description) + def modify(self, name, project_manaber, description=None): + """Modifies a project + arguments: name project_manager [description]""" + self.manager.modify_project(name, project_manager, description) + def delete(self, name): """Deletes an existing project arguments: name""" -- cgit From 8d04f68f00d5cd37f13028a4ee5909530ea9c92c Mon Sep 17 00:00:00 2001 From: Ryan Lane Date: Mon, 10 Jan 2011 15:13:30 +0000 Subject: Typo fix --- bin/nova-manage | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/nova-manage b/bin/nova-manage index a81e407e9..3f5957190 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -333,7 +333,7 @@ class ProjectCommands(object): arguments: name project_manager [description]""" self.manager.create_project(name, project_manager, description) - def modify(self, name, project_manaber, description=None): + def modify(self, name, project_manager, description=None): """Modifies a project arguments: name project_manager [description]""" self.manager.modify_project(name, project_manager, description) -- cgit From 94f3782eb27fd63c64845f9ab59039d07ac7ba8c Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Mon, 10 Jan 2011 14:59:32 -0600 Subject: remove uneeded superclass --- bin/nova-combined | 3 ++- nova/console/driver.py | 60 -------------------------------------------------- nova/console/fake.py | 3 +-- nova/console/xvp.py | 7 ++++-- 4 files changed, 8 insertions(+), 65 deletions(-) delete mode 100644 nova/console/driver.py diff --git a/bin/nova-combined b/bin/nova-combined index 53322f1a0..4e49fd1fc 100755 --- a/bin/nova-combined +++ b/bin/nova-combined @@ -60,7 +60,8 @@ if __name__ == '__main__': scheduler = service.Service.create(binary='nova-scheduler') #objectstore = service.Service.create(binary='nova-objectstore') - service.serve(compute, network, volume, scheduler) + #service.serve(compute, network, volume, scheduler) + service.serve(compute, network, scheduler) server = wsgi.Server() server.start(api.API('os'), FLAGS.osapi_port, host=FLAGS.osapi_host) diff --git a/nova/console/driver.py b/nova/console/driver.py deleted file mode 100644 index c4cf5e5e9..000000000 --- a/nova/console/driver.py +++ /dev/null @@ -1,60 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2010 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. - -""" -ConsoleProxy base class that all ConsoleProxies should inherit from -""" - -import os - -from nova import exception - - -class ConsoleProxy(object): - """The base class for all ConsoleProxy driver classes.""" - - @property - def console_type(self): - raise NotImplementedError("Must specify type in subclass") - - def setup_console(self, context, console): - """Sets up actual proxies""" - raise NotImplementedError("Must implement setup in subclass") - - def teardown_console(self, context, console): - """Tears down actual proxies""" - raise NotImplementedError("Must implement teardown in subclass") - - def init_host(self): - """Start up any config'ed consoles on start""" - pass - - def generate_password(self, length=8): - """Returns random console password""" - return os.urandom(length * 2).encode('base64')[:length] - - def get_port(self, context): - """get available port for consoles that need one""" - return None - - def fix_pool_password(self, password): - """Trim password to length, and any other massaging""" - return password - - def fix_console_password(self, password): - """Trim password to length, and any other massaging""" - return password diff --git a/nova/console/fake.py b/nova/console/fake.py index 46782bdb3..7a90d5221 100644 --- a/nova/console/fake.py +++ b/nova/console/fake.py @@ -20,10 +20,9 @@ Fake ConsoleProxy driver for tests. """ from nova import exception -from nova.console import driver -class FakeConsoleProxy(driver.ConsoleProxy): +class FakeConsoleProxy(object): """Fake ConsoleProxy driver.""" @property diff --git a/nova/console/xvp.py b/nova/console/xvp.py index 161b5ce20..2a76223da 100644 --- a/nova/console/xvp.py +++ b/nova/console/xvp.py @@ -32,7 +32,6 @@ from nova import db from nova import exception from nova import flags from nova import utils -from nova.console import driver flags.DEFINE_string('console_xvp_conf_template', utils.abspath('console/xvp.conf.template'), @@ -52,7 +51,7 @@ flags.DEFINE_integer('console_xvp_multiplex_port', FLAGS = flags.FLAGS -class XVPConsoleProxy(driver.ConsoleProxy): +class XVPConsoleProxy(object): """Sets up XVP config, and manages xvp daemon""" def __init__(self): @@ -92,6 +91,10 @@ class XVPConsoleProxy(driver.ConsoleProxy): """Trim password to length, and encode""" return self._xvp_encrypt(password) + def generate_password(self, length=8): + """Returns random console password""" + return os.urandom(length * 2).encode('base64')[:length] + def _rebuild_xvp_conf(self, context): logging.debug("Rebuilding xvp conf") pools = [pool for pool in -- cgit From d6a66d13718a41d5146d713ced192e795e72457e Mon Sep 17 00:00:00 2001 From: Monsyne Dragon Date: Mon, 10 Jan 2011 15:00:30 -0600 Subject: whups, fix accidental change to nova-combined --- bin/nova-combined | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/nova-combined b/bin/nova-combined index 4e49fd1fc..53322f1a0 100755 --- a/bin/nova-combined +++ b/bin/nova-combined @@ -60,8 +60,7 @@ if __name__ == '__main__': scheduler = service.Service.create(binary='nova-scheduler') #objectstore = service.Service.create(binary='nova-objectstore') - #service.serve(compute, network, volume, scheduler) - service.serve(compute, network, scheduler) + service.serve(compute, network, volume, scheduler) server = wsgi.Server() server.start(api.API('os'), FLAGS.osapi_port, host=FLAGS.osapi_host) -- cgit From 77e75fefc7c9c4085a64eabb5ef44ffd5fff3229 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 11 Jan 2011 03:38:40 -0400 Subject: Changed shared_ip_group detail routing --- nova/api/openstack/shared_ip_groups.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nova/api/openstack/shared_ip_groups.py b/nova/api/openstack/shared_ip_groups.py index ec399db93..bd3cc23a8 100644 --- a/nova/api/openstack/shared_ip_groups.py +++ b/nova/api/openstack/shared_ip_groups.py @@ -48,8 +48,6 @@ class Controller(wsgi.Controller): def show(self, req, id): """ Shows in-depth information on a specific Shared IP Group """ - #if id == 'detail': - # return _translate_detail_keys({}) return _translate_keys({}) def update(self, req, id): @@ -60,7 +58,7 @@ class Controller(wsgi.Controller): """ Deletes a Shared IP Group """ raise faults.Fault(exc.HTTPNotImplemented()) - def detail(self, req, id): + def detail(self, req): """ Returns a complete list of Shared IP Groups """ return _translate_detail_keys({}) -- cgit From ea4cde387c04e450c7bea9407772ca4276ea54f4 Mon Sep 17 00:00:00 2001 From: Soren Hansen Date: Tue, 11 Jan 2011 11:06:16 +0100 Subject: Fixed a number of issues with the iptables firewall backend: * Port specifications for firewalls come back from the data store as integers, but were compared as strings. * --icmp-type was misspelled as --icmp_type (underscore vs dash) * There weren't any unit tests for these issues. --- nova/tests/test_virt.py | 44 +++++++++++++++++++++++++++++++++++++++++++- nova/virt/libvirt_conn.py | 6 +++--- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 59053f4d0..c69dbaab3 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -276,6 +276,20 @@ class IptablesFirewallTestCase(test.TestCase): 'name': 'testgroup', 'description': 'test group'}) + db.security_group_rule_create(admin_ctxt, + {'parent_group_id': secgroup['id'], + 'protocol': 'icmp', + 'from_port': -1, + 'to_port': -1, + 'cidr': '192.168.11.0/24'}) + + db.security_group_rule_create(admin_ctxt, + {'parent_group_id': secgroup['id'], + 'protocol': 'icmp', + 'from_port': 8, + 'to_port': -1, + 'cidr': '192.168.11.0/24'}) + db.security_group_rule_create(admin_ctxt, {'parent_group_id': secgroup['id'], 'protocol': 'tcp', @@ -297,7 +311,35 @@ class IptablesFirewallTestCase(test.TestCase): self.assertTrue(rule in out_rules, 'Rule went missing: %s' % rule) - print '\n'.join(out_rules) + instance_chain = None + for rule in out_rules: + # This is pretty crude, but it'll do for now + if '-d 10.11.12.13 -j' in rule: + instance_chain = rule.split(' ')[-1] + break + self.assertTrue(instance_chain, "The instance chain wasn't added") + + security_group_chain = None + for rule in out_rules: + # This is pretty crude, but it'll do for now + if '-A %s -j' % instance_chain in rule: + security_group_chain = rule.split(' ')[-1] + break + self.assertTrue(security_group_chain, + "The security group chain wasn't added") + + self.assertTrue('-A %s -p icmp -s 192.168.11.0/24 -j ACCEPT' % \ + security_group_chain in out_rules, + "ICMP acceptance rule wasn't added") + + self.assertTrue('-A %s -p icmp -s 192.168.11.0/24 -m icmp --icmp-type' + ' 8 -j ACCEPT' % security_group_chain in out_rules, + "ICMP Echo Request acceptance rule wasn't added") + + self.assertTrue('-A %s -p tcp -s 192.168.10.0/24 -m multiport ' + '--dports 80:81 -j ACCEPT' % security_group_chain \ + in out_rules, + "TCP port 80/81 acceptance rule wasn't added") class NWFilterTestCase(test.TestCase): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 3a4b6d469..759ef62ab 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -1105,15 +1105,15 @@ class IptablesFirewallDriver(FirewallDriver): icmp_type = rule.from_port icmp_code = rule.to_port - if icmp_type == '-1': + if icmp_type == -1: icmp_type_arg = None else: icmp_type_arg = '%s' % icmp_type - if not icmp_code == '-1': + if not icmp_code == -1: icmp_type_arg += '/%s' % icmp_code if icmp_type_arg: - args += ['-m', 'icmp', '--icmp_type', icmp_type_arg] + args += ['-m', 'icmp', '--icmp-type', icmp_type_arg] args += ['-j ACCEPT'] our_rules += [' '.join(args)] -- cgit From 4c66b2bb2a1f2b44acf9fa62216b78faa10fdfd7 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Tue, 11 Jan 2011 13:11:30 -0500 Subject: Now tries to install virtualenv via easy_install if not present --- tools/install_venv.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/install_venv.py b/tools/install_venv.py index 32c372352..4e3941210 100644 --- a/tools/install_venv.py +++ b/tools/install_venv.py @@ -66,7 +66,8 @@ def check_dependencies(): # Try installing it via easy_install... if HAS_EASY_INSTALL: print 'Installing virtualenv via easy_install...', - if not run_command(['which', 'easy_install']): + if not (run_command(['which', 'easy_install']) and + run_command(['easy_install', 'virtualenv'])): die('ERROR: virtualenv not found.\n\nNova development requires virtualenv,' ' please install it using your favorite package management tool') print 'done.' -- cgit From d09772b6b1701eaf1ca02b9afafd93b4cafe2342 Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Tue, 11 Jan 2011 14:59:51 -0500 Subject: Added my name to Authors list --- Authors | 1 + 1 file changed, 1 insertion(+) diff --git a/Authors b/Authors index a482c34f0..6e6ecfe1b 100644 --- a/Authors +++ b/Authors @@ -24,6 +24,7 @@ Josh Kearney Joshua McKenty Justin Santa Barbara Ken Pepple +Lorin Hochstein Matt Dietz Michael Gundlach Monsyne Dragon -- cgit From 0ac0cd5976ad6b053aa011071194614ee4f70c48 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 11 Jan 2011 18:03:15 -0500 Subject: Raise meaningful exception when there aren't enough params for a sec group rule. --- nova/api/ec2/cloud.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 99a9677c4..135836348 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -399,8 +399,8 @@ class CloudController(object): criteria = self._revoke_rule_args_to_dict(context, **kwargs) if criteria == None: - raise exception.ApiError(_("No rule for the specified " - "parameters.")) + raise exception.ApiError(_("Not enough parameters to build a " + "valid rule.")) for rule in security_group.rules: match = True @@ -427,6 +427,9 @@ class CloudController(object): group_name) values = self._revoke_rule_args_to_dict(context, **kwargs) + if values is None: + raise exception.ApiError(_("Not enough parameters to build a " + "valid rule.")) values['parent_group_id'] = security_group.id if self._security_group_rule_exists(security_group, values): -- cgit