Skip to content

Commit 0794429

Browse files
committed
Simplify middleware audit startup
Delay certain expensive parts of middlewared auditing startup (an initial scan for file changes and removing unnecessary refreservations on old audit datasets) until after the system READY event, and only perform it on first boot after upgrade or fresh install. This commit also removes the AuditSetupAlertClass. It could only be called during the first boot for a boot environment and would never be dismissed. Removal decreases complexity of operations during system ready events on first boot.
1 parent 41e7930 commit 0794429

File tree

10 files changed

+77
-44
lines changed

10 files changed

+77
-44
lines changed

docs/source/middleware/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Middleware Daemon
66
:caption: Contents:
77

88
development.rst
9+
state.rst
910
process_pool.rst
1011
jobs.rst
1112
roles.rst

docs/source/middleware/state.rst

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
Middleware State Directories
2+
############################
3+
4+
.. contents:: Table of Contents
5+
:depth: 4
6+
7+
The middlewared process stores state in several directories on the local filesystem. There
8+
are many situations in which the developer may opt to store state information outside of the
9+
configuration database. In this case the onus is on the developer to choose an appropriate
10+
location for this state based on persistence requirements.
11+
12+
13+
Volatile state
14+
**************
15+
16+
Volatile middleware state is stored in the middleware run directory `/var/run/middleware`.
17+
The expected permissions on the volatile state directory are 0o755. This is typically where
18+
sentinel files should be placed. This is defined by `MIDDLEWARE_RUN_DIR` in `middlewared/utils`.
19+
20+
21+
Persistent state
22+
****************
23+
24+
There are several directories that are used to store persistent state related to the middlewared
25+
process and TrueNAS servers.
26+
27+
`/data` -- this ZFS dataset contains the TrueNAS configuration file `freenas-v1.db` and various install-specific
28+
configuration files that must persist across TrueNAS upgrades. Items that need to be included in the configuration
29+
tarball should generally be placed here. Permissions on this directory must be 0o755, but many files here should
30+
be set to 0o700. All files and directories should be owned by root:root.
31+
32+
`/data/subsystems` -- this directory contains application-specific configuration that must persist between
33+
installs that is not suitable for datastore insertion. The convention is to create a new directory with the name of
34+
the middleware plugin that needs persistent state. Configuration information stored in these directories must be
35+
included in the TrueNAS configuration backup and restored on configuration upload.
36+
37+
`/var/lib/truenas-middleware` -- this directory contains persistent middleware state that is applicable to the
38+
current boot environment only. It is a safe place to store data that we want to persist across reboots, but not
39+
across upgrades. This is defined by the `MIDDLEWARE_BOOT_ENV_STATE_DIR` in `middlewared/utils`. The permissions
40+
on this directory should be 0o755 and it should be owned by root:root.
41+
42+
`/root` -- this dataset contains the middleware directory services cache. The permissions on this directory
43+
should be 0o700 and it should be owned by root:root.

src/middlewared/middlewared/alert/source/audit.py

-7
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,6 @@ class AuditBackendSetupAlertClass(AlertClass, SimpleOneShotAlertClass):
1414
text = "Audit service failed backend setup: %(service)s. See /var/log/middlewared.log"
1515

1616

17-
class AuditSetupAlertClass(AlertClass, SimpleOneShotAlertClass):
18-
category = AlertCategory.AUDIT
19-
level = AlertLevel.ERROR
20-
title = "Audit Service Setup Failed"
21-
text = "Audit service failed to complete setup. See /var/log/middlewared.log"
22-
23-
2417
# --------------- Monitored Alerts ----------------
2518
class AuditServiceHealthAlertClass(AlertClass):
2619
category = AlertCategory.AUDIT

src/middlewared/middlewared/plugins/audit/__init__.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1+
async def init_audit(middleware, event_type, args):
2+
if await middleware.call('system.boot_env_first_boot'):
3+
try:
4+
await middleware.call("audit.setup")
5+
except Exception:
6+
middleware.logger.error("Failed to perform setup tasks for auditing.", exc_info=True)
7+
8+
19
async def setup(middleware):
10+
middleware.event_subscribe('system.ready', init_audit)
11+
212
try:
313
# Set up connections to the auditing databases
414
await middleware.call("auditbackend.setup")
515
except Exception:
616
middleware.logger.error("Failed to set up auditing backend.", exc_info=True)
7-
if await middleware.call("keyvalue.get", "run_migration", False):
8-
# If this is an upgrade then free up space used by refreservation on
9-
# deactivated boot environments
10-
try:
11-
await middleware.call("audit.setup")
12-
except Exception:
13-
middleware.logger.error("Failed to perform setup tasks for auditing.", exc_info=True)

