lockss-debugpanel 0.8.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lockss_debugpanel-0.8.2/CHANGELOG.rst +85 -0
- lockss_debugpanel-0.8.2/LICENSE +27 -0
- lockss_debugpanel-0.8.2/PKG-INFO +75 -0
- lockss_debugpanel-0.8.2/README.rst +43 -0
- lockss_debugpanel-0.8.2/pyproject.toml +82 -0
- lockss_debugpanel-0.8.2/src/lockss/debugpanel/__init__.py +361 -0
- lockss_debugpanel-0.8.2/src/lockss/debugpanel/__main__.py +37 -0
- lockss_debugpanel-0.8.2/src/lockss/debugpanel/cli.py +379 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
=============
|
|
2
|
+
Release Notes
|
|
3
|
+
=============
|
|
4
|
+
|
|
5
|
+
-----
|
|
6
|
+
0.8.2
|
|
7
|
+
-----
|
|
8
|
+
|
|
9
|
+
Released: 2026-02-03
|
|
10
|
+
|
|
11
|
+
* Requires Python 3.9-3.13.
|
|
12
|
+
|
|
13
|
+
-----
|
|
14
|
+
0.8.1
|
|
15
|
+
-----
|
|
16
|
+
|
|
17
|
+
Released: 2025-08-13
|
|
18
|
+
|
|
19
|
+
* **Bug Fixes**
|
|
20
|
+
|
|
21
|
+
* Fixed bug in the processing of ``--nodes`` and ``--auids`` options.
|
|
22
|
+
|
|
23
|
+
* **Version 0.8.1-post1** (released: 2026-02-03)
|
|
24
|
+
|
|
25
|
+
* Requires Python 3.9-3.13.
|
|
26
|
+
|
|
27
|
+
-----
|
|
28
|
+
0.8.0
|
|
29
|
+
-----
|
|
30
|
+
|
|
31
|
+
Released: 2025-07-01
|
|
32
|
+
|
|
33
|
+
* **Features**
|
|
34
|
+
|
|
35
|
+
* Now using *lockss-pybasic* and *pydantic-argparse* internally.
|
|
36
|
+
|
|
37
|
+
* **Changes**
|
|
38
|
+
|
|
39
|
+
* Bare arguments are no longer allowed and treated as node references; all node references must be specified via ``--node/-n`` or ``--nodes/-N`` options.
|
|
40
|
+
|
|
41
|
+
* The ``usage`` command has been removed.
|
|
42
|
+
|
|
43
|
+
-----
|
|
44
|
+
0.7.0
|
|
45
|
+
-----
|
|
46
|
+
|
|
47
|
+
Released: 2023-05-02
|
|
48
|
+
|
|
49
|
+
* **Features**
|
|
50
|
+
|
|
51
|
+
* CLI improvements.
|
|
52
|
+
|
|
53
|
+
-----
|
|
54
|
+
0.6.1
|
|
55
|
+
-----
|
|
56
|
+
|
|
57
|
+
Released: 2023-03-16
|
|
58
|
+
|
|
59
|
+
* **Bug Fixes**
|
|
60
|
+
|
|
61
|
+
* Files from ``--auids`` were appended to nodes.
|
|
62
|
+
|
|
63
|
+
-----
|
|
64
|
+
0.6.0
|
|
65
|
+
-----
|
|
66
|
+
|
|
67
|
+
Released: 2023-03-15
|
|
68
|
+
|
|
69
|
+
* **Features**
|
|
70
|
+
|
|
71
|
+
* Now providing a Python library.
|
|
72
|
+
|
|
73
|
+
-----
|
|
74
|
+
0.5.0
|
|
75
|
+
-----
|
|
76
|
+
|
|
77
|
+
Released: 2023-03-10
|
|
78
|
+
|
|
79
|
+
* **Features**
|
|
80
|
+
|
|
81
|
+
* Completely refactored to be in the package ``lockss.debugpanel``.
|
|
82
|
+
|
|
83
|
+
* Using Poetry to make uploadable to and installable from PyPI as `lockss-debugpanel <https://pypi.org/project/lockss-debugpanel>`_.
|
|
84
|
+
|
|
85
|
+
* Added the ``verify-files`` command.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
|
4
|
+
modification, are permitted provided that the following conditions are met:
|
|
5
|
+
|
|
6
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
7
|
+
this list of conditions and the following disclaimer.
|
|
8
|
+
|
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
|
11
|
+
and/or other materials provided with the distribution.
|
|
12
|
+
|
|
13
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
14
|
+
may be used to endorse or promote products derived from this software without
|
|
15
|
+
specific prior written permission.
|
|
16
|
+
|
|
17
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
18
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
19
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
20
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
21
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
22
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
23
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
24
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
25
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
26
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
27
|
+
POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lockss-debugpanel
|
|
3
|
+
Version: 0.8.2
|
|
4
|
+
Summary: Library and command line tool to interact with the LOCKSS 1.x DebugPanel servlet
|
|
5
|
+
License: BSD-3-Clause
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Thib Guicherd-Callin
|
|
8
|
+
Author-email: thib@cs.stanford.edu
|
|
9
|
+
Maintainer: Thib Guicherd-Callin
|
|
10
|
+
Maintainer-email: thib@cs.stanford.edu
|
|
11
|
+
Requires-Python: >=3.9,<3.14
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Framework :: Pydantic :: 2
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: System Administrators
|
|
17
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Classifier: Topic :: System :: Archiving
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Requires-Dist: lockss-pybasic (>=0.1.0,<0.2.0)
|
|
23
|
+
Requires-Dist: pydantic (>=2.11.0,<3.0.0)
|
|
24
|
+
Requires-Dist: pydantic-argparse (>=0.10.0,<0.11.0)
|
|
25
|
+
Requires-Dist: tabulate (>=0.9.0,<0.10.0)
|
|
26
|
+
Project-URL: Documentation, https://docs.lockss.org/en/latest/software/debugpanel
|
|
27
|
+
Project-URL: Repository, https://github.com/lockss/lockss-debugpanel
|
|
28
|
+
Project-URL: changelog, https://github.com/lockss/lockss-debugpanel/blob/main/CHANGELOG.rst
|
|
29
|
+
Project-URL: issues, https://github.com/lockss/lockss-debugpanel/issues
|
|
30
|
+
Description-Content-Type: text/x-rst
|
|
31
|
+
|
|
32
|
+
==========
|
|
33
|
+
Debugpanel
|
|
34
|
+
==========
|
|
35
|
+
|
|
36
|
+
.. |RELEASE| replace:: 0.8.2
|
|
37
|
+
.. |RELEASE_DATE| replace:: 2026-02-03
|
|
38
|
+
.. |DEBUGPANEL| replace:: **Debugpanel**
|
|
39
|
+
|
|
40
|
+
.. image:: https://assets.lockss.org/images/logos/debugpanel/debugpanel_128x128.png
|
|
41
|
+
:alt: Debugpanel logo
|
|
42
|
+
:align: right
|
|
43
|
+
|
|
44
|
+
|DEBUGPANEL| is a library and command line tool to interact with the LOCKSS 1.x DebugPanel servlet.
|
|
45
|
+
|
|
46
|
+
:Latest release: |RELEASE| (|RELEASE_DATE|)
|
|
47
|
+
:Documentation: https://docs.lockss.org/en/latest/software/debugpanel
|
|
48
|
+
:Release notes: `CHANGELOG.rst <https://github.com/lockss/lockss-debugpanel/blob/main/CHANGELOG.rst>`_
|
|
49
|
+
:License: `LICENSE <https://github.com/lockss/lockss-debugpanel/blob/main/LICENSE>`_
|
|
50
|
+
:Repository: https://github.com/lockss/lockss-debugpanel
|
|
51
|
+
:Issues: https://github.com/lockss/lockss-debugpanel/issues
|
|
52
|
+
|
|
53
|
+
----
|
|
54
|
+
|
|
55
|
+
Quick Start::
|
|
56
|
+
|
|
57
|
+
# Requires Python 3.9-3.13
|
|
58
|
+
python --version
|
|
59
|
+
|
|
60
|
+
# Install with pipx
|
|
61
|
+
pipx install lockss-debugpanel
|
|
62
|
+
|
|
63
|
+
# Verify installation and discover all the commands
|
|
64
|
+
debugpanel --help
|
|
65
|
+
|
|
66
|
+
# Reload config on lockss1.example.edu:8081
|
|
67
|
+
debugpanel reload-config -n lockss1.example.edu:8081
|
|
68
|
+
|
|
69
|
+
# Crawl AUIDs from list.txt on lockss1.example.edu:8081 and lockss2.example.edu:8081
|
|
70
|
+
# ...First alternative: each node gets a -n
|
|
71
|
+
debugpanel crawl -A list.txt -n lockss1.example.edu:8081 -n lockss2.example.edu:8081
|
|
72
|
+
|
|
73
|
+
# ...Second alternative: each -n can have more than argument
|
|
74
|
+
debugpanel crawl -A list.txt -n lockss1.example.edu:8081 lockss2.example.edu:8081
|
|
75
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
==========
|
|
2
|
+
Debugpanel
|
|
3
|
+
==========
|
|
4
|
+
|
|
5
|
+
.. |RELEASE| replace:: 0.8.2
|
|
6
|
+
.. |RELEASE_DATE| replace:: 2026-02-03
|
|
7
|
+
.. |DEBUGPANEL| replace:: **Debugpanel**
|
|
8
|
+
|
|
9
|
+
.. image:: https://assets.lockss.org/images/logos/debugpanel/debugpanel_128x128.png
|
|
10
|
+
:alt: Debugpanel logo
|
|
11
|
+
:align: right
|
|
12
|
+
|
|
13
|
+
|DEBUGPANEL| is a library and command line tool to interact with the LOCKSS 1.x DebugPanel servlet.
|
|
14
|
+
|
|
15
|
+
:Latest release: |RELEASE| (|RELEASE_DATE|)
|
|
16
|
+
:Documentation: https://docs.lockss.org/en/latest/software/debugpanel
|
|
17
|
+
:Release notes: `CHANGELOG.rst <https://github.com/lockss/lockss-debugpanel/blob/main/CHANGELOG.rst>`_
|
|
18
|
+
:License: `LICENSE <https://github.com/lockss/lockss-debugpanel/blob/main/LICENSE>`_
|
|
19
|
+
:Repository: https://github.com/lockss/lockss-debugpanel
|
|
20
|
+
:Issues: https://github.com/lockss/lockss-debugpanel/issues
|
|
21
|
+
|
|
22
|
+
----
|
|
23
|
+
|
|
24
|
+
Quick Start::
|
|
25
|
+
|
|
26
|
+
# Requires Python 3.9-3.13
|
|
27
|
+
python --version
|
|
28
|
+
|
|
29
|
+
# Install with pipx
|
|
30
|
+
pipx install lockss-debugpanel
|
|
31
|
+
|
|
32
|
+
# Verify installation and discover all the commands
|
|
33
|
+
debugpanel --help
|
|
34
|
+
|
|
35
|
+
# Reload config on lockss1.example.edu:8081
|
|
36
|
+
debugpanel reload-config -n lockss1.example.edu:8081
|
|
37
|
+
|
|
38
|
+
# Crawl AUIDs from list.txt on lockss1.example.edu:8081 and lockss2.example.edu:8081
|
|
39
|
+
# ...First alternative: each node gets a -n
|
|
40
|
+
debugpanel crawl -A list.txt -n lockss1.example.edu:8081 -n lockss2.example.edu:8081
|
|
41
|
+
|
|
42
|
+
# ...Second alternative: each -n can have more than argument
|
|
43
|
+
debugpanel crawl -A list.txt -n lockss1.example.edu:8081 lockss2.example.edu:8081
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
|
|
2
|
+
#
|
|
3
|
+
# Redistribution and use in source and binary forms, with or without
|
|
4
|
+
# modification, are permitted provided that the following conditions are met:
|
|
5
|
+
#
|
|
6
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
7
|
+
# this list of conditions and the following disclaimer.
|
|
8
|
+
#
|
|
9
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
10
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
11
|
+
# and/or other materials provided with the distribution.
|
|
12
|
+
#
|
|
13
|
+
# 3. Neither the name of the copyright holder nor the names of its contributors
|
|
14
|
+
# may be used to endorse or promote products derived from this software without
|
|
15
|
+
# specific prior written permission.
|
|
16
|
+
#
|
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
18
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
19
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
20
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
21
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
22
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
23
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
24
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
25
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
26
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
27
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
28
|
+
|
|
29
|
+
[project]
|
|
30
|
+
name = "lockss-debugpanel"
|
|
31
|
+
version = "0.8.2" # Always change in __init__.py, and at release time in README.rst and CHANGELOG.rst
|
|
32
|
+
description = "Library and command line tool to interact with the LOCKSS 1.x DebugPanel servlet"
|
|
33
|
+
license = { text = "BSD-3-Clause" }
|
|
34
|
+
readme = "README.rst"
|
|
35
|
+
requires-python = ">=3.9,<3.14"
|
|
36
|
+
authors = [
|
|
37
|
+
{ name = "Thib Guicherd-Callin", email = "thib@cs.stanford.edu" },
|
|
38
|
+
]
|
|
39
|
+
maintainers = [
|
|
40
|
+
{ name = "Thib Guicherd-Callin", email = "thib@cs.stanford.edu" }
|
|
41
|
+
]
|
|
42
|
+
dependencies = [
|
|
43
|
+
"lockss-pybasic (>=0.1.0,<0.2.0)",
|
|
44
|
+
"pydantic (>=2.11.0,<3.0.0)",
|
|
45
|
+
"pydantic-argparse (>=0.10.0,<0.11.0)",
|
|
46
|
+
"tabulate (>=0.9.0,<0.10.0)"
|
|
47
|
+
]
|
|
48
|
+
classifiers = [
|
|
49
|
+
"Development Status :: 5 - Production/Stable",
|
|
50
|
+
"Environment :: Console",
|
|
51
|
+
"Framework :: Pydantic :: 2",
|
|
52
|
+
"Intended Audience :: Developers",
|
|
53
|
+
"Intended Audience :: System Administrators",
|
|
54
|
+
"License :: OSI Approved :: BSD License",
|
|
55
|
+
"Programming Language :: Python",
|
|
56
|
+
"Topic :: Software Development :: Libraries",
|
|
57
|
+
"Topic :: System :: Archiving",
|
|
58
|
+
"Topic :: Utilities",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
[project.urls]
|
|
62
|
+
changelog = "https://github.com/lockss/lockss-debugpanel/blob/main/CHANGELOG.rst"
|
|
63
|
+
documentation = "https://docs.lockss.org/en/latest/software/debugpanel"
|
|
64
|
+
issues = "https://github.com/lockss/lockss-debugpanel/issues"
|
|
65
|
+
repository = "https://github.com/lockss/lockss-debugpanel"
|
|
66
|
+
|
|
67
|
+
[tool.poetry]
|
|
68
|
+
include = [
|
|
69
|
+
"CHANGELOG.rst",
|
|
70
|
+
"LICENSE",
|
|
71
|
+
"README.rst",
|
|
72
|
+
]
|
|
73
|
+
packages = [
|
|
74
|
+
{ include = "lockss", from = "src" }
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
[tool.poetry.scripts]
|
|
78
|
+
debugpanel = 'lockss.debugpanel.cli:main'
|
|
79
|
+
|
|
80
|
+
[build-system]
|
|
81
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
82
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
|
|
4
|
+
#
|
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
|
7
|
+
#
|
|
8
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
# this list of conditions and the following disclaimer.
|
|
10
|
+
#
|
|
11
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
# and/or other materials provided with the distribution.
|
|
14
|
+
#
|
|
15
|
+
# 3. Neither the name of the copyright holder nor the names of its contributors
|
|
16
|
+
# may be used to endorse or promote products derived from this software without
|
|
17
|
+
# specific prior written permission.
|
|
18
|
+
#
|
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
22
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
23
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
24
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
25
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
26
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
27
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
28
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
29
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
Library and command line tool to interact with the LOCKSS 1.x DebugPanel servlet.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
__version__ = '0.8.2'
|
|
36
|
+
|
|
37
|
+
__copyright__ = '''
|
|
38
|
+
Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
|
|
39
|
+
'''.strip()
|
|
40
|
+
|
|
41
|
+
__license__ = __copyright__ + '\n\n' + '''
|
|
42
|
+
Redistribution and use in source and binary forms, with or without
|
|
43
|
+
modification, are permitted provided that the following conditions are met:
|
|
44
|
+
|
|
45
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
46
|
+
this list of conditions and the following disclaimer.
|
|
47
|
+
|
|
48
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
49
|
+
this list of conditions and the following disclaimer in the documentation
|
|
50
|
+
and/or other materials provided with the distribution.
|
|
51
|
+
|
|
52
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
53
|
+
may be used to endorse or promote products derived from this software without
|
|
54
|
+
specific prior written permission.
|
|
55
|
+
|
|
56
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
57
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
58
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
59
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
60
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
61
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
62
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
63
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
64
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
65
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
66
|
+
POSSIBILITY OF SUCH DAMAGE.
|
|
67
|
+
'''.strip()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
from base64 import b64encode
|
|
71
|
+
from urllib.request import Request, urlopen
|
|
72
|
+
from typing import Any, Dict
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
type RequestUrlOpenT = Any
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
DEFAULT_DEPTH = 123
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class Node(object):
|
|
82
|
+
"""
|
|
83
|
+
Connector to a LOCKSS 1.x node.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
DEFAULT_PROTOCOL = 'http'
|
|
87
|
+
|
|
88
|
+
def __init__(self, node_reference: str, u: str, p: str) -> None:
|
|
89
|
+
"""
|
|
90
|
+
Constructs a new ``Node`` instance.
|
|
91
|
+
|
|
92
|
+
:param node_reference: A LOCKSS 1.x node reference, typically of the
|
|
93
|
+
form ``http://lockss.university.edu:8081``. If
|
|
94
|
+
no protocol is specified,
|
|
95
|
+
``Node.DEFAULT_PROTOCOL`` is assumed. If a final
|
|
96
|
+
slash is included, it is ignored.
|
|
97
|
+
:type node_reference: str
|
|
98
|
+
:param u: The username for the given node's Web user interface.
|
|
99
|
+
:type u: str
|
|
100
|
+
:param p: The password for the given node's Web user interface.
|
|
101
|
+
:type p: str
|
|
102
|
+
"""
|
|
103
|
+
super().__init__()
|
|
104
|
+
if '://' not in node_reference:
|
|
105
|
+
node_reference = f'{Node.DEFAULT_PROTOCOL}://{node_reference}'
|
|
106
|
+
if node_reference.endswith('/'):
|
|
107
|
+
node_reference = node_reference[:-1]
|
|
108
|
+
self._url: str = node_reference
|
|
109
|
+
self._basic: str = b64encode(f'{u}:{p}'.encode('utf-8')).decode('utf-8')
|
|
110
|
+
|
|
111
|
+
def authenticate(self, req: Request) -> None:
|
|
112
|
+
"""
|
|
113
|
+
Does what is necessary to authenticate with the given ``Request``
|
|
114
|
+
object.
|
|
115
|
+
|
|
116
|
+
:param req: A ``Request`` instance.
|
|
117
|
+
:type req: Request
|
|
118
|
+
"""
|
|
119
|
+
req.add_header('Authorization', f'Basic {self._basic}')
|
|
120
|
+
|
|
121
|
+
def get_url(self) -> str:
|
|
122
|
+
"""
|
|
123
|
+
Returns the full URL corresponding to this node (with protocol but no
|
|
124
|
+
final slash).
|
|
125
|
+
|
|
126
|
+
:return: The full URL corresponding to this node.
|
|
127
|
+
:rtype: str
|
|
128
|
+
"""
|
|
129
|
+
return self._url
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def check_substance(node: Node, auid: str) -> RequestUrlOpenT:
|
|
133
|
+
"""
|
|
134
|
+
Performs the DebugPanel servlet "Check Substance" operation on the given
|
|
135
|
+
node for the given AUID.
|
|
136
|
+
|
|
137
|
+
:param node: A ``Node`` instance.
|
|
138
|
+
:type node: Node
|
|
139
|
+
:param auid: An AUID.
|
|
140
|
+
:type auid: str
|
|
141
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
142
|
+
:rtype: RequestUrlOpenT
|
|
143
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
144
|
+
"""
|
|
145
|
+
return _auid_action(node, auid, 'Check Substance')
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def crawl(node: Node, auid: str) -> RequestUrlOpenT:
|
|
149
|
+
"""
|
|
150
|
+
Performs the DebugPanel servlet "Force Start Crawl" operation on the given
|
|
151
|
+
node for the given AUID.
|
|
152
|
+
|
|
153
|
+
:param node: A ``Node`` instance.
|
|
154
|
+
:type node: Node
|
|
155
|
+
:param auid: An AUID.
|
|
156
|
+
:type auid: str
|
|
157
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
158
|
+
:rtype: RequestUrlOpenT
|
|
159
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
160
|
+
"""
|
|
161
|
+
return _auid_action(node, auid, 'Force Start Crawl')
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def crawl_plugins(node: Node) -> RequestUrlOpenT:
|
|
165
|
+
"""
|
|
166
|
+
Performs the DebugPanel servlet "Crawl Plugins" operation on the given
|
|
167
|
+
node.
|
|
168
|
+
|
|
169
|
+
:param node: A ``Node`` instance.
|
|
170
|
+
:type node: Node
|
|
171
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
172
|
+
:rtype: RequestUrlOpenT
|
|
173
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
174
|
+
"""
|
|
175
|
+
return _node_action(node, 'Crawl Plugins')
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def deep_crawl(node: Node, auid: str, depth: int=DEFAULT_DEPTH) -> RequestUrlOpenT:
|
|
179
|
+
"""
|
|
180
|
+
Performs the DebugPanel servlet "Force Deep Crawl" operation on the given
|
|
181
|
+
node for the given AUID, with the given depth (default ``DEFAULT_DEPTH``).
|
|
182
|
+
|
|
183
|
+
:param node: A ``Node`` instance.
|
|
184
|
+
:type node: Node
|
|
185
|
+
:param auid: An AUID.
|
|
186
|
+
:type auid: str
|
|
187
|
+
:param depth: A strictly positive refetch depth.
|
|
188
|
+
:type auid: int
|
|
189
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
190
|
+
:rtype: RequestUrlOpenT
|
|
191
|
+
:raises ValueError: If depth is negative or zero.
|
|
192
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
193
|
+
"""
|
|
194
|
+
if depth < 1:
|
|
195
|
+
raise ValueError(f'depth must be a strictly positive integer, got {depth}')
|
|
196
|
+
return _auid_action(node, auid, 'Force Deep Crawl', depth=depth)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def disable_indexing(node: Node, auid: str) -> RequestUrlOpenT:
|
|
200
|
+
"""
|
|
201
|
+
Performs the DebugPanel servlet "Disable Indexing" operation on the given
|
|
202
|
+
node for the given AUID.
|
|
203
|
+
|
|
204
|
+
:param node: A ``Node`` instance.
|
|
205
|
+
:type node: Node
|
|
206
|
+
:param auid: An AUID.
|
|
207
|
+
:type auid: str
|
|
208
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
209
|
+
:rtype: RequestUrlOpenT
|
|
210
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
211
|
+
"""
|
|
212
|
+
return _auid_action(node, auid, 'Disable Indexing')
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def node(node_reference: str, u: str, p: str) -> Node:
|
|
216
|
+
"""
|
|
217
|
+
DEPRECATED: Constructs a new ``Node`` instance (see the ``Node``
|
|
218
|
+
constructor); THIS FUNCTION WILL BE REMOVED IN VERSION 0.9.0.
|
|
219
|
+
|
|
220
|
+
:param node_reference: See the ``Node`` constructor.
|
|
221
|
+
:type node_reference: str
|
|
222
|
+
:param u: See the ``Node`` constructor.
|
|
223
|
+
:type node_reference: str
|
|
224
|
+
:param p: See the ``Node`` constructor.
|
|
225
|
+
:type node_reference: str
|
|
226
|
+
:return: See the ``Node`` constructor.
|
|
227
|
+
:rtype: Node
|
|
228
|
+
"""
|
|
229
|
+
return Node(node_reference, u, p)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def poll(node: Node, auid: str) -> RequestUrlOpenT:
|
|
233
|
+
"""
|
|
234
|
+
Performs the DebugPanel servlet "Start V3 Poll" operation on the given
|
|
235
|
+
node for the given AUID.
|
|
236
|
+
|
|
237
|
+
:param node: A ``Node`` instance.
|
|
238
|
+
:type node: Node
|
|
239
|
+
:param auid: An AUID.
|
|
240
|
+
:type auid: str
|
|
241
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
242
|
+
:rtype: RequestUrlOpenT
|
|
243
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
244
|
+
"""
|
|
245
|
+
return _auid_action(node, auid, 'Start V3 Poll')
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def reindex_metadata(node: Node, auid: str) -> RequestUrlOpenT:
|
|
249
|
+
"""
|
|
250
|
+
Performs the DebugPanel servlet "Force Reindex Metadata" operation on the
|
|
251
|
+
given node for the given AUID.
|
|
252
|
+
|
|
253
|
+
:param node: A ``Node`` instance.
|
|
254
|
+
:type node: Node
|
|
255
|
+
:param auid: An AUID.
|
|
256
|
+
:type auid: str
|
|
257
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
258
|
+
:rtype: RequestUrlOpenT
|
|
259
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
260
|
+
"""
|
|
261
|
+
return _auid_action(node, auid, 'Force Reindex Metadata')
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def reload_config(node: Node) -> RequestUrlOpenT:
|
|
265
|
+
"""
|
|
266
|
+
Performs the DebugPanel servlet "Reload Config" operation on the given
|
|
267
|
+
node.
|
|
268
|
+
|
|
269
|
+
:param node: A ``Node`` instance.
|
|
270
|
+
:type node: Node
|
|
271
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
272
|
+
:rtype: RequestUrlOpenT
|
|
273
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
274
|
+
"""
|
|
275
|
+
return _node_action(node, 'Reload Config')
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def validate_files(node: Node, auid: str) -> RequestUrlOpenT:
|
|
279
|
+
"""
|
|
280
|
+
Performs the DebugPanel servlet "Validate Files" operation on the given
|
|
281
|
+
node for the given AUID.
|
|
282
|
+
|
|
283
|
+
:param node: A ``Node`` instance.
|
|
284
|
+
:type node: Node
|
|
285
|
+
:param auid: An AUID.
|
|
286
|
+
:type auid: str
|
|
287
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
288
|
+
:rtype: RequestUrlOpenT
|
|
289
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
290
|
+
"""
|
|
291
|
+
return _auid_action(node, auid, 'Validate Files')
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _auid_action(node: Node, auid: str, action: str, **kwargs) -> RequestUrlOpenT:
|
|
295
|
+
"""
|
|
296
|
+
Performs one AUID-centric action.
|
|
297
|
+
|
|
298
|
+
:param node: A ``Node`` instance.
|
|
299
|
+
:type node: Node
|
|
300
|
+
:param auid: An AUID.
|
|
301
|
+
:type auid: str
|
|
302
|
+
:param action: An AUID-oriented DebugPanel servlet action string, e.g.
|
|
303
|
+
``Force Deep Crawl``.
|
|
304
|
+
:type action: str
|
|
305
|
+
:param kwargs: Key-value pairs of additional query string arguments.
|
|
306
|
+
:type kwargs: Dict[str, Any]
|
|
307
|
+
:return: The result of calling `urllib.request.urlopen`` on an appropriate
|
|
308
|
+
URL.
|
|
309
|
+
:rtype: RequestUrlOpenT
|
|
310
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
311
|
+
"""
|
|
312
|
+
action_encoded = action.replace(" ", "%20")
|
|
313
|
+
auid_encoded = auid.replace('%', '%25').replace('|', '%7C').replace('&', '%26').replace('~', '%7E')
|
|
314
|
+
req = _make_request(node, f'action={action_encoded}&auid={auid_encoded}', **kwargs)
|
|
315
|
+
return urlopen(req)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _make_request(node: Node, query: str, **kwargs) -> Request:
|
|
319
|
+
"""
|
|
320
|
+
Constructs and authenticates an HTTP request.
|
|
321
|
+
|
|
322
|
+
:param node: A ``Node`` instance.
|
|
323
|
+
:type node: Node
|
|
324
|
+
:param query: A primary ampersand-separated query string, e.g.
|
|
325
|
+
``"action=MyAction&auid=MyAuid"``.
|
|
326
|
+
:type query: str
|
|
327
|
+
:param kwargs: Key-value pairs of additional query string arguments, e.g.
|
|
328
|
+
``(..., depth=99)`` to add ``"&depth=99"``.
|
|
329
|
+
:type kwargs: Dict[str, Any]
|
|
330
|
+
:return: An authenticated ``Request`` instance (before
|
|
331
|
+
``urllib.request.urlopen`` is called).
|
|
332
|
+
:rtype: Request
|
|
333
|
+
"""
|
|
334
|
+
for key, val in kwargs.items():
|
|
335
|
+
query = f'{query}&{key}={val}'
|
|
336
|
+
url = f'{node.get_url()}/DebugPanel?{query}'
|
|
337
|
+
req: Request = Request(url)
|
|
338
|
+
node.authenticate(req)
|
|
339
|
+
return req
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _node_action(node: Node, action: str, **kwargs) -> RequestUrlOpenT:
|
|
343
|
+
"""
|
|
344
|
+
Performs one node-centric action.
|
|
345
|
+
|
|
346
|
+
:param node: A ``Node`` instance.
|
|
347
|
+
:type node: Node
|
|
348
|
+
:param action: A node-oriented DebugPanel servlet action string, e.g.
|
|
349
|
+
``Reload Config``.
|
|
350
|
+
:type action: str
|
|
351
|
+
:param kwargs: Key-value pairs of additional query string arguments, e.g.
|
|
352
|
+
``(..., depth=99)`` to add ``"&depth=99"``.
|
|
353
|
+
:type kwargs: Dict[str, Any]
|
|
354
|
+
:return: The result of calling `urllib.request.urlopen`` on an appropriate
|
|
355
|
+
URL.
|
|
356
|
+
:rtype: RequestUrlOpenT
|
|
357
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
358
|
+
"""
|
|
359
|
+
action_encoded = action.replace(" ", "%20")
|
|
360
|
+
req = _make_request(node, f'action={action_encoded}', **kwargs)
|
|
361
|
+
return urlopen(req)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
|
|
4
|
+
#
|
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
|
7
|
+
#
|
|
8
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
# this list of conditions and the following disclaimer.
|
|
10
|
+
#
|
|
11
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
# and/or other materials provided with the distribution.
|
|
14
|
+
#
|
|
15
|
+
# 3. Neither the name of the copyright holder nor the names of its contributors
|
|
16
|
+
# may be used to endorse or promote products derived from this software without
|
|
17
|
+
# specific prior written permission.
|
|
18
|
+
#
|
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
22
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
23
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
24
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
25
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
26
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
27
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
28
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
29
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
Entry point for the ``lockss.debugpanel`` module.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from .cli import main
|
|
36
|
+
|
|
37
|
+
main()
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
|
|
4
|
+
#
|
|
5
|
+
# Redistribution and use in source and binary forms, with or without
|
|
6
|
+
# modification, are permitted provided that the following conditions are met:
|
|
7
|
+
#
|
|
8
|
+
# 1. Redistributions of source code must retain the above copyright notice,
|
|
9
|
+
# this list of conditions and the following disclaimer.
|
|
10
|
+
#
|
|
11
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
# this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
# and/or other materials provided with the distribution.
|
|
14
|
+
#
|
|
15
|
+
# 3. Neither the name of the copyright holder nor the names of its contributors
|
|
16
|
+
# may be used to endorse or promote products derived from this software without
|
|
17
|
+
# specific prior written permission.
|
|
18
|
+
#
|
|
19
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
22
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
23
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
24
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
25
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
26
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
27
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
28
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
29
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
Command line tool to interact with the LOCKSS 1.x DebugPanel servlet.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from collections.abc import Callable
|
|
36
|
+
from concurrent.futures import Executor, Future, ProcessPoolExecutor, ThreadPoolExecutor, as_completed
|
|
37
|
+
from enum import Enum
|
|
38
|
+
from getpass import getpass
|
|
39
|
+
from itertools import chain
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
42
|
+
|
|
43
|
+
from pydantic.v1 import BaseModel, Field, FilePath, root_validator, validator
|
|
44
|
+
from pydantic.v1.types import PositiveInt
|
|
45
|
+
from tabulate import tabulate
|
|
46
|
+
|
|
47
|
+
from lockss.pybasic.cliutil import BaseCli, StringCommand, at_most_one_from_enum, get_from_enum, COPYRIGHT_DESCRIPTION, LICENSE_DESCRIPTION, VERSION_DESCRIPTION
|
|
48
|
+
from lockss.pybasic.errorutil import InternalError
|
|
49
|
+
from lockss.pybasic.fileutil import file_lines, path
|
|
50
|
+
from lockss.pybasic.outpututil import OutputFormatOptions
|
|
51
|
+
from . import Node, RequestUrlOpenT, check_substance, crawl, crawl_plugins, deep_crawl, disable_indexing, poll, reload_config, reindex_metadata, validate_files, DEFAULT_DEPTH, __copyright__, __license__, __version__
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class JobPool(Enum):
|
|
55
|
+
"""
|
|
56
|
+
An enum of job pool types.
|
|
57
|
+
|
|
58
|
+
See also ``DEFAULT_POOL_TYPE``.
|
|
59
|
+
"""
|
|
60
|
+
thread_pool = 'thread-pool'
|
|
61
|
+
process_pool = 'process-pool'
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def from_option(name: str) -> str:
|
|
65
|
+
"""
|
|
66
|
+
Given an option name with hyphens, return the enum constant name with
|
|
67
|
+
underscores.
|
|
68
|
+
|
|
69
|
+
:param name: An option name with hyphens.
|
|
70
|
+
:type name: str
|
|
71
|
+
:return: The corresponding enum constant name with underscores.
|
|
72
|
+
:rtype: str
|
|
73
|
+
"""
|
|
74
|
+
return JobPool(name.replace('-', '_'))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
DEFAULT_POOL_SIZE: Optional[int] = None
|
|
78
|
+
DEFAULT_POOL_TYPE: JobPool = JobPool.thread_pool
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class NodesOptions(BaseModel):
|
|
82
|
+
"""
|
|
83
|
+
The --node/-n, --nodes/-N, --password/-p and --username/-u options.
|
|
84
|
+
"""
|
|
85
|
+
node: Optional[List[str]] = Field([], aliases=['-n'], description='(nodes) add one or more nodes to the set of nodes to process')
|
|
86
|
+
nodes: Optional[List[FilePath]] = Field([], aliases=['-N'], description='(nodes) add the nodes listed in one or more files to the set of nodes to process')
|
|
87
|
+
password: Optional[str] = Field(aliases=['-p'], description='(nodes) UI password; interactive prompt if not specified')
|
|
88
|
+
username: Optional[str] = Field(aliases=['-u'], description='(nodes) UI username; interactive prompt if not unspecified')
|
|
89
|
+
|
|
90
|
+
@validator('nodes', each_item=True, pre=True)
|
|
91
|
+
def _expand_each_nodes_path(cls, v: Path):
|
|
92
|
+
return path(v)
|
|
93
|
+
|
|
94
|
+
def get_nodes(self):
|
|
95
|
+
ret = [*self.node, *chain.from_iterable(file_lines(file_path) for file_path in self.nodes)]
|
|
96
|
+
if len(ret) == 0:
|
|
97
|
+
raise RuntimeError('empty list of nodes')
|
|
98
|
+
return ret
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class AuidsOptions(BaseModel):
|
|
102
|
+
"""
|
|
103
|
+
The --auid/-a and --auids/-A options.
|
|
104
|
+
"""
|
|
105
|
+
auid: Optional[List[str]] = Field([], aliases=['-a'], description='(AUIDs) add one or more AUIDs to the set of AUIDs to process')
|
|
106
|
+
auids: Optional[List[FilePath]] = Field([], aliases=['-A'], description='(AUIDs) add the AUIDs listed in one or more files to the set of AUIDs to process')
|
|
107
|
+
|
|
108
|
+
@validator('auids', each_item=True, pre=True)
|
|
109
|
+
def _expand_each_auids_path(cls, v: Path):
|
|
110
|
+
return path(v)
|
|
111
|
+
|
|
112
|
+
def get_auids(self):
|
|
113
|
+
ret = [*self.auid, *chain.from_iterable(file_lines(file_path) for file_path in self.auids)]
|
|
114
|
+
if len(ret) == 0:
|
|
115
|
+
raise RuntimeError('empty list of AUIDs')
|
|
116
|
+
return ret
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class DepthOptions(BaseModel):
|
|
120
|
+
"""
|
|
121
|
+
The --depth/-d option.
|
|
122
|
+
"""
|
|
123
|
+
depth: Optional[int] = Field(DEFAULT_DEPTH, aliases=['-d'], description='(deep crawl) set crawl depth')
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class JobPoolOptions(BaseModel):
|
|
127
|
+
"""
|
|
128
|
+
The --pool-size, --process-pool and --thread-pool options.
|
|
129
|
+
"""
|
|
130
|
+
pool_size: Optional[PositiveInt] = Field(description='(job pool) set the job pool size')
|
|
131
|
+
process_pool: Optional[bool] = Field(False, description='(job pool) use a process pool', enum=JobPool)
|
|
132
|
+
thread_pool: Optional[bool] = Field(False, description='(job pool) use a thread pool', enum=JobPool)
|
|
133
|
+
|
|
134
|
+
@root_validator
|
|
135
|
+
def _at_most_one_pool_type(cls, values):
|
|
136
|
+
return at_most_one_from_enum(cls, values, JobPool)
|
|
137
|
+
|
|
138
|
+
def get_pool_size(self) -> Optional[int]:
|
|
139
|
+
return self.pool_size if hasattr(self, 'pool_size') else DEFAULT_POOL_SIZE
|
|
140
|
+
|
|
141
|
+
def get_pool_type(self) -> JobPool:
|
|
142
|
+
return get_from_enum(self, JobPool, DEFAULT_POOL_TYPE)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class NodeCommand(OutputFormatOptions, JobPoolOptions, NodesOptions):
|
|
146
|
+
"""
|
|
147
|
+
A pydantic-argparse command for node commands.
|
|
148
|
+
"""
|
|
149
|
+
pass
|
|
150
|
+
|
|
151
|
+
class AuidCommand(NodeCommand, OutputFormatOptions, JobPoolOptions, AuidsOptions, NodesOptions):
|
|
152
|
+
"""
|
|
153
|
+
A pydantic-argparse command for AUID commands except deep-crawl.
|
|
154
|
+
"""
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
class DeepCrawlCommand(AuidCommand, OutputFormatOptions, JobPoolOptions, DepthOptions, AuidsOptions, NodesOptions):
|
|
158
|
+
"""
|
|
159
|
+
A pydantic-argparse command for deep-crawl.
|
|
160
|
+
"""
|
|
161
|
+
pass
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class DebugPanelCommand(BaseModel):
|
|
165
|
+
"""
|
|
166
|
+
The pydantic-argparse model for the top-level debugpanel command.
|
|
167
|
+
"""
|
|
168
|
+
check_substance: Optional[AuidCommand] = Field(description='cause nodes to check the substance of AUs', alias='check-substance')
|
|
169
|
+
copyright: Optional[StringCommand.type(__copyright__)] = Field(description=COPYRIGHT_DESCRIPTION)
|
|
170
|
+
cp: Optional[NodeCommand] = Field(description='synonym for: crawl-plugins')
|
|
171
|
+
cr: Optional[AuidCommand] = Field(description='synonym for: crawl')
|
|
172
|
+
crawl: Optional[AuidCommand] = Field(description='cause nodes to crawl AUs')
|
|
173
|
+
crawl_plugins: Optional[NodeCommand] = Field(description='cause nodes to crawl plugins', alias='crawl-plugins')
|
|
174
|
+
cs: Optional[AuidCommand] = Field(description='synonym for: check-substance')
|
|
175
|
+
dc: Optional[DeepCrawlCommand] = Field(description='synonym for: deep-crawl')
|
|
176
|
+
deep_crawl: Optional[DeepCrawlCommand] = Field(description='cause nodes to deeply crawl AUs', alias='deep-crawl')
|
|
177
|
+
di: Optional[AuidCommand] = Field(description='synonym for: disable-indexing')
|
|
178
|
+
disable_indexing: Optional[AuidCommand] = Field(description='cause nodes to disable metadata indexing for AUs', alias='disable-indexing')
|
|
179
|
+
license: Optional[StringCommand.type(__license__)] = Field(description=LICENSE_DESCRIPTION)
|
|
180
|
+
po: Optional[AuidCommand] = Field(description='synonym for: poll')
|
|
181
|
+
poll: Optional[AuidCommand] = Field(description='cause nodes to poll AUs')
|
|
182
|
+
rc: Optional[NodeCommand] = Field(description='synonym for: reload-config')
|
|
183
|
+
reindex_metadata: Optional[AuidCommand] = Field(description='cause nodes to reindex the metadata of AUs', alias='reindex-metadata')
|
|
184
|
+
reload_config: Optional[NodeCommand] = Field(description='cause nodes to reload their configuration', alias='reload-config')
|
|
185
|
+
ri: Optional[AuidCommand] = Field(description='synonym for: reindex-metadata')
|
|
186
|
+
validate_files: Optional[AuidCommand] = Field(description='cause nodes to validate the files of AUs', alias='validate-files')
|
|
187
|
+
version: Optional[StringCommand.type(__version__)] = Field(description=VERSION_DESCRIPTION)
|
|
188
|
+
vf: Optional[AuidCommand] = Field(description='synonym for: validate-files')
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class DebugPanelCli(BaseCli[DebugPanelCommand]):
|
|
192
|
+
"""
|
|
193
|
+
The debugpanel command line tool.
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
def __init__(self):
|
|
197
|
+
"""
|
|
198
|
+
Constructs a new ``DebugPanelCli`` instance.
|
|
199
|
+
"""
|
|
200
|
+
super().__init__(model=DebugPanelCommand,
|
|
201
|
+
prog='debugpanel',
|
|
202
|
+
description='Tool to interact with the LOCKSS 1.x DebugPanel servlet')
|
|
203
|
+
self._auids: Optional[List[str]] = None
|
|
204
|
+
self._auth: Optional[Any] = None
|
|
205
|
+
self._executor: Optional[Executor] = None
|
|
206
|
+
self._nodes: Optional[List[str]] = None
|
|
207
|
+
|
|
208
|
+
def _check_substance(self, auid_command: AuidCommand) -> None:
|
|
209
|
+
self._do_auid_command(auid_command, check_substance)
|
|
210
|
+
|
|
211
|
+
def _copyright(self, string_command: StringCommand) -> None:
|
|
212
|
+
self._do_string_command(string_command)
|
|
213
|
+
|
|
214
|
+
def _cp(self, node_command: NodeCommand) -> None:
|
|
215
|
+
self._crawl_plugins(node_command)
|
|
216
|
+
|
|
217
|
+
def _cr(self, auid_command: AuidCommand) -> None:
|
|
218
|
+
self._crawl(auid_command)
|
|
219
|
+
|
|
220
|
+
def _crawl(self, auid_command: AuidCommand) -> None:
|
|
221
|
+
self._do_auid_command(auid_command, crawl)
|
|
222
|
+
|
|
223
|
+
def _crawl_plugins(self, node_command: NodeCommand) -> None:
|
|
224
|
+
self._do_node_command(node_command, crawl_plugins)
|
|
225
|
+
|
|
226
|
+
def _cs(self, auid_command: AuidCommand) -> None:
|
|
227
|
+
self._check_substance(auid_command)
|
|
228
|
+
|
|
229
|
+
def _dc(self, deep_crawl_command: DeepCrawlCommand) -> None:
|
|
230
|
+
self._deep_crawl(deep_crawl_command)
|
|
231
|
+
|
|
232
|
+
def _deep_crawl(self, deep_crawl_command: DeepCrawlCommand) -> None:
|
|
233
|
+
self._do_auid_command(deep_crawl_command, deep_crawl, depth=deep_crawl_command.depth)
|
|
234
|
+
|
|
235
|
+
def _di(self, auid_command: AuidCommand) -> None:
|
|
236
|
+
self._disable_indexing(auid_command)
|
|
237
|
+
|
|
238
|
+
def _disable_indexing(self, auid_command: AuidCommand) -> None:
|
|
239
|
+
self._do_auid_command(auid_command, disable_indexing)
|
|
240
|
+
|
|
241
|
+
def _do_auid_command(self, auid_command: AuidCommand, node_auid_func: Callable[[Node, str], RequestUrlOpenT], **kwargs: Dict[str, Any]) -> None:
|
|
242
|
+
"""
|
|
243
|
+
Performs one AUID-centric command.
|
|
244
|
+
|
|
245
|
+
:param auid_command: An ``AuidCommand`` model.
|
|
246
|
+
:type auid_command: AuidCommand
|
|
247
|
+
:param node_auid_func: A function that applies to a ``Node`` and an AUID
|
|
248
|
+
and returns what ``urllib.request.urlopen``
|
|
249
|
+
returns.
|
|
250
|
+
:type node_auid_func: ``RequestUrlOpenT``
|
|
251
|
+
:param kwargs: Keyword arguments (needed for the ``depth`` command).
|
|
252
|
+
:type kwargs: Dict[str, Any]
|
|
253
|
+
"""
|
|
254
|
+
self._initialize_auth(auid_command)
|
|
255
|
+
self._initialize_executor(auid_command)
|
|
256
|
+
self._nodes = auid_command.get_nodes()
|
|
257
|
+
self._auids = auid_command.get_auids()
|
|
258
|
+
node_objects = [Node(node, *self._auth) for node in self._nodes]
|
|
259
|
+
futures: Dict[Future, Tuple[str, str]] = {self._executor.submit(node_auid_func, node_object, auid, **kwargs): (node, auid) for auid in self._auids for node, node_object in zip(self._nodes, node_objects)}
|
|
260
|
+
results: Dict[Tuple[str, str], Any] = {}
|
|
261
|
+
for future in as_completed(futures):
|
|
262
|
+
node_auid = futures[future]
|
|
263
|
+
try:
|
|
264
|
+
resp: RequestUrlOpenT = future.result()
|
|
265
|
+
status: int = resp.status
|
|
266
|
+
reason: str = resp.reason
|
|
267
|
+
results[node_auid] = 'Requested' if status == 200 else reason
|
|
268
|
+
except Exception as exc:
|
|
269
|
+
results[node_auid] = exc
|
|
270
|
+
print(tabulate([[auid, *[results[(node, auid)] for node in self._nodes]] for auid in self._auids],
|
|
271
|
+
headers=['AUID', *self._nodes],
|
|
272
|
+
tablefmt=auid_command.output_format))
|
|
273
|
+
|
|
274
|
+
def _do_node_command(self, node_command: NodeCommand, node_func: Callable[[Node], RequestUrlOpenT], **kwargs: Dict[str, Any]) -> None:
|
|
275
|
+
"""
|
|
276
|
+
Performs one node-centric command.
|
|
277
|
+
|
|
278
|
+
:param node_command: A ``NodeCommand`` model.
|
|
279
|
+
:type auid_command: NodeCommand
|
|
280
|
+
:param node_func: A function that applies to a ``Node`` and returns
|
|
281
|
+
what ``urllib.request.urlopen`` returns.
|
|
282
|
+
:type node_auid_func: ``RequestUrlOpenT``
|
|
283
|
+
:param kwargs: Keyword arguments (not currently needed by any command).
|
|
284
|
+
:type kwargs: Dict[str, Any]
|
|
285
|
+
"""
|
|
286
|
+
self._initialize_auth(node_command)
|
|
287
|
+
self._initialize_executor(node_command)
|
|
288
|
+
self._nodes = node_command.get_nodes()
|
|
289
|
+
node_objects = [Node(node, *self._auth) for node in self._nodes]
|
|
290
|
+
futures: Dict[Future, str] = {self._executor.submit(node_func, node_object, **kwargs): node for node, node_object in zip(self._nodes, node_objects)}
|
|
291
|
+
results: Dict[str, Any] = {}
|
|
292
|
+
for future in as_completed(futures):
|
|
293
|
+
node = futures[future]
|
|
294
|
+
try:
|
|
295
|
+
resp: RequestUrlOpenT = future.result()
|
|
296
|
+
status: int = resp.status
|
|
297
|
+
reason: str = resp.reason
|
|
298
|
+
results[node] = 'Requested' if status == 200 else reason
|
|
299
|
+
except Exception as exc:
|
|
300
|
+
results[node] = exc
|
|
301
|
+
print(tabulate([[node, results[node]] for node in self._nodes],
|
|
302
|
+
headers=['Node', 'Result'],
|
|
303
|
+
tablefmt=node_command.output_format))
|
|
304
|
+
|
|
305
|
+
def _do_string_command(self, string_command: StringCommand) -> None:
|
|
306
|
+
"""
|
|
307
|
+
Performs one string command.
|
|
308
|
+
|
|
309
|
+
:param string_command: A ``StringCommand`` model.
|
|
310
|
+
:type auid_command: StringCommand
|
|
311
|
+
"""
|
|
312
|
+
string_command()
|
|
313
|
+
|
|
314
|
+
def _initialize_auth(self, nodes_options: NodesOptions) -> None:
|
|
315
|
+
"""
|
|
316
|
+
Computes the ``self._auth`` value, possibly after asking for interactive
|
|
317
|
+
input.
|
|
318
|
+
|
|
319
|
+
:param nodes_options: A ``NodesOptions`` model.
|
|
320
|
+
:type node_options: ``NodesOptions``
|
|
321
|
+
"""
|
|
322
|
+
_u = nodes_options.username or input('UI username: ')
|
|
323
|
+
_p = nodes_options.password or getpass('UI password: ')
|
|
324
|
+
self._auth = (_u, _p)
|
|
325
|
+
|
|
326
|
+
def _initialize_executor(self, job_pool_options: JobPoolOptions) -> None:
|
|
327
|
+
"""
|
|
328
|
+
Initializes the ``Executor``.
|
|
329
|
+
|
|
330
|
+
:param job_pool_options: A ``JobPoolOptions`` model.
|
|
331
|
+
:type job_pool_options: ``JobPoolOptions``.
|
|
332
|
+
"""
|
|
333
|
+
if job_pool_options.get_pool_type() == JobPool.thread_pool:
|
|
334
|
+
self._executor = ThreadPoolExecutor(max_workers=job_pool_options.get_pool_size())
|
|
335
|
+
elif job_pool_options.get_pool_type() == JobPool.process_pool:
|
|
336
|
+
self._executor = ProcessPoolExecutor(max_workers=job_pool_options.get_pool_size())
|
|
337
|
+
else:
|
|
338
|
+
raise InternalError()
|
|
339
|
+
|
|
340
|
+
def _license(self, string_command: StringCommand) -> None:
|
|
341
|
+
self._do_string_command(string_command)
|
|
342
|
+
|
|
343
|
+
def _po(self, auid_command: AuidCommand) -> None:
|
|
344
|
+
self._poll(auid_command)
|
|
345
|
+
|
|
346
|
+
def _poll(self, auid_command: AuidCommand) -> None:
|
|
347
|
+
self._do_auid_command(auid_command, poll)
|
|
348
|
+
|
|
349
|
+
def _rc(self, node_command: NodeCommand):
|
|
350
|
+
self._reload_config(node_command)
|
|
351
|
+
|
|
352
|
+
def _ri(self, auid_command: AuidCommand) -> None:
|
|
353
|
+
self._reindex_metadata(auid_command)
|
|
354
|
+
|
|
355
|
+
def _reindex_metadata(self, auid_command: AuidCommand) -> None:
|
|
356
|
+
self._do_auid_command(auid_command, reindex_metadata)
|
|
357
|
+
|
|
358
|
+
def _reload_config(self, node_command: NodeCommand):
|
|
359
|
+
self._do_node_command(node_command, reload_config)
|
|
360
|
+
|
|
361
|
+
def _validate_files(self, auid_command: AuidCommand) -> None:
|
|
362
|
+
self._do_auid_command(auid_command, validate_files)
|
|
363
|
+
|
|
364
|
+
def _vf(self, auid_command: AuidCommand) -> None:
|
|
365
|
+
self._validate_files(auid_command)
|
|
366
|
+
|
|
367
|
+
def _version(self, string_command: StringCommand) -> None:
|
|
368
|
+
self._do_string_command(string_command)
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def main() -> None:
|
|
372
|
+
"""
|
|
373
|
+
Entry point for the debugpanel command line tool.
|
|
374
|
+
"""
|
|
375
|
+
DebugPanelCli().run()
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
if __name__ == '__main__':
|
|
379
|
+
main()
|