mirror of
https://github.com/ceph/ceph
synced 2025-01-19 09:32:00 +00:00
Merge pull request #42043 from tchaikov/wip-ceph-argparse-ann
pybind/ceph_argparse: do not set self.typeargs in ctor of CephArgtype Reviewed-by: Sebastian Wagner <sewagner@redhat.com>
This commit is contained in:
commit
85b7b1d91e
@ -23,7 +23,7 @@ import threading
|
||||
import uuid
|
||||
|
||||
from collections import abc
|
||||
from typing import Any, Callable, Dict, Generic, List, Optional, Sequence, Tuple, Union
|
||||
from typing import cast, Any, Callable, Dict, Generic, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from typing import get_args, get_origin
|
||||
@ -127,7 +127,7 @@ class CephArgtype(object):
|
||||
set any per-instance validation parameters here
|
||||
from kwargs (fixed string sets, integer ranges, etc)
|
||||
"""
|
||||
self.typeargs = None
|
||||
pass
|
||||
|
||||
def valid(self, s, partial=False):
|
||||
"""
|
||||
@ -147,10 +147,7 @@ class CephArgtype(object):
|
||||
probably also override __str__() to give a more user-friendly
|
||||
'name/type' description for use in command format help messages.
|
||||
"""
|
||||
a = ''
|
||||
if self.typeargs is not None:
|
||||
a = self.typeargs
|
||||
return '{0}(\'{1}\')'.format(self.__class__.__name__, a)
|
||||
return self.__class__.__name__
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
@ -340,20 +337,20 @@ class CephString(CephArgtype):
|
||||
[c for c in printable if re.match(goodchars, c)]
|
||||
)
|
||||
|
||||
def valid(self, s, partial=False):
|
||||
def valid(self, s: str, partial: bool = False) -> None:
|
||||
sset = set(s)
|
||||
if self.goodset and not sset <= self.goodset:
|
||||
raise ArgumentFormat("invalid chars {0} in {1}".
|
||||
format(''.join(sset - self.goodset), s))
|
||||
self.val = s
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
b = ''
|
||||
if self.goodchars:
|
||||
b += '(goodchars {0})'.format(self.goodchars)
|
||||
return '<string{0}>'.format(b)
|
||||
|
||||
def complete(self, s):
|
||||
def complete(self, s) -> List[str]:
|
||||
if s == '':
|
||||
return []
|
||||
else:
|
||||
@ -369,13 +366,13 @@ class CephSocketpath(CephArgtype):
|
||||
"""
|
||||
Admin socket path; check that it's readable and S_ISSOCK
|
||||
"""
|
||||
def valid(self, s, partial=False):
|
||||
def valid(self, s: str, partial: bool = False) -> None:
|
||||
mode = os.stat(s).st_mode
|
||||
if not stat.S_ISSOCK(mode):
|
||||
raise ArgumentValid('socket path {0} is not a socket'.format(s))
|
||||
self.val = s
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return '<admin-socket-path>'
|
||||
|
||||
|
||||
@ -438,7 +435,7 @@ class CephEntityAddr(CephIPAddr):
|
||||
"""
|
||||
EntityAddress, that is, IP address[/nonce]
|
||||
"""
|
||||
def valid(self, s, partial=False):
|
||||
def valid(self, s: str, partial: bool = False) -> None:
|
||||
nonce = None
|
||||
if '/' in s:
|
||||
ip, nonce = s.split('/')
|
||||
@ -458,7 +455,7 @@ class CephEntityAddr(CephIPAddr):
|
||||
)
|
||||
self.val = s
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return '<EntityAddr>'
|
||||
|
||||
|
||||
@ -466,7 +463,7 @@ class CephPoolname(CephArgtype):
|
||||
"""
|
||||
Pool name; very little utility
|
||||
"""
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return '<poolname>'
|
||||
|
||||
|
||||
@ -475,7 +472,7 @@ class CephObjectname(CephArgtype):
|
||||
Object name. Maybe should be combined with Pool name as they're always
|
||||
present in pairs, and then could be checked for presence
|
||||
"""
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return '<objectname>'
|
||||
|
||||
|
||||
@ -705,14 +702,14 @@ class CephUUID(CephArgtype):
|
||||
"""
|
||||
CephUUID: pretty self-explanatory
|
||||
"""
|
||||
def valid(self, s, partial=False):
|
||||
def valid(self, s: str, partial: bool = False) -> None:
|
||||
try:
|
||||
uuid.UUID(s)
|
||||
except Exception as e:
|
||||
raise ArgumentFormat('invalid UUID {0}: {1}'.format(s, e))
|
||||
self.val = s
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return '<uuid>'
|
||||
|
||||
|
||||
@ -720,10 +717,10 @@ class CephPrefix(CephArgtype):
|
||||
"""
|
||||
CephPrefix: magic type for "all the first n fixed strings"
|
||||
"""
|
||||
def __init__(self, prefix=''):
|
||||
def __init__(self, prefix: str = '') -> None:
|
||||
self.prefix = prefix
|
||||
|
||||
def valid(self, s, partial=False):
|
||||
def valid(self, s: str, partial: bool = False) -> None:
|
||||
try:
|
||||
s = str(s)
|
||||
if isinstance(s, bytes):
|
||||
@ -746,10 +743,10 @@ class CephPrefix(CephArgtype):
|
||||
|
||||
raise ArgumentPrefix("no match for {0}".format(s))
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return self.prefix
|
||||
|
||||
def complete(self, s):
|
||||
def complete(self, s) -> List[str]:
|
||||
if self.prefix.startswith(s):
|
||||
return [self.prefix.rstrip(' ')]
|
||||
else:
|
||||
@ -778,7 +775,7 @@ class argdesc(object):
|
||||
valid() will later be called with input to validate against it,
|
||||
and will store the validated value in self.instance.val for extraction.
|
||||
"""
|
||||
def __init__(self, t, name=None, n=1, req=True, positional=True, **kwargs):
|
||||
def __init__(self, t, name=None, n=1, req=True, positional=True, **kwargs) -> None:
|
||||
if isinstance(t, basestring):
|
||||
self.t = CephPrefix
|
||||
self.typeargs = {'prefix': t}
|
||||
@ -911,7 +908,7 @@ def descsort_key(sh):
|
||||
return concise_sig(sh['sig'])
|
||||
|
||||
|
||||
def parse_funcsig(sig: Sequence[Union[str, Dict[str, str]]]) -> List[argdesc]:
|
||||
def parse_funcsig(sig: Sequence[Union[str, Dict[str, Any]]]) -> List[argdesc]:
|
||||
"""
|
||||
parse a single descriptor (array of strings or dicts) into a
|
||||
dict of function descriptor/validators (objects of CephXXX type)
|
||||
@ -1008,7 +1005,12 @@ def parse_json_funcsigs(s: str,
|
||||
return sigdict
|
||||
|
||||
|
||||
def validate_one(word, desc, is_kwarg, partial=False):
|
||||
ArgValT = Union[bool, int, float, str, Tuple[str, str]]
|
||||
|
||||
def validate_one(word: str,
|
||||
desc,
|
||||
is_kwarg: bool,
|
||||
partial: bool = False) -> List[ArgValT]:
|
||||
"""
|
||||
validate_one(word, desc, is_kwarg, partial=False)
|
||||
|
||||
@ -1033,7 +1035,9 @@ def validate_one(word, desc, is_kwarg, partial=False):
|
||||
return vals
|
||||
|
||||
|
||||
def matchnum(args, signature, partial=False):
|
||||
def matchnum(args: List[str],
|
||||
signature: List[argdesc],
|
||||
partial: bool = False) -> int:
|
||||
"""
|
||||
matchnum(s, signature, partial=False)
|
||||
|
||||
@ -1081,7 +1085,7 @@ ValidatedArg = Union[bool, int, float, str,
|
||||
ValidatedArgs = Dict[str, ValidatedArg]
|
||||
|
||||
|
||||
def store_arg(desc: argdesc, args: List[ValidatedArg], d: ValidatedArgs):
|
||||
def store_arg(desc: argdesc, args: Sequence[ValidatedArg], d: ValidatedArgs):
|
||||
'''
|
||||
Store argument described by, and held in, thanks to valid(),
|
||||
desc into the dictionary d, keyed by desc.name. Three cases:
|
||||
@ -1109,7 +1113,7 @@ def store_arg(desc: argdesc, args: List[ValidatedArg], d: ValidatedArgs):
|
||||
|
||||
def validate(args: List[str],
|
||||
signature: Sequence[argdesc],
|
||||
flags: Optional[int] = 0,
|
||||
flags: int = 0,
|
||||
partial: Optional[bool] = False) -> ValidatedArgs:
|
||||
"""
|
||||
validate(args, signature, flags=0, partial=False)
|
||||
@ -1133,11 +1137,11 @@ def validate(args: List[str],
|
||||
mysig = copy.deepcopy(signature)
|
||||
reqsiglen = len([desc for desc in mysig if desc.req])
|
||||
matchcnt = 0
|
||||
d = dict()
|
||||
d: ValidatedArgs = dict()
|
||||
save_exception = None
|
||||
|
||||
arg_descs_by_name = dict([desc.name, desc] for desc in mysig
|
||||
if desc.t != CephPrefix)
|
||||
arg_descs_by_name: Dict[str, argdesc] = \
|
||||
dict((desc.name, desc) for desc in mysig if desc.t != CephPrefix)
|
||||
|
||||
# Special case: detect "injectargs" (legacy way of modifying daemon
|
||||
# configs) and permit "--" string arguments if so.
|
||||
@ -1149,7 +1153,7 @@ def validate(args: List[str],
|
||||
|
||||
while desc.numseen < desc.n:
|
||||
if myargs:
|
||||
myarg = myargs.pop(0)
|
||||
myarg: Optional[str] = myargs.pop(0)
|
||||
else:
|
||||
myarg = None
|
||||
|
||||
@ -1253,6 +1257,7 @@ def validate(args: List[str],
|
||||
break
|
||||
|
||||
# Have an arg; validate it
|
||||
assert myarg is not None
|
||||
try:
|
||||
args = validate_one(myarg, desc, False)
|
||||
except ArgumentError as e:
|
||||
@ -1295,7 +1300,7 @@ def validate(args: List[str],
|
||||
|
||||
|
||||
def validate_command(sigdict: Dict[str, Dict[str, Any]],
|
||||
args: Sequence[str],
|
||||
args: List[str],
|
||||
verbose: Optional[bool] = False) -> ValidatedArgs:
|
||||
"""
|
||||
Parse positional arguments into a parameter dict, according to
|
||||
@ -1315,19 +1320,19 @@ def validate_command(sigdict: Dict[str, Dict[str, Any]],
|
||||
"""
|
||||
if verbose:
|
||||
print("validate_command: " + " ".join(args), file=sys.stderr)
|
||||
found = []
|
||||
found: Optional[Dict[str, Any]] = None
|
||||
valid_dict = {}
|
||||
|
||||
# look for best match, accumulate possibles in bestcmds
|
||||
# (so we can maybe give a more-useful error message)
|
||||
best_match_cnt = 0
|
||||
bestcmds = []
|
||||
best_match_cnt = 0.0
|
||||
bestcmds: List[Dict[str, Any]] = []
|
||||
for cmd in sigdict.values():
|
||||
flags = cmd.get('flags', 0)
|
||||
if flags & Flag.OBSOLETE:
|
||||
continue
|
||||
sig = cmd['sig']
|
||||
matched = matchnum(args, sig, partial=True)
|
||||
matched: float = matchnum(args, sig, partial=True)
|
||||
if (matched >= math.floor(best_match_cnt) and
|
||||
matched == matchnum(args, sig, partial=False)):
|
||||
# prefer those fully matched over partial patch
|
||||
@ -1357,7 +1362,7 @@ def validate_command(sigdict: Dict[str, Dict[str, Any]],
|
||||
print("bestcmds_sorted: ", file=sys.stderr)
|
||||
pprint.PrettyPrinter(stream=sys.stderr).pprint(bestcmds_sorted)
|
||||
|
||||
ex = None
|
||||
ex: Optional[ArgumentError] = None
|
||||
# for everything in bestcmds, look for a true match
|
||||
for cmd in bestcmds_sorted:
|
||||
sig = cmd['sig']
|
||||
@ -1405,7 +1410,7 @@ def validate_command(sigdict: Dict[str, Dict[str, Any]],
|
||||
return valid_dict
|
||||
|
||||
|
||||
def find_cmd_target(childargs: List[str]) -> Tuple[str, str]:
|
||||
def find_cmd_target(childargs: List[str]) -> Tuple[str, Optional[str]]:
|
||||
"""
|
||||
Using a minimal validation, figure out whether the command
|
||||
should be sent to a monitor or an osd. We do this before even
|
||||
@ -1425,6 +1430,7 @@ def find_cmd_target(childargs: List[str]) -> Tuple[str, str]:
|
||||
# if this fails, something is horribly wrong, as it just
|
||||
# validated successfully above
|
||||
name.valid(valid_dict['target'])
|
||||
assert name.nametype is not None
|
||||
return name.nametype, name.nameid
|
||||
|
||||
sig = parse_funcsig(['tell', {'name': 'pgid', 'type': 'CephPgid'}])
|
||||
@ -1435,7 +1441,9 @@ def find_cmd_target(childargs: List[str]) -> Tuple[str, str]:
|
||||
else:
|
||||
if len(valid_dict) == 2:
|
||||
# pg doesn't need revalidation; the string is fine
|
||||
return 'pg', valid_dict['pgid']
|
||||
pgid = valid_dict['pgid']
|
||||
assert isinstance(pgid, str)
|
||||
return 'pg', pgid
|
||||
|
||||
# If we reached this far it must mean that so far we've been unable to
|
||||
# obtain a proper target from childargs. This may mean that we are not
|
||||
@ -1459,6 +1467,8 @@ def find_cmd_target(childargs: List[str]) -> Tuple[str, str]:
|
||||
# CephName.valid() raises on validation error; find_cmd_target()'s
|
||||
# caller should handle them
|
||||
name.valid(childargs[1])
|
||||
assert name.nametype is not None
|
||||
assert name.nameid is not None
|
||||
return name.nametype, name.nameid
|
||||
|
||||
sig = parse_funcsig(['pg', {'name': 'pgid', 'type': 'CephPgid'}])
|
||||
@ -1468,7 +1478,9 @@ def find_cmd_target(childargs: List[str]) -> Tuple[str, str]:
|
||||
pass
|
||||
else:
|
||||
if len(valid_dict) == 2:
|
||||
return 'pg', valid_dict['pgid']
|
||||
pgid = valid_dict['pgid']
|
||||
assert isinstance(pgid, str)
|
||||
return 'pg', pgid
|
||||
|
||||
return 'mon', ''
|
||||
|
||||
@ -1488,8 +1500,8 @@ class RadosThread(threading.Thread):
|
||||
self.exception = e
|
||||
|
||||
|
||||
def run_in_thread(func: Callable[[Any, Any], int],
|
||||
*args: Any, **kwargs: Any) -> int:
|
||||
def run_in_thread(func: Callable[[Any, Any], Tuple[int, bytes, str]],
|
||||
*args: Any, **kwargs: Any) -> Tuple[int, bytes, str]:
|
||||
timeout = kwargs.pop('timeout', 0)
|
||||
if timeout == 0 or timeout is None:
|
||||
# python threading module will just get blocked if timeout is `None`,
|
||||
@ -1537,8 +1549,8 @@ def send_command_retry(*args: Any, **kwargs: Any) -> Tuple[int, bytes, str]:
|
||||
|
||||
|
||||
def send_command(cluster,
|
||||
target: Optional[Tuple[str, str]] = ('mon', ''),
|
||||
cmd: Optional[List[str]] = None,
|
||||
target: Tuple[str, Optional[str]] = ('mon', ''),
|
||||
cmd: Optional[str] = None,
|
||||
inbuf: Optional[bytes] = b'',
|
||||
timeout: Optional[int] = 0,
|
||||
verbose: Optional[bool] = False) -> Tuple[int, bytes, str]:
|
||||
@ -1553,10 +1565,10 @@ def send_command(cluster,
|
||||
|
||||
If target is osd.N, send command to that osd (except for pgid cmds)
|
||||
"""
|
||||
cmd = cmd or []
|
||||
try:
|
||||
if target[0] == 'osd':
|
||||
osdid = target[1]
|
||||
assert osdid is not None
|
||||
|
||||
if verbose:
|
||||
print('submit {0} to osd.{1}'.format(cmd, osdid),
|
||||
@ -1637,9 +1649,9 @@ def send_command(cluster,
|
||||
|
||||
|
||||
def json_command(cluster,
|
||||
target: Optional[Tuple[str, str]] = ('mon', ''),
|
||||
target: Tuple[str, Optional[str]] = ('mon', ''),
|
||||
prefix: Optional[str] = None,
|
||||
argdict: Optional[Dict[str, str]] = None,
|
||||
argdict: Optional[ValidatedArgs] = None,
|
||||
inbuf: Optional[bytes] = b'',
|
||||
timeout: Optional[int] = 0,
|
||||
verbose: Optional[bool] = False) -> Tuple[int, bytes, str]:
|
||||
@ -1654,14 +1666,14 @@ def json_command(cluster,
|
||||
:param prefix: String to inject into command arguments as 'prefix'
|
||||
:param argdict: Command arguments
|
||||
"""
|
||||
cmddict = {}
|
||||
cmddict: ValidatedArgs = {}
|
||||
if prefix:
|
||||
cmddict.update({'prefix': prefix})
|
||||
|
||||
if argdict:
|
||||
cmddict.update(argdict)
|
||||
if 'target' in argdict:
|
||||
target = argdict.get('target')
|
||||
target = cast(Tuple[str, str], argdict['target'])
|
||||
|
||||
try:
|
||||
if target[0] == 'osd':
|
||||
@ -1669,7 +1681,7 @@ def json_command(cluster,
|
||||
osdtarget = '{0}.{1}'.format(*target)
|
||||
# prefer target from cmddict if present and valid
|
||||
if 'target' in cmddict:
|
||||
osdtarget = cmddict.pop('target')
|
||||
osdtarget = cast(str, cmddict.pop('target'))
|
||||
try:
|
||||
osdtarg.valid(osdtarget)
|
||||
target = ('osd', osdtarg.nameid)
|
||||
|
@ -22,7 +22,7 @@ from prettytable import PrettyTable, HEADER
|
||||
from signal import signal, Signals, SIGWINCH
|
||||
from termios import TIOCGWINSZ
|
||||
from types import FrameType
|
||||
from typing import Any, Callable, Dict, Optional, Sequence, TextIO, Tuple
|
||||
from typing import Any, Callable, Dict, List, Optional, Sequence, TextIO, Tuple
|
||||
|
||||
from ceph_argparse import parse_json_funcsigs, validate_command
|
||||
|
||||
@ -32,7 +32,7 @@ READ_CHUNK_SIZE = 4096
|
||||
|
||||
|
||||
def admin_socket(asok_path: str,
|
||||
cmd: Sequence[str],
|
||||
cmd: List[str],
|
||||
format: Optional[str] = '') -> bytes:
|
||||
"""
|
||||
Send a daemon (--admin-daemon) command 'cmd'. asok_path is the
|
||||
|
Loading…
Reference in New Issue
Block a user