bmctools 0.1.0__py3-none-any.whl
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.
- bmctools/__init__.py +0 -0
- bmctools/cli/__init__.py +3 -0
- bmctools/cli/commands/__init__.py +1 -0
- bmctools/cli/commands/common.py +138 -0
- bmctools/cli/commands/ipmi.py +292 -0
- bmctools/cli/commands/racadm.py +266 -0
- bmctools/cli/commands/redfish.py +666 -0
- bmctools/cli/formatters/__init__.py +31 -0
- bmctools/cli/formatters/json.py +19 -0
- bmctools/cli/formatters/table.py +75 -0
- bmctools/cli/formatters/text.py +32 -0
- bmctools/cli/main.py +252 -0
- bmctools/cli/utils.py +331 -0
- bmctools/ipmi/__init__.py +0 -0
- bmctools/ipmi/ipmitool.py +131 -0
- bmctools/misc/__init__.py +0 -0
- bmctools/misc/utils.py +57 -0
- bmctools/racadm/__init__.py +0 -0
- bmctools/racadm/racadm.py +173 -0
- bmctools/redfish/__init__.py +0 -0
- bmctools/redfish/asusfish.py +491 -0
- bmctools/redfish/dellfish.py +812 -0
- bmctools/redfish/fishapi.py +174 -0
- bmctools/redfish/redfish.py +233 -0
- bmctools/redfish/smcfish.py +43 -0
- bmctools/sum/__init__.py +0 -0
- bmctools/sum/sum.py +0 -0
- bmctools-0.1.0.dist-info/METADATA +225 -0
- bmctools-0.1.0.dist-info/RECORD +33 -0
- bmctools-0.1.0.dist-info/WHEEL +5 -0
- bmctools-0.1.0.dist-info/entry_points.txt +2 -0
- bmctools-0.1.0.dist-info/licenses/LICENSE +674 -0
- bmctools-0.1.0.dist-info/top_level.txt +1 -0
bmctools/__init__.py
ADDED
|
File without changes
|
bmctools/cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CLI command handlers."""
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Common command utilities shared across all commands."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from bmctools.cli.formatters import format_output
|
|
5
|
+
from bmctools.cli.utils import (
|
|
6
|
+
handle_error,
|
|
7
|
+
get_exit_code,
|
|
8
|
+
print_verbose,
|
|
9
|
+
print_success
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def wrap_command(func, args):
|
|
14
|
+
"""Wrap command execution with common error handling.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
func: Command function to execute
|
|
18
|
+
args: Parsed arguments
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Exit code (0 for success, non-zero for error)
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
result = func(args)
|
|
25
|
+
if result is not None:
|
|
26
|
+
output = format_output(result, args.output)
|
|
27
|
+
print(output)
|
|
28
|
+
return 0
|
|
29
|
+
except Exception as e:
|
|
30
|
+
handle_error(e, args)
|
|
31
|
+
return get_exit_code(e)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def read_file_lines(file_path):
|
|
35
|
+
"""Read lines from a file.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
file_path: Path to file
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
List of lines (stripped of whitespace)
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
FileNotFoundError: If file doesn't exist
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
with open(file_path, 'r') as f:
|
|
48
|
+
lines = [line.strip() for line in f if line.strip()]
|
|
49
|
+
return lines
|
|
50
|
+
except FileNotFoundError:
|
|
51
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
52
|
+
except Exception as e:
|
|
53
|
+
raise ValueError(f"Failed to read file {file_path}: {e}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def parse_comma_list(value):
|
|
57
|
+
"""Parse comma-separated list.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
value: Comma-separated string
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
List of strings
|
|
64
|
+
"""
|
|
65
|
+
if not value:
|
|
66
|
+
return []
|
|
67
|
+
return [item.strip() for item in value.split(',') if item.strip()]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def confirm_action(prompt, force=False):
|
|
71
|
+
"""Prompt user for confirmation on destructive operations.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
prompt: Confirmation prompt message
|
|
75
|
+
force: If True, skip confirmation and return True
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
True if confirmed, False otherwise
|
|
79
|
+
"""
|
|
80
|
+
if force:
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
response = input(f"{prompt} [y/N]: ").strip().lower()
|
|
85
|
+
return response in ('y', 'yes')
|
|
86
|
+
except (KeyboardInterrupt, EOFError):
|
|
87
|
+
print() # New line after interrupt
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def show_progress(message, done=False):
|
|
92
|
+
"""Display progress indicator.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
message: Progress message
|
|
96
|
+
done: If True, show completion, else show in-progress
|
|
97
|
+
"""
|
|
98
|
+
if done:
|
|
99
|
+
print(f"✓ {message}", file=sys.stderr)
|
|
100
|
+
else:
|
|
101
|
+
print(f"⋯ {message}...", file=sys.stderr, end='', flush=True)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def validate_file_exists(file_path):
|
|
105
|
+
"""Validate that a file exists.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
file_path: Path to file
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
FileNotFoundError: If file doesn't exist
|
|
112
|
+
"""
|
|
113
|
+
import os
|
|
114
|
+
if not os.path.exists(file_path):
|
|
115
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
116
|
+
if not os.path.isfile(file_path):
|
|
117
|
+
raise ValueError(f"Not a file: {file_path}")
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def parse_key_value_pairs(pairs):
|
|
121
|
+
"""Parse key=value pairs from list of strings.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
pairs: List of "key=value" strings
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Dict of key-value pairs
|
|
128
|
+
|
|
129
|
+
Raises:
|
|
130
|
+
ValueError: If format is invalid
|
|
131
|
+
"""
|
|
132
|
+
result = {}
|
|
133
|
+
for pair in pairs:
|
|
134
|
+
if '=' not in pair:
|
|
135
|
+
raise ValueError(f"Invalid key=value format: {pair}")
|
|
136
|
+
key, value = pair.split('=', 1)
|
|
137
|
+
result[key.strip()] = value.strip()
|
|
138
|
+
return result
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""IPMI command handlers."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from bmctools.cli.utils import (
|
|
5
|
+
establish_ipmi_connection,
|
|
6
|
+
print_verbose,
|
|
7
|
+
)
|
|
8
|
+
from bmctools.cli.commands.common import wrap_command
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def setup_ipmi_commands(parser):
|
|
12
|
+
"""Setup IPMI subcommands.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
parser: IPMI subparser
|
|
16
|
+
"""
|
|
17
|
+
subparsers = parser.add_subparsers(dest='ipmi_group', help='IPMI operation group')
|
|
18
|
+
|
|
19
|
+
# Power management commands
|
|
20
|
+
power_parser = subparsers.add_parser('power', help='Power management')
|
|
21
|
+
setup_power_commands(power_parser)
|
|
22
|
+
|
|
23
|
+
# BMC management commands
|
|
24
|
+
bmc_parser = subparsers.add_parser('bmc', help='BMC management')
|
|
25
|
+
setup_bmc_commands(bmc_parser)
|
|
26
|
+
|
|
27
|
+
# SEL commands
|
|
28
|
+
sel_parser = subparsers.add_parser('sel', help='System Event Log')
|
|
29
|
+
setup_sel_commands(sel_parser)
|
|
30
|
+
|
|
31
|
+
# SOL commands
|
|
32
|
+
sol_parser = subparsers.add_parser('sol', help='Serial Over LAN')
|
|
33
|
+
setup_sol_commands(sol_parser)
|
|
34
|
+
|
|
35
|
+
# Raw command
|
|
36
|
+
raw_parser = subparsers.add_parser('raw', help='Execute raw IPMI command')
|
|
37
|
+
raw_parser.add_argument('command', help='Raw IPMI command')
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def setup_power_commands(parser):
|
|
41
|
+
"""Setup power management subcommands."""
|
|
42
|
+
subparsers = parser.add_subparsers(dest='power_action', help='Power action')
|
|
43
|
+
|
|
44
|
+
subparsers.add_parser('status', help='Get power status')
|
|
45
|
+
subparsers.add_parser('on', help='Power on system')
|
|
46
|
+
subparsers.add_parser('off', help='Power off system')
|
|
47
|
+
subparsers.add_parser('reset', help='Hard reset system')
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def setup_bmc_commands(parser):
|
|
51
|
+
"""Setup BMC management subcommands."""
|
|
52
|
+
subparsers = parser.add_subparsers(dest='bmc_action', help='BMC action')
|
|
53
|
+
|
|
54
|
+
subparsers.add_parser('reset-warm', help='Warm reset BMC')
|
|
55
|
+
subparsers.add_parser('reset-cold', help='Cold reset BMC')
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def setup_sel_commands(parser):
|
|
59
|
+
"""Setup SEL subcommands."""
|
|
60
|
+
subparsers = parser.add_subparsers(dest='sel_action', help='SEL action')
|
|
61
|
+
|
|
62
|
+
p = subparsers.add_parser('list', help='List system event log')
|
|
63
|
+
p.add_argument('--elist', action='store_true',
|
|
64
|
+
help='Use extended list format')
|
|
65
|
+
p.add_argument('--raw', action='store_true',
|
|
66
|
+
help='Show raw data')
|
|
67
|
+
p.add_argument('--age',
|
|
68
|
+
help='Filter by age (e.g., 7d, 24h)')
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def setup_sol_commands(parser):
|
|
72
|
+
"""Setup SOL subcommands."""
|
|
73
|
+
subparsers = parser.add_subparsers(dest='sol_action', help='SOL action')
|
|
74
|
+
|
|
75
|
+
subparsers.add_parser('deactivate', help='Deactivate Serial Over LAN')
|
|
76
|
+
|
|
77
|
+
p = subparsers.add_parser('looptest', help='Run SOL loopback test')
|
|
78
|
+
p.add_argument('--loops', type=int, default=10,
|
|
79
|
+
help='Number of loops (default: 10)')
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# Power Management Handlers
|
|
83
|
+
|
|
84
|
+
def handle_power_status(args):
|
|
85
|
+
"""Handle 'ipmi power status' command."""
|
|
86
|
+
ipmi = establish_ipmi_connection(args)
|
|
87
|
+
status = ipmi.power_status()
|
|
88
|
+
return {'power_status': status}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def handle_power_on(args):
|
|
92
|
+
"""Handle 'ipmi power on' command."""
|
|
93
|
+
ipmi = establish_ipmi_connection(args)
|
|
94
|
+
print_verbose("Powering on system...", args)
|
|
95
|
+
result = ipmi.power_on()
|
|
96
|
+
return {'message': 'Power on command sent', 'result': result}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def handle_power_off(args):
|
|
100
|
+
"""Handle 'ipmi power off' command."""
|
|
101
|
+
ipmi = establish_ipmi_connection(args)
|
|
102
|
+
print_verbose("Powering off system...", args)
|
|
103
|
+
result = ipmi.power_off()
|
|
104
|
+
return {'message': 'Power off command sent', 'result': result}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def handle_power_reset(args):
|
|
108
|
+
"""Handle 'ipmi power reset' command."""
|
|
109
|
+
ipmi = establish_ipmi_connection(args)
|
|
110
|
+
print_verbose("Resetting system...", args)
|
|
111
|
+
result = ipmi.power_reset()
|
|
112
|
+
return {'message': 'Power reset command sent', 'result': result}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# BMC Management Handlers
|
|
116
|
+
|
|
117
|
+
def handle_bmc_reset_warm(args):
|
|
118
|
+
"""Handle 'ipmi bmc reset-warm' command."""
|
|
119
|
+
ipmi = establish_ipmi_connection(args)
|
|
120
|
+
print_verbose("Performing warm reset of BMC...", args)
|
|
121
|
+
result = ipmi.bmc_reset_warm()
|
|
122
|
+
return {'message': 'BMC warm reset command sent', 'result': result}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def handle_bmc_reset_cold(args):
|
|
126
|
+
"""Handle 'ipmi bmc reset-cold' command."""
|
|
127
|
+
ipmi = establish_ipmi_connection(args)
|
|
128
|
+
print_verbose("Performing cold reset of BMC...", args)
|
|
129
|
+
result = ipmi.bmc_reset_cold()
|
|
130
|
+
return {'message': 'BMC cold reset command sent', 'result': result}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# SEL Handlers
|
|
134
|
+
|
|
135
|
+
def handle_sel_list(args):
|
|
136
|
+
"""Handle 'ipmi sel list' command."""
|
|
137
|
+
ipmi = establish_ipmi_connection(args)
|
|
138
|
+
|
|
139
|
+
elist = getattr(args, 'elist', False)
|
|
140
|
+
raw = getattr(args, 'raw', False)
|
|
141
|
+
age = getattr(args, 'age', None)
|
|
142
|
+
|
|
143
|
+
print_verbose(f"Fetching SEL (elist={elist}, raw={raw}, age={age})...", args)
|
|
144
|
+
|
|
145
|
+
result = ipmi.sel_list(elist=elist, raw=raw, age=age)
|
|
146
|
+
|
|
147
|
+
return {'sel_entries': result}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# SOL Handlers
|
|
151
|
+
|
|
152
|
+
def handle_sol_deactivate(args):
|
|
153
|
+
"""Handle 'ipmi sol deactivate' command."""
|
|
154
|
+
ipmi = establish_ipmi_connection(args)
|
|
155
|
+
print_verbose("Deactivating Serial Over LAN...", args)
|
|
156
|
+
result = ipmi.sol_deactivate()
|
|
157
|
+
return {'message': 'SOL deactivate command sent', 'result': result}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def handle_sol_looptest(args):
|
|
161
|
+
"""Handle 'ipmi sol looptest' command."""
|
|
162
|
+
ipmi = establish_ipmi_connection(args)
|
|
163
|
+
loops = getattr(args, 'loops', 10)
|
|
164
|
+
|
|
165
|
+
print_verbose(f"Running SOL loopback test ({loops} loops)...", args)
|
|
166
|
+
|
|
167
|
+
result = ipmi.sol_looptest(num_loops=loops)
|
|
168
|
+
|
|
169
|
+
return {'message': f'SOL loopback test completed', 'result': result}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# Raw Command Handler
|
|
173
|
+
|
|
174
|
+
def handle_raw(args):
|
|
175
|
+
"""Handle 'ipmi raw' command."""
|
|
176
|
+
ipmi = establish_ipmi_connection(args)
|
|
177
|
+
|
|
178
|
+
print_verbose(f"Executing raw IPMI command: {args.command}", args)
|
|
179
|
+
|
|
180
|
+
result = ipmi.ipmitool_command(args.command)
|
|
181
|
+
|
|
182
|
+
return {'command': args.command, 'result': result}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# Dispatch Functions
|
|
186
|
+
|
|
187
|
+
def dispatch(args):
|
|
188
|
+
"""Dispatch IPMI command to appropriate handler.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
args: Parsed arguments
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Exit code
|
|
195
|
+
"""
|
|
196
|
+
group = args.ipmi_group
|
|
197
|
+
|
|
198
|
+
if group == 'power':
|
|
199
|
+
return dispatch_power(args)
|
|
200
|
+
elif group == 'bmc':
|
|
201
|
+
return dispatch_bmc(args)
|
|
202
|
+
elif group == 'sel':
|
|
203
|
+
return dispatch_sel(args)
|
|
204
|
+
elif group == 'sol':
|
|
205
|
+
return dispatch_sol(args)
|
|
206
|
+
elif group == 'raw':
|
|
207
|
+
return wrap_command(handle_raw, args)
|
|
208
|
+
else:
|
|
209
|
+
print(f"Error: Unknown IPMI group: {group}", file=sys.stderr)
|
|
210
|
+
return 1
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def dispatch_power(args):
|
|
214
|
+
"""Dispatch power command."""
|
|
215
|
+
action = args.power_action
|
|
216
|
+
handlers = {
|
|
217
|
+
'status': handle_power_status,
|
|
218
|
+
'on': handle_power_on,
|
|
219
|
+
'off': handle_power_off,
|
|
220
|
+
'reset': handle_power_reset,
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if action in handlers:
|
|
224
|
+
return wrap_command(handlers[action], args)
|
|
225
|
+
else:
|
|
226
|
+
print(f"Error: Unknown power action: {action}", file=sys.stderr)
|
|
227
|
+
return 1
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def dispatch_bmc(args):
|
|
231
|
+
"""Dispatch BMC command."""
|
|
232
|
+
action = args.bmc_action
|
|
233
|
+
handlers = {
|
|
234
|
+
'reset-warm': handle_bmc_reset_warm,
|
|
235
|
+
'reset-cold': handle_bmc_reset_cold,
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if action in handlers:
|
|
239
|
+
return wrap_command(handlers[action], args)
|
|
240
|
+
else:
|
|
241
|
+
print(f"Error: Unknown BMC action: {action}", file=sys.stderr)
|
|
242
|
+
return 1
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def dispatch_sel(args):
|
|
246
|
+
"""Dispatch SEL command."""
|
|
247
|
+
action = args.sel_action
|
|
248
|
+
handlers = {
|
|
249
|
+
'list': handle_sel_list,
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if action in handlers:
|
|
253
|
+
return wrap_command(handlers[action], args)
|
|
254
|
+
else:
|
|
255
|
+
print(f"Error: Unknown SEL action: {action}", file=sys.stderr)
|
|
256
|
+
return 1
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def dispatch_sol(args):
|
|
260
|
+
"""Dispatch SOL command."""
|
|
261
|
+
action = args.sol_action
|
|
262
|
+
handlers = {
|
|
263
|
+
'deactivate': handle_sol_deactivate,
|
|
264
|
+
'looptest': handle_sol_looptest,
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if action in handlers:
|
|
268
|
+
return wrap_command(handlers[action], args)
|
|
269
|
+
else:
|
|
270
|
+
print(f"Error: Unknown SOL action: {action}", file=sys.stderr)
|
|
271
|
+
return 1
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def handle_alias(args, target):
|
|
275
|
+
"""Handle aliased commands.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
args: Parsed arguments
|
|
279
|
+
target: Alias target identifier
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Exit code
|
|
283
|
+
"""
|
|
284
|
+
if target == 'ipmi_power_on':
|
|
285
|
+
return wrap_command(handle_power_on, args)
|
|
286
|
+
elif target == 'ipmi_power_off':
|
|
287
|
+
return wrap_command(handle_power_off, args)
|
|
288
|
+
elif target == 'ipmi_power_status':
|
|
289
|
+
return wrap_command(handle_power_status, args)
|
|
290
|
+
else:
|
|
291
|
+
print(f"Error: Unknown IPMI alias: {target}", file=sys.stderr)
|
|
292
|
+
return 1
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""RACADM command handlers."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from bmctools.cli.utils import (
|
|
5
|
+
establish_racadm_connection,
|
|
6
|
+
print_verbose,
|
|
7
|
+
)
|
|
8
|
+
from bmctools.cli.commands.common import wrap_command
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def setup_racadm_commands(parser):
|
|
12
|
+
"""Setup RACADM subcommands.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
parser: RACADM subparser
|
|
16
|
+
"""
|
|
17
|
+
subparsers = parser.add_subparsers(dest='racadm_group', help='RACADM operation group')
|
|
18
|
+
|
|
19
|
+
# Get configuration
|
|
20
|
+
p = subparsers.add_parser('get', help='Get configuration')
|
|
21
|
+
p.add_argument('endpoint', help='Configuration endpoint')
|
|
22
|
+
p.add_argument('--format', action='store_true',
|
|
23
|
+
help='Use formatted output')
|
|
24
|
+
|
|
25
|
+
# Set configuration
|
|
26
|
+
p = subparsers.add_parser('set', help='Set configuration')
|
|
27
|
+
p.add_argument('endpoint', help='Configuration endpoint')
|
|
28
|
+
p.add_argument('--args',
|
|
29
|
+
help='Comma-separated arguments')
|
|
30
|
+
|
|
31
|
+
# Storage commands
|
|
32
|
+
storage_parser = subparsers.add_parser('storage', help='Storage management')
|
|
33
|
+
setup_storage_commands(storage_parser)
|
|
34
|
+
|
|
35
|
+
# Job queue commands
|
|
36
|
+
job_parser = subparsers.add_parser('job', help='Job queue management')
|
|
37
|
+
setup_job_commands(job_parser)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def setup_storage_commands(parser):
|
|
41
|
+
"""Setup storage subcommands."""
|
|
42
|
+
subparsers = parser.add_subparsers(dest='storage_action', help='Storage action')
|
|
43
|
+
|
|
44
|
+
p = subparsers.add_parser('get', help='Get storage configuration')
|
|
45
|
+
p.add_argument('endpoint', help='Storage endpoint')
|
|
46
|
+
|
|
47
|
+
p = subparsers.add_parser('check-vdisk', help='Check virtual disks')
|
|
48
|
+
p.add_argument('--format', action='store_true',
|
|
49
|
+
help='Use formatted output')
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def setup_job_commands(parser):
|
|
53
|
+
"""Setup job queue subcommands."""
|
|
54
|
+
subparsers = parser.add_subparsers(dest='job_action', help='Job action')
|
|
55
|
+
|
|
56
|
+
p = subparsers.add_parser('view', help='View job details')
|
|
57
|
+
p.add_argument('-j', '--job-id', required=True,
|
|
58
|
+
help='Job ID')
|
|
59
|
+
|
|
60
|
+
p = subparsers.add_parser('status', help='Get job status')
|
|
61
|
+
p.add_argument('-j', '--job-id', required=True,
|
|
62
|
+
help='Job ID')
|
|
63
|
+
|
|
64
|
+
p = subparsers.add_parser('wait', help='Wait for job completion')
|
|
65
|
+
p.add_argument('-j', '--job-id', required=True,
|
|
66
|
+
help='Job ID')
|
|
67
|
+
p.add_argument('--timeout', type=int, default=300,
|
|
68
|
+
help='Timeout in seconds (default: 300)')
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# Configuration Handlers
|
|
72
|
+
|
|
73
|
+
def handle_get(args):
|
|
74
|
+
"""Handle 'racadm get' command."""
|
|
75
|
+
racadm = establish_racadm_connection(args)
|
|
76
|
+
|
|
77
|
+
use_format = getattr(args, 'format', False)
|
|
78
|
+
|
|
79
|
+
print_verbose(f"Getting config: {args.endpoint} (format={use_format})", args)
|
|
80
|
+
|
|
81
|
+
result = racadm.get(
|
|
82
|
+
endpoint=args.endpoint,
|
|
83
|
+
arguments=None,
|
|
84
|
+
format=use_format
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
'endpoint': args.endpoint,
|
|
89
|
+
'result': result
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def handle_set(args):
|
|
94
|
+
"""Handle 'racadm set' command."""
|
|
95
|
+
racadm = establish_racadm_connection(args)
|
|
96
|
+
|
|
97
|
+
# Parse arguments
|
|
98
|
+
arguments = None
|
|
99
|
+
if args.args:
|
|
100
|
+
arguments = [arg.strip() for arg in args.args.split(',')]
|
|
101
|
+
|
|
102
|
+
print_verbose(f"Setting config: {args.endpoint} (args={arguments})", args)
|
|
103
|
+
|
|
104
|
+
result = racadm.set(
|
|
105
|
+
endpoint=args.endpoint,
|
|
106
|
+
arguments=arguments
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
'endpoint': args.endpoint,
|
|
111
|
+
'arguments': arguments,
|
|
112
|
+
'result': result
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# Storage Handlers
|
|
117
|
+
|
|
118
|
+
def handle_storage_get(args):
|
|
119
|
+
"""Handle 'racadm storage get' command."""
|
|
120
|
+
racadm = establish_racadm_connection(args)
|
|
121
|
+
|
|
122
|
+
print_verbose(f"Getting storage config: {args.endpoint}", args)
|
|
123
|
+
|
|
124
|
+
result = racadm.storage_get(
|
|
125
|
+
endpoint=args.endpoint,
|
|
126
|
+
arguments=None
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
'endpoint': args.endpoint,
|
|
131
|
+
'result': result
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def handle_storage_check_vdisk(args):
|
|
136
|
+
"""Handle 'racadm storage check-vdisk' command."""
|
|
137
|
+
racadm = establish_racadm_connection(args)
|
|
138
|
+
|
|
139
|
+
use_format = getattr(args, 'format', False)
|
|
140
|
+
|
|
141
|
+
print_verbose(f"Checking virtual disks (format={use_format})", args)
|
|
142
|
+
|
|
143
|
+
result = racadm.check_vdisk(format=use_format)
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
'result': result
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# Job Queue Handlers
|
|
151
|
+
|
|
152
|
+
def handle_job_view(args):
|
|
153
|
+
"""Handle 'racadm job view' command."""
|
|
154
|
+
racadm = establish_racadm_connection(args)
|
|
155
|
+
|
|
156
|
+
print_verbose(f"Viewing job: {args.job_id}", args)
|
|
157
|
+
|
|
158
|
+
result = racadm.jobqueue_view(job=args.job_id)
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
'job_id': args.job_id,
|
|
162
|
+
'details': result
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def handle_job_status(args):
|
|
167
|
+
"""Handle 'racadm job status' command."""
|
|
168
|
+
racadm = establish_racadm_connection(args)
|
|
169
|
+
|
|
170
|
+
print_verbose(f"Getting job status: {args.job_id}", args)
|
|
171
|
+
|
|
172
|
+
result = racadm.jobqueue_status(job=args.job_id)
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
'job_id': args.job_id,
|
|
176
|
+
'status': result
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def handle_job_wait(args):
|
|
181
|
+
"""Handle 'racadm job wait' command."""
|
|
182
|
+
racadm = establish_racadm_connection(args)
|
|
183
|
+
|
|
184
|
+
timeout = getattr(args, 'timeout', 300)
|
|
185
|
+
|
|
186
|
+
print_verbose(f"Waiting for job completion: {args.job_id} (timeout={timeout}s)", args)
|
|
187
|
+
|
|
188
|
+
result = racadm.jobqueue_wait(job=args.job_id)
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
'job_id': args.job_id,
|
|
192
|
+
'completed': True,
|
|
193
|
+
'result': result
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# Dispatch Functions
|
|
198
|
+
|
|
199
|
+
def dispatch(args):
|
|
200
|
+
"""Dispatch RACADM command to appropriate handler.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
args: Parsed arguments
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Exit code
|
|
207
|
+
"""
|
|
208
|
+
group = args.racadm_group
|
|
209
|
+
|
|
210
|
+
if group == 'get':
|
|
211
|
+
return wrap_command(handle_get, args)
|
|
212
|
+
elif group == 'set':
|
|
213
|
+
return wrap_command(handle_set, args)
|
|
214
|
+
elif group == 'storage':
|
|
215
|
+
return dispatch_storage(args)
|
|
216
|
+
elif group == 'job':
|
|
217
|
+
return dispatch_job(args)
|
|
218
|
+
else:
|
|
219
|
+
print(f"Error: Unknown RACADM group: {group}", file=sys.stderr)
|
|
220
|
+
return 1
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def dispatch_storage(args):
|
|
224
|
+
"""Dispatch storage command."""
|
|
225
|
+
action = args.storage_action
|
|
226
|
+
handlers = {
|
|
227
|
+
'get': handle_storage_get,
|
|
228
|
+
'check-vdisk': handle_storage_check_vdisk,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if action in handlers:
|
|
232
|
+
return wrap_command(handlers[action], args)
|
|
233
|
+
else:
|
|
234
|
+
print(f"Error: Unknown storage action: {action}", file=sys.stderr)
|
|
235
|
+
return 1
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def dispatch_job(args):
|
|
239
|
+
"""Dispatch job queue command."""
|
|
240
|
+
action = args.job_action
|
|
241
|
+
handlers = {
|
|
242
|
+
'view': handle_job_view,
|
|
243
|
+
'status': handle_job_status,
|
|
244
|
+
'wait': handle_job_wait,
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if action in handlers:
|
|
248
|
+
return wrap_command(handlers[action], args)
|
|
249
|
+
else:
|
|
250
|
+
print(f"Error: Unknown job action: {action}", file=sys.stderr)
|
|
251
|
+
return 1
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def handle_alias(args, target):
|
|
255
|
+
"""Handle aliased commands.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
args: Parsed arguments
|
|
259
|
+
target: Alias target identifier
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Exit code
|
|
263
|
+
"""
|
|
264
|
+
# No RACADM aliases currently defined
|
|
265
|
+
print(f"Error: Unknown RACADM alias: {target}", file=sys.stderr)
|
|
266
|
+
return 1
|