diff options
40 files changed, 390 insertions, 412 deletions
diff --git a/bin/nova-rootwrap b/bin/nova-rootwrap index c8e880d79..72a8c6309 100755 --- a/bin/nova-rootwrap +++ b/bin/nova-rootwrap @@ -16,20 +16,18 @@ # License for the specific language governing permissions and limitations # under the License. -"""Root wrapper for Nova +"""Root wrapper for OpenStack services - Filters which commands nova is allowed to run as another user. + Filters which commands a service is allowed to run as another user. - To use this, you should set the following in nova.conf: + To use this with nova, you should set the following in nova.conf: rootwrap_config=/etc/nova/rootwrap.conf You also need to let the nova user run nova-rootwrap as root in sudoers: nova ALL = (root) NOPASSWD: /usr/bin/nova-rootwrap /etc/nova/rootwrap.conf * - To make allowed commands node-specific, your packaging should only - install {compute,network,volume}.filters respectively on compute, network - and volume nodes (i.e. nova-api nodes should not have any of those files - installed). + Service packaging should deploy .filters files only on nodes where they are + needed, to avoid allowing more than is necessary. """ import ConfigParser @@ -75,7 +73,7 @@ if __name__ == '__main__': if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")): sys.path.insert(0, possible_topdir) - from nova.rootwrap import wrapper + from nova.openstack.common.rootwrap import wrapper # Load configuration try: diff --git a/etc/nova/nova.conf.sample b/etc/nova/nova.conf.sample index 96118eb76..36a7b0d9c 100644 --- a/etc/nova/nova.conf.sample +++ b/etc/nova/nova.conf.sample @@ -1756,7 +1756,7 @@ # value) #hyperv_attaching_volume_retry_count=10 -# The seconds to wait between an volume attachment attempt +# The seconds to wait between a volume attachment attempt # (integer value) #hyperv_wait_between_attach_retry=5 @@ -2282,6 +2282,10 @@ # value) #cinder_http_retries=3 +# Allow to perform insecure SSL (https) requests to cinder +# (boolean value) +#cinder_api_insecure=false + [conductor] @@ -2476,7 +2480,7 @@ # # Do not set this out of dev/test environments. If a node does -# not have an fixed PXE IP address, volumes are exported with +# not have a fixed PXE IP address, volumes are exported with # globally opened ACL (boolean value) #use_unsafe_iscsi=false @@ -2546,4 +2550,4 @@ #keymap=en-us -# Total option count: 519 +# Total option count: 520 diff --git a/etc/nova/rootwrap.d/baremetal-compute-pxe.filters b/etc/nova/rootwrap.d/baremetal-compute-pxe.filters deleted file mode 100644 index 35fa61723..000000000 --- a/etc/nova/rootwrap.d/baremetal-compute-pxe.filters +++ /dev/null @@ -1,11 +0,0 @@ -# nova-rootwrap command filters for compute nodes -# This file should be owned by (and only-writeable by) the root user - -[Filters] - -# nova/virt/baremetal/pxe.py: 'dnsmasq', ... -dnsmasq: CommandFilter, /usr/sbin/dnsmasq, root - -# nova/virt/baremetal/pxe.py: 'kill', '-TERM', str(dnsmasq_pid) -kill_dnsmasq: KillFilter, root, /usr/sbin/dnsmasq, -15, -TERM - diff --git a/nova/api/openstack/compute/contrib/admin_actions.py b/nova/api/openstack/compute/contrib/admin_actions.py index fa7836b37..1c053ea59 100644 --- a/nova/api/openstack/compute/contrib/admin_actions.py +++ b/nova/api/openstack/compute/contrib/admin_actions.py @@ -130,7 +130,7 @@ class AdminActionsController(wsgi.Controller): @wsgi.action('resetNetwork') def _reset_network(self, req, id, body): - """Permit admins to reset networking on an server.""" + """Permit admins to reset networking on a server.""" context = req.environ['nova.context'] authorize(context, 'resetNetwork') try: diff --git a/nova/compute/api.py b/nova/compute/api.py index 22ee82bbf..4b15a3e27 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -946,11 +946,10 @@ class API(base.Base): if (old['vm_state'] != vm_states.SOFT_DELETED and old['task_state'] not in (task_states.DELETING, task_states.SOFT_DELETING)): - reservations = QUOTAS.reserve(context, - project_id=project_id, - instances=-1, - cores=-instance['vcpus'], - ram=-instance['memory_mb']) + reservations = self._create_reservations(context, + old, + updated, + project_id) if not host: # Just update database, nothing else we can do @@ -1026,6 +1025,45 @@ class API(base.Base): reservations, project_id=project_id) + def _create_reservations(self, context, old_instance, new_instance, + project_id): + instance_vcpus = old_instance['vcpus'] + instance_memory_mb = old_instance['memory_mb'] + # NOTE(wangpan): if the instance is resizing, and the resources + # are updated to new instance type, we should use + # the old instance type to create reservation. + # see https://bugs.launchpad.net/nova/+bug/1099729 for more details + if old_instance['task_state'] in (task_states.RESIZE_MIGRATED, + task_states.RESIZE_FINISH): + get_migration = self.db.migration_get_by_instance_and_status + try: + migration_ref = get_migration(context.elevated(), + old_instance['uuid'], 'post-migrating') + except exception.MigrationNotFoundByStatus: + migration_ref = None + if (migration_ref and + new_instance['instance_type_id'] == + migration_ref['new_instance_type_id']): + old_inst_type_id = migration_ref['old_instance_type_id'] + get_inst_type_by_id = instance_types.get_instance_type + try: + old_inst_type = get_inst_type_by_id(old_inst_type_id) + except exception.InstanceTypeNotFound: + LOG.warning(_("instance type %(old_inst_type_id)d " + "not found") % locals()) + pass + else: + instance_vcpus = old_inst_type['vcpus'] + instance_memory_mb = old_inst_type['memory_mb'] + LOG.debug(_("going to delete a resizing instance")) + + reservations = QUOTAS.reserve(context, + project_id=project_id, + instances=-1, + cores=-instance_vcpus, + ram=-instance_memory_mb) + return reservations + def _local_delete(self, context, instance, bdms): LOG.warning(_("instance's host %s is down, deleting from " "database") % instance['host'], instance=instance) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 86f41cd3c..03c54a363 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -2464,8 +2464,11 @@ class ComputeManager(manager.SchedulerDependentManager): @lockutils.synchronized(instance['uuid'], 'nova-') def do_reserve(): + bdms = self.conductor_api.block_device_mapping_get_all_by_instance( + context, instance) result = compute_utils.get_device_name_for_instance(context, instance, + bdms, device) # NOTE(vish): create bdm here to avoid race condition values = {'instance_uuid': instance['uuid'], diff --git a/nova/compute/utils.py b/nova/compute/utils.py index f07346c6b..2b1286e16 100644 --- a/nova/compute/utils.py +++ b/nova/compute/utils.py @@ -71,7 +71,7 @@ def add_instance_fault_from_exc(context, instance, fault, exc_info=None): db.instance_fault_create(context, values) -def get_device_name_for_instance(context, instance, device): +def get_device_name_for_instance(context, instance, bdms, device): """Validates (or generates) a device name for instance. If device is not set, it will generate a unique device appropriate @@ -88,8 +88,6 @@ def get_device_name_for_instance(context, instance, device): req_prefix, req_letters = block_device.match_device(device) except (TypeError, AttributeError, ValueError): raise exception.InvalidDevicePath(path=device) - bdms = db.block_device_mapping_get_all_by_instance(context, - instance['uuid']) mappings = block_device.instance_block_mapping(instance, bdms) try: prefix = block_device.match_device(mappings['root'])[0] diff --git a/nova/conductor/api.py b/nova/conductor/api.py index 31ee19601..138e72f70 100644 --- a/nova/conductor/api.py +++ b/nova/conductor/api.py @@ -285,6 +285,9 @@ class LocalAPI(object): return self._manager.compute_node_update(context, node, values, prune_stats) + def service_update(self, context, service, values): + return self._manager.service_update(context, service, values) + class API(object): """Conductor API that does updates via RPC to the ConductorManager.""" @@ -548,3 +551,6 @@ class API(object): def compute_node_update(self, context, node, values, prune_stats=False): return self.conductor_rpcapi.compute_node_update(context, node, values, prune_stats) + + def service_update(self, context, service, values): + return self.conductor_rpcapi.service_update(context, service, values) diff --git a/nova/conductor/manager.py b/nova/conductor/manager.py index 9b18d1e00..0ff2e1400 100644 --- a/nova/conductor/manager.py +++ b/nova/conductor/manager.py @@ -43,7 +43,7 @@ datetime_fields = ['launched_at', 'terminated_at'] class ConductorManager(manager.SchedulerDependentManager): """Mission: TBD.""" - RPC_API_VERSION = '1.33' + RPC_API_VERSION = '1.34' def __init__(self, *args, **kwargs): super(ConductorManager, self).__init__(service_name='conductor', @@ -310,3 +310,8 @@ class ConductorManager(manager.SchedulerDependentManager): result = self.db.compute_node_update(context, node['id'], values, prune_stats) return jsonutils.to_primitive(result) + + @rpc_common.client_exceptions(exception.ServiceNotFound) + def service_update(self, context, service, values): + svc = self.db.service_update(context, service['id'], values) + return jsonutils.to_primitive(svc) diff --git a/nova/conductor/rpcapi.py b/nova/conductor/rpcapi.py index 95e332840..6dc8aef04 100644 --- a/nova/conductor/rpcapi.py +++ b/nova/conductor/rpcapi.py @@ -66,6 +66,7 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy): 1.31 - Added migration_get_in_progress_by_host_and_node 1.32 - Added optional node to instance_get_all_by_host 1.33 - Added compute_node_create and compute_node_update + 1.34 - Added service_update """ BASE_RPC_API_VERSION = '1.0' @@ -316,3 +317,8 @@ class ConductorAPI(nova.openstack.common.rpc.proxy.RpcProxy): msg = self.make_msg('compute_node_update', node=node_p, values=values, prune_stats=prune_stats) return self.call(context, msg, version='1.33') + + def service_update(self, context, service, values): + service_p = jsonutils.to_primitive(service) + msg = self.make_msg('service_update', service=service_p, values=values) + return self.call(context, msg, version='1.34') diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 970332896..cc83ec4f5 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -448,6 +448,7 @@ def service_update(context, service_id, values): service_ref = service_get(context, service_id, session=session) service_ref.update(values) service_ref.save(session=session) + return service_ref ################### @@ -4713,49 +4714,44 @@ def _ec2_instance_get_query(context, session=None): @require_admin_context -def task_log_get(context, task_name, period_beginning, - period_ending, host, state=None, session=None): +def _task_log_get_query(context, task_name, period_beginning, + period_ending, host=None, state=None, session=None): query = model_query(context, models.TaskLog, session=session).\ filter_by(task_name=task_name).\ filter_by(period_beginning=period_beginning).\ - filter_by(period_ending=period_ending).\ - filter_by(host=host) + filter_by(period_ending=period_ending) + if host is not None: + query = query.filter_by(host=host) if state is not None: query = query.filter_by(state=state) + return query + - return query.first() +@require_admin_context +def task_log_get(context, task_name, period_beginning, period_ending, host, + state=None): + return _task_log_get_query(task_name, period_beginning, period_ending, + host, state).first() @require_admin_context -def task_log_get_all(context, task_name, period_beginning, - period_ending, host=None, state=None, session=None): - query = model_query(context, models.TaskLog, session=session).\ - filter_by(task_name=task_name).\ - filter_by(period_beginning=period_beginning).\ - filter_by(period_ending=period_ending) - if host is not None: - query = query.filter_by(host=host) - if state is not None: - query = query.filter_by(state=state) - return query.all() +def task_log_get_all(context, task_name, period_beginning, period_ending, + host=None, state=None): + return _task_log_get_query(task_name, period_beginning, period_ending, + host, state).all() @require_admin_context -def task_log_begin_task(context, task_name, - period_beginning, - period_ending, - host, - task_items=None, - message=None, - session=None): - session = session or get_session() +def task_log_begin_task(context, task_name, period_beginning, period_ending, + host, task_items=None, message=None): + # NOTE(boris-42): This method has a race condition and will be rewritten + # after bp/db-unique-keys implementation. + session = get_session() with session.begin(): - task = task_log_get(context, task_name, - period_beginning, - period_ending, - host, - session=session) - if task: + task_ref = _task_log_get_query(context, task_name, period_beginning, + period_ending, host, session=session).\ + first() + if task_ref: #It's already run(ning)! raise exception.TaskAlreadyRunning(task_name=task_name, host=host) task = models.TaskLog() @@ -4769,30 +4765,20 @@ def task_log_begin_task(context, task_name, if task_items: task.task_items = task_items task.save(session=session) - return task @require_admin_context -def task_log_end_task(context, task_name, - period_beginning, - period_ending, - host, - errors, - message=None, - session=None): - session = session or get_session() +def task_log_end_task(context, task_name, period_beginning, period_ending, + host, errors, message=None): + values = dict(state="DONE", errors=errors) + if message: + values["message"] = message + + session = get_session() with session.begin(): - task = task_log_get(context, task_name, - period_beginning, - period_ending, - host, - session=session) - if not task: + rows = _task_log_get_query(context, task_name, period_beginning, + period_ending, host, session=session).\ + update(values) + if rows == 0: #It's not running! raise exception.TaskNotRunning(task_name=task_name, host=host) - task.state = "DONE" - if message: - task.message = message - task.errors = errors - task.save(session=session) - return task diff --git a/nova/network/manager.py b/nova/network/manager.py index 91b1c92d7..e4a97f162 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -147,9 +147,6 @@ network_opts = [ cfg.BoolOpt('auto_assign_floating_ip', default=False, help='Autoassigning floating ip to VM'), - cfg.StrOpt('network_host', - default=socket.getfqdn(), - help='Network host to use for ip allocation in flat modes'), cfg.BoolOpt('fake_network', default=False, help='If passed, use fake network devices and addresses'), @@ -482,7 +479,7 @@ class FloatingIP(object): @wrap_check_policy def deallocate_floating_ip(self, context, address, affect_auto_assigned=False): - """Returns an floating ip to the pool.""" + """Returns a floating ip to the pool.""" floating_ip = self.db.floating_ip_get_by_address(context, address) # handle auto_assigned diff --git a/nova/network/quantumv2/api.py b/nova/network/quantumv2/api.py index 78f5ad036..29e5e2f06 100644 --- a/nova/network/quantumv2/api.py +++ b/nova/network/quantumv2/api.py @@ -238,11 +238,62 @@ class API(base.Base): def add_fixed_ip_to_instance(self, context, instance, network_id): """Add a fixed ip to the instance from specified network.""" - raise NotImplementedError() + search_opts = {'network_id': network_id} + data = quantumv2.get_client(context).list_subnets(**search_opts) + ipam_subnets = data.get('subnets', []) + if not ipam_subnets: + raise exception.NetworkNotFoundForInstance( + instance_id=instance['uuid']) + + zone = 'compute:%s' % instance['availability_zone'] + search_opts = {'device_id': instance['uuid'], + 'device_owner': zone, + 'network_id': network_id} + data = quantumv2.get_client(context).list_ports(**search_opts) + ports = data['ports'] + for p in ports: + fixed_ips = p['fixed_ips'] + for subnet in ipam_subnets: + fixed_ip = {'subnet_id': subnet['id']} + fixed_ips.append(fixed_ip) + port_req_body = {'port': {'fixed_ips': fixed_ips}} + try: + quantumv2.get_client(context).update_port(p['id'], + port_req_body) + except Exception as ex: + msg = _("Unable to update port %(portid)s with" + " failure: %(exception)s") + LOG.debug(msg, {'portid': p['id'], 'exception': ex}) + return + raise exception.NetworkNotFoundForInstance( + instance_id=instance['uuid']) def remove_fixed_ip_from_instance(self, context, instance, address): """Remove a fixed ip from the instance.""" - raise NotImplementedError() + zone = 'compute:%s' % instance['availability_zone'] + search_opts = {'device_id': instance['uuid'], + 'device_owner': zone, + 'fixed_ips': 'ip_address=%s' % address} + data = quantumv2.get_client(context).list_ports(**search_opts) + ports = data['ports'] + for p in ports: + fixed_ips = p['fixed_ips'] + new_fixed_ips = [] + for fixed_ip in fixed_ips: + if fixed_ip['ip_address'] != address: + new_fixed_ips.append(fixed_ip) + port_req_body = {'port': {'fixed_ips': new_fixed_ips}} + try: + quantumv2.get_client(context).update_port(p['id'], + port_req_body) + except Exception as ex: + msg = _("Unable to update port %(portid)s with" + " failure: %(exception)s") + LOG.debug(msg, {'portid': p['id'], 'exception': ex}) + return + + raise exception.FixedIpNotFoundForSpecificInstance( + instance_uuid=instance['uuid'], ip=address) def validate_networks(self, context, requested_networks): """Validate that the tenant can use the requested networks.""" diff --git a/nova/openstack/common/eventlet_backdoor.py b/nova/openstack/common/eventlet_backdoor.py index f18e84f6d..118385427 100644 --- a/nova/openstack/common/eventlet_backdoor.py +++ b/nova/openstack/common/eventlet_backdoor.py @@ -46,7 +46,7 @@ def _find_objects(t): def _print_greenthreads(): - for i, gt in enumerate(find_objects(greenlet.greenlet)): + for i, gt in enumerate(_find_objects(greenlet.greenlet)): print i, gt traceback.print_stack(gt.gr_frame) print diff --git a/nova/rootwrap/__init__.py b/nova/openstack/common/rootwrap/__init__.py index 671d3c173..671d3c173 100644 --- a/nova/rootwrap/__init__.py +++ b/nova/openstack/common/rootwrap/__init__.py diff --git a/nova/rootwrap/filters.py b/nova/openstack/common/rootwrap/filters.py index 8958f1ba1..905bbabea 100644 --- a/nova/rootwrap/filters.py +++ b/nova/openstack/common/rootwrap/filters.py @@ -20,7 +20,7 @@ import re class CommandFilter(object): - """Command filter only checking that the 1st argument matches exec_path.""" + """Command filter only checking that the 1st argument matches exec_path""" def __init__(self, exec_path, run_as, *args): self.name = '' @@ -30,7 +30,7 @@ class CommandFilter(object): self.real_exec = None def get_exec(self, exec_dirs=[]): - """Returns existing executable, or empty string if none found.""" + """Returns existing executable, or empty string if none found""" if self.real_exec is not None: return self.real_exec self.real_exec = "" @@ -46,7 +46,7 @@ class CommandFilter(object): return self.real_exec def match(self, userargs): - """Only check that the first argument (command) matches exec_path.""" + """Only check that the first argument (command) matches exec_path""" if (os.path.basename(self.exec_path) == userargs[0]): return True return False @@ -60,12 +60,12 @@ class CommandFilter(object): return [to_exec] + userargs[1:] def get_environment(self, userargs): - """Returns specific environment to set, None if none.""" + """Returns specific environment to set, None if none""" return None class RegExpFilter(CommandFilter): - """Command filter doing regexp matching for every argument.""" + """Command filter doing regexp matching for every argument""" def match(self, userargs): # Early skip if command or number of args don't match @@ -89,15 +89,15 @@ class RegExpFilter(CommandFilter): class DnsmasqFilter(CommandFilter): - """Specific filter for the dnsmasq call (which includes env).""" + """Specific filter for the dnsmasq call (which includes env)""" CONFIG_FILE_ARG = 'CONFIG_FILE' def match(self, userargs): if (userargs[0] == 'env' and - userargs[1].startswith(self.CONFIG_FILE_ARG) and - userargs[2].startswith('NETWORK_ID=') and - userargs[3] == 'dnsmasq'): + userargs[1].startswith(self.CONFIG_FILE_ARG) and + userargs[2].startswith('NETWORK_ID=') and + userargs[3] == 'dnsmasq'): return True return False @@ -114,7 +114,7 @@ class DnsmasqFilter(CommandFilter): class DeprecatedDnsmasqFilter(DnsmasqFilter): - """Variant of dnsmasq filter to support old-style FLAGFILE.""" + """Variant of dnsmasq filter to support old-style FLAGFILE""" CONFIG_FILE_ARG = 'FLAGFILE' @@ -164,7 +164,7 @@ class KillFilter(CommandFilter): class ReadFileFilter(CommandFilter): - """Specific filter for the utils.read_file_as_root call.""" + """Specific filter for the utils.read_file_as_root call""" def __init__(self, file_path, *args): self.file_path = file_path diff --git a/nova/rootwrap/wrapper.py b/nova/openstack/common/rootwrap/wrapper.py index 70bd63c47..4452177fe 100644 --- a/nova/rootwrap/wrapper.py +++ b/nova/openstack/common/rootwrap/wrapper.py @@ -22,7 +22,7 @@ import logging.handlers import os import string -from nova.rootwrap import filters +from nova.openstack.common.rootwrap import filters class NoFilterMatched(Exception): @@ -93,7 +93,7 @@ def setup_syslog(execname, facility, level): def build_filter(class_name, *args): - """Returns a filter object of class class_name.""" + """Returns a filter object of class class_name""" if not hasattr(filters, class_name): logging.warning("Skipping unknown filter class (%s) specified " "in filter definitions" % class_name) @@ -103,7 +103,7 @@ def build_filter(class_name, *args): def load_filters(filters_path): - """Load filters from a list of directories.""" + """Load filters from a list of directories""" filterlist = [] for filterdir in filters_path: if not os.path.isdir(filterdir): diff --git a/nova/service.py b/nova/service.py index 0fde14baa..df8cf020f 100644 --- a/nova/service.py +++ b/nova/service.py @@ -411,7 +411,7 @@ class Service(object): self.db_allowed = db_allowed self.conductor_api = conductor.API(use_local=db_allowed) self.conductor_api.wait_until_ready(context.get_admin_context()) - self.servicegroup_api = servicegroup.API() + self.servicegroup_api = servicegroup.API(db_allowed=db_allowed) def start(self): verstr = version.version_string_with_package() @@ -421,12 +421,11 @@ class Service(object): self.model_disconnected = False ctxt = context.get_admin_context() try: - service_ref = self.conductor_api.service_get_by_args(ctxt, - self.host, - self.binary) - self.service_id = service_ref['id'] + self.service_ref = self.conductor_api.service_get_by_args(ctxt, + self.host, self.binary) + self.service_id = self.service_ref['id'] except exception.NotFound: - self._create_service_ref(ctxt) + self.service_ref = self._create_service_ref(ctxt) if self.backdoor_port is not None: self.manager.backdoor_port = self.backdoor_port @@ -479,6 +478,7 @@ class Service(object): } service = self.conductor_api.service_create(context, svc_values) self.service_id = service['id'] + return service def __getattr__(self, key): manager = self.__dict__.get('manager', None) diff --git a/nova/servicegroup/api.py b/nova/servicegroup/api.py index ebd0ee6ac..358b7dcbc 100644 --- a/nova/servicegroup/api.py +++ b/nova/servicegroup/api.py @@ -45,6 +45,15 @@ class API(object): @lockutils.synchronized('nova.servicegroup.api.new', 'nova-') def __new__(cls, *args, **kwargs): + '''Create an instance of the servicegroup API. + + args and kwargs are passed down to the servicegroup driver when it gets + created. No args currently exist, though. Valid kwargs are: + + db_allowed - Boolean. False if direct db access is not allowed and + alternative data access (conductor) should be used + instead. + ''' if not cls._driver: LOG.debug(_('ServiceGroup driver defined as an instance of %s'), @@ -55,7 +64,8 @@ class API(object): except KeyError: raise TypeError(_("unknown ServiceGroup driver name: %s") % driver_name) - cls._driver = importutils.import_object(driver_class) + cls._driver = importutils.import_object(driver_class, + *args, **kwargs) utils.check_isinstance(cls._driver, ServiceGroupDriver) # we don't have to check that cls._driver is not NONE, # check_isinstance does it diff --git a/nova/servicegroup/drivers/db.py b/nova/servicegroup/drivers/db.py index 075db3ed8..686ee728b 100644 --- a/nova/servicegroup/drivers/db.py +++ b/nova/servicegroup/drivers/db.py @@ -14,8 +14,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from nova import conductor from nova import context -from nova import db from nova import exception from nova.openstack.common import cfg from nova.openstack.common import log as logging @@ -32,6 +32,10 @@ LOG = logging.getLogger(__name__) class DbDriver(api.ServiceGroupDriver): + def __init__(self, *args, **kwargs): + self.db_allowed = kwargs.get('db_allowed', True) + self.conductor_api = conductor.API(use_local=self.db_allowed) + def join(self, member_id, group_id, service=None): """Join the given service with it's group.""" @@ -53,6 +57,11 @@ class DbDriver(api.ServiceGroupDriver): Check whether a service is up based on last heartbeat. """ last_heartbeat = service_ref['updated_at'] or service_ref['created_at'] + if isinstance(last_heartbeat, basestring): + # NOTE(russellb) If this service_ref came in over rpc via + # conductor, then the timestamp will be a string and needs to be + # converted back to a datetime. + last_heartbeat = timeutils.parse_strtime(last_heartbeat) # Timestamps in DB are UTC. elapsed = utils.total_seconds(timeutils.utcnow() - last_heartbeat) LOG.debug('DB_Driver.is_up last_heartbeat = %(lhb)s elapsed = %(el)s', @@ -66,7 +75,8 @@ class DbDriver(api.ServiceGroupDriver): LOG.debug(_('DB_Driver: get_all members of the %s group') % group_id) rs = [] ctxt = context.get_admin_context() - for service in db.service_get_all_by_topic(ctxt, group_id): + services = self.conductor_api.service_get_all_by_topic(ctxt, group_id) + for service in services: if self.is_up(service): rs.append(service['host']) return rs @@ -76,18 +86,11 @@ class DbDriver(api.ServiceGroupDriver): ctxt = context.get_admin_context() state_catalog = {} try: - try: - service_ref = db.service_get(ctxt, service.service_id) - except exception.NotFound: - LOG.debug(_('The service database object disappeared, ' - 'Recreating it.')) - service._create_service_ref(ctxt) - service_ref = db.service_get(ctxt, service.service_id) - - state_catalog['report_count'] = service_ref['report_count'] + 1 + report_count = service.service_ref['report_count'] + 1 + state_catalog['report_count'] = report_count - db.service_update(ctxt, - service.service_id, state_catalog) + service.service_ref = self.conductor_api.service_update(ctxt, + service.service_ref, state_catalog) # TODO(termie): make this pattern be more elegant. if getattr(service, 'model_disconnected', False): diff --git a/nova/tests/baremetal/test_pxe.py b/nova/tests/baremetal/test_pxe.py index 45c9ede43..ad4975849 100644 --- a/nova/tests/baremetal/test_pxe.py +++ b/nova/tests/baremetal/test_pxe.py @@ -147,12 +147,6 @@ class PXEClassMethodsTestCase(BareMetalPXETestCase): config = pxe.build_network_config(net) self.assertIn('eth0', config) self.assertNotIn('eth1', config) - self.assertIn('hwaddress ether fake', config) - self.assertNotIn('hwaddress ether aa:bb:cc:dd', config) - - net[0][1]['mac'] = 'aa:bb:cc:dd' - config = pxe.build_network_config(net) - self.assertIn('hwaddress ether aa:bb:cc:dd', config) net = utils.get_test_network_info(2) config = pxe.build_network_config(net) @@ -306,15 +300,6 @@ class PXEPrivateMethodsTestCase(BareMetalPXETestCase): macs = self.driver._collect_mac_addresses(self.context, self.node) self.assertEqual(macs, address_list) - def test_generate_udev_rules(self): - self._create_node() - address_list = [nic['address'] for nic in self.nic_info] - address_list.append(self.node_info['prov_mac_address']) - - rules = self.driver._generate_udev_rules(self.context, self.node) - for address in address_list: - self.assertIn('ATTR{address}=="%s"' % address, rules) - def test_cache_tftp_images(self): self.instance['kernel_id'] = 'aaaa' self.instance['ramdisk_id'] = 'bbbb' @@ -357,8 +342,6 @@ class PXEPrivateMethodsTestCase(BareMetalPXETestCase): # nova.virt.disk.api._inject_*_into_fs self._create_node() files = [] - files.append(('/etc/udev/rules.d/70-persistent-net.rules', - self.driver._generate_udev_rules(self.context, self.node))) self.instance['hostname'] = 'fake hostname' files.append(('/etc/hostname', 'fake hostname')) self.instance['key_data'] = 'fake ssh key' diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py index 596668048..092fd940a 100644 --- a/nova/tests/compute/test_compute.py +++ b/nova/tests/compute/test_compute.py @@ -3987,6 +3987,38 @@ class ComputeAPITestCase(BaseTestCase): db.instance_destroy(self.context, instance['uuid']) + def test_delete_in_resizing(self): + def fake_quotas_reserve(context, expire=None, project_id=None, + **deltas): + old_type = instance_types.get_instance_type_by_name('m1.tiny') + # ensure using old instance type to create reservations + self.assertEqual(deltas['cores'], -old_type['vcpus']) + self.assertEqual(deltas['ram'], -old_type['memory_mb']) + + self.stubs.Set(QUOTAS, 'reserve', fake_quotas_reserve) + + instance, instance_uuid = self._run_instance(params={ + 'host': CONF.host}) + + # create a fake migration record (manager does this) + new_inst_type = instance_types.get_instance_type_by_name('m1.small') + db.migration_create(self.context.elevated(), + {'instance_uuid': instance['uuid'], + 'old_instance_type_id': instance['instance_type_id'], + 'new_instance_type_id': new_inst_type['id'], + 'status': 'post-migrating'}) + + # update instance type to resized one + db.instance_update(self.context, instance['uuid'], + {'instance_type_id': new_inst_type['id'], + 'vcpus': new_inst_type['vcpus'], + 'memory_mb': new_inst_type['memory_mb'], + 'task_state': task_states.RESIZE_FINISH}) + + self.compute_api.delete(self.context, instance) + + db.instance_destroy(self.context, instance['uuid']) + def test_delete_in_resized(self): instance, instance_uuid = self._run_instance(params={ 'host': CONF.host}) diff --git a/nova/tests/compute/test_compute_utils.py b/nova/tests/compute/test_compute_utils.py index f29c68627..6e7227d4c 100644 --- a/nova/tests/compute/test_compute_utils.py +++ b/nova/tests/compute/test_compute_utils.py @@ -69,8 +69,11 @@ class ComputeValidateDeviceTestCase(test.TestCase): lambda context, instance: self.data) def _validate_device(self, device=None): + bdms = db.block_device_mapping_get_all_by_instance( + self.context, self.instance['uuid']) return compute_utils.get_device_name_for_instance(self.context, self.instance, + bdms, device) @staticmethod diff --git a/nova/tests/conductor/test_conductor.py b/nova/tests/conductor/test_conductor.py index b29db92e7..d010b454f 100644 --- a/nova/tests/conductor/test_conductor.py +++ b/nova/tests/conductor/test_conductor.py @@ -747,6 +747,14 @@ class ConductorAPITestCase(_BaseTestCase, test.TestCase): def test_service_destroy(self): self._test_stubbed('service_destroy', '', returns=False) + def test_service_update(self): + ctxt = self.context + self.mox.StubOutWithMock(db, 'service_update') + db.service_update(ctxt, '', {}).AndReturn('fake-result') + self.mox.ReplayAll() + result = self.conductor.service_update(self.context, {'id': ''}, {}) + self.assertEqual(result, 'fake-result') + def test_instance_get_all_by_host(self): self._test_stubbed('instance_get_all_by_host', self.context.elevated(), 'host') diff --git a/nova/tests/network/test_quantumv2.py b/nova/tests/network/test_quantumv2.py index 95b3a936b..c9b2e43b3 100644 --- a/nova/tests/network/test_quantumv2.py +++ b/nova/tests/network/test_quantumv2.py @@ -1002,6 +1002,54 @@ class TestQuantumv2(test.TestCase): self.mox.ReplayAll() api.disassociate_floating_ip(self.context, self.instance, address) + def test_add_fixed_ip_to_instance(self): + api = quantumapi.API() + network_id = 'my_netid1' + search_opts = {'network_id': network_id} + self.moxed_client.list_subnets( + **search_opts).AndReturn({'subnets': self.subnet_data1}) + + zone = 'compute:%s' % self.instance['availability_zone'] + search_opts = {'device_id': self.instance['uuid'], + 'device_owner': 'compute:nova', + 'network_id': network_id} + self.moxed_client.list_ports( + **search_opts).AndReturn({'ports': self.port_data1}) + port_req_body = { + 'port': { + 'fixed_ips': [{'subnet_id': 'my_subid1'}], + }, + } + port = self.port_data1[0] + port['fixed_ips'] = [{'subnet_id': 'my_subid1'}] + self.moxed_client.update_port('my_portid1', + MyComparator(port_req_body)).AndReturn({'port': port}) + + self.mox.ReplayAll() + api.add_fixed_ip_to_instance(self.context, self.instance, network_id) + + def test_remove_fixed_ip_from_instance(self): + api = quantumapi.API() + address = '10.0.0.3' + zone = 'compute:%s' % self.instance['availability_zone'] + search_opts = {'device_id': self.instance['uuid'], + 'device_owner': zone, + 'fixed_ips': 'ip_address=%s' % address} + self.moxed_client.list_ports( + **search_opts).AndReturn({'ports': self.port_data1}) + port_req_body = { + 'port': { + 'fixed_ips': [], + }, + } + port = self.port_data1[0] + port['fixed_ips'] = [] + self.moxed_client.update_port('my_portid1', + MyComparator(port_req_body)).AndReturn({'port': port}) + + self.mox.ReplayAll() + api.remove_fixed_ip_from_instance(self.context, self.instance, address) + class TestQuantumv2ModuleMethods(test.TestCase): def test_ensure_requested_network_ordering_no_preference(self): diff --git a/nova/tests/test_cinder.py b/nova/tests/test_cinder.py index 29e2e978b..79b5ae66a 100644 --- a/nova/tests/test_cinder.py +++ b/nova/tests/test_cinder.py @@ -98,13 +98,14 @@ class FakeHTTPClient(cinder.cinder_client.client.HTTPClient): class FakeCinderClient(cinder.cinder_client.Client): def __init__(self, username, password, project_id=None, auth_url=None, - retries=None): + insecure=False, retries=None): super(FakeCinderClient, self).__init__(username, password, project_id=project_id, auth_url=auth_url, + insecure=insecure, retries=retries) self.client = FakeHTTPClient(username, password, project_id, auth_url, - retries=retries) + insecure=insecure, retries=retries) # keep a ref to the clients callstack for factory's assert_called self.callstack = self.client.callstack = [] @@ -177,6 +178,15 @@ class CinderTestCase(test.TestCase): self.assertTrue('volume_image_metadata' in volume) self.assertEqual(volume['volume_image_metadata'], _image_metadata) + def test_cinder_api_insecure(self): + # The True/False negation is awkward, but better for the client + # to pass us insecure=True and we check verify_cert == False + self.flags(cinder_api_insecure=True) + volume = self.api.get(self.context, '1234') + self.assert_called('GET', '/volumes/1234') + self.assertEquals( + self.fake_client_factory.client.client.verify_cert, False) + def test_cinder_http_retries(self): retries = 42 self.flags(cinder_http_retries=retries) diff --git a/nova/tests/test_nova_rootwrap.py b/nova/tests/test_nova_rootwrap.py deleted file mode 100644 index 1029e0c2c..000000000 --- a/nova/tests/test_nova_rootwrap.py +++ /dev/null @@ -1,198 +0,0 @@ -# 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 ConfigParser -import logging -import logging.handlers -import os -import subprocess - -from nova.rootwrap import filters -from nova.rootwrap import wrapper -from nova import test - - -class RootwrapTestCase(test.TestCase): - - def setUp(self): - super(RootwrapTestCase, self).setUp() - self.filters = [ - filters.RegExpFilter("/bin/ls", "root", 'ls', '/[a-z]+'), - filters.CommandFilter("/usr/bin/foo_bar_not_exist", "root"), - filters.RegExpFilter("/bin/cat", "root", 'cat', '/[a-z]+'), - filters.CommandFilter("/nonexistent/cat", "root"), - filters.CommandFilter("/bin/cat", "root") # Keep this one last - ] - - def test_RegExpFilter_match(self): - usercmd = ["ls", "/root"] - filtermatch = wrapper.match_filter(self.filters, usercmd) - self.assertFalse(filtermatch is None) - self.assertEqual(filtermatch.get_command(usercmd), - ["/bin/ls", "/root"]) - - def test_RegExpFilter_reject(self): - usercmd = ["ls", "root"] - self.assertRaises(wrapper.NoFilterMatched, - wrapper.match_filter, self.filters, usercmd) - - def test_missing_command(self): - valid_but_missing = ["foo_bar_not_exist"] - invalid = ["foo_bar_not_exist_and_not_matched"] - self.assertRaises(wrapper.FilterMatchNotExecutable, - wrapper.match_filter, self.filters, valid_but_missing) - self.assertRaises(wrapper.NoFilterMatched, - wrapper.match_filter, self.filters, invalid) - - def _test_DnsmasqFilter(self, filter_class, config_file_arg): - usercmd = ['env', config_file_arg + '=A', 'NETWORK_ID=foobar', - 'dnsmasq', 'foo'] - f = filter_class("/usr/bin/dnsmasq", "root") - self.assertTrue(f.match(usercmd)) - self.assertEqual(f.get_command(usercmd), ['/usr/bin/dnsmasq', 'foo']) - env = f.get_environment(usercmd) - self.assertEqual(env.get(config_file_arg), 'A') - self.assertEqual(env.get('NETWORK_ID'), 'foobar') - - def test_DnsmasqFilter(self): - self._test_DnsmasqFilter(filters.DnsmasqFilter, 'CONFIG_FILE') - - def test_DeprecatedDnsmasqFilter(self): - self._test_DnsmasqFilter(filters.DeprecatedDnsmasqFilter, 'FLAGFILE') - - def test_KillFilter(self): - if not os.path.exists("/proc/%d" % os.getpid()): - self.skipTest("Test requires /proc filesystem (procfs)") - p = subprocess.Popen(["cat"], stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - try: - f = filters.KillFilter("root", "/bin/cat", "-9", "-HUP") - f2 = filters.KillFilter("root", "/usr/bin/cat", "-9", "-HUP") - usercmd = ['kill', '-ALRM', p.pid] - # Incorrect signal should fail - self.assertFalse(f.match(usercmd) or f2.match(usercmd)) - usercmd = ['kill', p.pid] - # Providing no signal should fail - self.assertFalse(f.match(usercmd) or f2.match(usercmd)) - # Providing matching signal should be allowed - usercmd = ['kill', '-9', p.pid] - self.assertTrue(f.match(usercmd) or f2.match(usercmd)) - - f = filters.KillFilter("root", "/bin/cat") - f2 = filters.KillFilter("root", "/usr/bin/cat") - usercmd = ['kill', os.getpid()] - # Our own PID does not match /bin/sleep, so it should fail - self.assertFalse(f.match(usercmd) or f2.match(usercmd)) - usercmd = ['kill', 999999] - # Nonexistent PID should fail - self.assertFalse(f.match(usercmd) or f2.match(usercmd)) - usercmd = ['kill', p.pid] - # Providing no signal should work - self.assertTrue(f.match(usercmd) or f2.match(usercmd)) - finally: - # Terminate the "cat" process and wait for it to finish - p.terminate() - p.wait() - - def test_KillFilter_no_raise(self): - # Makes sure ValueError from bug 926412 is gone. - f = filters.KillFilter("root", "") - # Providing anything other than kill should be False - usercmd = ['notkill', 999999] - self.assertFalse(f.match(usercmd)) - # Providing something that is not a pid should be False - usercmd = ['kill', 'notapid'] - self.assertFalse(f.match(usercmd)) - - def test_KillFilter_deleted_exe(self): - # Makes sure deleted exe's are killed correctly. - # See bug #967931. - def fake_readlink(blah): - return '/bin/commandddddd (deleted)' - - f = filters.KillFilter("root", "/bin/commandddddd") - usercmd = ['kill', 1234] - # Providing no signal should work - self.stubs.Set(os, 'readlink', fake_readlink) - self.assertTrue(f.match(usercmd)) - - def test_ReadFileFilter(self): - goodfn = '/good/file.name' - f = filters.ReadFileFilter(goodfn) - usercmd = ['cat', '/bad/file'] - self.assertFalse(f.match(['cat', '/bad/file'])) - usercmd = ['cat', goodfn] - self.assertEqual(f.get_command(usercmd), ['/bin/cat', goodfn]) - self.assertTrue(f.match(usercmd)) - - def test_exec_dirs_search(self): - # This test supposes you have /bin/cat or /usr/bin/cat locally - f = filters.CommandFilter("cat", "root") - usercmd = ['cat', '/f'] - self.assertTrue(f.match(usercmd)) - self.assertTrue(f.get_command(usercmd, exec_dirs=['/bin', - '/usr/bin']) in (['/bin/cat', '/f'], ['/usr/bin/cat', '/f'])) - - def test_skips(self): - # Check that all filters are skipped and that the last matches - usercmd = ["cat", "/"] - filtermatch = wrapper.match_filter(self.filters, usercmd) - self.assertTrue(filtermatch is self.filters[-1]) - - def test_RootwrapConfig(self): - raw = ConfigParser.RawConfigParser() - - # Empty config should raise ConfigParser.Error - self.assertRaises(ConfigParser.Error, wrapper.RootwrapConfig, raw) - - # Check default values - raw.set('DEFAULT', 'filters_path', '/a,/b') - config = wrapper.RootwrapConfig(raw) - self.assertEqual(config.filters_path, ['/a', '/b']) - self.assertEqual(config.exec_dirs, os.environ["PATH"].split(':')) - self.assertFalse(config.use_syslog) - self.assertEqual(config.syslog_log_facility, - logging.handlers.SysLogHandler.LOG_SYSLOG) - self.assertEqual(config.syslog_log_level, logging.ERROR) - - # Check general values - raw.set('DEFAULT', 'exec_dirs', '/a,/x') - config = wrapper.RootwrapConfig(raw) - self.assertEqual(config.exec_dirs, ['/a', '/x']) - - raw.set('DEFAULT', 'use_syslog', 'oui') - self.assertRaises(ValueError, wrapper.RootwrapConfig, raw) - raw.set('DEFAULT', 'use_syslog', 'true') - config = wrapper.RootwrapConfig(raw) - self.assertTrue(config.use_syslog) - - raw.set('DEFAULT', 'syslog_log_facility', 'moo') - self.assertRaises(ValueError, wrapper.RootwrapConfig, raw) - raw.set('DEFAULT', 'syslog_log_facility', 'local0') - config = wrapper.RootwrapConfig(raw) - self.assertEqual(config.syslog_log_facility, - logging.handlers.SysLogHandler.LOG_LOCAL0) - raw.set('DEFAULT', 'syslog_log_facility', 'LOG_AUTH') - config = wrapper.RootwrapConfig(raw) - self.assertEqual(config.syslog_log_facility, - logging.handlers.SysLogHandler.LOG_AUTH) - - raw.set('DEFAULT', 'syslog_log_level', 'bar') - self.assertRaises(ValueError, wrapper.RootwrapConfig, raw) - raw.set('DEFAULT', 'syslog_log_level', 'INFO') - config = wrapper.RootwrapConfig(raw) - self.assertEqual(config.syslog_log_level, logging.INFO) diff --git a/nova/virt/baremetal/net-dhcp.ubuntu.template b/nova/virt/baremetal/net-dhcp.ubuntu.template index e8824a88d..34a9e8be7 100644 --- a/nova/virt/baremetal/net-dhcp.ubuntu.template +++ b/nova/virt/baremetal/net-dhcp.ubuntu.template @@ -10,9 +10,6 @@ iface lo inet loopback #for $ifc in $interfaces auto ${ifc.name} iface ${ifc.name} inet dhcp -#if $ifc.hwaddress - hwaddress ether ${ifc.hwaddress} -#end if #if $use_ipv6 iface ${ifc.name} inet6 dhcp diff --git a/nova/virt/baremetal/net-static.ubuntu.template b/nova/virt/baremetal/net-static.ubuntu.template index f14f0ce8c..1fe5a1ab8 100644 --- a/nova/virt/baremetal/net-static.ubuntu.template +++ b/nova/virt/baremetal/net-static.ubuntu.template @@ -16,9 +16,6 @@ iface ${ifc.name} inet static #if $ifc.dns dns-nameservers ${ifc.dns} #end if -#if $ifc.hwaddress - hwaddress ether ${ifc.hwaddress} -#end if #if $use_ipv6 iface ${ifc.name} inet6 static diff --git a/nova/virt/baremetal/pxe.py b/nova/virt/baremetal/pxe.py index b94ac9032..47bfc55af 100644 --- a/nova/virt/baremetal/pxe.py +++ b/nova/virt/baremetal/pxe.py @@ -121,7 +121,6 @@ def build_network_config(network_info): gateway_v6 = mapping['gateway_v6'] interface = { 'name': 'eth%d' % id, - 'hwaddress': mapping['mac'], 'address': mapping['ips'][0]['ip'], 'gateway': mapping['gateway'], 'netmask': mapping['ips'][0]['netmask'], @@ -238,27 +237,12 @@ class PXE(base.NodeDriver): super(PXE, self).__init__() def _collect_mac_addresses(self, context, node): - macs = [] - macs.append(db.bm_node_get(context, node['id'])['prov_mac_address']) + macs = set() + macs.add(db.bm_node_get(context, node['id'])['prov_mac_address']) for nic in db.bm_interface_get_all_by_bm_node_id(context, node['id']): if nic['address']: - macs.append(nic['address']) - macs.sort() - return macs - - def _generate_udev_rules(self, context, node): - # TODO(deva): fix assumption that device names begin with "eth" - # and fix assumption of ordering - macs = self._collect_mac_addresses(context, node) - rules = '' - for (i, mac) in enumerate(macs): - rules += 'SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ' \ - 'ATTR{address}=="%(mac)s", ATTR{dev_id}=="0x0", ' \ - 'ATTR{type}=="1", KERNEL=="eth*", NAME="%(name)s"\n' \ - % {'mac': mac.lower(), - 'name': 'eth%d' % i, - } - return rules + macs.add(nic['address']) + return sorted(macs) def _cache_tftp_images(self, context, instance, image_info): """Fetch the necessary kernels and ramdisks for the instance.""" @@ -330,9 +314,6 @@ class PXE(base.NodeDriver): injected_files = [] net_config = build_network_config(network_info) - udev_rules = self._generate_udev_rules(context, node) - injected_files.append( - ('/etc/udev/rules.d/70-persistent-net.rules', udev_rules)) if instance['hostname']: injected_files.append(('/etc/hostname', instance['hostname'])) @@ -385,7 +366,6 @@ class PXE(base.NodeDriver): config ./pxelinux.cfg/ {mac} -> ../{uuid}/config - """ image_info = get_tftp_image_info(instance) (root_mb, swap_mb) = get_partition_sizes(instance) diff --git a/nova/virt/baremetal/volume_driver.py b/nova/virt/baremetal/volume_driver.py index 2e6f82b93..0a05dfedd 100644 --- a/nova/virt/baremetal/volume_driver.py +++ b/nova/virt/baremetal/volume_driver.py @@ -31,7 +31,7 @@ opts = [ cfg.BoolOpt('use_unsafe_iscsi', default=False, help='Do not set this out of dev/test environments. ' - 'If a node does not have an fixed PXE IP address, ' + 'If a node does not have a fixed PXE IP address, ' 'volumes are exported with globally opened ACL'), cfg.StrOpt('iscsi_iqn_prefix', default='iqn.2010-10.org.openstack.baremetal', diff --git a/nova/virt/hyperv/volumeops.py b/nova/virt/hyperv/volumeops.py index 200236233..192d6834c 100644 --- a/nova/virt/hyperv/volumeops.py +++ b/nova/virt/hyperv/volumeops.py @@ -37,7 +37,7 @@ hyper_volumeops_opts = [ help='The number of times we retry on attaching volume '), cfg.IntOpt('hyperv_wait_between_attach_retry', default=5, - help='The seconds to wait between an volume attachment attempt'), + help='The seconds to wait between a volume attachment attempt'), cfg.BoolOpt('force_volumeutils_v1', default=False, help='Force volumeutils v1'), diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 597aa39a0..115c6cd02 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -1995,7 +1995,7 @@ class LibvirtDriver(driver.ComputeDriver): def get_interfaces(self, xml): """ - Note that this function takes an domain xml. + Note that this function takes a domain xml. Returns a list of all network interfaces for this instance. """ diff --git a/nova/virt/xenapi/pool_states.py b/nova/virt/xenapi/pool_states.py index 5bf326117..138f84831 100644 --- a/nova/virt/xenapi/pool_states.py +++ b/nova/virt/xenapi/pool_states.py @@ -19,10 +19,10 @@ A pool may be 'created', in which case the admin has triggered its creation, but the underlying hypervisor pool has not actually being set up -yet. An pool may be 'changing', meaning that the underlying hypervisor -pool is being setup. An pool may be 'active', in which case the underlying -hypervisor pool is up and running. An pool may be 'dismissed' when it has -no hosts and it has been deleted. An pool may be in 'error' in all other +yet. A pool may be 'changing', meaning that the underlying hypervisor +pool is being setup. A pool may be 'active', in which case the underlying +hypervisor pool is up and running. A pool may be 'dismissed' when it has +no hosts and it has been deleted. A pool may be in 'error' in all other cases. A 'created' pool becomes 'changing' during the first request of adding a host. During a 'changing' status no other requests will be accepted; @@ -34,7 +34,7 @@ All other operations (e.g. add/remove hosts) that succeed will keep the pool in the 'active' state. If a number of continuous requests fail, an 'active' pool goes into an 'error' state. To recover from such a state, admin intervention is required. Currently an error state is irreversible, -that is, in order to recover from it an pool must be deleted. +that is, in order to recover from it a pool must be deleted. """ CREATED = 'created' diff --git a/nova/volume/cinder.py b/nova/volume/cinder.py index fccdedac8..3e1ccc66b 100644 --- a/nova/volume/cinder.py +++ b/nova/volume/cinder.py @@ -48,6 +48,9 @@ cinder_opts = [ cfg.IntOpt('cinder_http_retries', default=3, help='Number of cinderclient retries on failed http calls'), + cfg.BoolOpt('cinder_api_insecure', + default=False, + help='Allow to perform insecure SSL requests to cinder'), ] CONF = cfg.CONF @@ -88,6 +91,7 @@ def cinderclient(context): context.auth_token, project_id=context.project_id, auth_url=url, + insecure=CONF.cinder_api_insecure, retries=CONF.cinder_http_retries) # noauth extracts user_id:project_id from auth_token c.client.auth_token = context.auth_token or '%s:%s' % (context.user_id, diff --git a/openstack-common.conf b/openstack-common.conf index ea33ab235..a0b14e651 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=cfg,cliutils,context,excutils,eventlet_backdoor,fileutils,gettextutils,importutils,iniparser,jsonutils,local,lockutils,log,network_utils,notifier,plugin,policy,setup,timeutils,rpc,uuidutils +modules=cfg,cliutils,context,excutils,eventlet_backdoor,fileutils,gettextutils,importutils,iniparser,jsonutils,local,lockutils,log,network_utils,notifier,plugin,policy,rootwrap,setup,timeutils,rpc,uuidutils # The base module to hold the copy of openstack.common base=nova diff --git a/run_tests.sh b/run_tests.sh index 3a579ca36..39176d78b 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -121,7 +121,7 @@ function run_pep8 { srcfiles+=" setup.py" # Until all these issues get fixed, ignore. - ignore='--ignore=E12,E711,E721,E712' + ignore='--ignore=E12,E711,E721,E712,N403,N404' # First run the hacking selftest, to make sure it's right echo "Running hacking.py self test" diff --git a/tools/hacking.py b/tools/hacking.py index cfdd1b5b1..56f6694bd 100755 --- a/tools/hacking.py +++ b/tools/hacking.py @@ -21,7 +21,6 @@ built on top of pep8.py """ -import fnmatch import inspect import logging import os @@ -46,16 +45,15 @@ logging.disable('LOG') #N8xx git commit messages IMPORT_EXCEPTIONS = ['sqlalchemy', 'migrate', 'nova.db.sqlalchemy.session'] -DOCSTRING_TRIPLE = ['"""', "'''"] +START_DOCSTRING_TRIPLE = ['u"""', 'r"""', '"""', "u'''", "r'''", "'''"] +END_DOCSTRING_TRIPLE = ['"""', "'''"] VERBOSE_MISSING_IMPORT = os.getenv('HACKING_VERBOSE_MISSING_IMPORT', 'False') # Monkey patch broken excluded filter in pep8 # See https://github.com/jcrocholl/pep8/pull/111 def excluded(self, filename): - """ - Check if options.exclude contains a pattern that matches filename. - """ + """Check if options.exclude contains a pattern that matches filename.""" basename = os.path.basename(filename) return any((pep8.filename_match(filename, self.options.exclude, default=False), @@ -120,7 +118,7 @@ def nova_todo_format(physical_line): pos2 = physical_line.find('#') # make sure it's a comment # TODO(sdague): should be smarter on this test this_test = physical_line.find('N101: #TODO fail') - if (pos != pos1 and pos2 >= 0 and pos2 < pos and this_test == -1): + if pos != pos1 and pos2 >= 0 and pos2 < pos and this_test == -1: return pos, "N101: Use TODO(NAME)" @@ -187,7 +185,8 @@ def nova_import_module_only(logical_line): # TODO(sdague) actually get these tests working def importModuleCheck(mod, parent=None, added=False): - """ + """Import Module helper function. + If can't find module on first try, recursively check for relative imports """ @@ -258,8 +257,7 @@ def nova_import_module_only(logical_line): def nova_import_alphabetical(logical_line, blank_lines, previous_logical, indent_level, previous_indent_level): - r""" - Check for imports in alphabetical order. + r"""Check for imports in alphabetical order. nova HACKING guide recommendation for imports: imports in human alphabetical order @@ -294,6 +292,11 @@ def nova_import_no_db_in_virt(logical_line, filename): yield (0, "N307: nova.db import not allowed in nova/virt/*") +def in_docstring_position(previous_logical): + return (previous_logical.startswith("def ") or + previous_logical.startswith("class ")) + + def nova_docstring_start_space(physical_line, previous_logical): r"""Check for docstring not start with space. @@ -311,11 +314,10 @@ def nova_docstring_start_space(physical_line, previous_logical): # it's important that we determine this is actually a docstring, # and not a doc block used somewhere after the first line of a # function def - if (previous_logical.startswith("def ") or - previous_logical.startswith("class ")): - pos = max([physical_line.find(i) for i in DOCSTRING_TRIPLE]) - if (pos != -1 and len(physical_line) > pos + 4): - if (physical_line[pos + 3] == ' '): + if in_docstring_position(previous_logical): + pos = max([physical_line.find(i) for i in START_DOCSTRING_TRIPLE]) + if pos != -1 and len(physical_line) > pos + 4: + if physical_line[pos + 3] == ' ': return (pos, "N401: docstring should not start with" " a space") @@ -330,33 +332,50 @@ def nova_docstring_one_line(physical_line): N402: '''This is not''' N402: '''Bad punctuation,''' """ + #TODO(jogo) make this apply to multi line docstrings as well line = physical_line.lstrip() if line.startswith('"') or line.startswith("'"): - pos = max([line.find(i) for i in DOCSTRING_TRIPLE]) # start - end = max([line[-4:-1] == i for i in DOCSTRING_TRIPLE]) # end + pos = max([line.find(i) for i in START_DOCSTRING_TRIPLE]) # start + end = max([line[-4:-1] == i for i in END_DOCSTRING_TRIPLE]) # end - if (pos != -1 and end and len(line) > pos + 4): - if (line[-5] not in ['.', '?', '!']): + if pos != -1 and end and len(line) > pos + 4: + if line[-5] not in ['.', '?', '!']: return pos, "N402: one line docstring needs punctuation." -def nova_docstring_multiline_end(physical_line): +def nova_docstring_multiline_end(physical_line, previous_logical): r"""Check multi line docstring end. nova HACKING guide recommendation for docstring: Docstring should end on a new line - Okay: '''\nfoo\nbar\n''' - # This test is not triggered, don't think it's right, removing - # the colon prevents it from running - N403 '''\nfoo\nbar\n ''' \n\n + Okay: '''foobar\nfoo\nbar\n''' + N403: def foo():\n'''foobar\nfoo\nbar\n d'''\n\n """ - # TODO(sdague) actually get these tests working - pos = max([physical_line.find(i) for i in DOCSTRING_TRIPLE]) # start - if (pos != -1 and len(physical_line) == pos): - if (physical_line[pos + 3] == ' '): - return (pos, "N403: multi line docstring end on new line") + if in_docstring_position(previous_logical): + pos = max(physical_line.find(i) for i in END_DOCSTRING_TRIPLE) + if pos != -1 and len(physical_line) == pos + 4: + if physical_line.strip() not in START_DOCSTRING_TRIPLE: + return (pos, "N403: multi line docstring end on new line") + + +def nova_docstring_multiline_start(physical_line, previous_logical, tokens): + r"""Check multi line docstring start with summary. + + nova HACKING guide recommendation for docstring: + Docstring should start with A multi line docstring has a one-line summary + + Okay: '''foobar\nfoo\nbar\n''' + N404: def foo():\n'''\nfoo\nbar\n''' \n\n + """ + if in_docstring_position(previous_logical): + pos = max([physical_line.find(i) for i in START_DOCSTRING_TRIPLE]) + # start of docstring when len(tokens)==0 + if len(tokens) == 0 and pos != -1 and len(physical_line) == pos + 4: + if physical_line.strip() in START_DOCSTRING_TRIPLE: + return (pos, "N404: multi line docstring " + "should start with a summary") def nova_no_cr(physical_line): diff --git a/tools/test-requires b/tools/test-requires index 5f195d5c1..c691a6bca 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -12,4 +12,5 @@ pylint==0.25.2 python-subunit sphinx>=1.1.2 testrepository>=0.0.13 -testtools>=0.9.22 +# testtools 0.9.25 is broken, change this when upstream is fixed (bug 1102400) +testtools>=0.9.22,<=0.9.24 @@ -18,9 +18,9 @@ downloadcache = ~/cache/pip deps=pep8==1.3.3 commands = python tools/hacking.py --doctest - python tools/hacking.py --ignore=E12,E711,E721,E712 --repeat --show-source \ + python tools/hacking.py --ignore=E12,E711,E721,E712,N403,N404 --show-source \ --exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg . - python tools/hacking.py --ignore=E12,E711,E721,E712 --repeat --show-source \ + python tools/hacking.py --ignore=E12,E711,E721,E712,N403,N404 --show-source \ --filename=nova* bin [testenv:pylint] |