diff options
| author | Ryan Lane <rlane@wikimedia.org> | 2011-01-27 12:17:43 +0000 |
|---|---|---|
| committer | Ryan Lane <rlane@wikimedia.org> | 2011-01-27 12:17:43 +0000 |
| commit | c02a587ea03fecde26f49bec52f8d96aa551979a (patch) | |
| tree | 68e236b508d5019812bf7de65fc1a209d0df474f /nova | |
| parent | fc8f41e9c34c8d14d1c66ca03ce7098cc6b7f04d (diff) | |
| parent | caca4a1320638b0d806f1854ba8233d941f50e86 (diff) | |
Merge from trunk
Diffstat (limited to 'nova')
34 files changed, 473 insertions, 148 deletions
diff --git a/nova/adminclient.py b/nova/adminclient.py index b2609c8c4..3cdd8347f 100644 --- a/nova/adminclient.py +++ b/nova/adminclient.py @@ -190,6 +190,45 @@ class HostInfo(object): setattr(self, name, value) +class InstanceType(object): + """ + Information about a Nova instance type, as parsed through SAX. + + **Fields include** + + * name + * vcpus + * disk_gb + * memory_mb + * flavor_id + + """ + + def __init__(self, connection=None): + self.connection = connection + self.name = None + self.vcpus = None + self.disk_gb = None + self.memory_mb = None + self.flavor_id = None + + def __repr__(self): + return 'InstanceType:%s' % self.name + + def startElement(self, name, attrs, connection): + return None + + def endElement(self, name, value, connection): + if name == "memoryMb": + self.memory_mb = str(value) + elif name == "flavorId": + self.flavor_id = str(value) + elif name == "diskGb": + self.disk_gb = str(value) + else: + setattr(self, name, str(value)) + + class NovaAdminClient(object): def __init__( @@ -373,3 +412,8 @@ class NovaAdminClient(object): def get_hosts(self): return self.apiconn.get_list('DescribeHosts', {}, [('item', HostInfo)]) + + def get_instance_types(self): + """Grabs the list of all users.""" + return self.apiconn.get_list('DescribeInstanceTypes', {}, + [('item', InstanceType)]) diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 9938b23f8..e25943a13 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -33,6 +33,7 @@ from nova import log as logging from nova import utils from nova import wsgi from nova.api.ec2 import apirequest +from nova.api.ec2 import cloud from nova.auth import manager @@ -170,7 +171,7 @@ class Authenticate(wsgi.Middleware): req.path) # Be explicit for what exceptions are 403, the rest bubble as 500 except (exception.NotFound, exception.NotAuthorized) as ex: - LOG.audit(_("Authentication Failure: %s"), str(ex)) + LOG.audit(_("Authentication Failure: %s"), ex.args[0]) raise webob.exc.HTTPForbidden() # Authenticated! @@ -213,7 +214,8 @@ class Requestify(wsgi.Middleware): LOG.debug(_('arg: %(key)s\t\tval: %(value)s') % locals()) # Success! - api_request = apirequest.APIRequest(self.controller, action, args) + api_request = apirequest.APIRequest(self.controller, action, + req.params['Version'], args) req.environ['ec2.request'] = api_request req.environ['ec2.action_args'] = args return self.application @@ -313,18 +315,31 @@ class Executor(wsgi.Application): result = None try: result = api_request.invoke(context) + except exception.InstanceNotFound as ex: + LOG.info(_('InstanceNotFound raised: %s'), ex.args[0], + context=context) + ec2_id = cloud.id_to_ec2_id(ex.instance_id) + message = _('Instance %s not found') % ec2_id + return self._error(req, context, type(ex).__name__, message) + except exception.VolumeNotFound as ex: + LOG.info(_('VolumeNotFound raised: %s'), ex.args[0], + context=context) + ec2_id = cloud.id_to_ec2_id(ex.volume_id, 'vol-%08x') + message = _('Volume %s not found') % ec2_id + return self._error(req, context, type(ex).__name__, message) except exception.NotFound as ex: - LOG.info(_('NotFound raised: %s'), str(ex), context=context) - return self._error(req, context, type(ex).__name__, str(ex)) + LOG.info(_('NotFound raised: %s'), ex.args[0], context=context) + return self._error(req, context, type(ex).__name__, ex.args[0]) except exception.ApiError as ex: - LOG.exception(_('ApiError raised: %s'), str(ex), context=context) + LOG.exception(_('ApiError raised: %s'), ex.args[0], + context=context) if ex.code: - return self._error(req, context, ex.code, str(ex)) + return self._error(req, context, ex.code, ex.args[0]) else: - return self._error(req, context, type(ex).__name__, str(ex)) + return self._error(req, context, type(ex).__name__, ex.args[0]) except Exception as ex: extra = {'environment': req.environ} - LOG.exception(_('Unexpected error raised: %s'), str(ex), + LOG.exception(_('Unexpected error raised: %s'), ex.args[0], extra=extra, context=context) return self._error(req, context, @@ -347,7 +362,8 @@ class Executor(wsgi.Application): '<Response><Errors><Error><Code>%s</Code>' '<Message>%s</Message></Error></Errors>' '<RequestID>%s</RequestID></Response>' % - (code, message, context.request_id)) + (utils.utf8(code), utils.utf8(message), + utils.utf8(context.request_id))) return resp diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index 78ff1b3e0..d7e899d12 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -26,6 +26,7 @@ from nova import db from nova import exception from nova import log as logging from nova.auth import manager +from nova.compute import instance_types LOG = logging.getLogger('nova.api.ec2.admin') @@ -62,6 +63,14 @@ def host_dict(host): return {} +def instance_dict(name, inst): + return {'name': name, + 'memory_mb': inst['memory_mb'], + 'vcpus': inst['vcpus'], + 'disk_gb': inst['local_gb'], + 'flavor_id': inst['flavorid']} + + class AdminController(object): """ API Controller for users, hosts, nodes, and workers. @@ -70,6 +79,10 @@ class AdminController(object): def __str__(self): return 'AdminController' + def describe_instance_types(self, _context, **_kwargs): + return {'instanceTypeSet': [instance_dict(n, v) for n, v in + instance_types.INSTANCE_TYPES.iteritems()]} + def describe_user(self, _context, name, **_kwargs): """Returns user data, including access and secret keys.""" return user_dict(manager.AuthManager().get_user(name)) diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py index d8a2b5f53..7e72d67fb 100644 --- a/nova/api/ec2/apirequest.py +++ b/nova/api/ec2/apirequest.py @@ -83,9 +83,10 @@ def _try_convert(value): class APIRequest(object): - def __init__(self, controller, action, args): + def __init__(self, controller, action, version, args): self.controller = controller self.action = action + self.version = version self.args = args def invoke(self, context): @@ -132,7 +133,7 @@ class APIRequest(object): response_el = xml.createElement(self.action + 'Response') response_el.setAttribute('xmlns', - 'http://ec2.amazonaws.com/doc/2009-11-30/') + 'http://ec2.amazonaws.com/doc/%s/' % self.version) request_id_el = xml.createElement('requestId') request_id_el.appendChild(xml.createTextNode(request_id)) response_el.appendChild(request_id_el) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 3b228bf1a..9683e2ebd 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -529,11 +529,14 @@ class CloudController(object): def describe_volumes(self, context, volume_id=None, **kwargs): if volume_id: - volume_id = [ec2_id_to_id(x) for x in volume_id] - volumes = self.volume_api.get_all(context) - # NOTE(vish): volume_id is an optional list of volume ids to filter by. - volumes = [self._format_volume(context, v) for v in volumes - if volume_id is None or v['id'] in volume_id] + volumes = [] + for ec2_id in volume_id: + internal_id = ec2_id_to_id(ec2_id) + volume = self.volume_api.get(context, internal_id) + volumes.append(volume) + else: + volumes = self.volume_api.get_all(context) + volumes = [self._format_volume(context, v) for v in volumes] return {'volumeSet': volumes} def _format_volume(self, context, volume): @@ -658,8 +661,11 @@ class CloudController(object): reservations = {} # NOTE(vish): instance_id is an optional list of ids to filter by if instance_id: - instance_id = [ec2_id_to_id(x) for x in instance_id] - instances = [self.compute_api.get(context, x) for x in instance_id] + instances = [] + for ec2_id in instance_id: + internal_id = ec2_id_to_id(ec2_id) + instance = self.compute_api.get(context, internal_id) + instances.append(instance) else: instances = self.compute_api.get_all(context, **kwargs) for instance in instances: diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index f2caac483..c70bb39ed 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -38,9 +38,6 @@ from nova.api.openstack import shared_ip_groups LOG = logging.getLogger('nova.api.openstack') FLAGS = flags.FLAGS -flags.DEFINE_string('os_krm_mapping_file', - 'krm_mapping.json', - 'Location of OpenStack Flavor/OS:EC2 Kernel/Ramdisk/Machine JSON file.') flags.DEFINE_bool('allow_admin_api', False, 'When True, this API service will accept admin operations.') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index 037ed47a0..6d2fa16e8 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -54,7 +54,7 @@ def get_image_id_from_image_hash(image_service, context, image_hash): except NotImplementedError: items = image_service.index(context) for image in items: - image_id = image['imageId'] + image_id = image['id'] if abs(hash(image_id)) == int(image_hash): return image_id raise exception.NotFound(image_hash) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 22b4797c9..17c5519a1 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -124,17 +124,23 @@ 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 image_id in mapping: - return mapping[image_id] + def _get_kernel_ramdisk_from_image(self, req, image_id): + """ + Machine images are associated with Kernels and Ramdisk images via + metadata stored in Glance as 'image_properties' + """ + def lookup(param): + _image_id = image_id + try: + return image['properties'][param] + except KeyError: + raise exception.NotFound( + _("%(param)s property not found for image %(_image_id)s") % + locals()) - msg = _("No entry for image '%(image_id)s'" - " in mapping file '%(mapping_filename)s'") % locals() - raise exception.NotFound(msg) + image_id = str(image_id) + image = self._image_service.show(req.environ['nova.context'], image_id) + return lookup('kernel_id'), lookup('ramdisk_id') def create(self, req): """ Creates a new server for a given user """ @@ -146,7 +152,8 @@ class Controller(wsgi.Controller): 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) + kernel_id, ramdisk_id = self._get_kernel_ramdisk_from_image( + req, image_id) instances = self.compute_api.create( req.environ['nova.context'], instance_types.get_by_flavor_id(env['server']['flavorId']), diff --git a/nova/compute/api.py b/nova/compute/api.py index 1d8b9d79f..ac02dbcfa 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -318,7 +318,7 @@ class API(base.Base): def get(self, context, instance_id): """Get a single instance with the given ID.""" - rv = self.db.instance_get_by_id(context, instance_id) + rv = self.db.instance_get(context, instance_id) return dict(rv.iteritems()) def get_all(self, context, project_id=None, reservation_id=None, diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 376b1ed68..0f9bf301f 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -118,7 +118,7 @@ class ComputeManager(manager.Manager): """Do any initialization that needs to be run if this is a standalone service. """ - self.driver.init_host() + self.driver.init_host(host=self.host) def _update_state(self, context, instance_id): """Update the state of an instance from the driver info.""" diff --git a/nova/db/api.py b/nova/db/api.py index f9d561587..789cb8ebb 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -71,7 +71,6 @@ class NoMoreTargets(exception.Error): """No more available blades""" pass - ################### @@ -351,6 +350,11 @@ def instance_get_all_by_project(context, project_id): return IMPL.instance_get_all_by_project(context, project_id) +def instance_get_all_by_host(context, host): + """Get all instance belonging to a host.""" + return IMPL.instance_get_all_by_host(context, host) + + def instance_get_all_by_reservation(context, reservation_id): """Get all instance belonging to a reservation.""" return IMPL.instance_get_all_by_reservation(context, reservation_id) @@ -375,11 +379,6 @@ def instance_get_project_vpn(context, project_id): return IMPL.instance_get_project_vpn(context, project_id) -def instance_get_by_id(context, instance_id): - """Get an instance by id.""" - return IMPL.instance_get_by_id(context, instance_id) - - def instance_is_vpn(context, instance_id): """True if instance is a vpn.""" return IMPL.instance_is_vpn(context, instance_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 370ca651a..895e7eabe 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -685,6 +685,7 @@ def instance_get(context, instance_id, session=None): options(joinedload_all('fixed_ip.floating_ips')).\ options(joinedload_all('security_groups.rules')).\ options(joinedload('volumes')).\ + options(joinedload_all('fixed_ip.network')).\ filter_by(id=instance_id).\ filter_by(deleted=can_read_deleted(context)).\ first() @@ -698,7 +699,9 @@ def instance_get(context, instance_id, session=None): filter_by(deleted=False).\ first() if not result: - raise exception.NotFound(_('No instance for id %s') % instance_id) + raise exception.InstanceNotFound(_('Instance %s not found') + % instance_id, + instance_id) return result @@ -724,6 +727,17 @@ def instance_get_all_by_user(context, user_id): all() +@require_admin_context +def instance_get_all_by_host(context, host): + session = get_session() + return session.query(models.Instance).\ + options(joinedload_all('fixed_ip.floating_ips')).\ + options(joinedload('security_groups')).\ + filter_by(host=host).\ + filter_by(deleted=can_read_deleted(context)).\ + all() + + @require_context def instance_get_all_by_project(context, project_id): authorize_project_context(context, project_id) @@ -771,33 +785,6 @@ def instance_get_project_vpn(context, project_id): @require_context -def instance_get_by_id(context, instance_id): - session = get_session() - - if is_admin_context(context): - result = session.query(models.Instance).\ - options(joinedload_all('fixed_ip.floating_ips')).\ - options(joinedload('security_groups')).\ - options(joinedload_all('fixed_ip.network')).\ - filter_by(id=instance_id).\ - filter_by(deleted=can_read_deleted(context)).\ - first() - elif is_user_context(context): - result = session.query(models.Instance).\ - options(joinedload('security_groups')).\ - options(joinedload_all('fixed_ip.floating_ips')).\ - options(joinedload_all('fixed_ip.network')).\ - filter_by(project_id=context.project_id).\ - filter_by(id=instance_id).\ - filter_by(deleted=False).\ - first() - if not result: - raise exception.NotFound(_('Instance %s not found') % (instance_id)) - - return result - - -@require_context def instance_get_fixed_address(context, instance_id): session = get_session() with session.begin(): @@ -1396,17 +1383,20 @@ def volume_get(context, volume_id, session=None): if is_admin_context(context): result = session.query(models.Volume).\ + options(joinedload('instance')).\ filter_by(id=volume_id).\ filter_by(deleted=can_read_deleted(context)).\ first() elif is_user_context(context): result = session.query(models.Volume).\ + options(joinedload('instance')).\ filter_by(project_id=context.project_id).\ filter_by(id=volume_id).\ filter_by(deleted=False).\ first() if not result: - raise exception.NotFound(_('No volume for id %s') % volume_id) + raise exception.VolumeNotFound(_('Volume %s not found') % volume_id, + volume_id) return result @@ -1451,7 +1441,8 @@ def volume_get_instance(context, volume_id): options(joinedload('instance')).\ first() if not result: - raise exception.NotFound(_('Volume %s not found') % ec2_id) + raise exception.VolumeNotFound(_('Volume %s not found') % volume_id, + volume_id) return result.instance diff --git a/nova/db/sqlalchemy/session.py b/nova/db/sqlalchemy/session.py index c3876c02a..dc885f138 100644 --- a/nova/db/sqlalchemy/session.py +++ b/nova/db/sqlalchemy/session.py @@ -22,6 +22,7 @@ Session Handling for SQLAlchemy backend from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker +from nova import exception from nova import flags FLAGS = flags.FLAGS @@ -43,4 +44,6 @@ def get_session(autocommit=True, expire_on_commit=False): autocommit=autocommit, expire_on_commit=expire_on_commit)) session = _MAKER() + session.query = exception.wrap_db_error(session.query) + session.flush = exception.wrap_db_error(session.flush) return session diff --git a/nova/exception.py b/nova/exception.py index 2320e2214..7d65bd6a5 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -46,7 +46,6 @@ class Error(Exception): class ApiError(Error): - def __init__(self, message='Unknown', code='Unknown'): self.message = message self.code = code @@ -57,6 +56,18 @@ class NotFound(Error): pass +class InstanceNotFound(NotFound): + def __init__(self, message, instance_id): + self.instance_id = instance_id + super(InstanceNotFound, self).__init__(message) + + +class VolumeNotFound(NotFound): + def __init__(self, message, volume_id): + self.volume_id = volume_id + super(VolumeNotFound, self).__init__(message) + + class Duplicate(Error): pass @@ -81,6 +92,24 @@ class TimeoutException(Error): pass +class DBError(Error): + """Wraps an implementation specific exception""" + def __init__(self, inner_exception): + self.inner_exception = inner_exception + super(DBError, self).__init__(str(inner_exception)) + + +def wrap_db_error(f): + def _wrap(*args, **kwargs): + try: + return f(*args, **kwargs) + except Exception, e: + LOG.exception(_('DB exception wrapped')) + raise DBError(e) + return _wrap + _wrap.func_name = f.func_name + + def wrap_exception(f): def _wrap(*args, **kw): try: diff --git a/nova/image/local.py b/nova/image/local.py index b44593221..f78b9aa89 100644 --- a/nova/image/local.py +++ b/nova/image/local.py @@ -18,6 +18,7 @@ import cPickle as pickle import os.path import random +import tempfile from nova import exception from nova.image import service @@ -26,15 +27,12 @@ from nova.image import service class LocalImageService(service.BaseImageService): """Image service storing images to local disk. + It assumes that image_ids are integers. - It assumes that image_ids are integers.""" + """ def __init__(self): - self._path = "/tmp/nova/images" - try: - os.makedirs(self._path) - except OSError: # Exists - pass + self._path = tempfile.mkdtemp() def _path_to(self, image_id): return os.path.join(self._path, str(image_id)) @@ -56,9 +54,7 @@ class LocalImageService(service.BaseImageService): raise exception.NotFound def create(self, context, data): - """ - Store the image data and return the new image id. - """ + """Store the image data and return the new image id.""" id = random.randint(0, 2 ** 31 - 1) data['id'] = id self.update(context, id, data) @@ -72,8 +68,9 @@ class LocalImageService(service.BaseImageService): raise exception.NotFound def delete(self, context, image_id): - """ - Delete the given image. Raises OSError if the image does not exist. + """Delete the given image. + Raises OSError if the image does not exist. + """ try: os.unlink(self._path_to(image_id)) @@ -81,8 +78,13 @@ class LocalImageService(service.BaseImageService): raise exception.NotFound def delete_all(self): - """ - Clears out all images in local directory - """ + """Clears out all images in local directory.""" for id in self._ids(): os.unlink(self._path_to(id)) + + def delete_imagedir(self): + """Deletes the local directory. + Raises OSError if directory is not empty. + + """ + os.rmdir(self._path) diff --git a/nova/image/s3.py b/nova/image/s3.py index 7b04aa072..08a40f191 100644 --- a/nova/image/s3.py +++ b/nova/image/s3.py @@ -65,12 +65,19 @@ class S3ImageService(service.BaseImageService): 'image_id': image_id})) return image_id + def _fix_image_id(self, images): + """S3 has imageId but OpenStack wants id""" + for image in images: + if 'imageId' in image: + image['id'] = image['imageId'] + return images + def index(self, context): """Return a list of all images that a user can see.""" response = self._conn(context).make_request( method='GET', bucket='_images') - return json.loads(response.read()) + return self._fix_image_id(json.loads(response.read())) def show(self, context, image_id): """return a image object if the context has permissions""" diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py index d29e17603..cdd1f666a 100644 --- a/nova/network/linux_net.py +++ b/nova/network/linux_net.py @@ -198,9 +198,9 @@ def ensure_bridge(bridge, interface, net_attrs=None): net_attrs['broadcast'], net_attrs['netmask'])) if(FLAGS.use_ipv6): - _execute("sudo ifconfig %s add %s up" % \ - (bridge, - net_attrs['cidr_v6'])) + _execute("sudo ip -f inet6 addr change %s dev %s" % + (net_attrs['cidr_v6'], bridge)) + _execute("sudo ifconfig %s up" % bridge) else: _execute("sudo ifconfig %s up" % bridge) if FLAGS.use_nova_chains: @@ -298,10 +298,9 @@ interface %s % pid, check_exit_code=False) if conffile in out: try: - _execute('sudo kill -HUP %d' % pid) - return + _execute('sudo kill %d' % pid) except Exception as exc: # pylint: disable-msg=W0703 - LOG.debug(_("Hupping radvd threw %s"), exc) + LOG.debug(_("killing radvd threw %s"), exc) else: LOG.debug(_("Pid %d is stale, relaunching radvd"), pid) command = _ra_cmd(network_ref) diff --git a/nova/network/manager.py b/nova/network/manager.py index dd429d122..fbcbea131 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -395,6 +395,7 @@ class FlatDHCPManager(FlatManager): standalone service. """ super(FlatDHCPManager, self).init_host() + self.driver.init_host() self.driver.metadata_forward() def setup_compute_network(self, context, instance_id): @@ -427,6 +428,10 @@ class FlatDHCPManager(FlatManager): self.driver.ensure_bridge(network_ref['bridge'], FLAGS.flat_interface, network_ref) + if not FLAGS.fake_network: + self.driver.update_dhcp(context, network_id) + if(FLAGS.use_ipv6): + self.driver.update_ra(context, network_id) class VlanManager(NetworkManager): @@ -460,8 +465,8 @@ class VlanManager(NetworkManager): standalone service. """ super(VlanManager, self).init_host() - self.driver.metadata_forward() self.driver.init_host() + self.driver.metadata_forward() def allocate_fixed_ip(self, context, instance_id, *args, **kwargs): """Gets a fixed ip from the pool.""" @@ -496,7 +501,7 @@ class VlanManager(NetworkManager): network_ref['bridge']) def create_networks(self, context, cidr, num_networks, network_size, - vlan_start, vpn_start, cidr_v6): + cidr_v6, vlan_start, vpn_start): """Create networks based on parameters.""" fixed_net = IPy.IP(cidr) fixed_net_v6 = IPy.IP(cidr_v6) diff --git a/nova/service.py b/nova/service.py index 2c30997f2..59648adf2 100644 --- a/nova/service.py +++ b/nova/service.py @@ -157,8 +157,9 @@ class Service(object): report_interval = FLAGS.report_interval if not periodic_interval: periodic_interval = FLAGS.periodic_interval - logging.audit(_("Starting %s node (version %s)"), topic, - version.version_string_with_vcs()) + vcs_string = version.version_string_with_vcs() + logging.audit(_("Starting %(topic)s node (version %(vcs_string)s)") + % locals()) service_obj = cls(host, binary, topic, manager, report_interval, periodic_interval) diff --git a/nova/test.py b/nova/test.py index 881baccd5..a12cf9d32 100644 --- a/nova/test.py +++ b/nova/test.py @@ -69,9 +69,10 @@ class TestCase(unittest.TestCase): network_manager.VlanManager().create_networks(ctxt, FLAGS.fixed_range, 5, 16, + FLAGS.fixed_range_v6, FLAGS.vlan_start, FLAGS.vpn_start, - FLAGS.fixed_range_v6) + ) # emulate some of the mox stuff, we can't use the metaclass # because it screws with our generators diff --git a/nova/tests/api/openstack/test_images.py b/nova/tests/api/openstack/test_images.py index 5d9ddefbe..8ab4d7569 100644 --- a/nova/tests/api/openstack/test_images.py +++ b/nova/tests/api/openstack/test_images.py @@ -143,6 +143,7 @@ class LocalImageServiceTest(unittest.TestCase, def tearDown(self): self.service.delete_all() + self.service.delete_imagedir() self.stubs.UnsetAll() diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 29883e7c8..724f14f19 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -76,7 +76,7 @@ class ServersTest(unittest.TestCase): fakes.stub_out_key_pair_funcs(self.stubs) fakes.stub_out_image_service(self.stubs) self.stubs.Set(nova.db.api, 'instance_get_all', return_servers) - self.stubs.Set(nova.db.api, 'instance_get_by_id', return_server) + self.stubs.Set(nova.db.api, 'instance_get', return_server) self.stubs.Set(nova.db.api, 'instance_get_all_by_user', return_servers) self.stubs.Set(nova.db.api, 'instance_add_security_group', diff --git a/nova/tests/test_api.py b/nova/tests/test_api.py index 66a16b0cb..2569e262b 100644 --- a/nova/tests/test_api.py +++ b/nova/tests/test_api.py @@ -36,6 +36,7 @@ from nova.auth import manager class FakeHttplibSocket(object): """a fake socket implementation for httplib.HTTPResponse, trivial""" def __init__(self, response_string): + self.response_string = response_string self._buffer = StringIO.StringIO(response_string) def makefile(self, _mode, _other): @@ -66,13 +67,16 @@ class FakeHttplibConnection(object): # For some reason, the response doesn't have "HTTP/1.0 " prepended; I # guess that's a function the web server usually provides. resp = "HTTP/1.0 %s" % resp - sock = FakeHttplibSocket(resp) - self.http_response = httplib.HTTPResponse(sock) + self.sock = FakeHttplibSocket(resp) + self.http_response = httplib.HTTPResponse(self.sock) self.http_response.begin() def getresponse(self): return self.http_response + def getresponsebody(self): + return self.sock.response_string + def close(self): """Required for compatibility with boto/tornado""" pass @@ -104,7 +108,7 @@ class ApiEc2TestCase(test.TestCase): self.app = ec2.Authenticate(ec2.Requestify(ec2.Executor(), 'nova.api.ec2.cloud.CloudController')) - def expect_http(self, host=None, is_secure=False): + def expect_http(self, host=None, is_secure=False, api_version=None): """Returns a new EC2 connection""" self.ec2 = boto.connect_ec2( aws_access_key_id='fake', @@ -113,13 +117,31 @@ class ApiEc2TestCase(test.TestCase): region=regioninfo.RegionInfo(None, 'test', self.host), port=8773, path='/services/Cloud') + if api_version: + self.ec2.APIVersion = api_version self.mox.StubOutWithMock(self.ec2, 'new_http_connection') - http = FakeHttplibConnection( + self.http = FakeHttplibConnection( self.app, '%s:8773' % (self.host), False) # pylint: disable-msg=E1103 - self.ec2.new_http_connection(host, is_secure).AndReturn(http) - return http + self.ec2.new_http_connection(host, is_secure).AndReturn(self.http) + return self.http + + def test_xmlns_version_matches_request_version(self): + self.expect_http(api_version='2010-10-30') + self.mox.ReplayAll() + + user = self.manager.create_user('fake', 'fake', 'fake') + project = self.manager.create_project('fake', 'fake', 'fake') + + # Any request should be fine + self.ec2.get_all_instances() + self.assertTrue(self.ec2.APIVersion in self.http.getresponsebody(), + 'The version in the xmlns of the response does ' + 'not match the API version given in the request.') + + self.manager.delete_project(project) + self.manager.delete_user(user) def test_describe_instances(self): """Test that, after creating a user and a project, the describe diff --git a/nova/tests/test_localization.py b/nova/tests/test_localization.py new file mode 100644 index 000000000..6992773f5 --- /dev/null +++ b/nova/tests/test_localization.py @@ -0,0 +1,100 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2011 OpenStack LLC +# +# 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 glob +import logging +import os +import re +import sys +import unittest + +import nova + + +class LocalizationTestCase(unittest.TestCase): + def test_multiple_positional_format_placeholders(self): + pat = re.compile("\W_\(") + single_pat = re.compile("\W%\W") + root_path = os.path.dirname(nova.__file__) + problems = {} + for root, dirs, files in os.walk(root_path): + for fname in files: + if not fname.endswith(".py"): + continue + pth = os.path.join(root, fname) + txt = fulltext = file(pth).read() + txt_lines = fulltext.splitlines() + if not pat.search(txt): + continue + problems[pth] = [] + pos = txt.find("_(") + while pos > -1: + # Make sure that this isn't part of a dunder; + # e.g., __init__(... + # or something like 'self.assert_(...' + test_txt = txt[pos - 1: pos + 10] + if not (pat.search(test_txt)): + txt = txt[pos + 2:] + pos = txt.find("_(") + continue + pos += 2 + txt = txt[pos:] + innerChars = [] + # Count pairs of open/close parens until _() closing + # paren is found. + parenCount = 1 + pos = 0 + while parenCount > 0: + char = txt[pos] + if char == "(": + parenCount += 1 + elif char == ")": + parenCount -= 1 + innerChars.append(char) + pos += 1 + inner_all = "".join(innerChars) + # Filter out '%%' and '%(' + inner = inner_all.replace("%%", "").replace("%(", "") + # Filter out the single '%' operators + inner = single_pat.sub("", inner) + # Within the remaining content, count % + fmtCount = inner.count("%") + if fmtCount > 1: + inner_first = inner_all.splitlines()[0] + lns = ["%s" % (p + 1) + for p, t in enumerate(txt_lines) + if inner_first in t] + lnums = ", ".join(lns) + # Using ugly string concatenation to avoid having + # this test fail itself. + inner_all = "_" + "(" + "%s" % inner_all + problems[pth].append("Line: %s Text: %s" % + (lnums, inner_all)) + # Look for more + pos = txt.find("_(") + if not problems[pth]: + del problems[pth] + if problems: + out = ["Problem(s) found in localized string formatting", + "(see http://www.gnu.org/software/hello/manual/" + "gettext/Python.html for more information)", + "", + " ------------ Files to fix ------------"] + for pth in problems: + out.append(" %s:" % pth) + for val in set(problems[pth]): + out.append(" %s" % val) + raise AssertionError("\n".join(out)) diff --git a/nova/tests/test_virt.py b/nova/tests/test_virt.py index 0b9b847a0..6e5a0114b 100644 --- a/nova/tests/test_virt.py +++ b/nova/tests/test_virt.py @@ -221,7 +221,12 @@ class IptablesFirewallTestCase(test.TestCase): self.project = self.manager.create_project('fake', 'fake', 'fake') self.context = context.RequestContext('fake', 'fake') self.network = utils.import_object(FLAGS.network_manager) - self.fw = libvirt_conn.IptablesFirewallDriver() + + class FakeLibvirtConnection(object): + pass + self.fake_libvirt_connection = FakeLibvirtConnection() + self.fw = libvirt_conn.IptablesFirewallDriver( + get_connection=lambda: self.fake_libvirt_connection) def tearDown(self): self.manager.delete_project(self.project) @@ -474,6 +479,19 @@ class NWFilterTestCase(test.TestCase): 'project_id': 'fake'}) inst_id = instance_ref['id'] + ip = '10.11.12.13' + + network_ref = db.project_get_network(self.context, + 'fake') + + fixed_ip = {'address': ip, + 'network_id': network_ref['id']} + + admin_ctxt = context.get_admin_context() + db.fixed_ip_create(admin_ctxt, fixed_ip) + db.fixed_ip_update(admin_ctxt, ip, {'allocated': True, + 'instance_id': instance_ref['id']}) + def _ensure_all_called(): instance_filter = 'nova-instance-%s' % instance_ref['name'] secgroup_filter = 'nova-secgroup-%s' % self.security_group['id'] diff --git a/nova/utils.py b/nova/utils.py index 2f3bd2894..5f5225289 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -206,21 +206,17 @@ def last_octet(address): def get_my_linklocal(interface): try: if_str = execute("ip -f inet6 -o addr show %s" % interface) - condition = "\s+inet6\s+([0-9a-f:]+/\d+)\s+scope\s+link" + condition = "\s+inet6\s+([0-9a-f:]+)/\d+\s+scope\s+link" links = [re.search(condition, x) for x in if_str[0].split('\n')] address = [w.group(1) for w in links if w is not None] if address[0] is not None: return address[0] else: - return 'fe00::' - except IndexError as ex: - LOG.warn(_("Couldn't get Link Local IP of %(interface)s :%(ex)s") - % locals()) - except ProcessExecutionError as ex: - LOG.warn(_("Couldn't get Link Local IP of %(interface)s :%(ex)s") - % locals()) - except: - return 'fe00::' + raise exception.Error(_("Link Local address is not found.:%s") + % if_str) + except Exception as ex: + raise exception.Error(_("Couldn't get Link Local IP of %(interface)s" + " :%(ex)s") % locals()) def to_global_ipv6(prefix, mac): diff --git a/nova/virt/fake.py b/nova/virt/fake.py index f8b3c7807..161445b86 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -76,9 +76,10 @@ class FakeConnection(object): cls._instance = cls() return cls._instance - def init_host(self): + def init_host(self, host): """ - Initialize anything that is necessary for the driver to function + Initialize anything that is necessary for the driver to function, + including catching up with currently running VM's on the given host. """ return diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 5facb7aff..29d18dac5 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -113,7 +113,7 @@ class HyperVConnection(object): self._conn = wmi.WMI(moniker='//./root/virtualization') self._cim_conn = wmi.WMI(moniker='//./root/cimv2') - def init_host(self): + def init_host(self, host): #FIXME(chiradeep): implement this LOG.debug(_('In init host')) pass @@ -191,7 +191,7 @@ class HyperVConnection(object): vcpus = long(instance['vcpus']) procsetting.VirtualQuantity = vcpus procsetting.Reservation = vcpus - procsetting.Limit = vcpus + procsetting.Limit = 100000 # static assignment to 100% (job, ret_val) = vs_man_svc.ModifyVirtualSystemResources( vm.path_(), [procsetting.GetText_(1)]) diff --git a/nova/virt/images.py b/nova/virt/images.py index 9c987e14d..7a6fef330 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -111,5 +111,8 @@ def _image_path(path): def image_url(image): + if FLAGS.image_service == "nova.image.glance.GlanceImageService": + return "http://%s:%s/images/%s" % (FLAGS.glance_host, + FLAGS.glance_port, image) return "http://%s:%s/_images/%s/image" % (FLAGS.s3_host, FLAGS.s3_port, image) diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index 8139c3620..88bfbc668 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -75,11 +75,13 @@ <!-- <model type='virtio'/> CANT RUN virtio network right now --> <filterref filter="nova-instance-${name}"> <parameter name="IP" value="${ip_address}" /> - <parameter name="DHCPSERVER" value="${dhcp_server}" /> - <parameter name="RASERVER" value="${ra_server}" /> + <parameter name="DHCPSERVER" value="${dhcp_server}" /> #if $getVar('extra_params', False) ${extra_params} #end if +#if $getVar('ra_server', False) + <parameter name="RASERVER" value="${ra_server}" /> +#end if </filterref> </interface> diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 259e19a69..4e0fd106f 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -149,16 +149,34 @@ class LibvirtConnection(object): self._wrapped_conn = None self.read_only = read_only - self.nwfilter = NWFilterFirewall(self._get_connection) + fw_class = utils.import_class(FLAGS.firewall_driver) + self.firewall_driver = fw_class(get_connection=self._get_connection) - if not FLAGS.firewall_driver: - self.firewall_driver = self.nwfilter - self.nwfilter.handle_security_groups = True - else: - self.firewall_driver = utils.import_object(FLAGS.firewall_driver) + def init_host(self, host): + # Adopt existing VM's running here + ctxt = context.get_admin_context() + for instance in db.instance_get_all_by_host(ctxt, host): + try: + LOG.debug(_('Checking state of %s'), instance['name']) + state = self.get_info(instance['name'])['state'] + except exception.NotFound: + state = power_state.SHUTOFF - def init_host(self): - pass + LOG.debug(_('Current state of %(name)s was %(state)s.'), + {'name': instance['name'], 'state': state}) + db.instance_set_state(ctxt, instance['id'], state) + + if state == power_state.SHUTOFF: + # TODO(soren): This is what the compute manager does when you + # terminate # an instance. At some point I figure we'll have a + # "terminated" state and some sort of cleanup job that runs + # occasionally, cleaning them out. + db.instance_destroy(ctxt, instance['id']) + + if state != power_state.RUNNING: + continue + self.firewall_driver.prepare_instance_filter(instance) + self.firewall_driver.apply_instance_filter(instance) def _get_connection(self): if not self._wrapped_conn or not self._test_connection(): @@ -386,7 +404,7 @@ class LibvirtConnection(object): instance['id'], power_state.NOSTATE, 'launching') - self.nwfilter.setup_basic_filtering(instance) + self.firewall_driver.setup_basic_filtering(instance) self.firewall_driver.prepare_instance_filter(instance) self._create_image(instance, xml) self._conn.createXML(xml, 0) @@ -655,8 +673,7 @@ class LibvirtConnection(object): # Assume that the gateway also acts as the dhcp server. dhcp_server = network['gateway'] ra_server = network['ra_server'] - if not ra_server: - ra_server = 'fd00::' + if FLAGS.allow_project_net_traffic: if FLAGS.use_ipv6: net, mask = _get_net_and_mask(network['cidr']) @@ -695,11 +712,13 @@ class LibvirtConnection(object): 'mac_address': instance['mac_address'], 'ip_address': ip_address, 'dhcp_server': dhcp_server, - 'ra_server': ra_server, 'extra_params': extra_params, 'rescue': rescue, 'local': instance_type['local_gb'], 'driver_type': driver_type} + + if ra_server: + xml_info['ra_server'] = ra_server + "/128" if not rescue: if instance['kernel_id']: xml_info['kernel'] = xml_info['basepath'] + "/kernel" @@ -882,6 +901,20 @@ class FirewallDriver(object): the security group.""" raise NotImplementedError() + def setup_basic_filtering(self, instance): + """Create rules to block spoofing and allow dhcp. + + This gets called when spawning an instance, before + :method:`prepare_instance_filter`. + + """ + raise NotImplementedError() + + def _ra_server_for_instance(self, instance): + network = db.network_get_by_instance(context.get_admin_context(), + instance['id']) + return network['ra_server'] + class NWFilterFirewall(FirewallDriver): """ @@ -929,11 +962,15 @@ class NWFilterFirewall(FirewallDriver): """ - def __init__(self, get_connection): + def __init__(self, get_connection, **kwargs): self._libvirt_get_connection = get_connection self.static_filters_configured = False self.handle_security_groups = False + def apply_instance_filter(self, instance): + """No-op. Everything is done in prepare_instance_filter""" + pass + def _get_connection(self): return self._libvirt_get_connection() _conn = property(_get_connection) @@ -1092,7 +1129,9 @@ class NWFilterFirewall(FirewallDriver): 'nova-base-ipv6', 'nova-allow-dhcp-server'] if FLAGS.use_ipv6: - instance_secgroup_filter_children += ['nova-allow-ra-server'] + ra_server = self._ra_server_for_instance(instance) + if ra_server: + instance_secgroup_filter_children += ['nova-allow-ra-server'] ctxt = context.get_admin_context() @@ -1119,10 +1158,6 @@ class NWFilterFirewall(FirewallDriver): return - def apply_instance_filter(self, instance): - """No-op. Everything is done in prepare_instance_filter""" - pass - def refresh_security_group_rules(self, security_group_id): return self._define_filter( self.security_group_to_nwfilter_xml(security_group_id)) @@ -1170,9 +1205,14 @@ class NWFilterFirewall(FirewallDriver): class IptablesFirewallDriver(FirewallDriver): - def __init__(self, execute=None): + def __init__(self, execute=None, **kwargs): self.execute = execute or utils.execute self.instances = {} + self.nwfilter = NWFilterFirewall(kwargs['get_connection']) + + def setup_basic_filtering(self, instance): + """Use NWFilter from libvirt for this.""" + return self.nwfilter.setup_basic_filtering(instance) def apply_instance_filter(self, instance): """No-op. Everything is done in prepare_instance_filter""" @@ -1228,6 +1268,7 @@ class IptablesFirewallDriver(FirewallDriver): our_chains += [':nova-local - [0:0]'] our_rules += ['-A FORWARD -j nova-local'] + our_rules += ['-A OUTPUT -j nova-local'] security_groups = {} # Add our chains @@ -1268,13 +1309,23 @@ class IptablesFirewallDriver(FirewallDriver): if(ip_version == 4): # Allow DHCP responses dhcp_server = self._dhcp_server_for_instance(instance) - our_rules += ['-A %s -s %s -p udp --sport 67 --dport 68' % - (chain_name, dhcp_server)] + our_rules += ['-A %s -s %s -p udp --sport 67 --dport 68 ' + '-j ACCEPT ' % (chain_name, dhcp_server)] + #Allow project network traffic + if (FLAGS.allow_project_net_traffic): + cidr = self._project_cidr_for_instance(instance) + our_rules += ['-A %s -s %s -j ACCEPT' % (chain_name, cidr)] elif(ip_version == 6): # Allow RA responses ra_server = self._ra_server_for_instance(instance) - our_rules += ['-A %s -s %s -p icmpv6' % - (chain_name, ra_server)] + if ra_server: + our_rules += ['-A %s -s %s -p icmpv6 -j ACCEPT' % + (chain_name, ra_server + "/128")] + #Allow project network traffic + if (FLAGS.allow_project_net_traffic): + cidrv6 = self._project_cidrv6_for_instance(instance) + our_rules += ['-A %s -s %s -j ACCEPT' % + (chain_name, cidrv6)] # If nothing matches, jump to the fallback chain our_rules += ['-A %s -j nova-fallback' % (chain_name,)] @@ -1369,3 +1420,13 @@ class IptablesFirewallDriver(FirewallDriver): network = db.network_get_by_instance(context.get_admin_context(), instance['id']) return network['ra_server'] + + def _project_cidr_for_instance(self, instance): + network = db.network_get_by_instance(context.get_admin_context(), + instance['id']) + return network['cidr'] + + def _project_cidrv6_for_instance(self, instance): + network = db.network_get_by_instance(context.get_admin_context(), + instance['id']) + return network['cidr_v6'] diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 4afd28dd8..4bbd522c1 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -640,7 +640,7 @@ def with_vdi_attached_here(session, vdi, read_only, f): session.get_xenapi().VBD.plug(vbd) LOG.debug(_('Plugging VBD %s done.'), vbd) orig_dev = session.get_xenapi().VBD.get_device(vbd) - LOG.debug(_('VBD %s plugged as %s'), vbd, orig_dev) + LOG.debug(_('VBD %(vbd)s plugged as %(orig_dev)s') % locals()) dev = remap_vbd_dev(orig_dev) if dev != orig_dev: LOG.debug(_('VBD %(vbd)s plugged into wrong dev, ' diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 628a171fa..e84ce20c4 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -149,7 +149,7 @@ class VMOps(object): if isinstance(instance_or_vm, (int, long)): ctx = context.get_admin_context() try: - instance_obj = db.instance_get_by_id(ctx, instance_or_vm) + instance_obj = db.instance_get(ctx, instance_or_vm) instance_name = instance_obj.name except exception.NotFound: # The unit tests screw this up, as they use an integer for diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 78f0d14b9..a0b0499b8 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -141,7 +141,7 @@ class XenAPIConnection(object): self._vmops = VMOps(session) self._volumeops = VolumeOps(session) - def init_host(self): + def init_host(self, host): #FIXME(armando): implement this #NOTE(armando): would we need a method #to call when shutting down the host? |
