lockss-debugpanel 0.9.0.dev7__tar.gz → 0.10.0.dev2__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.9.0.dev7 → lockss_debugpanel-0.10.0.dev2}/CHANGELOG.rst +14 -2
- {lockss_debugpanel-0.9.0.dev7 → lockss_debugpanel-0.10.0.dev2}/LICENSE +1 -1
- {lockss_debugpanel-0.9.0.dev7 → lockss_debugpanel-0.10.0.dev2}/PKG-INFO +11 -11
- {lockss_debugpanel-0.9.0.dev7 → lockss_debugpanel-0.10.0.dev2}/README.rst +3 -8
- {lockss_debugpanel-0.9.0.dev7 → lockss_debugpanel-0.10.0.dev2}/pyproject.toml +9 -4
- lockss_debugpanel-0.10.0.dev2/src/lockss/debugpanel/__init__.py +41 -0
- {lockss_debugpanel-0.9.0.dev7 → lockss_debugpanel-0.10.0.dev2}/src/lockss/debugpanel/__main__.py +1 -1
- lockss_debugpanel-0.10.0.dev2/src/lockss/debugpanel/_core.py +396 -0
- lockss_debugpanel-0.10.0.dev2/src/lockss/debugpanel/cli.py +418 -0
- lockss_debugpanel-0.9.0.dev7/src/lockss/debugpanel/__init__.py +0 -333
- lockss_debugpanel-0.9.0.dev7/src/lockss/debugpanel/cli.py +0 -379
|
@@ -2,24 +2,36 @@
|
|
|
2
2
|
Release Notes
|
|
3
3
|
=============
|
|
4
4
|
|
|
5
|
+
------
|
|
6
|
+
0.10.0
|
|
7
|
+
------
|
|
8
|
+
|
|
9
|
+
Released: NOT YET RELEASED
|
|
10
|
+
|
|
11
|
+
Requires Python 3.10 or greater.
|
|
12
|
+
|
|
5
13
|
-----
|
|
6
14
|
0.9.0
|
|
7
15
|
-----
|
|
8
16
|
|
|
9
|
-
Released:
|
|
17
|
+
Released: 2026-03-18
|
|
10
18
|
|
|
11
|
-
Requires Python 3.10.
|
|
19
|
+
Requires Python 3.10 or greater.
|
|
12
20
|
|
|
13
21
|
* **Features**
|
|
14
22
|
|
|
15
23
|
* New command line infrastructure based on `Click Extra <https://kdeldycke.github.io/click-extra>`_, `Cloup <https://cloup.readthedocs.io/>`_ and `Click <https://click.palletsprojects.com/>`_, including expanded tabular output styles, progress bar, command sections and aliases.
|
|
16
24
|
|
|
25
|
+
* New ``--headings``//``--no-headings``, ``--progress``//``--no-progress`` output styles.
|
|
26
|
+
|
|
17
27
|
* **Changes**
|
|
18
28
|
|
|
19
29
|
* The alias ``-u`` of ``--username`` and ``-p`` of ``--password`` are deprecated in favor of ``-U`` and ``-P`` respectively.
|
|
20
30
|
|
|
21
31
|
* ``--process-pool`` and ``--thread-pool`` are deprecated in favor of ``--pool-type=process-pool`` and ``--pool-type=thread-pool`` respectively.
|
|
22
32
|
|
|
33
|
+
* ``--output-format`` has been renamed to ``--table-format``/``-T``.
|
|
34
|
+
|
|
23
35
|
-----
|
|
24
36
|
0.8.2
|
|
25
37
|
-----
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copyright (c) 2000-
|
|
1
|
+
Copyright (c) 2000-2026, Board of Trustees of Leland Stanford Jr. University
|
|
2
2
|
|
|
3
3
|
Redistribution and use in source and binary forms, with or without
|
|
4
4
|
modification, are permitted provided that the following conditions are met:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lockss-debugpanel
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0.dev2
|
|
4
4
|
Summary: Command line tool and Python library to interact with the LOCKSS 1.x DebugPanel servlet
|
|
5
5
|
License: BSD-3-Clause
|
|
6
6
|
License-File: LICENSE
|
|
@@ -19,8 +19,13 @@ Classifier: Programming Language :: Python
|
|
|
19
19
|
Classifier: Topic :: Software Development :: Libraries
|
|
20
20
|
Classifier: Topic :: System :: Archiving
|
|
21
21
|
Classifier: Topic :: Utilities
|
|
22
|
-
Requires-Dist:
|
|
23
|
-
Requires-Dist:
|
|
22
|
+
Requires-Dist: PyYAML (>=6.0.0,<6.1.0)
|
|
23
|
+
Requires-Dist: click-command-tree (>=1.2.0,<1.3.0)
|
|
24
|
+
Requires-Dist: click-extra[hjson,toml,xml,yaml] (>=8.1.1,<8.2.0)
|
|
25
|
+
Requires-Dist: click-plugins (>=1.1.1.2,<1.2.0)
|
|
26
|
+
Requires-Dist: cloup (>=3.0.7)
|
|
27
|
+
Requires-Dist: lockss-pybasic (==0.3.0.dev11)
|
|
28
|
+
Requires-Dist: pydantic (>=2.13.0,<2.14.0)
|
|
24
29
|
Project-URL: Documentation, https://docs.lockss.org/en/latest/software/debugpanel
|
|
25
30
|
Project-URL: Repository, https://github.com/lockss/lockss-debugpanel
|
|
26
31
|
Project-URL: changelog, https://github.com/lockss/lockss-debugpanel/blob/main/CHANGELOG.rst
|
|
@@ -31,7 +36,7 @@ Description-Content-Type: text/x-rst
|
|
|
31
36
|
Debugpanel
|
|
32
37
|
==========
|
|
33
38
|
|
|
34
|
-
.. |RELEASE| replace:: 0.
|
|
39
|
+
.. |RELEASE| replace:: 0.10.0-dev2
|
|
35
40
|
.. |RELEASE_DATE| replace:: NOT YET RELEASED
|
|
36
41
|
.. |DEBUGPANEL| replace:: **Debugpanel**
|
|
37
42
|
|
|
@@ -52,7 +57,7 @@ Debugpanel
|
|
|
52
57
|
|
|
53
58
|
Quick Start::
|
|
54
59
|
|
|
55
|
-
# Requires Python 3.
|
|
60
|
+
# Requires Python 3.10 or greater
|
|
56
61
|
python --version
|
|
57
62
|
|
|
58
63
|
# Install with pipx
|
|
@@ -65,14 +70,9 @@ Quick Start::
|
|
|
65
70
|
debugpanel reload-config -n lockss1.example.edu:8081
|
|
66
71
|
|
|
67
72
|
# Crawl AUIDs from list.txt on lockss1.example.edu:8081 and lockss2.example.edu:8081
|
|
68
|
-
|
|
69
|
-
# ...First alternative: each node gets a -n option
|
|
70
73
|
debugpanel crawl -A list.txt -n lockss1.example.edu:8081 -n lockss2.example.edu:8081
|
|
71
74
|
|
|
72
|
-
#
|
|
73
|
-
debugpanel crawl -A list.txt -n lockss1.example.edu:8081 lockss2.example.edu:8081
|
|
74
|
-
|
|
75
|
-
# ...Third alternative: list lockss1.example.edu:8081 and lockss2.example.edu:8081 in nodes.txt
|
|
75
|
+
# Alternatively, list lockss1.example.edu:8081 and lockss2.example.edu:8081 in nodes.txt
|
|
76
76
|
debugpanel crawl -A list.txt -N nodes.txt
|
|
77
77
|
|
|
78
78
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Debugpanel
|
|
3
3
|
==========
|
|
4
4
|
|
|
5
|
-
.. |RELEASE| replace:: 0.
|
|
5
|
+
.. |RELEASE| replace:: 0.10.0-dev2
|
|
6
6
|
.. |RELEASE_DATE| replace:: NOT YET RELEASED
|
|
7
7
|
.. |DEBUGPANEL| replace:: **Debugpanel**
|
|
8
8
|
|
|
@@ -23,7 +23,7 @@ Debugpanel
|
|
|
23
23
|
|
|
24
24
|
Quick Start::
|
|
25
25
|
|
|
26
|
-
# Requires Python 3.
|
|
26
|
+
# Requires Python 3.10 or greater
|
|
27
27
|
python --version
|
|
28
28
|
|
|
29
29
|
# Install with pipx
|
|
@@ -36,13 +36,8 @@ Quick Start::
|
|
|
36
36
|
debugpanel reload-config -n lockss1.example.edu:8081
|
|
37
37
|
|
|
38
38
|
# Crawl AUIDs from list.txt on lockss1.example.edu:8081 and lockss2.example.edu:8081
|
|
39
|
-
|
|
40
|
-
# ...First alternative: each node gets a -n option
|
|
41
39
|
debugpanel crawl -A list.txt -n lockss1.example.edu:8081 -n lockss2.example.edu:8081
|
|
42
40
|
|
|
43
|
-
#
|
|
44
|
-
debugpanel crawl -A list.txt -n lockss1.example.edu:8081 lockss2.example.edu:8081
|
|
45
|
-
|
|
46
|
-
# ...Third alternative: list lockss1.example.edu:8081 and lockss2.example.edu:8081 in nodes.txt
|
|
41
|
+
# Alternatively, list lockss1.example.edu:8081 and lockss2.example.edu:8081 in nodes.txt
|
|
47
42
|
debugpanel crawl -A list.txt -N nodes.txt
|
|
48
43
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (c) 2000-
|
|
1
|
+
# Copyright (c) 2000-2026, Board of Trustees of Leland Stanford Jr. University
|
|
2
2
|
#
|
|
3
3
|
# Redistribution and use in source and binary forms, with or without
|
|
4
4
|
# modification, are permitted provided that the following conditions are met:
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
[project]
|
|
30
30
|
name = "lockss-debugpanel"
|
|
31
|
-
version = "0.
|
|
31
|
+
version = "0.10.0-dev2" # Always change in __init__.py, and at release time in README.rst and CHANGELOG.rst
|
|
32
32
|
description = "Command line tool and Python library to interact with the LOCKSS 1.x DebugPanel servlet"
|
|
33
33
|
license = { text = "BSD-3-Clause" }
|
|
34
34
|
readme = "README.rst"
|
|
@@ -40,8 +40,13 @@ maintainers = [
|
|
|
40
40
|
{ name = "Thib Guicherd-Callin", email = "thib@cs.stanford.edu" }
|
|
41
41
|
]
|
|
42
42
|
dependencies = [
|
|
43
|
-
"click-
|
|
44
|
-
"
|
|
43
|
+
"click-command-tree (>=1.2.0,<1.3.0)",
|
|
44
|
+
"click-extra[hjson,toml,xml,yaml] (>=8.1.1,<8.2.0)",
|
|
45
|
+
"click-plugins (>=1.1.1.2,<1.2.0)",
|
|
46
|
+
"cloup (>=3.0.7)", # from Click Extra 8.2.1
|
|
47
|
+
"lockss-pybasic (==0.3.0-dev11)",
|
|
48
|
+
"pydantic (>=2.13.0,<2.14.0)", # from lockss-pybasic 0.3.0-dev11
|
|
49
|
+
"PyYAML (>=6.0.0,<6.1.0)",
|
|
45
50
|
]
|
|
46
51
|
classifiers = [
|
|
47
52
|
"Development Status :: 5 - Production/Stable",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Library and command line tool to interact with the LOCKSS 1.x DebugPanel servlet.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = '0.10.0-dev2'
|
|
8
|
+
|
|
9
|
+
__copyright__ = '''
|
|
10
|
+
Copyright (c) 2000-2026, Board of Trustees of Leland Stanford Jr. University
|
|
11
|
+
'''.strip()
|
|
12
|
+
|
|
13
|
+
__license__ = __copyright__ + '\n\n' + '''
|
|
14
|
+
Redistribution and use in source and binary forms, with or without
|
|
15
|
+
modification, are permitted provided that the following conditions are met:
|
|
16
|
+
|
|
17
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
18
|
+
this list of conditions and the following disclaimer.
|
|
19
|
+
|
|
20
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
21
|
+
this list of conditions and the following disclaimer in the documentation
|
|
22
|
+
and/or other materials provided with the distribution.
|
|
23
|
+
|
|
24
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
25
|
+
may be used to endorse or promote products derived from this software without
|
|
26
|
+
specific prior written permission.
|
|
27
|
+
|
|
28
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
29
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
30
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
31
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
32
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
33
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
34
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
35
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
36
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
37
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
38
|
+
POSSIBILITY OF SUCH DAMAGE.
|
|
39
|
+
'''.strip()
|
|
40
|
+
|
|
41
|
+
from ._core import *
|
{lockss_debugpanel-0.9.0.dev7 → lockss_debugpanel-0.10.0.dev2}/src/lockss/debugpanel/__main__.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
|
-
# Copyright (c) 2000-
|
|
3
|
+
# Copyright (c) 2000-2026, Board of Trustees of Leland Stanford Jr. University
|
|
4
4
|
#
|
|
5
5
|
# Redistribution and use in source and binary forms, with or without
|
|
6
6
|
# modification, are permitted provided that the following conditions are met:
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Copyright (c) 2000-2026, 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
|
+
from abc import ABC, abstractmethod
|
|
32
|
+
from base64 import b64encode
|
|
33
|
+
from typing import Any, Optional, TypeAlias
|
|
34
|
+
from urllib.request import Request, urlopen
|
|
35
|
+
|
|
36
|
+
from lockss.pybasic.errorutil import InternalError
|
|
37
|
+
from lockss.pybasic.nodeutil import NodeIdentifier, NodeSpec, NodeTypeEnum
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
#: A type alias for what ``urllib.request.urlopen`` returns.
|
|
41
|
+
UrlOpenT: TypeAlias = Any
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
#: A default depth for the deep crawl operation.
|
|
45
|
+
DEFAULT_DEPTH: int = 123
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class _DebugPanelClientInterface(ABC):
|
|
49
|
+
"""
|
|
50
|
+
Abstract base class for DebugPanel servlet clients.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def authenticate(self, u: str, p: str) -> _DebugPanelClientInterface:
|
|
55
|
+
"""
|
|
56
|
+
Stores authentication information for this node.
|
|
57
|
+
|
|
58
|
+
:param u: The UI username.
|
|
59
|
+
:type u: str
|
|
60
|
+
:param p: The UI password.
|
|
61
|
+
:type p: str
|
|
62
|
+
:return: This instance, for chaining.
|
|
63
|
+
:rtype: _DebugPanelClientInterface
|
|
64
|
+
"""
|
|
65
|
+
raise NotImplementedError
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def check_substance(self, auid: str) -> UrlOpenT:
|
|
69
|
+
"""
|
|
70
|
+
Performs the DebugPanel servlet "Check Substance" operation on this node
|
|
71
|
+
for the given AUID.
|
|
72
|
+
|
|
73
|
+
:param auid: An AUID.
|
|
74
|
+
:type auid: str
|
|
75
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
76
|
+
:rtype: UrlOpenT
|
|
77
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
78
|
+
"""
|
|
79
|
+
raise NotImplementedError
|
|
80
|
+
|
|
81
|
+
@abstractmethod
|
|
82
|
+
def crawl(self, auid: str) -> UrlOpenT:
|
|
83
|
+
"""
|
|
84
|
+
Performs the DebugPanel servlet "Force Start Crawl" operation on this
|
|
85
|
+
node for the given AUID.
|
|
86
|
+
|
|
87
|
+
:param auid: An AUID.
|
|
88
|
+
:type auid: str
|
|
89
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
90
|
+
:rtype: UrlOpenT
|
|
91
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
92
|
+
"""
|
|
93
|
+
raise NotImplementedError
|
|
94
|
+
|
|
95
|
+
@abstractmethod
|
|
96
|
+
def crawl_plugins(self) -> UrlOpenT:
|
|
97
|
+
"""
|
|
98
|
+
Performs the DebugPanel servlet "Crawl Plugins" operation on this node.
|
|
99
|
+
|
|
100
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
101
|
+
:rtype: UrlOpenT
|
|
102
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
103
|
+
"""
|
|
104
|
+
raise NotImplementedError
|
|
105
|
+
|
|
106
|
+
@abstractmethod
|
|
107
|
+
def deep_crawl(self, auid: str, depth: int = DEFAULT_DEPTH) -> UrlOpenT:
|
|
108
|
+
"""
|
|
109
|
+
Performs the DebugPanel servlet "Force Deep Crawl" operation on this
|
|
110
|
+
node for the given AUID, with the given depth (default
|
|
111
|
+
``DEFAULT_DEPTH``).
|
|
112
|
+
|
|
113
|
+
:param auid: An AUID.
|
|
114
|
+
:type auid: str
|
|
115
|
+
:param depth: A strictly positive refetch depth.
|
|
116
|
+
:type auid: int
|
|
117
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
118
|
+
:rtype: UrlOpenT
|
|
119
|
+
:raises ValueError: If depth is negative or zero.
|
|
120
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
121
|
+
"""
|
|
122
|
+
raise NotImplementedError
|
|
123
|
+
|
|
124
|
+
@abstractmethod
|
|
125
|
+
def disable_indexing(self, auid: str) -> UrlOpenT:
|
|
126
|
+
"""
|
|
127
|
+
Performs the DebugPanel servlet "Disable Indexing" operation on this
|
|
128
|
+
node for the given AUID.
|
|
129
|
+
|
|
130
|
+
:param auid: An AUID.
|
|
131
|
+
:type auid: str
|
|
132
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
133
|
+
:rtype: UrlOpenT
|
|
134
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
135
|
+
"""
|
|
136
|
+
raise NotImplementedError
|
|
137
|
+
|
|
138
|
+
@abstractmethod
|
|
139
|
+
def poll(self, auid: str) -> UrlOpenT:
|
|
140
|
+
"""
|
|
141
|
+
Performs the DebugPanel servlet "Start V3 Poll" operation on this node
|
|
142
|
+
for the given AUID.
|
|
143
|
+
|
|
144
|
+
:param auid: An AUID.
|
|
145
|
+
:type auid: str
|
|
146
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
147
|
+
:rtype: UrlOpenT
|
|
148
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
149
|
+
"""
|
|
150
|
+
raise NotImplementedError
|
|
151
|
+
|
|
152
|
+
@abstractmethod
|
|
153
|
+
def reindex_metadata(self, auid: str) -> UrlOpenT:
|
|
154
|
+
"""
|
|
155
|
+
Performs the DebugPanel servlet "Force Reindex Metadata" operation on
|
|
156
|
+
this node for the given AUID.
|
|
157
|
+
|
|
158
|
+
:param auid: An AUID.
|
|
159
|
+
:type auid: str
|
|
160
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
161
|
+
:rtype: UrlOpenT
|
|
162
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
163
|
+
"""
|
|
164
|
+
raise NotImplementedError
|
|
165
|
+
|
|
166
|
+
@abstractmethod
|
|
167
|
+
def reload_config(self) -> UrlOpenT:
|
|
168
|
+
"""
|
|
169
|
+
Performs the DebugPanel servlet "Reload Config" operation on this node.
|
|
170
|
+
|
|
171
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
172
|
+
:rtype: UrlOpenT
|
|
173
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
174
|
+
"""
|
|
175
|
+
raise NotImplementedError
|
|
176
|
+
|
|
177
|
+
@abstractmethod
|
|
178
|
+
def validate_files(self, auid: str) -> UrlOpenT:
|
|
179
|
+
"""
|
|
180
|
+
Performs the DebugPanel servlet "Validate Files" operation on this node
|
|
181
|
+
for the given AUID.
|
|
182
|
+
|
|
183
|
+
:param auid: An AUID.
|
|
184
|
+
:type auid: str
|
|
185
|
+
:return: The result of ``urllib.request.urlopen``.
|
|
186
|
+
:rtype: UrlOpenT
|
|
187
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
188
|
+
"""
|
|
189
|
+
raise NotImplementedError
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class _DebugPanelClient1(_DebugPanelClientInterface):
|
|
193
|
+
"""
|
|
194
|
+
_DebugPanelAdapter implementation for LOCKSS 1.x.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
def __init__(self, client: DebugPanelClient) -> None:
|
|
198
|
+
super().__init__()
|
|
199
|
+
self._client: DebugPanelClient = client
|
|
200
|
+
self._basic: Optional[str] = None
|
|
201
|
+
|
|
202
|
+
def authenticate(self, u: str, p: str) -> _DebugPanelClient1:
|
|
203
|
+
self._basic: str = b64encode(f'{u}:{p}'.encode('utf-8')).decode('utf-8')
|
|
204
|
+
return self
|
|
205
|
+
|
|
206
|
+
def check_substance(self, auid: str) -> UrlOpenT:
|
|
207
|
+
return self._auid_action(auid, 'Check Substance')
|
|
208
|
+
|
|
209
|
+
def crawl(self, auid: str) -> UrlOpenT:
|
|
210
|
+
return self._auid_action(auid, 'Force Start Crawl')
|
|
211
|
+
|
|
212
|
+
def crawl_plugins(self) -> UrlOpenT:
|
|
213
|
+
return self._node_action('Crawl Plugins')
|
|
214
|
+
|
|
215
|
+
def deep_crawl(self, auid: str, depth: int = DEFAULT_DEPTH) -> UrlOpenT:
|
|
216
|
+
if depth < 1:
|
|
217
|
+
raise ValueError(f'depth must be a strictly positive integer, got {depth}')
|
|
218
|
+
return self._auid_action(auid, 'Force Deep Crawl', depth=depth)
|
|
219
|
+
|
|
220
|
+
def disable_indexing(self, auid: str) -> UrlOpenT:
|
|
221
|
+
return self._auid_action(auid, 'Disable Indexing')
|
|
222
|
+
|
|
223
|
+
def poll(self, auid: str) -> UrlOpenT:
|
|
224
|
+
return self._auid_action(auid, 'Start V3 Poll')
|
|
225
|
+
|
|
226
|
+
def reindex_metadata(self, auid: str) -> UrlOpenT:
|
|
227
|
+
return self._auid_action(auid, 'Force Reindex Metadata')
|
|
228
|
+
|
|
229
|
+
def reload_config(self) -> UrlOpenT:
|
|
230
|
+
return self._node_action('Reload Config')
|
|
231
|
+
|
|
232
|
+
def validate_files(self, auid: str) -> UrlOpenT:
|
|
233
|
+
return self._auid_action(auid, 'Validate Files')
|
|
234
|
+
|
|
235
|
+
def _auid_action(self, auid: str, action: str, **kwargs) -> UrlOpenT:
|
|
236
|
+
"""
|
|
237
|
+
Performs one AUID-centric action.
|
|
238
|
+
|
|
239
|
+
:param auid: An AUID.
|
|
240
|
+
:type auid: str
|
|
241
|
+
:param action: An AUID-oriented DebugPanel servlet action string, e.g.
|
|
242
|
+
``Force Deep Crawl``.
|
|
243
|
+
:type action: str
|
|
244
|
+
:param kwargs: Key-value pairs of additional query string arguments.
|
|
245
|
+
:type kwargs: dict[str, Any]
|
|
246
|
+
:return: The result of calling `urllib.request.urlopen`` on an appropriate
|
|
247
|
+
URL.
|
|
248
|
+
:rtype: RequestUrlOpenT
|
|
249
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
250
|
+
"""
|
|
251
|
+
action_encoded = action.replace(" ", "%20")
|
|
252
|
+
auid_encoded = auid.replace('%', '%25').replace('|', '%7C').replace('&', '%26').replace('~', '%7E')
|
|
253
|
+
req = self._make_request(f'action={action_encoded}&auid={auid_encoded}', **kwargs)
|
|
254
|
+
return urlopen(req)
|
|
255
|
+
|
|
256
|
+
def _make_request(self, query: str, **kwargs) -> Request:
|
|
257
|
+
"""
|
|
258
|
+
Constructs and authenticates an HTTP request.
|
|
259
|
+
|
|
260
|
+
:param query: A primary ampersand-separated query string, e.g.
|
|
261
|
+
``"action=MyAction&auid=MyAuid"``.
|
|
262
|
+
:type query: str
|
|
263
|
+
:param kwargs: Key-value pairs of additional query string arguments, e.g.
|
|
264
|
+
``(..., depth=99)`` to add ``"&depth=99"``.
|
|
265
|
+
:type kwargs: dict[str, Any]
|
|
266
|
+
:return: An authenticated ``Request`` instance (before
|
|
267
|
+
``urllib.request.urlopen`` is called).
|
|
268
|
+
:rtype: Request
|
|
269
|
+
"""
|
|
270
|
+
for key, val in kwargs.items():
|
|
271
|
+
query = f'{query}&{key}={val}'
|
|
272
|
+
url: str = f'{(ns := self._client.get_node_spec()).protocol.value}://{ns.host}:{ns.ui}/DebugPanel?{query}'
|
|
273
|
+
req: Request = Request(url)
|
|
274
|
+
req.add_header('Authorization', f'Basic {self._basic}')
|
|
275
|
+
return req
|
|
276
|
+
|
|
277
|
+
def _node_action(self, action: str, **kwargs) -> UrlOpenT:
|
|
278
|
+
"""
|
|
279
|
+
Performs one node-centric action.
|
|
280
|
+
|
|
281
|
+
:param action: A node-oriented DebugPanel servlet action string, e.g.
|
|
282
|
+
``Reload Config``.
|
|
283
|
+
:type action: str
|
|
284
|
+
:param kwargs: Key-value pairs of additional query string arguments, e.g.
|
|
285
|
+
``(..., depth=99)`` to add ``"&depth=99"``.
|
|
286
|
+
:type kwargs: dict[str, Any]
|
|
287
|
+
:return: The result of calling `urllib.request.urlopen`` on an appropriate
|
|
288
|
+
URL.
|
|
289
|
+
:rtype: RequestUrlOpenT
|
|
290
|
+
:raises Exception: Whatever ``urllib.request.urlopen`` might raise.
|
|
291
|
+
"""
|
|
292
|
+
action_encoded: str = action.replace(" ", "%20")
|
|
293
|
+
req: Request = self._make_request(f'action={action_encoded}', **kwargs)
|
|
294
|
+
return urlopen(req)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class _DebugPanelClient2(_DebugPanelClientInterface):
|
|
298
|
+
"""
|
|
299
|
+
_DebugPanelAdapter implementation for LOCKSS 2.x, which raises
|
|
300
|
+
``NotImplementedError`` for everything.
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
def authenticate(self, u: str, p: str) -> _DebugPanelClient2:
|
|
304
|
+
raise NotImplementedError
|
|
305
|
+
|
|
306
|
+
def check_substance(self, auid: str) -> UrlOpenT:
|
|
307
|
+
raise NotImplementedError
|
|
308
|
+
|
|
309
|
+
def crawl(self, auid: str) -> UrlOpenT:
|
|
310
|
+
raise NotImplementedError
|
|
311
|
+
|
|
312
|
+
def crawl_plugins(self) -> UrlOpenT:
|
|
313
|
+
raise NotImplementedError
|
|
314
|
+
|
|
315
|
+
def deep_crawl(self, auid: str, depth: int = DEFAULT_DEPTH) -> UrlOpenT:
|
|
316
|
+
raise NotImplementedError
|
|
317
|
+
|
|
318
|
+
def disable_indexing(self, auid: str) -> UrlOpenT:
|
|
319
|
+
raise NotImplementedError
|
|
320
|
+
|
|
321
|
+
def poll(self, auid: str) -> UrlOpenT:
|
|
322
|
+
raise NotImplementedError
|
|
323
|
+
|
|
324
|
+
def reindex_metadata(self, auid: str) -> UrlOpenT:
|
|
325
|
+
raise NotImplementedError
|
|
326
|
+
|
|
327
|
+
def reload_config(self) -> UrlOpenT:
|
|
328
|
+
raise NotImplementedError
|
|
329
|
+
|
|
330
|
+
def validate_files(self, auid: str) -> UrlOpenT:
|
|
331
|
+
raise NotImplementedError
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class DebugPanelClient(_DebugPanelClientInterface):
|
|
335
|
+
"""
|
|
336
|
+
A DebugPanel servlet client for either LOCKSS 1.x or 2.x.
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
def __init__(self, node_spec: NodeSpec):
|
|
340
|
+
self._node_spec: NodeSpec = node_spec
|
|
341
|
+
match typ := node_spec.type:
|
|
342
|
+
case NodeTypeEnum.V1.value:
|
|
343
|
+
self._impl: _DebugPanelClientInterface = _DebugPanelClient1(self)
|
|
344
|
+
case NodeTypeEnum.V2.value:
|
|
345
|
+
self._impl: _DebugPanelClientInterface = _DebugPanelClient2()
|
|
346
|
+
case _:
|
|
347
|
+
raise InternalError from ValueError(typ)
|
|
348
|
+
|
|
349
|
+
def authenticate(self, u: str, p: str) -> DebugPanelClient:
|
|
350
|
+
self._impl.authenticate(u, p)
|
|
351
|
+
return self
|
|
352
|
+
|
|
353
|
+
def check_substance(self, auid: str) -> UrlOpenT:
|
|
354
|
+
return self._impl.check_substance(auid)
|
|
355
|
+
|
|
356
|
+
def crawl(self, auid: str) -> UrlOpenT:
|
|
357
|
+
return self._impl.crawl(auid)
|
|
358
|
+
|
|
359
|
+
def crawl_plugins(self) -> UrlOpenT:
|
|
360
|
+
return self._impl.crawl_plugins()
|
|
361
|
+
|
|
362
|
+
def deep_crawl(self, auid: str, depth: int = DEFAULT_DEPTH) -> UrlOpenT:
|
|
363
|
+
return self._impl.deep_crawl(auid, depth=depth)
|
|
364
|
+
|
|
365
|
+
def disable_indexing(self, auid: str) -> UrlOpenT:
|
|
366
|
+
return self._impl.disable_indexing(auid)
|
|
367
|
+
|
|
368
|
+
def get_id(self) -> NodeIdentifier:
|
|
369
|
+
"""
|
|
370
|
+
Returns this client's node identifier, from the node spec.
|
|
371
|
+
|
|
372
|
+
:return: This client's node identifier.
|
|
373
|
+
:rtype: NodeIdentifier
|
|
374
|
+
"""
|
|
375
|
+
return self.get_node_spec().id
|
|
376
|
+
|
|
377
|
+
def get_node_spec(self) -> NodeSpec:
|
|
378
|
+
"""
|
|
379
|
+
Returns this client's node spec.
|
|
380
|
+
|
|
381
|
+
:return: This client's node spec.
|
|
382
|
+
:rtype: NodeSpec
|
|
383
|
+
"""
|
|
384
|
+
return self._node_spec.model_copy()
|
|
385
|
+
|
|
386
|
+
def poll(self, auid: str) -> UrlOpenT:
|
|
387
|
+
return self._impl.poll(auid)
|
|
388
|
+
|
|
389
|
+
def reindex_metadata(self, auid: str) -> UrlOpenT:
|
|
390
|
+
return self._impl.reindex_metadata(auid)
|
|
391
|
+
|
|
392
|
+
def reload_config(self) -> UrlOpenT:
|
|
393
|
+
return self._impl.reload_config()
|
|
394
|
+
|
|
395
|
+
def validate_files(self, auid: str) -> UrlOpenT:
|
|
396
|
+
return self._impl.validate_files(auid)
|