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/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)))