Skip to content

Commit

Permalink
reporting: add new logging type events
Browse files Browse the repository at this point in the history
Adds new reporting type events "INFO", "WARNING", and "ERROR" to be
used for context logging. These can be invoked with the new `.info`,
`.warning`, and `.error` methods on the context object accordingly.
Useful for things like warning/errors on autoinstall configuartions.
  • Loading branch information
Chris-Peterson444 committed Mar 20, 2024
1 parent c25e28e commit 980411a
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 4 deletions.
24 changes: 23 additions & 1 deletion subiquity/server/controllers/reporting.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,17 @@
import logging

from curtin.reporter import available_handlers, update_configuration
from curtin.reporter.events import report_finish_event, report_start_event, status
from curtin.reporter.events import (
ReportingEvent,
report_event,
report_finish_event,
report_start_event,
status,
)
from curtin.reporter.handlers import LogHandler as CurtinLogHandler

from subiquity.server.controller import NonInteractiveController
from subiquitycore.context import Context


class LogHandler(CurtinLogHandler):
Expand Down Expand Up @@ -76,3 +83,18 @@ def report_finish_event(self, context, description, result):
report_finish_event(
context.full_name(), description, result, level=context.level
)

def report_info_event(self, context: Context, message: str):
"""Report an "info" event."""
event = ReportingEvent("info", context.full_name(), message, level="INFO")
report_event(event)

def report_warning_event(self, context: Context, message: str):
"""Report a "warning" event."""
event = ReportingEvent("warning", context.full_name(), message, level="WARNING")
report_event(event)

def report_error_event(self, context: Context, message: str):
"""Report an "error" event."""
event = ReportingEvent("error", context.full_name(), message, level="ERROR")
report_event(event)
88 changes: 88 additions & 0 deletions subiquity/server/controllers/tests/test_reporting.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from unittest.mock import Mock, patch

import jsonschema
from curtin.reporter.events import status as CurtinStatus
from jsonschema.validators import validator_for

from subiquity.server.controllers.reporting import ReportingController
from subiquitycore.context import Context
from subiquitycore.context import Status as ContextStatus
from subiquitycore.tests import SubiTestCase
from subiquitycore.tests.mocks import MockedApplication, make_app


class TestReportingController(SubiTestCase):
Expand All @@ -29,3 +35,85 @@ def test_valid_schema(self):
)

JsonValidator.check_schema(ReportingController.autoinstall_schema)


@patch("subiquity.server.controllers.reporting.report_event")
class TestReportingCurtinCalls(SubiTestCase):
def setUp(self):
app: MockedApplication = make_app()
self.controller: ReportingController = ReportingController(app)
self.context: Context = app.context

@patch("subiquity.server.controllers.reporting.report_start_event")
def test_start_event(self, report_start_event, report_event):
self.controller.report_start_event(self.context, "description")

# Calls specific start event method
report_start_event.assert_called_with(
self.context.full_name(), "description", level=self.context.level
)

# Not the generic one
report_event.assert_not_called()

@patch("subiquity.server.controllers.reporting.report_finish_event")
def test_finish_event(self, report_finish_event, report_event):
self.controller.report_finish_event(
self.context, "description", ContextStatus.FAIL
)

# Calls specific finish event method
report_finish_event.assert_called_with(
self.context.full_name(),
"description",
CurtinStatus.FAIL,
level=self.context.level,
)

# Not the generic one
report_event.assert_not_called()

# Test default WARN
status = Mock()
status.name = "NEW LEVEL"
self.controller.report_finish_event(self.context, "description", status)

report_finish_event.assert_called_with(
self.context.full_name(),
"description",
CurtinStatus.WARN,
level=self.context.level,
)

@patch("subiquity.server.controllers.reporting.ReportingEvent")
def test_info_event(self, mock_class, report_event):
self.controller.report_info_event(self.context, "description")

mock_class.assert_called_with(
"info",
self.context.full_name(),
"description",
level="INFO",
)

@patch("subiquity.server.controllers.reporting.ReportingEvent")
def test_warning_event(self, mock_class, report_event):
self.controller.report_warning_event(self.context, "description")

mock_class.assert_called_with(
"warning",
self.context.full_name(),
"description",
level="WARNING",
)

@patch("subiquity.server.controllers.reporting.ReportingEvent")
def test_error_event(self, mock_class, report_event):
self.controller.report_error_event(self.context, "description")