src/middlewared/middlewared/plugins/audit/audit.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -550,9 +550,9 @@ async def setup(self):
550550
configuration to the current boot environment.
551551
"""
552552
try:
553-
os.mkdir(AUDIT_REPORTS_DIR, 0o700)
553+
await self.middleware.run_in_thread(os.mkdir, AUDIT_REPORTS_DIR, 0o700)
554554
except FileExistsError:
555-
os.chmod(AUDIT_REPORTS_DIR, 0o700)
555+
await self.middleware.run_in_thread(os.chmod, AUDIT_REPORTS_DIR, 0o700)
556556

557557
cur = await self.middleware.call('audit.get_audit_dataset')
558558
parent = os.path.dirname(cur['id'])
@@ -589,13 +589,10 @@ async def setup(self):
589589
'cleanup may be required', ds['id'], exc_info=True
590590
)
591591

592-
# Dismiss any existing AuditSetup one-shot alerts
593-
await self.middleware.call('alert.oneshot_delete', 'AuditSetup', None)
594592
audit_config = await self.middleware.call('audit.config')
595593
try:
596594
await self.middleware.call('audit.update_audit_dataset', audit_config)
597595
except Exception:
598-
await self.middleware.call('alert.oneshot_create', 'AuditSetup', None)
599596
self.logger.error('Failed to apply auditing dataset configuration.', exc_info=True)
600597

601598
# Generate the initial truenas_verify file

src/middlewared/middlewared/plugins/system/__init__.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from middlewared.utils import BOOTREADY
55

6-
from .utils import FIRST_INSTALL_SENTINEL, lifecycle_conf
6+
from .utils import FIRST_INSTALL_SENTINEL, BOOTENV_FIRSTBOOT_SENTINEL, lifecycle_conf
77

88

99
def firstboot(middleware):
@@ -20,6 +20,15 @@ def firstboot(middleware):
2020
middleware.call_sync('datastore.update', 'system.advanced', config['id'], {'adv_autotune': True})
2121

2222

23+
def firstboot_after_upgrade(middleware):
24+
if not os.path.exists(BOOTENV_FIRSTBOOT_SENTINEL):
25+
os.makedirs(os.path.dirname(BOOTENV_FIRSTBOOT_SENTINEL), mode=0o700)
26+
with open(BOOTENV_FIRSTBOOT_SENTINEL, 'w'):
27+
pass
28+
29+
lifecycle_conf.SYSTEM_BOOT_ENV_FIRST_BOOT = True
30+
31+
2332
def read_system_boot_id(middleware):
2433
try:
2534
with open('/proc/sys/kernel/random/boot_id', 'r') as f:
@@ -36,6 +45,7 @@ async def setup(middleware):
3645
middleware.event_register('system.shutdown', 'Started shutdown process', roles=['SYSTEM_GENERAL_READ'])
3746

3847
await middleware.run_in_thread(firstboot, middleware)
48+
await middleware.run_in_thread(firstboot_after_upgrade, middleware)
3949

4050
settings = await middleware.call('system.general.config')
4151
middleware.logger.debug('Setting timezone to %r', settings['timezone'])

src/middlewared/middlewared/plugins/system/lifecycle.py

+5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ class SystemService(Service):
2323
async def first_boot(self):
2424
return lifecycle_conf.SYSTEM_FIRST_BOOT
2525

26+
@private
27+
async def boot_env_first_boot(self):
28+
# First boot after upgrading server
29+
return lifecycle_conf.SYSTEM_BOOT_ENV_FIRST_BOOT
30+
2631
@api_method(
2732
SystemBootIdArgs,
2833
SystemBootIdResult,

src/middlewared/middlewared/plugins/system/utils.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import os
22
import re
33

4-
from middlewared.utils import MIDDLEWARE_RUN_DIR
4+
from middlewared.utils import MIDDLEWARE_RUN_DIR, MIDDLEWARE_BOOT_ENV_STATE_DIR
55

66

77
DEBUG_MAX_SIZE = 30
88
FIRST_INSTALL_SENTINEL = '/data/first-boot'
9+
BOOTENV_FIRSTBOOT_SENTINEL = os.path.join(MIDDLEWARE_BOOT_ENV_STATE_DIR, '.first-boot')
910
RE_KDUMP_CONFIGURED = re.compile(r'current state\s*:\s*(ready to kdump)', flags=re.M)
1011

1112

@@ -17,6 +18,8 @@ def __init__(self):
1718
self.SYSTEM_READY = False
1819
# Flag telling whether the system is shutting down
1920
self.SYSTEM_SHUTTING_DOWN = False
21+
self.SYSTEM_BOOT_ENV_FIRST_BOOT = False
22+
# Flag telling whether post-install
2023

2124

2225
def get_debug_execution_dir(system_dataset_path: str, iteration: int = 0) -> str:

src/middlewared/middlewared/utils/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class ProductNames:
3434

3535
MID_PID = None
3636
MIDDLEWARE_RUN_DIR = '/var/run/middleware'
37+
MIDDLEWARE_BOOT_ENV_STATE_DIR = '/var/lib/truenas-middleware'
3738
MIDDLEWARE_STARTED_SENTINEL_PATH = f'{MIDDLEWARE_RUN_DIR}/middlewared-started'
3839
BOOTREADY = f'{MIDDLEWARE_RUN_DIR}/.bootready'
3940
BOOT_POOL_NAME_VALID = ['freenas-boot', 'boot-pool']

tests/api2/test_audit_alerts.py

-23
Original file line numberDiff line numberDiff line change
@@ -81,29 +81,6 @@ def test_audit_backend_alert(setup_state):
8181
assert class_alerts[0]['formatted'].startswith("Audit service failed backend setup"), class_alerts
8282

8383

84-
@pytest.mark.parametrize(
85-
'setup_state', [
86-
[None, 'AuditSetup', 'audit.setup']
87-
],
88-
indirect=True
89-
)
90-
def test_audit_setup_alert(setup_state):
91-
with mock("audit.update_audit_dataset", """
92-
from middlewared.service import private
93-
@private
94-
async def mock(self, new):
95-
raise Exception()
96-
"""):
97-
unused, alert_class, audit_method = setup_state
98-
call(audit_method)
99-
sleep(1)
100-
alerts = call("alert.list")
101-
class_alerts = [alert for alert in alerts if alert['klass'] == alert_class]
102-
assert len(class_alerts) > 0, class_alerts
103-
assert class_alerts[0]['klass'] == 'AuditSetup', class_alerts
104-
assert class_alerts[0]['formatted'].startswith("Audit service failed to complete setup"), class_alerts
105-
106-
10784
def test_audit_health_monitor_alert():
10885
with mock("auditbackend.query", """
10986
from middlewared.service import private

0 commit comments

Comments
 (0)