wnm 0.0.12__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.
Potentially problematic release.
This version of wnm might be problematic. Click here for more details.
- wnm/__init__.py +3 -0
- wnm/__main__.py +236 -0
- wnm/actions.py +45 -0
- wnm/common.py +23 -0
- wnm/config.py +673 -0
- wnm/decision_engine.py +388 -0
- wnm/executor.py +1299 -0
- wnm/firewall/__init__.py +13 -0
- wnm/firewall/base.py +71 -0
- wnm/firewall/factory.py +95 -0
- wnm/firewall/null_firewall.py +71 -0
- wnm/firewall/ufw_manager.py +118 -0
- wnm/migration.py +42 -0
- wnm/models.py +459 -0
- wnm/process_managers/__init__.py +23 -0
- wnm/process_managers/base.py +203 -0
- wnm/process_managers/docker_manager.py +371 -0
- wnm/process_managers/factory.py +83 -0
- wnm/process_managers/launchd_manager.py +592 -0
- wnm/process_managers/setsid_manager.py +340 -0
- wnm/process_managers/systemd_manager.py +529 -0
- wnm/reports.py +286 -0
- wnm/utils.py +407 -0
- wnm/wallets.py +177 -0
- wnm-0.0.12.dist-info/METADATA +367 -0
- wnm-0.0.12.dist-info/RECORD +29 -0
- wnm-0.0.12.dist-info/WHEEL +5 -0
- wnm-0.0.12.dist-info/entry_points.txt +2 -0
- wnm-0.0.12.dist-info/top_level.txt +1 -0
wnm/config.py
ADDED
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import configargparse
|
|
10
|
+
from dotenv import load_dotenv
|
|
11
|
+
from sqlalchemy import create_engine, delete, insert, select, text, update
|
|
12
|
+
from sqlalchemy.orm import scoped_session, sessionmaker
|
|
13
|
+
|
|
14
|
+
from wnm.common import (
|
|
15
|
+
DEAD,
|
|
16
|
+
DEFAULT_CRISIS_BYTES,
|
|
17
|
+
DISABLED,
|
|
18
|
+
DONATE,
|
|
19
|
+
MIGRATING,
|
|
20
|
+
QUEEN,
|
|
21
|
+
REMOVING,
|
|
22
|
+
RESTARTING,
|
|
23
|
+
RUNNING,
|
|
24
|
+
STOPPED,
|
|
25
|
+
UPGRADING,
|
|
26
|
+
)
|
|
27
|
+
from wnm.models import Base, Machine, Node
|
|
28
|
+
from wnm.wallets import validate_rewards_address
|
|
29
|
+
|
|
30
|
+
logging.getLogger("sqlalchemy.engine.Engine").disabled = True
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ============================================================================
|
|
34
|
+
# Platform Detection and Path Constants
|
|
35
|
+
# ============================================================================
|
|
36
|
+
|
|
37
|
+
PLATFORM = platform.system() # 'Linux', 'Darwin', 'Windows'
|
|
38
|
+
|
|
39
|
+
# Determine if running as root on Linux
|
|
40
|
+
IS_ROOT = PLATFORM == "Linux" and os.geteuid() == 0
|
|
41
|
+
|
|
42
|
+
# Platform-specific base directories
|
|
43
|
+
if PLATFORM == "Darwin":
|
|
44
|
+
# macOS: Use standard macOS application directories
|
|
45
|
+
BASE_DIR = os.path.expanduser("~/Library/Application Support/autonomi")
|
|
46
|
+
NODE_STORAGE = os.path.expanduser("~/Library/Application Support/autonomi/node")
|
|
47
|
+
LOG_DIR = os.path.expanduser("~/Library/Logs/autonomi")
|
|
48
|
+
BOOTSTRAP_CACHE_DIR = os.path.expanduser("~/Library/Caches/autonomi/bootstrap-cache")
|
|
49
|
+
elif PLATFORM == "Linux":
|
|
50
|
+
if IS_ROOT:
|
|
51
|
+
# Linux root: Use legacy /var/antctl paths for backwards compatibility
|
|
52
|
+
BASE_DIR = "/var/antctl"
|
|
53
|
+
NODE_STORAGE = "/var/antctl/services"
|
|
54
|
+
LOG_DIR = "/var/log/antnode"
|
|
55
|
+
BOOTSTRAP_CACHE_DIR = "/var/antctl/bootstrap-cache"
|
|
56
|
+
else:
|
|
57
|
+
# Linux user: Use XDG Base Directory specification
|
|
58
|
+
BASE_DIR = os.path.expanduser("~/.local/share/autonomi")
|
|
59
|
+
NODE_STORAGE = os.path.expanduser("~/.local/share/autonomi/node")
|
|
60
|
+
LOG_DIR = os.path.expanduser("~/.local/share/autonomi/logs")
|
|
61
|
+
BOOTSTRAP_CACHE_DIR = os.path.expanduser("~/.local/share/autonomi/bootstrap-cache")
|
|
62
|
+
else:
|
|
63
|
+
# Windows or other platforms
|
|
64
|
+
BASE_DIR = os.path.expanduser("~/autonomi")
|
|
65
|
+
NODE_STORAGE = os.path.expanduser("~/autonomi/node")
|
|
66
|
+
LOG_DIR = os.path.expanduser("~/autonomi/logs")
|
|
67
|
+
BOOTSTRAP_CACHE_DIR = os.path.expanduser("~/autonomi/bootstrap-cache")
|
|
68
|
+
|
|
69
|
+
# Derived paths
|
|
70
|
+
LOCK_FILE = os.path.join(BASE_DIR, "wnm_active")
|
|
71
|
+
DEFAULT_DB_PATH = f"sqlite:///{os.path.join(BASE_DIR, 'colony.db')}"
|
|
72
|
+
|
|
73
|
+
# Create directories if they don't exist (except in test mode)
|
|
74
|
+
if not os.getenv("WNM_TEST_MODE"):
|
|
75
|
+
os.makedirs(BASE_DIR, exist_ok=True)
|
|
76
|
+
os.makedirs(NODE_STORAGE, exist_ok=True)
|
|
77
|
+
os.makedirs(LOG_DIR, exist_ok=True)
|
|
78
|
+
os.makedirs(BOOTSTRAP_CACHE_DIR, exist_ok=True)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Config file parser
|
|
82
|
+
# This is a simple wrapper around configargparse that reads the config file from the default locations
|
|
83
|
+
# and allows for command line overrides. It also sets up the logging level and database path
|
|
84
|
+
def load_config():
|
|
85
|
+
c = configargparse.ArgParser(
|
|
86
|
+
default_config_files=["~/.local/share/wnm/config", "~/wnm/config"],
|
|
87
|
+
description="wnm - Weave Node Manager",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
c.add("-c", "--config", is_config_file=True, help="config file path")
|
|
91
|
+
c.add("-v", help="verbose", action="store_true")
|
|
92
|
+
c.add(
|
|
93
|
+
"--dbpath",
|
|
94
|
+
env_var="DBPATH",
|
|
95
|
+
help="Path to the database",
|
|
96
|
+
default="sqlite:///colony.db",
|
|
97
|
+
)
|
|
98
|
+
c.add("--loglevel", env_var="LOGLEVEL", help="Log level")
|
|
99
|
+
c.add(
|
|
100
|
+
"--dry_run", env_var="DRY_RUN", help="Do not save changes", action="store_true"
|
|
101
|
+
)
|
|
102
|
+
c.add("--init", help="Initialize a cluster", action="store_true")
|
|
103
|
+
c.add("--migrate_anm", help="Migrate a cluster from anm", action="store_true")
|
|
104
|
+
c.add("--teardown", help="Remove a cluster", action="store_true")
|
|
105
|
+
c.add("--confirm", help="Confirm teardown without ui", action="store_true")
|
|
106
|
+
c.add("--node_cap", env_var="NODE_CAP", help="Node Capacity")
|
|
107
|
+
c.add("--cpu_less_than", env_var="CPU_LESS_THAN", help="CPU Add Threshold")
|
|
108
|
+
c.add("--cpu_remove", env_var="CPU_REMOVE", help="CPU Remove Threshold")
|
|
109
|
+
c.add("--mem_less_than", env_var="MEM_LESS_THAN", help="Memory Add Threshold")
|
|
110
|
+
c.add("--mem_remove", env_var="MEM_REMOVE", help="Memory Remove Threshold")
|
|
111
|
+
c.add("--hd_less_than", env_var="HD_LESS_THAN", help="Hard Drive Add Threshold")
|
|
112
|
+
c.add("--hd_remove", env_var="HD_REMOVE", help="Hard Drive Remove Threshold")
|
|
113
|
+
c.add("--delay_start", env_var="DELAY_START", help="Delay Start Timer")
|
|
114
|
+
c.add("--delay_restart", env_var="DELAY_RESTART", help="Delay Restart Timer")
|
|
115
|
+
c.add("--delay_upgrade", env_var="DELAY_UPGRADE", help="Delay Upgrade Timer")
|
|
116
|
+
c.add("--delay_remove", env_var="DELAY_REMOVE", help="Delay Remove Timer")
|
|
117
|
+
c.add("--node_storage", env_var="NODE_STORAGE", help="Node Storage Path")
|
|
118
|
+
c.add("--rewards_address", env_var="REWARDS_ADDRESS", help="Rewards Address")
|
|
119
|
+
c.add("--donate_address", env_var="DONATE_ADDRESS", help="Donate Address")
|
|
120
|
+
c.add(
|
|
121
|
+
"--max_load_average_allowed",
|
|
122
|
+
env_var="MAX_LOAD_AVERAGE_ALLOWED",
|
|
123
|
+
help="Max Load Average Allowed Remove Threshold",
|
|
124
|
+
)
|
|
125
|
+
c.add(
|
|
126
|
+
"--desired_load_average",
|
|
127
|
+
env_var="DESIRED_LOAD_AVERAGE",
|
|
128
|
+
help="Desired Load Average Add Threshold",
|
|
129
|
+
)
|
|
130
|
+
c.add(
|
|
131
|
+
"--port_start", env_var="PORT_START", help="Range to begin Node port assignment"
|
|
132
|
+
) # Only allowed during init
|
|
133
|
+
c.add(
|
|
134
|
+
"--metrics_port_start",
|
|
135
|
+
env_var="METRICS_PORT_START",
|
|
136
|
+
help="Range to begin Metrics port assignment",
|
|
137
|
+
) # Only allowed during init
|
|
138
|
+
c.add(
|
|
139
|
+
"--hdio_read_less_than",
|
|
140
|
+
env_var="HDIO_READ_LESS_THAN",
|
|
141
|
+
help="Hard Drive IO Read Add Threshold",
|
|
142
|
+
)
|
|
143
|
+
c.add(
|
|
144
|
+
"--hdio_read_remove",
|
|
145
|
+
env_var="HDIO_READ_REMOVE",
|
|
146
|
+
help="Hard Drive IO Read Remove Threshold",
|
|
147
|
+
)
|
|
148
|
+
c.add(
|
|
149
|
+
"--hdio_write_less_than",
|
|
150
|
+
env_var="HDIO_WRITE_LESS_THAN",
|
|
151
|
+
help="Hard Drive IO Write Add Threshold",
|
|
152
|
+
)
|
|
153
|
+
c.add(
|
|
154
|
+
"--hdio_write_remove",
|
|
155
|
+
env_var="HDIO_WRITE_REMOVE",
|
|
156
|
+
help="Hard Drive IO Write Remove Threshold",
|
|
157
|
+
)
|
|
158
|
+
c.add(
|
|
159
|
+
"--netio_read_less_than",
|
|
160
|
+
env_var="NETIO_READ_LESS_THAN",
|
|
161
|
+
help="Network IO Read Add Threshold",
|
|
162
|
+
)
|
|
163
|
+
c.add(
|
|
164
|
+
"--netio_read_remove",
|
|
165
|
+
env_var="NETIO_READ_REMOVE",
|
|
166
|
+
help="Network IO Read Remove Threshold",
|
|
167
|
+
)
|
|
168
|
+
c.add(
|
|
169
|
+
"--netio_write_less_than",
|
|
170
|
+
env_var="NETIO_WRITE_LESS_THAN",
|
|
171
|
+
help="Network IO Write Add Threshold",
|
|
172
|
+
)
|
|
173
|
+
c.add(
|
|
174
|
+
"--netio_write_remove",
|
|
175
|
+
env_var="NETIO_WRITE_REMOVE",
|
|
176
|
+
help="Network IO Write Remove Threshold",
|
|
177
|
+
)
|
|
178
|
+
c.add("--crisis_bytes", env_var="CRISIS_BYTES", help="Crisis Bytes Threshold")
|
|
179
|
+
c.add("--last_stopped_at", env_var="LAST_STOPPED_AT", help="Last Stopped Timestamp")
|
|
180
|
+
c.add("--host", env_var="HOST", help="Hostname")
|
|
181
|
+
c.add(
|
|
182
|
+
"--environment", env_var="ENVIRONMENT", help="Environment variables for antnode"
|
|
183
|
+
)
|
|
184
|
+
c.add(
|
|
185
|
+
"--start_args",
|
|
186
|
+
env_var="START_ARGS",
|
|
187
|
+
help="Arguments to pass to antnode",
|
|
188
|
+
)
|
|
189
|
+
c.add(
|
|
190
|
+
"--force_action",
|
|
191
|
+
env_var="FORCE_ACTION",
|
|
192
|
+
help="Force an action: add, remove, upgrade, start, stop, disable, teardown, survey",
|
|
193
|
+
choices=["add", "remove", "upgrade", "start", "stop", "disable", "teardown", "survey"],
|
|
194
|
+
)
|
|
195
|
+
c.add(
|
|
196
|
+
"--service_name",
|
|
197
|
+
env_var="SERVICE_NAME",
|
|
198
|
+
help="Node name for targeted operations or comma-separated list for reports and survey (e.g., antnode0001,antnode0003)",
|
|
199
|
+
)
|
|
200
|
+
c.add(
|
|
201
|
+
"--report",
|
|
202
|
+
env_var="REPORT",
|
|
203
|
+
help="Generate a report: node-status, node-status-details",
|
|
204
|
+
choices=["node-status", "node-status-details"],
|
|
205
|
+
)
|
|
206
|
+
c.add(
|
|
207
|
+
"--report_format",
|
|
208
|
+
env_var="REPORT_FORMAT",
|
|
209
|
+
help="Report output format: text or json (default: text)",
|
|
210
|
+
choices=["text", "json"],
|
|
211
|
+
default="text",
|
|
212
|
+
)
|
|
213
|
+
c.add(
|
|
214
|
+
"--count",
|
|
215
|
+
env_var="COUNT",
|
|
216
|
+
help="Number of nodes to affect when using --force_action (default: 1). Works with add, remove, start, stop, upgrade actions.",
|
|
217
|
+
type=int,
|
|
218
|
+
default=1,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
options = c.parse_known_args()[0] or []
|
|
222
|
+
# Return the first result from parse_known_args, ignore unknown options
|
|
223
|
+
return options
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# Merge the changes from the config file with the database
|
|
227
|
+
def merge_config_changes(options, machine_config):
|
|
228
|
+
# Collect updates
|
|
229
|
+
cfg = {}
|
|
230
|
+
if options.node_cap and int(options.node_cap) != machine_config.node_cap:
|
|
231
|
+
cfg["node_cap"] = int(options.node_cap)
|
|
232
|
+
if options.cpu_less_than and int(options.cpu_less_than) != machine_config.cpu_less_than:
|
|
233
|
+
cfg["cpu_less_than"] = int(options.cpu_less_than)
|
|
234
|
+
if options.cpu_remove and int(options.cpu_remove) != machine_config.cpu_remove:
|
|
235
|
+
cfg["cpu_remove"] = int(options.cpu_remove)
|
|
236
|
+
if options.mem_less_than and int(options.mem_less_than) != machine_config.mem_less_than:
|
|
237
|
+
cfg["mem_less_than"] = int(options.mem_less_than)
|
|
238
|
+
if options.mem_remove and int(options.mem_remove) != machine_config.mem_remove:
|
|
239
|
+
cfg["mem_remove"] = int(options.mem_remove)
|
|
240
|
+
if options.hd_less_than and int(options.hd_less_than) != machine_config.hd_less_than:
|
|
241
|
+
cfg["hd_less_than"] = int(options.hd_less_than)
|
|
242
|
+
if options.hd_remove and int(options.hd_remove) != machine_config.hd_remove:
|
|
243
|
+
cfg["hd_remove"] = int(options.hd_remove)
|
|
244
|
+
if options.delay_start and int(options.delay_start) != machine_config.delay_start:
|
|
245
|
+
cfg["delay_start"] = int(options.delay_start)
|
|
246
|
+
if (
|
|
247
|
+
options.delay_restart
|
|
248
|
+
and int(options.delay_restart) != machine_config.delay_restart
|
|
249
|
+
):
|
|
250
|
+
cfg["delay_restart"] = int(options.delay_restart)
|
|
251
|
+
if (
|
|
252
|
+
options.delay_upgrade
|
|
253
|
+
and int(options.delay_upgrade) != machine_config.delay_upgrade
|
|
254
|
+
):
|
|
255
|
+
cfg["delay_upgrade"] = int(options.delay_upgrade)
|
|
256
|
+
if options.delay_remove and int(options.delay_remove) != machine_config.delay_remove:
|
|
257
|
+
cfg["delay_remove"] = int(options.delay_remove)
|
|
258
|
+
if options.node_storage and options.node_storage != machine_config.node_storage:
|
|
259
|
+
cfg["node_storage"] = options.node_storage
|
|
260
|
+
if (
|
|
261
|
+
options.rewards_address
|
|
262
|
+
and options.rewards_address != machine_config.rewards_address
|
|
263
|
+
):
|
|
264
|
+
# Validate the new rewards_address
|
|
265
|
+
is_valid, error_msg = validate_rewards_address(
|
|
266
|
+
options.rewards_address, machine_config.donate_address
|
|
267
|
+
)
|
|
268
|
+
if not is_valid:
|
|
269
|
+
logging.error(f"Invalid rewards_address: {error_msg}")
|
|
270
|
+
sys.exit(1)
|
|
271
|
+
cfg["rewards_address"] = options.rewards_address
|
|
272
|
+
if options.donate_address and options.donate_address != machine_config.donate_address:
|
|
273
|
+
cfg["donate_address"] = options.donate_address
|
|
274
|
+
if (
|
|
275
|
+
options.max_load_average_allowed
|
|
276
|
+
and float(options.max_load_average_allowed) != machine_config.max_load_average_allowed
|
|
277
|
+
):
|
|
278
|
+
cfg["max_load_average_allowed"] = float(options.max_load_average_allowed)
|
|
279
|
+
if (
|
|
280
|
+
options.desired_load_average
|
|
281
|
+
and float(options.desired_load_average) != machine_config.desired_load_average
|
|
282
|
+
):
|
|
283
|
+
cfg["desired_load_average"] = float(options.desired_load_average)
|
|
284
|
+
if options.port_start and int(options.port_start) != machine_config.port_start:
|
|
285
|
+
cfg["port_start"] = int(options.port_start)
|
|
286
|
+
if (
|
|
287
|
+
options.hdio_read_less_than
|
|
288
|
+
and int(options.hdio_read_less_than) != machine_config.hdio_read_less_than
|
|
289
|
+
):
|
|
290
|
+
cfg["hdio_read_less_than"] = int(options.hdio_read_less_than)
|
|
291
|
+
if (
|
|
292
|
+
options.hdio_read_remove
|
|
293
|
+
and int(options.hdio_read_remove) != machine_config.hdio_read_remove
|
|
294
|
+
):
|
|
295
|
+
cfg["hdio_read_remove"] = int(options.hdio_read_remove)
|
|
296
|
+
if (
|
|
297
|
+
options.hdio_write_less_than
|
|
298
|
+
and int(options.hdio_write_less_than) != machine_config.hdio_write_less_than
|
|
299
|
+
):
|
|
300
|
+
cfg["hdio_write_less_than"] = int(options.hdio_write_less_than)
|
|
301
|
+
if (
|
|
302
|
+
options.hdio_write_remove
|
|
303
|
+
and int(options.hdio_write_remove) != machine_config.hdio_write_remove
|
|
304
|
+
):
|
|
305
|
+
cfg["hdio_write_remove"] = int(options.hdio_write_remove)
|
|
306
|
+
if (
|
|
307
|
+
options.netio_read_less_than
|
|
308
|
+
and int(options.netio_read_less_than) != machine_config.netio_read_less_than
|
|
309
|
+
):
|
|
310
|
+
cfg["netio_read_less_than"] = int(options.netio_read_less_than)
|
|
311
|
+
if (
|
|
312
|
+
options.netio_read_remove
|
|
313
|
+
and int(options.netio_read_remove) != machine_config.netio_read_remove
|
|
314
|
+
):
|
|
315
|
+
cfg["netio_read_remove"] = int(options.netio_read_remove)
|
|
316
|
+
if (
|
|
317
|
+
options.netio_write_less_than
|
|
318
|
+
and int(options.netio_write_less_than) != machine_config.netio_write_less_than
|
|
319
|
+
):
|
|
320
|
+
cfg["netio_write_less_than"] = int(options.netio_write_less_than)
|
|
321
|
+
if (
|
|
322
|
+
options.netio_write_remove
|
|
323
|
+
and int(options.netio_write_remove) != machine_config.netio_write_remove
|
|
324
|
+
):
|
|
325
|
+
cfg["netio_write_remove"] = int(options.netio_write_remove)
|
|
326
|
+
if options.crisis_bytes and int(options.crisis_bytes) != machine_config.crisis_bytes:
|
|
327
|
+
cfg["crisis_bytes"] = int(options.crisis_bytes)
|
|
328
|
+
if (
|
|
329
|
+
options.metrics_port_start
|
|
330
|
+
and int(options.metrics_port_start) != machine_config.metrics_port_start
|
|
331
|
+
):
|
|
332
|
+
cfg["metrics_port_start"] = int(options.metrics_port_start)
|
|
333
|
+
if options.environment and options.environment != machine_config.environment:
|
|
334
|
+
cfg["environment"] = options.environment
|
|
335
|
+
if options.start_args and options.start_args != machine_config.start_args:
|
|
336
|
+
cfg["start_args"] = options.start_args
|
|
337
|
+
|
|
338
|
+
return cfg
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
# Get anm configuration
|
|
342
|
+
def load_anm_config(options):
|
|
343
|
+
anm_config = {}
|
|
344
|
+
|
|
345
|
+
# Let's get the real count of CPU's available to this process
|
|
346
|
+
if PLATFORM == "Linux":
|
|
347
|
+
# Linux: use sched_getaffinity for accurate count (respects cgroups/taskset)
|
|
348
|
+
anm_config["cpu_count"] = len(os.sched_getaffinity(0))
|
|
349
|
+
else:
|
|
350
|
+
# macOS/other: use os.cpu_count()
|
|
351
|
+
anm_config["cpu_count"] = os.cpu_count() or 1
|
|
352
|
+
|
|
353
|
+
# What can we save from /var/antctl/config
|
|
354
|
+
if os.path.exists("/var/antctl/config"):
|
|
355
|
+
load_dotenv("/var/antctl/config")
|
|
356
|
+
anm_config["node_cap"] = int(os.getenv("NodeCap") or options.node_cap or 20)
|
|
357
|
+
anm_config["cpu_less_than"] = int(
|
|
358
|
+
os.getenv("CpuLessThan") or options.cpu_less_than or 50
|
|
359
|
+
)
|
|
360
|
+
anm_config["cpu_remove"] = int(os.getenv("CpuRemove") or options.cpu_remove or 70)
|
|
361
|
+
anm_config["mem_less_than"] = int(
|
|
362
|
+
os.getenv("MemLessThan") or options.mem_less_than or 70
|
|
363
|
+
)
|
|
364
|
+
anm_config["mem_remove"] = int(os.getenv("MemRemove") or options.mem_remove or 90)
|
|
365
|
+
anm_config["hd_less_than"] = int(os.getenv("HDLessThan") or options.hd_less_than or 70)
|
|
366
|
+
anm_config["hd_remove"] = int(os.getenv("HDRemove") or options.hd_remove or 90)
|
|
367
|
+
anm_config["delay_start"] = int(os.getenv("DelayStart") or options.delay_start or 300)
|
|
368
|
+
anm_config["delay_upgrade"] = int(
|
|
369
|
+
os.getenv("DelayUpgrade") or options.delay_upgrade or 300
|
|
370
|
+
)
|
|
371
|
+
anm_config["delay_restart"] = int(
|
|
372
|
+
os.getenv("DelayRestart") or options.delay_restart or 600
|
|
373
|
+
)
|
|
374
|
+
anm_config["delay_remove"] = int(
|
|
375
|
+
os.getenv("DelayRemove") or options.delay_remove or 300
|
|
376
|
+
)
|
|
377
|
+
anm_config["node_storage"] = (
|
|
378
|
+
os.getenv("NodeStorage") or options.node_storage or NODE_STORAGE
|
|
379
|
+
)
|
|
380
|
+
# Default to the faucet donation address
|
|
381
|
+
try:
|
|
382
|
+
anm_config["rewards_address"] = re.findall(
|
|
383
|
+
r"--rewards-address ([\dA-Fa-fXx]+)", os.getenv("RewardsAddress")
|
|
384
|
+
)[0]
|
|
385
|
+
except (IndexError, TypeError) as e:
|
|
386
|
+
try:
|
|
387
|
+
anm_config["rewards_address"] = re.findall(
|
|
388
|
+
r"([\dA-Fa-fXx]+)", os.getenv("RewardsAddress")
|
|
389
|
+
)[0]
|
|
390
|
+
except (IndexError, TypeError) as e:
|
|
391
|
+
logging.debug(f"Unable to parse RewardsAddress from env: {e}")
|
|
392
|
+
anm_config["rewards_address"] = options.rewards_address
|
|
393
|
+
if not anm_config["rewards_address"]:
|
|
394
|
+
logging.warning("Unable to detect RewardsAddress")
|
|
395
|
+
sys.exit(1)
|
|
396
|
+
anm_config["donate_address"] = (
|
|
397
|
+
os.getenv("DonateAddress") or options.donate_address or DONATE
|
|
398
|
+
)
|
|
399
|
+
anm_config["max_load_average_allowed"] = float(
|
|
400
|
+
os.getenv("MaxLoadAverageAllowed") or anm_config["cpu_count"]
|
|
401
|
+
)
|
|
402
|
+
anm_config["desired_load_average"] = float(
|
|
403
|
+
os.getenv("DesiredLoadAverage") or (anm_config["cpu_count"] * 0.6)
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
try:
|
|
407
|
+
with open("/usr/bin/anms.sh", "r") as file:
|
|
408
|
+
data = file.read()
|
|
409
|
+
anm_config["port_start"] = int(re.findall(r"ntpr\=(\d+)", data)[0])
|
|
410
|
+
except (FileNotFoundError, IndexError, ValueError) as e:
|
|
411
|
+
logging.debug(f"Unable to read PortStart from anms.sh: {e}")
|
|
412
|
+
anm_config["port_start"] = options.port_start or 55
|
|
413
|
+
|
|
414
|
+
anm_config["metrics_port_start"] = (
|
|
415
|
+
options.metrics_port_start or 13
|
|
416
|
+
) # This is hardcoded in the anm.sh script
|
|
417
|
+
|
|
418
|
+
anm_config["hdio_read_less_than"] = int(os.getenv("HDIOReadLessThan") or 0)
|
|
419
|
+
anm_config["hdio_read_remove"] = int(os.getenv("HDIOReadRemove") or 0)
|
|
420
|
+
anm_config["hdio_write_less_than"] = int(os.getenv("HDIOWriteLessThan") or 0)
|
|
421
|
+
anm_config["hdio_write_remove"] = int(os.getenv("HDIOWriteRemove") or 0)
|
|
422
|
+
anm_config["netio_read_less_than"] = int(os.getenv("NetIOReadLessThan") or 0)
|
|
423
|
+
anm_config["netio_read_remove"] = int(os.getenv("NetIOReadRemove") or 0)
|
|
424
|
+
anm_config["netio_write_less_than"] = int(os.getenv("NetIOWriteLessThan") or 0)
|
|
425
|
+
anm_config["netio_write_remove"] = int(os.getenv("NetIOWriteRemove") or 0)
|
|
426
|
+
# Timer for last stopped nodes
|
|
427
|
+
anm_config["last_stopped_at"] = 0
|
|
428
|
+
anm_config["host"] = os.getenv("Host") or options.host or "127.0.0.1"
|
|
429
|
+
anm_config["crisis_bytes"] = options.host or DEFAULT_CRISIS_BYTES
|
|
430
|
+
anm_config["environment"] = options.environment or ""
|
|
431
|
+
anm_config["start_args"] = options.start_args or ""
|
|
432
|
+
|
|
433
|
+
return anm_config
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
# This belongs someplace else
|
|
437
|
+
def migrate_anm(options):
|
|
438
|
+
if os.path.exists("/var/antctl/system"):
|
|
439
|
+
# Is anm scheduled to run
|
|
440
|
+
if os.path.exists("/etc/cron.d/anm"):
|
|
441
|
+
# remove cron to disable old anm
|
|
442
|
+
try:
|
|
443
|
+
subprocess.run(["sudo", "rm", "/etc/cron.d/anm"])
|
|
444
|
+
except Exception as error:
|
|
445
|
+
template = (
|
|
446
|
+
"In GAV - An exception of type {0} occurred. Arguments:\n{1!r}"
|
|
447
|
+
)
|
|
448
|
+
message = template.format(type(error).__name__, error.args)
|
|
449
|
+
logging.info(message)
|
|
450
|
+
sys.exit(1)
|
|
451
|
+
# Is anm sitll running? We'll wait
|
|
452
|
+
if os.path.exists("/var/antctl/block"):
|
|
453
|
+
logging.info("anm still running, waiting...")
|
|
454
|
+
sys.exit(1)
|
|
455
|
+
# Ok, load anm config
|
|
456
|
+
return load_anm_config(options)
|
|
457
|
+
else:
|
|
458
|
+
return False
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
# Teardown the machine
|
|
462
|
+
def teardown_machine(machine_config):
|
|
463
|
+
logging.info("Teardown machine")
|
|
464
|
+
pass
|
|
465
|
+
# disable cron
|
|
466
|
+
# with S() as session:
|
|
467
|
+
# select Nodes
|
|
468
|
+
# for node in nodes:
|
|
469
|
+
# delete node
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def define_machine(options):
|
|
473
|
+
if not options.rewards_address:
|
|
474
|
+
logging.warning("Rewards Address is required")
|
|
475
|
+
return False
|
|
476
|
+
|
|
477
|
+
# Determine donate_address that will be used for validation
|
|
478
|
+
donate_address = options.donate_address or DONATE
|
|
479
|
+
|
|
480
|
+
# Validate rewards_address format
|
|
481
|
+
is_valid, error_msg = validate_rewards_address(options.rewards_address, donate_address)
|
|
482
|
+
if not is_valid:
|
|
483
|
+
logging.error(f"Invalid rewards_address: {error_msg}")
|
|
484
|
+
return False
|
|
485
|
+
|
|
486
|
+
if PLATFORM == "Linux":
|
|
487
|
+
# Linux: use sched_getaffinity for accurate count (respects cgroups/taskset)
|
|
488
|
+
cpucount = len(os.sched_getaffinity(0))
|
|
489
|
+
else:
|
|
490
|
+
# macOS/other: use os.cpu_count()
|
|
491
|
+
cpucount = os.cpu_count() or 1
|
|
492
|
+
machine = {
|
|
493
|
+
"id": 1,
|
|
494
|
+
"cpu_count": cpucount,
|
|
495
|
+
"node_cap": int(options.node_cap) if options.node_cap else 20,
|
|
496
|
+
"cpu_less_than": int(options.cpu_less_than) if options.cpu_less_than else 50,
|
|
497
|
+
"cpu_remove": int(options.cpu_remove) if options.cpu_remove else 70,
|
|
498
|
+
"mem_less_than": int(options.mem_less_than) if options.mem_less_than else 70,
|
|
499
|
+
"mem_remove": int(options.mem_remove) if options.mem_remove else 90,
|
|
500
|
+
"hd_less_than": int(options.hd_less_than) if options.hd_less_than else 70,
|
|
501
|
+
"hd_remove": int(options.hd_remove) if options.hd_remove else 90,
|
|
502
|
+
"delay_start": int(options.delay_start) if options.delay_start else 300,
|
|
503
|
+
"delay_upgrade": int(options.delay_upgrade) if options.delay_upgrade else 300,
|
|
504
|
+
"delay_remove": int(options.delay_remove) if options.delay_remove else 300,
|
|
505
|
+
"node_storage": options.node_storage or NODE_STORAGE,
|
|
506
|
+
"rewards_address": options.rewards_address,
|
|
507
|
+
"donate_address": options.donate_address
|
|
508
|
+
or "0x00455d78f850b0358E8cea5be24d415E01E107CF",
|
|
509
|
+
"max_load_average_allowed": (
|
|
510
|
+
float(options.max_load_average_allowed)
|
|
511
|
+
if options.max_load_average_allowed
|
|
512
|
+
else cpucount
|
|
513
|
+
),
|
|
514
|
+
"desired_load_average": (
|
|
515
|
+
float(options.desired_load_average)
|
|
516
|
+
if options.desired_load_average
|
|
517
|
+
else cpucount * 0.6
|
|
518
|
+
),
|
|
519
|
+
"port_start": int(options.port_start) if options.port_start else 55,
|
|
520
|
+
"hdio_read_less_than": (
|
|
521
|
+
int(options.hdio_read_less_than) if options.hdio_read_less_than else 0
|
|
522
|
+
),
|
|
523
|
+
"hdio_read_remove": int(options.hdio_read_remove) if options.hdio_read_remove else 0,
|
|
524
|
+
"hdio_write_less_than": (
|
|
525
|
+
int(options.hdio_write_less_than) if options.hdio_write_less_than else 0
|
|
526
|
+
),
|
|
527
|
+
"hdio_write_remove": (
|
|
528
|
+
int(options.hdio_write_remove) if options.hdio_write_remove else 0
|
|
529
|
+
),
|
|
530
|
+
"netio_read_less_than": (
|
|
531
|
+
int(options.netio_read_less_than) if options.netio_read_less_than else 0
|
|
532
|
+
),
|
|
533
|
+
"netio_read_remove": (
|
|
534
|
+
int(options.netio_read_remove) if options.netio_read_remove else 0
|
|
535
|
+
),
|
|
536
|
+
"netio_write_less_than": (
|
|
537
|
+
int(options.netio_write_less_than) if options.netio_write_less_than else 0
|
|
538
|
+
),
|
|
539
|
+
"netio_write_remove": (
|
|
540
|
+
int(options.netio_write_remove) if options.netio_write_remove else 0
|
|
541
|
+
),
|
|
542
|
+
"last_stopped_at": 0,
|
|
543
|
+
"host": options.host or "127.0.0.1",
|
|
544
|
+
"crisis_bytes": (
|
|
545
|
+
int(options.crisis_bytes) if options.crisis_bytes else DEFAULT_CRISIS_BYTES
|
|
546
|
+
),
|
|
547
|
+
"metrics_port_start": (
|
|
548
|
+
int(options.metrics_port_start) if options.metrics_port_start else 13
|
|
549
|
+
),
|
|
550
|
+
"environment": options.environment if options.environment else "",
|
|
551
|
+
"start_args": options.start_args if options.start_args else "",
|
|
552
|
+
}
|
|
553
|
+
with S() as session:
|
|
554
|
+
session.execute(insert(Machine), [machine])
|
|
555
|
+
session.commit()
|
|
556
|
+
return True
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
# Apply changes to system
|
|
560
|
+
def apply_config_updates(config_updates):
|
|
561
|
+
global machine_config
|
|
562
|
+
if config_updates:
|
|
563
|
+
with S() as session:
|
|
564
|
+
session.query(Machine).filter(Machine.id == 1).update(config_updates)
|
|
565
|
+
session.commit()
|
|
566
|
+
# Reload the machine config
|
|
567
|
+
machine_config = session.execute(select(Machine)).first()
|
|
568
|
+
# Get Machine from Row
|
|
569
|
+
machine_config = machine_config[0]
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
# Load options now so we know what database to load
|
|
573
|
+
options = load_config()
|
|
574
|
+
|
|
575
|
+
# Setup Database engine
|
|
576
|
+
engine = create_engine(options.dbpath, echo=True)
|
|
577
|
+
|
|
578
|
+
# Generate ORM
|
|
579
|
+
Base.metadata.create_all(engine)
|
|
580
|
+
|
|
581
|
+
# Create a connection to the ORM
|
|
582
|
+
session_factory = sessionmaker(bind=engine)
|
|
583
|
+
S = scoped_session(session_factory)
|
|
584
|
+
|
|
585
|
+
# Remember if we init a new machine
|
|
586
|
+
did_we_init = False
|
|
587
|
+
|
|
588
|
+
# Skip machine configuration check in test mode
|
|
589
|
+
if os.getenv("WNM_TEST_MODE"):
|
|
590
|
+
# In test mode, use a minimal machine config or None
|
|
591
|
+
machine_config = None
|
|
592
|
+
else:
|
|
593
|
+
# Check if we have a defined machine
|
|
594
|
+
with S() as session:
|
|
595
|
+
machine_config = session.execute(select(Machine)).first()
|
|
596
|
+
|
|
597
|
+
# No machine configured
|
|
598
|
+
if not machine_config and not os.getenv("WNM_TEST_MODE"):
|
|
599
|
+
# Are we initializing a new machine?
|
|
600
|
+
if options.init:
|
|
601
|
+
# Init and dry-run are mutually exclusive
|
|
602
|
+
if options.dry_run:
|
|
603
|
+
logging.error("dry run not supported during init.")
|
|
604
|
+
sys.exit(1)
|
|
605
|
+
else:
|
|
606
|
+
# Did we get a request to migrate from anm?
|
|
607
|
+
if options.migrate_anm:
|
|
608
|
+
if anm_config := migrate_anm(options):
|
|
609
|
+
# Save and reload config
|
|
610
|
+
with S() as session:
|
|
611
|
+
session.execute(insert(Machine), [anm_config])
|
|
612
|
+
session.commit()
|
|
613
|
+
machine_config = session.execute(select(Machine)).first()
|
|
614
|
+
if not machine_config:
|
|
615
|
+
print("Unable to locate record after successful migration")
|
|
616
|
+
sys.exit(1)
|
|
617
|
+
# Get Machine from Row
|
|
618
|
+
machine_config = machine_config[0]
|
|
619
|
+
did_we_init = True
|
|
620
|
+
else:
|
|
621
|
+
print("Failed to migrate machine from anm")
|
|
622
|
+
sys.exit(1)
|
|
623
|
+
else:
|
|
624
|
+
if define_machine(options):
|
|
625
|
+
with S() as session:
|
|
626
|
+
machine_config = session.execute(select(Machine)).first()
|
|
627
|
+
if not machine_config:
|
|
628
|
+
print(
|
|
629
|
+
"Failed to locate record after successfully defining a machine"
|
|
630
|
+
)
|
|
631
|
+
sys.exit(1)
|
|
632
|
+
# Get Machine from Row
|
|
633
|
+
machine_config = machine_config[0]
|
|
634
|
+
did_we_init = True
|
|
635
|
+
else:
|
|
636
|
+
print("Failed to create machine")
|
|
637
|
+
sys.exit(1)
|
|
638
|
+
else:
|
|
639
|
+
print("No config found")
|
|
640
|
+
sys.exit(1)
|
|
641
|
+
else:
|
|
642
|
+
# Fail if we are trying to init a machine that is already initialized
|
|
643
|
+
if options.init:
|
|
644
|
+
logging.warning("Machine already initialized")
|
|
645
|
+
sys.exit(1)
|
|
646
|
+
# Initate a teardown of the machine
|
|
647
|
+
if options.teardown:
|
|
648
|
+
if options.confirm:
|
|
649
|
+
if options.dry_run:
|
|
650
|
+
logging.info("DRY_RUN: Initiate Teardown")
|
|
651
|
+
else:
|
|
652
|
+
teardown_machine(machine_config)
|
|
653
|
+
sys.exit(0)
|
|
654
|
+
else:
|
|
655
|
+
logging.warning("Please confirm the teardown with --confirm")
|
|
656
|
+
sys.exit(1)
|
|
657
|
+
# Get Machine from Row (skip in test mode)
|
|
658
|
+
if not os.getenv("WNM_TEST_MODE"):
|
|
659
|
+
machine_config = machine_config[0]
|
|
660
|
+
|
|
661
|
+
# Collect the proposed changes unless we are initializing (skip in test mode)
|
|
662
|
+
config_updates = merge_config_changes(options, machine_config) if not os.getenv("WNM_TEST_MODE") else {}
|
|
663
|
+
# Failfirst on invalid config change
|
|
664
|
+
if (
|
|
665
|
+
"port_start" in config_updates or "metrics_port_start" in config_updates
|
|
666
|
+
) and not did_we_init:
|
|
667
|
+
logging.warning("Can not change start port numbers on an active machine")
|
|
668
|
+
sys.exit(1)
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
if __name__ == "__main__":
|
|
672
|
+
print("Changes:", json.loads(json.dumps(config_updates)))
|
|
673
|
+
print(json.loads(json.dumps(machine_config)))
|