mock_class.assert_called_with(
"error",
self.context.full_name(),
"description",
level="ERROR",
)
22 changes: 19 additions & 3 deletions subiquity/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,11 +372,14 @@ def _maybe_push_to_journal(
# - special sections of the install, which set "is-install-context"
# where we want to report the event anyways
#
# - special event types:
# - warn
# - error
#
# For non-interactive installs (i.e., full autoinstall) we report
# everything.

force_reporting: bool = install_context
force_reporting: bool = install_context or event_type in ["warning", "error"]

# self.interactive=None could be an interactive install, we just
# haven't found out yet
Expand All @@ -388,8 +391,6 @@ def _maybe_push_to_journal(
if controller is None or controller.interactive():
return

# Otherwise it came from the server

# Create the message out of the name of the reporter and optionally
# the description
name: str = context.full_name()
Expand Down Expand Up @@ -432,6 +433,21 @@ def report_finish_event(self, context, description, status):
listener.report_finish_event(context, description, status)
self._maybe_push_to_journal("finish", context, description)

def report_info_event(self, context: Context, message: str) -> None:
for listener in self.event_listeners:
listener.report_info_event(context, message)
self._maybe_push_to_journal("info", context, message)

def report_warning_event(self, context: Context, message: str) -> None:
for listener in self.event_listeners:
listener.report_warning_event(context, message)
self._maybe_push_to_journal("warning", context, message)

def report_error_event(self, context: Context, message: str) -> None:
for listener in self.event_listeners:
listener.report_error_event(context, message)
self._maybe_push_to_journal("error", context, message)

@property
def state(self):
return self._state
Expand Down
78 changes: 78 additions & 0 deletions subiquity/server/tests/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,3 +378,81 @@ async def test_maybe_push_to_journal(
journal_send_mock.assert_called_once()
else:
journal_send_mock.assert_not_called()

@parameterized.expand(
(
# interactive, pushed to journal
(True, False),
(None, False),
(False, True),
)
)
def test_push_info_events(self, interactive, expect_pushed):
"""Test info event publication"""

context: Context = Context(
self.server, "MockContext", "description", None, "INFO"
)
self.server.interactive = interactive

with patch("subiquity.server.server.journal.send") as journal_send_mock:
self.server.report_info_event(context, "message")

if not expect_pushed:
journal_send_mock.assert_not_called()
else:
journal_send_mock.assert_called_once()
# message is the only positional argument
(message,) = journal_send_mock.call_args.args
self.assertIn("message", message)
self.assertNotIn("description", message)

@parameterized.expand(
(
# interactive
(True,),
(None,),
(False,),
)
)
def test_push_warning_events(self, interactive):
"""Test warning event publication"""

context: Context = Context(
self.server, "MockContext", "description", None, "INFO"
)
self.server.interactive = interactive

with patch("subiquity.server.server.journal.send") as journal_send_mock:
self.server.report_warning_event(context, "message")

journal_send_mock.assert_called_once()
# message is the only positional argument
(message,) = journal_send_mock.call_args.args
self.assertIn("message", message)
self.assertNotIn("description", message)

@parameterized.expand(
(
# interactive
(True,),
(None,),
(False,),
)
)
def test_push_error_events(self, interactive):
"""Test error event publication"""

context: Context = Context(
self.server, "MockContext", "description", None, "INFO"
)
self.server.interactive = interactive

with patch("subiquity.server.server.journal.send") as journal_send_mock:
self.server.report_error_event(context, "message")

journal_send_mock.assert_called_once()
# message is the only positional argument
(message,) = journal_send_mock.call_args.args
self.assertIn("message", message)
self.assertNotIn("description", message)
9 changes: 9 additions & 0 deletions subiquitycore/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,15 @@ def get(self, key, default=None):
c = c.parent
return default

def info(self, message: str) -> None:
self.app.report_info_event(self, message)

def warning(self, message: str) -> None:
self.app.report_warning_event(self, message)

def error(self, message: str) -> None:
self.app.report_error_event(self, message)


def with_context(name=None, description="", **context_kw):
def decorate(meth):
Expand Down
1 change: 1 addition & 0 deletions subiquitycore/tests/mocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def make_app(model=None):
app.base_model = model
else:
app.base_model = mock.Mock()
app.add_event_listener = mock.Mock()
app.controllers = mock.Mock()
app.context = Context.new(app)
app.exit = mock.Mock()
Expand Down

0 comments on commit 980411a

Please sign in to comment.