From 8a1eb3901bdb6f7d67668be99151eda8022af478 Mon Sep 17 00:00:00 2001 From: Ian Wienand Date: Fri, 1 Jun 2018 08:40:03 +1000 Subject: [PATCH] Initial code motion Rename things to just afsmon, and split out cmd from library. --- MANIFEST.in | 2 +- README.rst | 4 +- pyafsmon/pyafsmon.py => afsmon/__init__.py | 116 +++++------------- {pyafsmon/tests => afsmon/cmd}/__init__.py | 0 afsmon/cmd/main.py | 53 ++++++++ afsmon/tests/__init__.py | 0 {pyafsmon => afsmon}/tests/base.py | 0 .../tests/test_afsmon.py | 0 setup.cfg | 8 +- 9 files changed, 93 insertions(+), 90 deletions(-) rename pyafsmon/pyafsmon.py => afsmon/__init__.py (79%) rename {pyafsmon/tests => afsmon/cmd}/__init__.py (100%) create mode 100644 afsmon/cmd/main.py create mode 100644 afsmon/tests/__init__.py rename {pyafsmon => afsmon}/tests/base.py (100%) rename pyafsmon/tests/test_pyafsmon.py => afsmon/tests/test_afsmon.py (100%) diff --git a/MANIFEST.in b/MANIFEST.in index bb3ec5f..9561fb1 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1 @@ -include README.md +include README.rst diff --git a/README.rst b/README.rst index 4dc6964..69e130e 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,3 @@ -pyafsmon -======== +afsmon +====== diff --git a/pyafsmon/pyafsmon.py b/afsmon/__init__.py similarity index 79% rename from pyafsmon/pyafsmon.py rename to afsmon/__init__.py index 5f501d7..21fdb5b 100644 --- a/pyafsmon/pyafsmon.py +++ b/afsmon/__init__.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,8 +11,6 @@ # License for the specific language governing permissions and limitations # under the License. -import argparse -import configparser import collections import logging import io @@ -25,9 +22,6 @@ from datetime import datetime from enum import Enum from prettytable import PrettyTable -config = None - - # # Fileserver # @@ -48,8 +42,13 @@ Volume = collections.namedtuple( class FileServerStats: '''AFS fileserver status + Call ``get_stats()`` to populate the statistics for the server. Note most attributes are only set if ``status`` is NORMAL + Args: + hostname (str): The hostname of server to query + i.e. argument to ``-server`` for cmd line tools + Attributes: status (FileServerStatus): enum of possible status timestamp(:obj:`datetime.datetime`): time statistics retrieved @@ -59,9 +58,10 @@ class FileServerStats: partition on the server calls_waiting (:obj:`int`): number of calls waiting for a thread idle_threads (:obj:`int`): number of currently idle threads - volumes (:obj:`list`): list of :obj:`Volume` tuples for each + volumes (:obj:`list`): list of :obj:`Volume` tuples for each volume present on the server table (:obj:`PrettyTable`): a printable PrettyTable representation + ''' def _get_volumes(self): @@ -70,6 +70,12 @@ class FileServerStats: output = subprocess.check_output( cmd, stderr=subprocess.STDOUT).decode('ascii') + # Matching: + # mirror.yum-puppetlabs.readonly 536871036 RO 63026403 K On-line + vol_regex = re.compile( + '^(?P[^\s]+)\s+(?P\d+)\s(?PR[OW])\s+(?P\d+) K' + ) + # Read the output into chunks where each chunk is the info for # one volume. chunks = [] @@ -86,16 +92,15 @@ class FileServerStats: chunk += lines.readline() # convert it to a Volume() # todo: there's a bunch more we could extract... - m = re.search( - '^(?P[^\s]+)\s+(?P\d+)\s(?PR[OW])\s+(?P\d+) K', - chunk) + m = vol_regex.search(chunk) q = re.search('MaxQuota\s+(?P\d+) K', chunk) used = int(m['used']) quota = int(q['quota']) percent_used = round(float(used) / float(quota) * 100, 2) - self.volumes.append(Volume( - m['volume'], m['id'], m['perms'], used, quota, percent_used)) - + self.volumes.append( + Volume(m['vol'], m['id'], m['perms'], + used, quota, percent_used)) + def _get_calls_waiting(self): cmd = ["rxdebug", self.hostname, "7000", "-rxstats", "-noconns"] @@ -161,13 +166,6 @@ class FileServerStats: '''Get the complete stats set for the fileserver''' self.timestamp = datetime.now() - self.restart = None - self.uptime = None - self.partitions = [] - self.volumes = [] - self.calls_waiting = None - self.idle_threads = None - self._get_fs_stats() if self.status == FileServerStatus.NORMAL: self._get_partition_stats() @@ -193,6 +191,8 @@ class FileServerStats: self.table.add_row(["%s %%used" % n, "%s%%" % p.percent_used]) for v in self.volumes: + # Only add the RW volumes to the table as for now we're + # mostly just worried about viewing the quota. if v.perms == 'RW': n = v.volume self.table.add_row(["%s used" % n, v.used]) @@ -206,12 +206,21 @@ class FileServerStats: def __init__(self, hostname): self.hostname = hostname -# -# Volume -# + self.timestamp = None + self.restart = None + self.uptime = None + self.partitions = [] + self.volumes = [] + self.calls_waiting = None + self.idle_threads = None + def get_fs_addresses(cell): - '''Get the fileservers associated with a cell''' + '''Get the fileservers associated with a cell + + :arg str cell: The cell (e.g. ``openstack.org``) + :returns: list of fileservers for the cell + ''' fs = [] cmd = ["vos", "listaddrs", "-noauth", "-cell", cell] logging.debug("Running: %s" % cmd) @@ -227,62 +236,3 @@ def get_fs_addresses(cell): fs.append(line) return fs - -def get_volumes(cell): - '''Get the volumes in a cell''' - volumes = [] - cmd = ["vos", "listvldb", "-quiet", "-noauth", - "-noresolve", "-nosort", "-cell", cell] - logging.debug("Running: %s" % cmd) - try: - output = subprocess.check_output( - cmd, stderr=subprocess.STDOUT).decode('ascii') - except subprocess.CalledProcessError: - logging.debug(" ... failed!") - return [] - - # details about the volumes are inset, so just look for non-blank lines - for line in output.split('\n'): - if line and not line.startswith(' '): - volumes.append(line.strip()) - - return volumes - - -def main(args=None): - global config - - if args is None: - args = sys.argv[1:] - - parser = argparse.ArgumentParser( - description='An AFS monitoring tool') - - parser.add_argument("config", help="Path to config file") - parser.add_argument("-d", '--debug', action="store_true") - - args = parser.parse_args(args) - - if args.debug: - logging.basicConfig(level=logging.DEBUG) - logging.debug("Debugging enabled") - - config = configparser.RawConfigParser() - config.read(args.config) - - cell = config.get('main', 'cell').strip() - -# volumes = get_volumes(cell) -# logging.debug(volumes) - - fileservers = get_fs_addresses(cell) - print(fileservers) - - for fileserver in fileservers: - logging.debug("Finding stats for: %s" % fileserver) - - fs = FileServerStats(fileserver) - fs.get_stats() - print(fs) - - sys.exit(0) diff --git a/pyafsmon/tests/__init__.py b/afsmon/cmd/__init__.py similarity index 100% rename from pyafsmon/tests/__init__.py rename to afsmon/cmd/__init__.py diff --git a/afsmon/cmd/main.py b/afsmon/cmd/main.py new file mode 100644 index 0000000..cd8c91a --- /dev/null +++ b/afsmon/cmd/main.py @@ -0,0 +1,53 @@ +# +# 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 argparse +import configparser +import logging +import sys + +import afsmon + +def main(args=None): + + if args is None: + args = sys.argv[1:] + + parser = argparse.ArgumentParser( + description='An AFS monitoring tool') + + parser.add_argument("config", help="Path to config file") + parser.add_argument("-d", '--debug', action="store_true") + + args = parser.parse_args(args) + + if args.debug: + logging.basicConfig(level=logging.DEBUG) + logging.debug("Debugging enabled") + + config = configparser.RawConfigParser() + config.read(args.config) + + cell = config.get('main', 'cell').strip() + + fileservers = afsmon.get_fs_addresses(cell) + logging.debug("Found fileservers: %s" % ", ".join(fileservers)) + + for fileserver in fileservers: + logging.debug("Finding stats for: %s" % fileserver) + + fs = afsmon.FileServerStats(fileserver) + fs.get_stats() + print(fs) + + sys.exit(0) diff --git a/afsmon/tests/__init__.py b/afsmon/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyafsmon/tests/base.py b/afsmon/tests/base.py similarity index 100% rename from pyafsmon/tests/base.py rename to afsmon/tests/base.py diff --git a/pyafsmon/tests/test_pyafsmon.py b/afsmon/tests/test_afsmon.py similarity index 100% rename from pyafsmon/tests/test_pyafsmon.py rename to afsmon/tests/test_afsmon.py diff --git a/setup.cfg b/setup.cfg index d9dabf1..13f0831 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] -name = pyafsmon -summary = Monitoring for AFS +name = afsmon +summary = Helpers for AFS monitoring in Python description-file = README.rst author = Ian Wienand @@ -19,9 +19,9 @@ classifier = [files] packages = - pyafsmon + afsmon [entry_points] console_scripts = - afsmon = pyafsmon.pyafsmon:main + afsmon = afsmon.cmd.main:main