Skip to content

Commit

Permalink
failover.reboot.add_reason
Browse files Browse the repository at this point in the history
  • Loading branch information
themylogin committed Sep 12, 2024
1 parent 505fa8a commit 43a8a7a
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 22 deletions.
1 change: 1 addition & 0 deletions src/middlewared/middlewared/api/base/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
def api_method(
accepts: type[BaseModel],
returns: type[BaseModel],
*,
audit: str | None = None,
audit_callback: bool = False,
audit_extended: Callable[..., str] | None = None,
Expand Down
1 change: 1 addition & 0 deletions src/middlewared/middlewared/api/v25_04_0/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .cloud_sync import * # noqa
from .common import * # noqa
from .core import * # noqa
from .failover_reboot import * # noqa
from .group import * # noqa
from .privilege import * # noqa
from .user import * # noqa
Expand Down
28 changes: 28 additions & 0 deletions src/middlewared/middlewared/api/v25_04_0/failover_reboot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright (c) - iXsystems Inc.
#
# Licensed under the terms of the TrueNAS Enterprise License Agreement
# See the file LICENSE.IX for complete terms and conditions
from middlewared.api.base import BaseModel, single_argument_result

__all__ = ["FailoverRebootRequiredArgs", "FailoverRebootRequiredResult"]


class FailoverRebootRequiredArgs(BaseModel):
pass


class FailoverRebootRequiredResultThisNode(BaseModel):
id: str
reboot_required: bool
reboot_required_reasons: list[str]


class FailoverRebootRequiredResultOtherNode(FailoverRebootRequiredResultThisNode):
id: str | None
reboot_required: bool | None


@single_argument_result
class FailoverRebootRequiredResult(BaseModel):
this_node: FailoverRebootRequiredResultThisNode
other_node: FailoverRebootRequiredResultOtherNode
57 changes: 35 additions & 22 deletions src/middlewared/middlewared/plugins/failover_/reboot.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import errno
import time

from middlewared.api import api_method
from middlewared.api.current import FailoverRebootRequiredArgs, FailoverRebootRequiredResult
from middlewared.schema import accepts, Bool, Dict, UUID, returns
from middlewared.service import CallError, job, private, Service

Expand All @@ -18,6 +20,17 @@ class Config:
cli_namespace = 'system.failover.reboot'
namespace = 'failover.reboot'

reboot_reasons : dict[str, str] = {}

@private
async def add_reason(self, key: str, value: str):
"""
Adds a reason on why this system needs a reboot.
:param key: unique identifier for the reason.
:param value: text explanation for the reason.
"""
self.reboot_reasons[key] = value

@private
async def boot_ids(self):
info = {
Expand All @@ -39,36 +52,36 @@ async def boot_ids(self):
async def info_impl(self):
# initial state
current_info = await self.boot_ids()
current_info['this_node'].update({'reboot_required': None})
current_info['other_node'].update({'reboot_required': None})
current_info['this_node'].update({'reboot_required': None, 'reboot_required_reasons': []})
current_info['other_node'].update({'reboot_required': None, 'reboot_required_reasons': []})

fips_change_info = await self.middleware.call('keyvalue.get', FIPS_KEY, False)
if not fips_change_info:
for i in current_info:
current_info[i]['reboot_required'] = False
return current_info

for key in ('this_node', 'other_node'):
current_info[key]['reboot_required'] = all((
fips_change_info[key]['id'] == current_info[key]['id'],
fips_change_info[key]['reboot_required']
))

if all((
current_info['this_node']['reboot_required'] is False,
current_info['other_node']['reboot_required'] is False,
)):
# no reboot required for either controller so delete
await self.middleware.call('keyvalue.delete', FIPS_KEY)
else:
for key in ('this_node', 'other_node'):
current_info[key]['reboot_required'] = all((
fips_change_info[key]['id'] == current_info[key]['id'],
fips_change_info[key]['reboot_required']
))
if current_info[key]['reboot_required']:
current_info[key]['reboot_required_reasons'].append('FIPS configuration was changed.')

if all((
current_info['this_node']['reboot_required'] is False,
current_info['other_node']['reboot_required'] is False,
)):
# no reboot required for either controller so delete
await self.middleware.call('keyvalue.delete', FIPS_KEY)

for reason in self.reboot_reasons.values():
current_info['this_node']['reboot_required'] = True
current_info['this_node']['reboot_required_reasons'].append(reason)

return current_info

@accepts(roles=['FAILOVER_READ'])
@returns(Dict(
'info',
Dict('this_node', UUID('id'), Bool('reboot_required')),
Dict('other_node', UUID('id', null=True), Bool('reboot_required', null=True)),
))
@api_method(FailoverRebootRequiredArgs, FailoverRebootRequiredResult, roles=['FAILOVER_READ'])
async def info(self):
"""Returns the local and remote nodes boot_ids along with their
reboot statuses (i.e. does a reboot need to take place)"""
Expand Down
15 changes: 15 additions & 0 deletions tests/api2/test_zz.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# These tests need to be executing at the very last stage of the testing process as they semi-break the system
# in different ways.
from middlewared.test.integration.utils import call


def test_failover_reboot_add_reason():
info = call("failover.reboot.info")
assert not info["this_node"]["reboot_required"]
assert info["this_node"]["reboot_required_reasons"] == []

call("failover.reboot.add_reason", "test", "Test reason.")

info = call("failover.reboot.info")
assert info["this_node"]["reboot_required"]
assert info["this_node"]["reboot_required_reasons"] == ["Test reason."]

0 comments on commit 43a8a7a

Please sign in to comment.