Merge "mypy: cmd/manage.py"

This commit is contained in:
Zuul 2022-11-18 17:00:55 +00:00 committed by Gerrit Code Review
commit c210c87495
2 changed files with 84 additions and 39 deletions

View File

@ -50,6 +50,8 @@
"""CLI interface for cinder management.""" """CLI interface for cinder management."""
from __future__ import annotations
import collections import collections
import collections.abc as collections_abc import collections.abc as collections_abc
import errno import errno
@ -60,6 +62,8 @@ import os
import re import re
import sys import sys
import time import time
import typing
from typing import Any, Callable, Optional, Tuple, Union # noqa: H301
from oslo_config import cfg from oslo_config import cfg
from oslo_db import exception as db_exc from oslo_db import exception as db_exc
@ -101,6 +105,7 @@ OVO_VERSION = ovo_base.OBJ_VERSIONS.get_current()
# Decorators for actions # Decorators for actions
@typing.no_type_check
def args(*args, **kwargs): def args(*args, **kwargs):
args = list(args) args = list(args)
if not args[0].startswith('-') and '-' in args[0]: if not args[0].startswith('-') and '-' in args[0]:
@ -118,7 +123,7 @@ class HostCommands(object):
@args('zone', nargs='?', default=None, @args('zone', nargs='?', default=None,
help='Availability Zone (default: %(default)s)') help='Availability Zone (default: %(default)s)')
def list(self, zone=None): def list(self, zone: Optional[str] = None) -> None:
"""Show a list of all physical hosts. """Show a list of all physical hosts.
Can be filtered by zone. Can be filtered by zone.
@ -129,7 +134,7 @@ class HostCommands(object):
services = objects.ServiceList.get_all(ctxt) services = objects.ServiceList.get_all(ctxt)
if zone: if zone:
services = [s for s in services if s.availability_zone == zone] services = [s for s in services if s.availability_zone == zone]
hosts = [] hosts: list[dict[str, Any]] = []
for srv in services: for srv in services:
if not [h for h in hosts if h['host'] == srv['host']]: if not [h for h in hosts if h['host'] == srv['host']]:
hosts.append(srv) hosts.append(srv)
@ -152,7 +157,8 @@ class DbCommands(object):
# preceed any element of the "online_migrations" tuple, like this: # preceed any element of the "online_migrations" tuple, like this:
# # Added in Queens remove in Rocky # # Added in Queens remove in Rocky
# db.service_uuids_online_data_migration, # db.service_uuids_online_data_migration,
online_migrations = ( online_migrations: Tuple[Callable[[context.RequestContext, int],
Tuple[int, int]], ...] = (
# TODO: (Z Release) Remove next line and this comment # TODO: (Z Release) Remove next line and this comment
# TODO: (Y Release) Uncomment next line and remove this comment # TODO: (Y Release) Uncomment next line and remove this comment
# db.remove_temporary_admin_metadata_data_migration, # db.remove_temporary_admin_metadata_data_migration,
@ -172,7 +178,9 @@ class DbCommands(object):
help='Update RPC and Objects versions when doing offline upgrades, ' help='Update RPC and Objects versions when doing offline upgrades, '
'with this we no longer need to restart the services twice ' 'with this we no longer need to restart the services twice '
'after the upgrade to prevent ServiceTooOld exceptions.') 'after the upgrade to prevent ServiceTooOld exceptions.')
def sync(self, version=None, bump_versions=False): def sync(self,
version: Optional[int] = None,
bump_versions: bool = False) -> None:
"""Sync the database up to the most recent version.""" """Sync the database up to the most recent version."""
if version is not None and version > db.MAX_INT: if version is not None and version > db.MAX_INT:
print(_('Version should be less than or equal to ' print(_('Version should be less than or equal to '
@ -199,13 +207,13 @@ class DbCommands(object):
print(_('Error during service version bump: %s') % ex) print(_('Error during service version bump: %s') % ex)
sys.exit(2) sys.exit(2)
def version(self): def version(self) -> None:
"""Print the current database version.""" """Print the current database version."""
print(db_migration.db_version()) print(db_migration.db_version())
@args('age_in_days', type=int, @args('age_in_days', type=int,
help='Purge deleted rows older than age in days') help='Purge deleted rows older than age in days')
def purge(self, age_in_days): def purge(self, age_in_days: int) -> None:
"""Purge deleted rows older than a given age from cinder tables.""" """Purge deleted rows older than a given age from cinder tables."""
age_in_days = int(age_in_days) age_in_days = int(age_in_days)
if age_in_days < 0: if age_in_days < 0:
@ -223,7 +231,9 @@ class DbCommands(object):
"logs for more details.")) "logs for more details."))
sys.exit(1) sys.exit(1)
def _run_migration(self, ctxt, max_count): def _run_migration(self,
ctxt: context.RequestContext,
max_count: int) -> Tuple[dict, bool]:
ran = 0 ran = 0
exceptions = False exceptions = False
migrations = {} migrations = {}
@ -254,7 +264,7 @@ class DbCommands(object):
@args('--max_count', metavar='<number>', dest='max_count', type=int, @args('--max_count', metavar='<number>', dest='max_count', type=int,
help='Maximum number of objects to consider.') help='Maximum number of objects to consider.')
def online_data_migrations(self, max_count=None): def online_data_migrations(self, max_count: Optional[int] = None) -> None:
"""Perform online data migrations for the release in batches.""" """Perform online data migrations for the release in batches."""
ctxt = context.get_admin_context() ctxt = context.get_admin_context()
if max_count is not None: if max_count is not None:
@ -269,7 +279,7 @@ class DbCommands(object):
ran = None ran = None
exceptions = False exceptions = False
migration_info = {} migration_info: dict[str, Any] = {}
while ran is None or ran != 0: while ran is None or ran != 0:
migrations, exceptions = self._run_migration(ctxt, max_count) migrations, exceptions = self._run_migration(ctxt, max_count)
ran = 0 ran = 0
@ -311,8 +321,10 @@ class DbCommands(object):
help='Change the active backend ID (default: %(default)s).') help='Change the active backend ID (default: %(default)s).')
@args('--backend-host', required=True, @args('--backend-host', required=True,
help='The backend host name.') help='The backend host name.')
def reset_active_backend(self, enable_replication, active_backend_id, def reset_active_backend(self,
backend_host): enable_replication: bool,
active_backend_id: Optional[str],
backend_host: str) -> None:
"""Reset the active backend for a host.""" """Reset the active backend for a host."""
ctxt = context.get_admin_context() ctxt = context.get_admin_context()
@ -336,7 +348,7 @@ class QuotaCommands(object):
@args('--project-id', default=None, @args('--project-id', default=None,
help=('The ID of the project where we want to sync the quotas ' help=('The ID of the project where we want to sync the quotas '
'(defaults to all projects).')) '(defaults to all projects).'))
def check(self, project_id): def check(self, project_id: Optional[str]) -> None:
"""Check if quotas and reservations are correct """Check if quotas and reservations are correct
This action checks quotas and reservations, for a specific project or This action checks quotas and reservations, for a specific project or
@ -356,7 +368,7 @@ class QuotaCommands(object):
@args('--project-id', default=None, @args('--project-id', default=None,
help=('The ID of the project where we want to sync the quotas ' help=('The ID of the project where we want to sync the quotas '
'(defaults to all projects).')) '(defaults to all projects).'))
def sync(self, project_id): def sync(self, project_id: Optional[str]) -> None:
"""Fix quotas and reservations """Fix quotas and reservations
This action refreshes existing quota usage and reservation count for a This action refreshes existing quota usage and reservation count for a
@ -373,7 +385,9 @@ class QuotaCommands(object):
self._check_sync(project_id, do_fix=True) self._check_sync(project_id, do_fix=True)
@db_api.main_context_manager.reader @db_api.main_context_manager.reader
def _get_quota_projects(self, context, project_id): def _get_quota_projects(self,
context: context.RequestContext,
project_id: Optional[str]) -> list[str]:
"""Get project ids that have quota_usage entries.""" """Get project ids that have quota_usage entries."""
if project_id: if project_id:
model = models.QuotaUsage model = models.QuotaUsage
@ -402,7 +416,10 @@ class QuotaCommands(object):
project_ids = [row.project_id for row in projects] project_ids = [row.project_id for row in projects]
return project_ids return project_ids
def _get_usages(self, context, resources, project_id): def _get_usages(self,
ctxt: context.RequestContext,
resources,
project_id: str) -> list:
"""Get data necessary to check out of sync quota usage. """Get data necessary to check out of sync quota usage.
Returns a list QuotaUsage instances for the specific project Returns a list QuotaUsage instances for the specific project
@ -414,7 +431,10 @@ class QuotaCommands(object):
).filter_by(project_id=project_id).with_for_update().all() ).filter_by(project_id=project_id).with_for_update().all()
return usages return usages
def _get_reservations(self, context, project_id, usage_id): def _get_reservations(self,
ctxt: context.RequestContext,
project_id: str,
usage_id: str) -> list:
"""Get reservations for a given project and usage id.""" """Get reservations for a given project and usage id."""
reservations = ( reservations = (
db_api.model_query( db_api.model_query(
@ -428,7 +448,10 @@ class QuotaCommands(object):
) )
return reservations return reservations
def _check_duplicates(self, context, usages, do_fix): def _check_duplicates(self,
context: context.RequestContext,
usages,
do_fix: bool) -> tuple[list, bool]:
"""Look for duplicated quota used entries (bug#1484343) """Look for duplicated quota used entries (bug#1484343)
If we have duplicates and we are fixing them, then we reassign the If we have duplicates and we are fixing them, then we reassign the
@ -470,7 +493,7 @@ class QuotaCommands(object):
result.append(keep_usage) result.append(keep_usage)
return result, duplicates_found return result, duplicates_found
def _check_sync(self, project_id, do_fix): def _check_sync(self, project_id: Optional[str], do_fix: bool) -> bool:
"""Check the quotas and reservations optionally fixing them.""" """Check the quotas and reservations optionally fixing them."""
ctxt = context.get_admin_context() ctxt = context.get_admin_context()
@ -496,7 +519,11 @@ class QuotaCommands(object):
return discrepancy return discrepancy
@db_api.main_context_manager.reader @db_api.main_context_manager.reader
def _check_project_sync(self, context, project, do_fix, resources): def _check_project_sync(self,
context: context.RequestContext,
project: str,
do_fix: bool,
resources) -> bool:
print('Processing quota usage for project %s' % project) print('Processing quota usage for project %s' % project)
discrepancy = False discrepancy = False
@ -579,7 +606,7 @@ class VolumeCommands(object):
@args('volume_id', @args('volume_id',
help='Volume ID to be deleted') help='Volume ID to be deleted')
def delete(self, volume_id): def delete(self, volume_id: str) -> None:
"""Delete a volume, bypassing the check that it must be available.""" """Delete a volume, bypassing the check that it must be available."""
ctxt = context.get_admin_context() ctxt = context.get_admin_context()
volume = objects.Volume.get_by_id(ctxt, volume_id) volume = objects.Volume.get_by_id(ctxt, volume_id)
@ -604,7 +631,7 @@ class VolumeCommands(object):
'the format host@backend#pool') 'the format host@backend#pool')
@args('--newhost', required=True, help='New volume host name in the ' @args('--newhost', required=True, help='New volume host name in the '
'format host@backend#pool') 'format host@backend#pool')
def update_host(self, currenthost, newhost): def update_host(self, currenthost: str, newhost: str) -> None:
"""Modify the host name associated with a volume. """Modify the host name associated with a volume.
Particularly to recover from cases where one has moved Particularly to recover from cases where one has moved
@ -627,7 +654,7 @@ class ConfigCommands(object):
@args('param', nargs='?', default=None, @args('param', nargs='?', default=None,
help='Configuration parameter to display (default: %(default)s)') help='Configuration parameter to display (default: %(default)s)')
def list(self, param=None): def list(self, param: Optional[str] = None) -> None:
"""List parameters configured for cinder. """List parameters configured for cinder.
Lists all parameters configured for cinder unless an optional argument Lists all parameters configured for cinder unless an optional argument
@ -646,7 +673,7 @@ class ConfigCommands(object):
class BackupCommands(object): class BackupCommands(object):
"""Methods for managing backups.""" """Methods for managing backups."""
def list(self): def list(self) -> None:
"""List all backups. """List all backups.
List all backups (including ones in progress) and the host List all backups (including ones in progress) and the host
@ -683,7 +710,7 @@ class BackupCommands(object):
@args('--currenthost', required=True, help='Existing backup host name') @args('--currenthost', required=True, help='Existing backup host name')
@args('--newhost', required=True, help='New backup host name') @args('--newhost', required=True, help='New backup host name')
def update_backup_host(self, currenthost, newhost): def update_backup_host(self, currenthost: str, newhost: str) -> None:
"""Modify the host name associated with a backup. """Modify the host name associated with a backup.
Particularly to recover from cases where one has moved Particularly to recover from cases where one has moved
@ -738,7 +765,7 @@ class ServiceCommands(BaseCommand):
help='Service to delete from the host.') help='Service to delete from the host.')
@args('host_name', type=str, @args('host_name', type=str,
help='Host from which to remove the service.') help='Host from which to remove the service.')
def remove(self, binary, host_name): def remove(self, binary: str, host_name: str) -> Optional[int]:
"""Completely removes a service.""" """Completely removes a service."""
ctxt = context.get_admin_context() ctxt = context.get_admin_context()
try: try:
@ -753,10 +780,12 @@ class ServiceCommands(BaseCommand):
print(_("Service %(service)s on host %(host)s removed.") % print(_("Service %(service)s on host %(host)s removed.") %
{'service': binary, 'host': host_name}) {'service': binary, 'host': host_name})
return None
class ClusterCommands(BaseCommand): class ClusterCommands(BaseCommand):
"""Methods for managing clusters.""" """Methods for managing clusters."""
def list(self): def list(self) -> None:
"""Show a list of all cinder services.""" """Show a list of all cinder services."""
ctxt = context.get_admin_context() ctxt = context.get_admin_context()
clusters = objects.ClusterList.get_all(ctxt, services_summary=True) clusters = objects.ClusterList.get_all(ctxt, services_summary=True)
@ -783,7 +812,10 @@ class ClusterCommands(BaseCommand):
@args('binary', type=str, @args('binary', type=str,
help='Service to delete from the cluster.') help='Service to delete from the cluster.')
@args('cluster-name', type=str, help='Cluster to delete.') @args('cluster-name', type=str, help='Cluster to delete.')
def remove(self, recursive, binary, cluster_name): def remove(self,
recursive: bool,
binary: str,
cluster_name: str) -> Optional[int]:
"""Completely removes a cluster.""" """Completely removes a cluster."""
ctxt = context.get_admin_context() ctxt = context.get_admin_context()
try: try:
@ -812,13 +844,18 @@ class ClusterCommands(BaseCommand):
'removed.') % {'msg': msg, 'num': len(cluster.services)}) 'removed.') % {'msg': msg, 'num': len(cluster.services)})
print(msg) print(msg)
return None
@args('--full-rename', dest='partial', @args('--full-rename', dest='partial',
action='store_false', default=True, action='store_false', default=True,
help='Do full cluster rename instead of just replacing provided ' help='Do full cluster rename instead of just replacing provided '
'current cluster name and preserving backend and/or pool info.') 'current cluster name and preserving backend and/or pool info.')
@args('current', help='Current cluster name.') @args('current', help='Current cluster name.')
@args('new', help='New cluster name.') @args('new', help='New cluster name.')
def rename(self, partial, current, new): def rename(self,
partial: bool,
current: Optional[str],
new: Optional[str]) -> Optional[int]:
"""Rename cluster name for Volumes and Consistency Groups. """Rename cluster name for Volumes and Consistency Groups.
Useful when you want to rename a cluster, particularly when the Useful when you want to rename a cluster, particularly when the
@ -851,13 +888,15 @@ class ClusterCommands(BaseCommand):
print(msg % {'current': current}) print(msg % {'current': current})
return 2 return 2
return None
class ConsistencyGroupCommands(object): class ConsistencyGroupCommands(object):
"""Methods for managing consistency groups.""" """Methods for managing consistency groups."""
@args('--currenthost', required=True, help='Existing CG host name') @args('--currenthost', required=True, help='Existing CG host name')
@args('--newhost', required=True, help='New CG host name') @args('--newhost', required=True, help='New CG host name')
def update_cg_host(self, currenthost, newhost): def update_cg_host(self, currenthost: str, newhost: str) -> None:
"""Modify the host name associated with a Consistency Group. """Modify the host name associated with a Consistency Group.
Particularly to recover from cases where one has moved Particularly to recover from cases where one has moved
@ -878,7 +917,9 @@ class UtilCommands(object):
"""Generic utils.""" """Generic utils."""
@staticmethod @staticmethod
def _get_resources_locks(): def _get_resources_locks() -> Tuple[collections.defaultdict,
collections.defaultdict,
collections.defaultdict]:
"""Get all vol/snap/backup file lock paths.""" """Get all vol/snap/backup file lock paths."""
backup_locks_prefix = 'cinder-cleanup_incomplete_backups_' backup_locks_prefix = 'cinder-cleanup_incomplete_backups_'
oslo_dir = os.path.abspath(cfg.CONF.oslo_concurrency.lock_path) oslo_dir = os.path.abspath(cfg.CONF.oslo_concurrency.lock_path)
@ -890,8 +931,8 @@ class UtilCommands(object):
if tooz_dir != oslo_dir: if tooz_dir != oslo_dir:
filenames += glob.glob(os.path.join(tooz_dir, 'cinder-*')) filenames += glob.glob(os.path.join(tooz_dir, 'cinder-*'))
volumes = collections.defaultdict(list) volumes: collections.defaultdict = collections.defaultdict(list)
snapshots = collections.defaultdict(list) snapshots: collections.defaultdict = collections.defaultdict(list)
backups = collections.defaultdict(list) backups = collections.defaultdict(list)
matcher = re.compile('.*?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-' matcher = re.compile('.*?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-'
'[0-9a-f]{4}-[0-9a-f]{12}).*?', re.IGNORECASE) '[0-9a-f]{4}-[0-9a-f]{12}).*?', re.IGNORECASE)
@ -908,7 +949,7 @@ class UtilCommands(object):
return volumes, snapshots, backups return volumes, snapshots, backups
def _exclude_running_backups(self, backups): def _exclude_running_backups(self, backups: dict) -> None:
"""Remove backup entries from the dict for running backup services.""" """Remove backup entries from the dict for running backup services."""
for backup_pgrp in list(backups.keys()): for backup_pgrp in list(backups.keys()):
# The PGRP is the same as the PID of the parent process, so we know # The PGRP is the same as the PID of the parent process, so we know
@ -928,7 +969,7 @@ class UtilCommands(object):
@args('--services-offline', dest='online', @args('--services-offline', dest='online',
action='store_false', default=True, action='store_false', default=True,
help='All locks can be deleted as Cinder services are not running.') help='All locks can be deleted as Cinder services are not running.')
def clean_locks(self, online): def clean_locks(self, online: bool) -> None:
"""Clean file locks for vols, snaps, and backups on the current host. """Clean file locks for vols, snaps, and backups on the current host.
Should be run on any host where we are running a Cinder service (API, Should be run on any host where we are running a Cinder service (API,
@ -955,6 +996,8 @@ class UtilCommands(object):
self.ctxt = context.get_admin_context() self.ctxt = context.get_admin_context()
# Find volume and snapshots ids, and backups PGRP based on the existing # Find volume and snapshots ids, and backups PGRP based on the existing
# file locks # file locks
volumes: Union[collections.defaultdict, dict]
snapshots: Union[collections.defaultdict, dict]
volumes, snapshots, backups = self._get_resources_locks() volumes, snapshots, backups = self._get_resources_locks()
# If services are online we cannot delete locks for existing resources # If services are online we cannot delete locks for existing resources
@ -999,7 +1042,7 @@ CATEGORIES = {
} }
def methods_of(obj): def methods_of(obj) -> list:
"""Return non-private methods from an object. """Return non-private methods from an object.
Get all callable methods of an object that don't start with underscore Get all callable methods of an object that don't start with underscore
@ -1007,13 +1050,14 @@ def methods_of(obj):
""" """
result = [] result = []
for i in dir(obj): for i in dir(obj):
if isinstance(getattr(obj, i), if (isinstance(getattr(obj, i),
collections_abc.Callable) and not i.startswith('_'): collections_abc.Callable) and # type: ignore
not i.startswith('_')):
result.append((i, getattr(obj, i))) result.append((i, getattr(obj, i)))
return result return result
def missing_action(help_func): def missing_action(help_func: Callable) -> Callable:
def wrapped(): def wrapped():
help_func() help_func()
exit(2) exit(2)
@ -1033,7 +1077,7 @@ def add_command_parsers(subparsers):
for (action, action_fn) in methods_of(command_object): for (action, action_fn) in methods_of(command_object):
parser = category_subparsers.add_parser(action) parser = category_subparsers.add_parser(action)
action_kwargs = [] action_kwargs: list = []
for args, kwargs in getattr(action_fn, 'args', []): for args, kwargs in getattr(action_fn, 'args', []):
parser.add_argument(*args, **kwargs) parser.add_argument(*args, **kwargs)

View File

@ -9,6 +9,7 @@ cinder/context.py
cinder/coordination.py cinder/coordination.py
cinder/cmd/api.py cinder/cmd/api.py
cinder/cmd/backup.py cinder/cmd/backup.py
cinder/cmd/manage.py
cinder/cmd/scheduler.py cinder/cmd/scheduler.py
cinder/cmd/status.py cinder/cmd/status.py
cinder/cmd/volume.py cinder/cmd/volume.py