From: David Howells Date: Mon, 7 Apr 2014 22:41:20 +0000 (+0100) Subject: Create 'vos listvldb' command X-Git-Url: https://www.infradead.org/git/?a=commitdiff_plain;h=c40d42d4e567044ff93e4c0359d3948858187e93;p=users%2Fdhowells%2Fkafs-utils.git Create 'vos listvldb' command --- diff --git a/.gitignore b/.gitignore index 64e3998..9c2285b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ afs_py.h afs_xg.c afs_xg.h *.so +__pycache__ diff --git a/suite/afs b/suite/afs new file mode 100755 index 0000000..7699e4c --- /dev/null +++ b/suite/afs @@ -0,0 +1,48 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# -*- python-mode -*- + +"""AFS Toolkit Volume management command set +""" + +__copyright__ = """ +Copyright (C) 2014 Red Hat, Inc. All Rights Reserved. +Written by David Howells (dhowells@redhat.com) + +Copyright (C) 2005, Catalin Marinas + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public Licence version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public Licence for more details. + +You should have received a copy of the GNU General Public Licence +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +import sys, os + +# Try to detect where it is run from and set prefix and the search path. +# It is assumed that the user installed Afs using the --prefix= option +prefix, bin = os.path.split(sys.path[0]) + +if bin == 'bin' and prefix != sys.prefix: + sys.prefix = prefix + sys.exec_prefix = prefix + + major, minor = sys.version_info[0:2] + local_path = [os.path.join(prefix, 'lib', 'python'), + os.path.join(prefix, 'lib', 'python%s.%s' % (major, minor)), + os.path.join(prefix, 'lib', 'python%s.%s' % (major, minor), + 'site-packages')] + sys.path = local_path + sys.path + +from afs.main import main + +if __name__ == '__main__': + main() diff --git a/suite/argparse.py b/suite/argparse.py new file mode 100644 index 0000000..f0ffcec --- /dev/null +++ b/suite/argparse.py @@ -0,0 +1,214 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +# +# Parse an argument list for a subcommand +# +__copyright__ = """ +Copyright (C) 2014 Red Hat, Inc. All Rights Reserved. +Written by David Howells (dhowells@redhat.com) + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public Licence +as published by the Free Software Foundation; either version +2 of the Licence, or (at your option) any later version. +""" + +def get_cell(switch, params): + from afs.lib.cell import cell + return cell(params[0]) + +def get_vlserver(switch, params): + from afs.lib.vlserver import vlserver + return vlserver(params[0]) + +def get_volume_name(switch, params): + return params + +def get_machine_name(switch, params): + return params + +def get_path_name(switch, params): + return params + +def get_file_name(switch, params): + return params + +def get_partition_id(switch, params): + part = params[0] + if part.startswith("/vice"): + part = part[5:] + elif part.startswith("vice"): + part = part[4:] + + if part.isnumeric(): + part = int(part) + elif len(part) == 1 and part >= "a" and part <= "z": + part = bytes(part)[0] - 97 + elif len(part) == 2 and part[0] >= "a" and part[0] <= "z" and part[1] >= "a" and part[1] <= "z": + part = (bytes(part)[0] - 97) * 26 | bytes(part)[1] - 97 + else: + raise RuntimeError("Unparseable partition ID '" + params[0] + "'") + + if part > 255: + raise RuntimeError("Partition ID '" + params[0] + "' out of range") + return part + +def get_auth(switch, params): + return params + +def get_dummy(switch, params): + return params + +############################################################################### +# +# Parse an argument list according to a defined set of switches. +# +# The following is an excerpt from the AFS Reference Manual, Introduction to +# AFS commands: +# +# CONDITIONS FOR OMITTING SWITCHES +# +# It is always acceptable to type the switch part of an argument, but in +# many cases it is not necessary. Specifically, switches can be omitted if +# the following conditions are met. +# +# (*) All of the command's required arguments appear in the order prescribed +# by the syntax statement. +# +# (*) No switch is provided for any argument. +# +# (*) There is only one value for each argument (but note the important +# exception discussed in the following paragraph). +# +# Omitting switches is possible only because there is a prescribed order for +# each command's arguments. When the issuer does not include switches, the +# command interpreter relies instead on the order of arguments; it assumes +# that the first element after the operation code is the command's first +# argument, the next element is the command's second argument, and so +# on. The important exception is when a command's final required argument +# accepts multiple values. In this case, the command interpreter assumes +# that the issuer has correctly provided one value for each argument up +# through the final one, so any additional values at the end belong to the +# final argument. +# +# The following list describes the rules for omitting switches from the +# opposite perspective: an argument's switch must be provided when any of +# the following conditions apply. +# +# (*) The command's arguments do not appear in the prescribed order. +# +# (*) An optional argument is omitted but a subsequent optional argument is +# provided. +# +# (*) A switch is provided for a preceding argument. +# +# (*) More than one value is supplied for a preceding argument (which must +# take multiple values, of course); without a switch on the current +# argument, the command interpreter assumes that the current argument is +# another value for the preceding argument. +# +############################################################################### +def parse_arguments(args, available_arguments): + result = {} + need_switch = False + i = 0 # Given arguments index + av = 0 # Available arguments index + + #print args + + if len(args) == 0: + if len(available_arguments) > 0 and available_arguments[0][0] == "r": + raise RuntimeError("Missing required parameters") + return result + + # Process all the optional arguments or switch-based required arguments + while i < len(args): + match = False + params = [] + + if args[i][0] != "-": + # Deal with positional arguments + if need_switch: + raise RuntimeError("Need switch before argument " + i) + if av >= len(available_arguments): + raise RuntimeError("Unexpected positional argument") + match = available_arguments[av] + pattern = match[2] + if pattern[0] == "f": + raise RuntimeError("Unexpected positional argument") + av = av + 1 + + params.append(args[i]) + i = i + 1 + + if match[2][1] == "m": + # All remaining arguments up to the next switch belong to this + while i < len(args): + if args[i][0] == "-": + break + params.append(args[i]) + i = i + 1 + need_switch = True + + else: + # Deal with tagged arguments + switch = args[i][1:] + i = i + 1 + + if switch == "help": + raise RuntimeError("Print help message") + if switch == "": + raise RuntimeError("Missing switch name") + + # Look up the switch in the table of possible arguments and flags + for j in available_arguments: + if j[0] == switch: + match = j + break + if j[0].startswith(switch): + if match: + raise RuntimeError("Ambiguous switch name abbreviation '-" + switch + "'") + match = j + if not match: + raise RuntimeError("Unsupported switch '-" + switch + "'") + + # Reject repeat flags + if match[0] in result: + raise RuntimeError("Duplicate switch '-" + switch + "' not permitted") + + # Arrange the parameters associated with the switch into a list + while i < len(args): + if args[i][0] == "-": + break + params.append(args[i]) + i = i + 1 + + # Check that we have the number of arguments we're expecting + pattern = match[2] + if pattern[1] == "n" and len(params) != 0: + raise RuntimeError("Switch '-" + switch + "' expects no arguments") + if pattern[1] == "s" and len(params) != 1: + raise RuntimeError("Switch '-" + switch + "' expects one argument") + if pattern[1] == "m" and len(params) < 1: + raise RuntimeError("Switch '-" + switch + "' expects one or more arguments") + + # Call the syntax checker + syntax = match[1] + result[match[0]] = syntax(match[0], params) + + # Check for missing required arguments + for j in available_arguments: + switch = j[0] + pattern = j[2] + if j[2][0] != "r": + break + if switch not in result: + raise RuntimeError("Missing '-" + switch + "' argument") + + return result + + +#if len(sys.argv) < 2: +# raise RuntimeError("Need command name") +# +#print(parse_arguments(sys.argv[2:], fs_mkmount_arguments)) diff --git a/suite/commands/__init__.py b/suite/commands/__init__.py new file mode 100644 index 0000000..213839a --- /dev/null +++ b/suite/commands/__init__.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- + +__copyright__ = """ +Copyright (C) 2014 Red Hat, Inc. All Rights Reserved. +Written by David Howells (dhowells@redhat.com) + +Derived from StGIT: + +Copyright (C) 2005, Catalin Marinas +Copyright (C) 2008, Karl Hasselström + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public Licence version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public Licence for more details. + +You should have received a copy of the GNU General Public Licence +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +import os +import stat + +def get_command_sets(): + sets = [] + for p in __path__: + for fn in os.listdir(p): + if fn[0] == "_" or fn[-1] == "~" or not stat.S_ISDIR(os.stat(p + "/" + fn).st_mode): + continue + sets.append(fn) + return sets + +def import_command_set(cmdset): + return __import__(__name__ + '.' + cmdset, globals(), locals(), ['*']) + diff --git a/suite/commands/vos/__init__.py b/suite/commands/vos/__init__.py new file mode 100644 index 0000000..b1801d0 --- /dev/null +++ b/suite/commands/vos/__init__.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +__copyright__ = """ +Copyright (C) 2014 Red Hat, Inc. All Rights Reserved. +Written by David Howells (dhowells@redhat.com) + +Derived from StGIT: + +Copyright (C) 2005, Catalin Marinas +Copyright (C) 2008, Karl Hasselström + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public Licence version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public Licence for more details. + +You should have received a copy of the GNU General Public Licence +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +import os + +def get_command(mod): + """Import and return the given command module.""" + return __import__(__name__ + '.' + mod, globals(), locals(), ['*']) + +def get_command_list(): + commands = [] + for p in __path__: + for fn in os.listdir(p): + if not fn.startswith("_") and fn.endswith('.py'): + commands.append(fn[:-3]) + return commands + +def py_commands(commands, f): + f.write('command_list = {\n') + for key, val in sorted(commands.iteritems()): + f.write(' %r: %r,\n' % (key, val)) + f.write(' }\n') + +#def _command_list(commands): +# for cmd, (mod, help) in commands.iteritems(): + +def pretty_command_list(commands, f): + cmd_len = max(len(cmd) for cmd in commands.iterkeys()) + for cmd, help in cmds: + f.write(' %*s %s\n' % (-cmd_len, cmd, help)) + +def _write_underlined(s, u, f): + f.write(s + '\n') + f.write(u*len(s) + '\n') + +def asciidoc_command_list(commands, f): + for cmd, help in commands: + f.write('linkstgsub:%s[]::\n' % cmd) + f.write(' %s\n' % help) + f.write('\n') diff --git a/suite/commands/vos/listvldb.py b/suite/commands/vos/listvldb.py new file mode 100644 index 0000000..41aa96e --- /dev/null +++ b/suite/commands/vos/listvldb.py @@ -0,0 +1,143 @@ +# +# AFS Volume management toolkit: Volume location database query +# -*- coding: utf-8 -*- +# + +__copyright__ = """ +Copyright (C) 2014 Red Hat, Inc. All Rights Reserved. +Written by David Howells (dhowells@redhat.com) + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public Licence version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public Licence for more details. + +You should have received a copy of the GNU General Public Licence +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +from afs.argparse import * +import afs.lib.addrcache as addrcache +import kafs +from socket import inet_ntoa + +help = "Query the VLDB" + +command_arguments = [ + [ "name", get_volume_name, "os", "" ], + [ "server", get_vlserver, "os", "" ], + [ "partition", get_partition_id, "os", "" ], + [ "locked", get_dummy, "fn" ], + [ "quiet", get_dummy, "fn" ], + [ "nosort", get_dummy, "fn" ], + [ "cell", get_cell, "os", "" ], + [ "noauth", get_auth, "fn" ], + [ "localauth", get_auth, "fn" ], + [ "verbose", get_dummy, "fn" ], + [ "encrypt", get_dummy, "fn" ], + [ "noresolve", get_dummy, "fn" ], +] + +description = r""" +Displays a volume's VLDB entry +""" + +def print_record(params, vldb): + """Display a single VLDB record""" + print(vldb.name) + print(" RWrite: {:<12d} ROnly: {:<12d} Backup: {:<12d}".format(vldb.volumeId[0], + vldb.volumeId[1], + vldb.volumeId[2])) + print(" number of sites ->", vldb.nServers) + for i in range(0, vldb.nServers): + inaddr = bytearray(4) + inaddr[0] = (vldb.serverNumber[i] >> 24) & 0xff + inaddr[1] = (vldb.serverNumber[i] >> 16) & 0xff + inaddr[2] = (vldb.serverNumber[i] >> 8) & 0xff + inaddr[3] = (vldb.serverNumber[i] >> 0) & 0xff + addr = inet_ntoa(bytes(inaddr)) + + if "noresolve" not in params: + name = addrcache.addr2name(addr) + if name != None: + addr = name + + partnum = vldb.serverPartition[i] + if partnum < 26: + part = "{:c}".format(97 + partnum) + else: + partnum -= 26 + part = "{:c}{:c}".format(97 + partnum / 26, 97 + partnum % 26) + + flags = vldb.serverFlags[i] + if flags & kafs.VLSF_ROVOL: + ptype = "RO" + elif flags & kafs.VLSF_RWVOL: + ptype = "RW" + else: + ptype = "Back" + + print(" server {:s} partition /vicep{:s} {:s} Site".format(addr, part, ptype)) + + if vldb.flags & (kafs.VLOP_MOVE | kafs.VLOP_RELEASE | kafs.VLOP_BACKUP | kafs.VLOP_DELETE | kafs.VLOP_DUMP): + print(" Volume is currently LOCKED") + if vldb.flags & kafs.VLOP_MOVE: + print("Volume is locked for a move operation") + if vldb.flags & kafs.VLOP_RELEASE: + print("Volume is locked for a release operation") + if vldb.flags & kafs.VLOP_BACKUP: + print("Volume is locked for a backup operation") + if vldb.flags & kafs.VLOP_DELETE: + print("Volume is locked for a delete/misc operation") + if vldb.flags & kafs.VLOP_DUMP: + print("Volume is locked for a dump/restore operation") + +def main(params): + # Get a list of VLDB servers to query + cell = params["cell"] + z_conn = cell.open_vl_server() + quiet = "quiet" in params + + if "name" in params: + if "server" in params or "partition" in params or "locked" in params: + raise RuntimeError("Can't combine -name with -server, -partition or -locked") + ret = kafs.VL_GetEntryByName(z_conn, params["name"][0]) + vldb = ret.entry + print_record(params, vldb) + return + + if "name" in params: + raise RuntimeError("Can't combine -server, -partition or -locked with -name") + + attributes = kafs.VldbListByAttributes() + attributes.Mask = 0 + + if "server" in params: + attributes.Mask |= kafs.VLLIST_SERVER + attributes.server = addrcache.name2addr_int(str(params["server"])) + if "partition" in params: + attributes.Mask |= kafs.VLLIST_PARTITION + attributes.partition = params["partition"] + if "locked" in params: + attributes.Mask |= kafs.VLLIST_FLAG + attributes.flag = kafs.VLOP_MOVE | kafs.VLOP_RELEASE | kafs.VLOP_BACKUP | kafs.VLOP_DELETE | kafs.VLOP_DUMP + + ret = kafs.VL_ListAttributes(z_conn, attributes) + blkentries = ret.blkentries + + if not quiet and "server" in params: + print("VLDB entries for server", params["server"]) + + if "nosort" not in params: + blkentries.sort(key=lambda vldb: vldb.name) + + for vldb in blkentries: + print_record(params, vldb) + + if not quiet: + print("Total entries:", len(blkentries)) diff --git a/suite/exception.py b/suite/exception.py new file mode 100644 index 0000000..b031378 --- /dev/null +++ b/suite/exception.py @@ -0,0 +1,3 @@ +class AFSException(Exception): + """Base class for all AFS Toolkit exceptions.""" + pass diff --git a/suite/lib/addrcache.py b/suite/lib/addrcache.py new file mode 100644 index 0000000..e83d318 --- /dev/null +++ b/suite/lib/addrcache.py @@ -0,0 +1,109 @@ +# +# AFS Volume management toolkit: IP Address cache +# -*- coding: utf-8 -*- +# + +__copyright__ = """ +Copyright (C) 2014 Red Hat, Inc. All Rights Reserved. +Written by David Howells (dhowells@redhat.com) + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public Licence version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public Licence for more details. + +You should have received a copy of the GNU General Public Licence +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +from afs import exception +from socket import inet_pton, AF_INET, AF_INET6; +import dns.reversename +import dns.resolver + +cache_n2a = dict() +cache_a2n = dict() + +class NetAddressError(exception.AFSException): + """Error raised by L{address cache}.""" + +def add(name, addr): + name = name.lower().rstrip(".") + if name not in cache_n2a: + cache_n2a[name] = [] + if addr not in cache_n2a[name]: + cache_n2a[name].append(addr) + cache_a2n[addr] = name + +def name2addr(name): + # Try parsing as a numeric IPv4 address + try: + addr = inet_pton(AF_INET, name) + return name + except OSError: + pass + + # Try parsing as a numeric IPv6 address + try: + addr = inet_pton(AF_INET6, name) + raise NetAddressError("IPv6 is not currently supported") + except OSError: + pass + + if name in cache_n2a: + return cache_n2a[name] + + try: + for A in dns.resolver.query(name, 'A'): + addrcache.add(name, A.address) + except dns.resolver.NoAnswer: + pass + + return cache_n2a[name] + +def name2addr_int(name): + # Try parsing as a numeric IPv4 address + try: + addr = inet_pton(AF_INET, name) + inaddr = addr[0] << 24 + inaddr |= addr[1] << 16 + inaddr |= addr[2] << 8 + inaddr |= addr[3] << 0 + return inaddr + except OSError: + pass + + # Try parsing as a numeric IPv6 address + try: + addr = inet_pton(AF_INET6, name) + raise NetAddressError("IPv6 is not currently supported") + except OSError: + pass + + if name not in cache_n2a: + return None + addr = inet_pton(AF_INET, cache_n2a[name][0]) + inaddr = addr[0] << 24 + inaddr |= addr[1] << 16 + inaddr |= addr[2] << 8 + inaddr |= addr[3] << 0 + return inaddr + +def addr2name(addr): + if addr not in cache_a2n: + try: + revname = dns.reversename.from_address(addr) + for PTR in dns.resolver.query(revname, 'PTR'): + name = str(PTR).lower().rstrip(".") + add(name, addr) + return name + + return None + except dns.resolver.NoAnswer: + return None + return cache_a2n[addr] diff --git a/suite/lib/cell.py b/suite/lib/cell.py new file mode 100644 index 0000000..267c715 --- /dev/null +++ b/suite/lib/cell.py @@ -0,0 +1,140 @@ +# +# AFS Volume management toolkit: Cell record +# -*- coding: utf-8 -*- +# + +__copyright__ = """ +Copyright (C) 2014 Red Hat, Inc. All Rights Reserved. +Written by David Howells (dhowells@redhat.com) + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public Licence version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public Licence for more details. + +You should have received a copy of the GNU General Public Licence +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +from afs import exception +from afs.lib.vlserver import vlserver, VLServerError +from afs.lib.debug import debug +import dns.resolver +import linecache +import kafs + +class CellError(exception.AFSException): + """Error raised by L{cell} objects.""" + +class cell: + """Represents an AFS cell. We hold the cell name here and the list of + VL servers once we've looked it up""" + def __init__(self, name = None): + if name == None: + name = linecache.getline("/etc/afs/ThisCell", 1) + name = name.strip() + debug("Default Cell:", name) + if name != "": + debug("Found", name) + else: + raise CellError("Couldn't determine default cell") + + debug("New Cell", name) + self.__name = name + self.__looked_up = False + self.__vlserver_names = dict() + self.__vlservers = [] + self.__vlconn = None + + def __repr__(self): + return "<" + "AFS:" + self.__name + ">" + + def __str__(self): + return self.__name + + def add_server(self, dnstype, name): + n = str(name) + if n not in self.__vlserver_names: + s = vlserver(n) + self.__vlserver_names[n] = s + self.__vlservers.append(s) + + # Look up the servers + def look_up_vl_servers(self): + debug("-- Find VL servers for cell:", self.__name, "--") + + # Start by looking for SRV records in the DNS + try: + for SRV in dns.resolver.query("_afs3-vlserver._udp." + self.__name, "SRV"): + self.add_server("SRV", SRV.target) + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): + debug("Couldn't find any SRV records") + + # Then we look for AFSDB records in the DNS + try: + for AFSDB in dns.resolver.query(self.__name, "AFSDB"): + self.add_server("AFSDB", AFSDB.hostname) + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): + debug("Couldn't find any AFSDB records") + + # Then parse the local afsdb file + # ... TODO ... + + self.__looked_up = True + + # Get the VLDB database server list for the cell + def query_vl_servers(self): + if not self.__looked_up: + self.look_up_vl_servers() + if not self.__vlservers: + raise CellError("Couldn't find any VL servers") + return self.__vlservers + + # Force a particular set of servers + def override_vlserver_list(self, servers): + self.__vlservers = servers + for i in servers: + self.__vlserver_names[str(i)] = i + self.__looked_up = True + + # Get the VLDB database server addresses for the cell + def query_vl_addrs(self): + addrs = [] + for i in self.query_vl_servers(): + try: + for j in i.addrs(): + if j not in addrs: + addrs.append(j) + except VLServerError: + pass + + if len(addrs) == 0: + raise CellError("Couldn't find any VL servers in cell") + + return addrs + + # Open a VL Server connection + def open_vl_server(self): + if self.__vlconn: + return + + for vlserver in self.query_vl_addrs(): + debug("Trying", vlserver) + + z_conn = kafs.rx_new_connection(vlserver, kafs.VL_PORT, kafs.VL_SERVICE) + try: + ret = kafs.VL_Probe(z_conn) + self.__vlconn = z_conn + break + except ConnectionRefusedError: + pass + del z_conn + else: + raise CellError("Couldn't connect to a VL server") + + return self.__vlconn diff --git a/suite/lib/debug.py b/suite/lib/debug.py new file mode 100644 index 0000000..6d2097b --- /dev/null +++ b/suite/lib/debug.py @@ -0,0 +1,30 @@ +# +# AFS Volume management toolkit: Debugging +# -*- coding: utf-8 -*- +# + +__copyright__ = """ +Copyright (C) 2014 Red Hat, Inc. All Rights Reserved. +Written by David Howells (dhowells@redhat.com) + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public Licence version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public Licence for more details. + +You should have received a copy of the GNU General Public Licence +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +import sys + +debugging_level = 0 + +def debug(*args, **kwargs): + if debugging_level > 0: + print(*args, file=sys.stdout) diff --git a/suite/lib/vlserver.py b/suite/lib/vlserver.py new file mode 100644 index 0000000..9570fff --- /dev/null +++ b/suite/lib/vlserver.py @@ -0,0 +1,90 @@ +# +# AFS Volume management toolkit: Volume Location server record +# -*- coding: utf-8 -*- +# + +__copyright__ = """ +Copyright (C) 2014 Red Hat, Inc. All Rights Reserved. +Written by David Howells (dhowells@redhat.com) + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public Licence version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public Licence for more details. + +You should have received a copy of the GNU General Public Licence +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +from afs import exception +import afs.lib.addrcache as addrcache +from afs.lib.debug import debug +from socket import inet_pton, AF_INET, AF_INET6; +import dns.resolver + +class VLServerError(exception.AFSException): + """Error raised by L{vlserver} objects.""" + +class vlserver: + """Represents an AFS Volume Location server. We hold the server address here.""" + def __init__(self, name): + debug("New VLServer", name) + self.__name = name + self.__looked_up = False + + # We want to maintain the record order provided by the DNS to entry + # force rotation, so we don't want to use an unordered set here. + # + # Note that the addrs are stored as text strings of the form "a.b.c.d" + self.__addrs = [] + + def __repr__(self): + return "<" + "AFSVL:" + self.__name + ">" + + def __str__(self): + return self.__name + + def look_up_addresses(self): + addr = False + + # Try parsing as a numeric IPv4 address + try: + addr = inet_pton(AF_INET, self.__name) + self.__addrs.append(self.__name) + except OSError: + pass + + # Try parsing as a numeric IPv6 address + if not addr: + try: + addr = inet_pton(AF_INET6, self.__name) + raise VLServerError("IPv6 is not currently supported") + except OSError: + pass + + # Try looking up in the DNS + if not addr: + try: + for A in dns.resolver.query(self.__name, 'A'): + if A.address not in self.__addrs: + self.__addrs.append(A.address) + addrcache.add(self.__name, A.address) + except dns.resolver.NoAnswer: + pass + + self.__looked_up = True + + def look_up(self): + if not self.__looked_up: + self.look_up_addresses() + if len(self.__addrs) == 0: + raise VLServerError("No VLServer addresses available") + return self.__addrs + + def addrs(self): + return self.look_up() diff --git a/suite/main.py b/suite/main.py new file mode 100644 index 0000000..9a6cb2a --- /dev/null +++ b/suite/main.py @@ -0,0 +1,196 @@ +# +# AFS Toolset command switcher +# -*- coding: utf-8 -*- +# + +__copyright__ = """ +Copyright (C) 2014 Red Hat, Inc. All Rights Reserved. +Written by David Howells (dhowells@redhat.com) + +Derived from StGIT: + +Copyright (C) 2005, Catalin Marinas + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public Licence version 2 as +published by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public Licence for more details. + +You should have received a copy of the GNU General Public Licence +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +""" + +import sys, os, traceback + +import afs.commands +from afs.lib.debug import debug, debugging_level + +# +# The commands map +# +class Commands(dict): + """Commands class. It performs on-demand module loading + """ + def canonical_cmd(self, key): + """Return the canonical name for a possibly-shortened + command name. + """ + candidates = [cmd for cmd in self.keys() if cmd.startswith(key)] + + if not candidates: + out.error('Unknown command: %s' % key, + 'Try "%s help" for a list of supported commands' % prog) + sys.exit(2) + + if len(candidates) > 1: + out.error('Ambiguous command: %s' % key, + 'Candidates are: %s' % ', '.join(candidates)) + sys.exit(2) + + return candidates[0] + + def __getitem__(self, key): + cmd_mod = self.get(key) or self.get(self.canonical_cmd(key)) + return afs.commands.get_command(cmd_mod) + +#cmd_list = afs.commands.get_commands() +#print(cmd_list); + +#commands = Commands((cmd, mod) for cmd, (mod, kind, help) +# in cmd_list.iteritems()) + +def print_help(): + print('usage: %s [options]' % os.path.basename(sys.argv[0])) + print() + print('Generic commands:') + print(' help print the detailed command usage') + print(' version display version information') + print(' copyright display copyright information') + print() + afs.commands.pretty_command_list(cmd_list, sys.stdout) + +# +# The main function (command dispatcher) +# +def _main(): + """The main function + """ + global prog + + prog = os.path.basename(sys.argv[0]) + + cmdsets = afs.commands.get_command_sets() + #print("CMDSETS:", cmdsets) + + if prog == "afs": + if len(sys.argv) < 3: + print('usage: {:s} '.format(prog), file=sys.stderr) + print(' Try "{:s} help" for a list of supported command sets'.format(prog), file=sys.stderr) + sys.exit(2) + cmdset = sys.argv[1] + cmd = sys.argv[2] + del sys.argv[1:3] + else: + if len(sys.argv) < 2: + print('usage: {:s} '.format(prog), file=sys.stderr) + print(' Try "{:s} help" for a list of supported commands'.format(prog), file=sys.stderr) + sys.exit(2) + cmdset = prog + cmd = sys.argv[1] + del sys.argv[1:2] + + if cmdset not in cmdsets: + raise RuntimeError("Unsupported command set '" + cmdset + "'") + + if cmd in ['-v', '--version', 'version']: + from afs.version import version + print('AFS Toolkit %s' % version) + print('Python version %s' % sys.version) + sys.exit(0) + + if cmd in ['copyright']: + print(__copyright__) + sys.exit(0) + + # Import the command set + cmdsetmod = afs.commands.import_command_set(cmdset) + commands = cmdsetmod.get_command_list() + #print("CMDS:", commands) + + # See if the command is in the set + found = False + for i in commands: + if i == cmd: + found = cmd + break + if i.startswith(cmd): + if found: + raise RuntimeError("Command '" + cmd + "' is ambiguous") + found = cmd + if not found: + raise RuntimeError("Command '" + cmd + "' is unknown") + cmd = found + + # Rebuild the command line arguments + if prog == "afs": + sys.argv[0] += ' {:s} {:s}'.format(cmdset, cmd) + else: + sys.argv[0] += ' {:s}'.format(cmd) + + command = cmdsetmod.get_command(cmd) + #print("MOD:", command) + + # If it's an alias, then switch to the real module + if hasattr(command, "alias"): + cmd = command.alias + command = cmdsetmod.get_command(cmd) + #print("MOD:", command) + + # Parse the parameters + params = afs.argparse.parse_arguments(sys.argv[1:], command.command_arguments) + + # Stick in the default cell if there isn't one + if "cell" not in params: + from afs.lib.cell import cell + params["cell"] = cell() + + # These modules are only used from this point onwards and do not + # need to be imported earlier + from afs.exception import AFSException + + try: + debug_level = int(os.environ.get('AFS_DEBUG_LEVEL', 0)) + except ValueError: + print('Invalid AFS_DEBUG_LEVEL environment variable', file=sys.stderr) + sys.exit(2) + + try: + ret = command.main(params) + except (AFSException, IOError) as err: + print(str(err), file=sys.stderr) + if debug_level > 0: + traceback.print_exc() + sys.exit(1) + except SystemExit: + # Triggered by the option parser when it finds bad commandline + # parameters. + sys.exit(1) + except KeyboardInterrupt: + sys.exit(1) + except: + print('Unhandled exception:', file=sys.stderr) + traceback.print_exc() + sys.exit(3) + + sys.exit(ret or 0) + +def main(): + try: + _main() + finally: + pass