Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
silverdrake11 committed Aug 6, 2024
1 parent 04fed8d commit 708d166
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 54 deletions.
1 change: 1 addition & 0 deletions landscape/client/manager/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"SnapManager",
"SnapServicesManager",
"UbuntuProInfo",
"LivePatch",
]


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
import subprocess
import yaml

from landscape.client.monitor.plugin import DataWatcher

from landscape.client.manager.plugin import DataWatcherManager

class LivePatch(DataWatcher):

class LivePatch(DataWatcherManager):
"""
Plugin that captures and reports Livepatch status information
information.
Expand Down
72 changes: 72 additions & 0 deletions landscape/client/manager/plugin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import logging
from pathlib import Path

from twisted.internet.defer import maybeDeferred

from landscape.client.broker.client import BrokerClientPlugin
from landscape.lib.format import format_object
from landscape.lib.log import log_failure
from landscape.lib.persist import Persist

# Protocol messages! Same constants are defined in the server.
FAILED = 5
Expand Down Expand Up @@ -66,3 +70,71 @@ def send(args):
deferred.addCallback(send)

return deferred


class DataWatcherManager(ManagerPlugin):
"""
A utility for plugins which send data to the Landscape server
which does not constantly change. New messages will only be sent
when the result of get_data() has changed since the last time it
was called. Note this is the same as the DataWatcher plugin but
for Manager plugins instead of Monitor.Subclasses should provide
a get_data method
"""

message_type = None

def __init__(self):
super().__init__()
self._persist = None

def register(self, registry):
super().register(registry)
self._persist_filename = Path(
self.registry.config.data_path,
self.message_type + '.manager.bpkl',
)
self._persist = Persist(filename=self._persist_filename)
self.call_on_accepted(self.message_type, self.send_message)

def run(self):
return self.registry.broker.call_if_accepted(
self.message_type,
self.send_message,
)

def send_message(self):
"""Send a message to the broker if the data has changed since the last
call"""
result = self.get_new_data()
if not result:
logging.debug("{} unchanged so not sending".format(
self.message_type))
return
logging.debug("Sending new {} data!".format(self.message_type))
message = {"type": self.message_type, self.message_type: result}
return self.registry.broker.send_message(message, self._session_id)

def get_new_data(self):
"""Returns the data only if it has changed"""
data = self.get_data()
if self._persist is None: # Persist not initialized yet
return data
elif self._persist.get("data") != data:
self._persist.set("data", data)
return data
else: # Data not changed
return None

def get_data(self):
"""
The result of this will be cached and subclasses must implement this
and return the correct return type defined in the server bound message
schema
"""
raise NotImplementedError("Subclasses must implement get_data()")

def _reset(self):
"""Reset the persist."""
if self._persist:
self._persist.remove("data")
1 change: 1 addition & 0 deletions landscape/client/manager/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def test_plugin_factories(self):
"SnapManager",
"SnapServicesManager",
"UbuntuProInfo",
"LivePatch"
],
ALL_PLUGINS,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import yaml
from unittest import mock

from landscape.client.monitor.livepatch import LivePatch
from landscape.client.manager.livepatch import LivePatch
from landscape.client.tests.helpers import LandscapeTest, MonitorHelper


Expand All @@ -28,11 +28,11 @@ def setUp(self):
def test_livepatch(self):
"""Tests calling livepatch status."""
plugin = LivePatch()
self.monitor.add(plugin)

with mock.patch("subprocess.run") as run_mock:
run_mock.side_effect = subprocess_livepatch_mock
plugin.exchange()
self.monitor.add(plugin)
plugin.run()

messages = self.mstore.get_pending_messages()
self.assertTrue(len(messages) > 0)
Expand All @@ -47,11 +47,11 @@ def test_livepatch(self):
def test_livepatch_when_not_installed(self):
"""Tests calling livepatch when it is not installed."""
plugin = LivePatch()
self.monitor.add(plugin)

with mock.patch("subprocess.run") as run_mock:
run_mock.side_effect = FileNotFoundError("Not found!")
plugin.exchange()
self.monitor.add(plugin)
plugin.run()

messages = self.mstore.get_pending_messages()
message = json.loads(messages[0]["livepatch"])
Expand All @@ -64,11 +64,11 @@ def test_livepatch_when_not_installed(self):
def test_undefined_exception(self):
"""Tests calling livepatch when random exception occurs"""
plugin = LivePatch()
self.monitor.add(plugin)

with mock.patch("subprocess.run") as run_mock:
run_mock.side_effect = ValueError("Not found!")
plugin.exchange()
self.monitor.add(plugin)
plugin.run()

messages = self.mstore.get_pending_messages()
message = json.loads(messages[0]["livepatch"])
Expand All @@ -83,13 +83,13 @@ def test_yaml_json_parse_error(self):
If json or yaml parsing error than show exception and unparsed data
"""
plugin = LivePatch()
self.monitor.add(plugin)

invalid_data = "'"
with mock.patch("subprocess.run") as run_mock:
run_mock.return_value = mock.Mock(stdout=invalid_data)
run_mock.return_value.returncode = 0
plugin.exchange()
self.monitor.add(plugin)
plugin.run()

messages = self.mstore.get_pending_messages()
message = json.loads(messages[0]["livepatch"])
Expand All @@ -104,14 +104,14 @@ def test_empty_string(self):
If livepatch is disabled, stdout is empty string
"""
plugin = LivePatch()
self.monitor.add(plugin)

invalid_data = ""
with mock.patch("subprocess.run") as run_mock:
run_mock.return_value = mock.Mock(stdout=invalid_data,
stderr='Error')
run_mock.return_value.returncode = 1
plugin.exchange()
self.monitor.add(plugin)
plugin.run()

messages = self.mstore.get_pending_messages()
message = json.loads(messages[0]["livepatch"])
Expand All @@ -126,11 +126,11 @@ def test_timestamped_fields_deleted(self):
"""This is so data doesn't keep getting sent if not changed"""

plugin = LivePatch()
self.monitor.add(plugin)

with mock.patch("subprocess.run") as run_mock:
run_mock.side_effect = subprocess_livepatch_mock
plugin.exchange()
self.monitor.add(plugin)
plugin.run()

messages = self.mstore.get_pending_messages()
self.assertTrue(len(messages) > 0)
Expand Down
36 changes: 35 additions & 1 deletion landscape/client/manager/tests/test_plugin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from twisted.internet.defer import Deferred

from landscape.client.manager.plugin import FAILED
from landscape.client.manager.plugin import ManagerPlugin
from landscape.client.manager.plugin import ManagerPlugin, DataWatcherManager
from landscape.client.manager.plugin import SUCCEEDED
from landscape.client.tests.helpers import LandscapeTest
from landscape.client.tests.helpers import ManagerHelper
Expand Down Expand Up @@ -126,3 +126,37 @@ def assert_messages(ignored):
result.addCallback(assert_messages)
deferred.callback("blah")
return result


class StubDataWatchingPlugin(DataWatcherManager):

message_type = "wubble"

def __init__(self, data=None):
self.data = data

def get_data(self):
return self.data


class DataWatcherManagerTest(LandscapeTest):

helpers = [ManagerHelper]

def setUp(self):
LandscapeTest.setUp(self)
self.plugin = StubDataWatchingPlugin("hello world")
self.plugin.register(self.manager)

def test_get_message(self):
self.assertEqual(
self.plugin.get_new_data(),
"hello world",
)

def test_get_message_unchanging(self):
self.assertEqual(
self.plugin.get_new_data(),
"hello world",
)
self.assertEqual(self.plugin.get_new_data(), None)
41 changes: 4 additions & 37 deletions landscape/client/manager/ubuntuproinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
from datetime import datetime
from datetime import timedelta
from datetime import timezone
from pathlib import Path

from landscape.client import IS_CORE
from landscape.client import IS_SNAP
from landscape.client.manager.plugin import ManagerPlugin
from landscape.lib.persist import Persist
from landscape.client.manager.plugin import DataWatcherManager


class UbuntuProInfo(ManagerPlugin):
class UbuntuProInfo(DataWatcherManager):
"""
Plugin that captures and reports Ubuntu Pro registration
information.
Expand All @@ -25,41 +23,10 @@ class UbuntuProInfo(ManagerPlugin):
message_type = "ubuntu-pro-info"
run_interval = 900 # 15 minutes

def register(self, registry):
super().register(registry)
self._persist_filename = Path(
self.registry.config.data_path,
"ubuntu-pro-info.bpickle",
)
self._persist = Persist(filename=self._persist_filename)
self.call_on_accepted(self.message_type, self.send_message)

def run(self):
return self.registry.broker.call_if_accepted(
self.message_type,
self.send_message,
)

def send_message(self):
"""Send a message to the broker if the data has changed since the last
call"""
result = self.get_data()
if not result:
return
message = {"type": self.message_type, "ubuntu-pro-info": result}
return self.registry.broker.send_message(message, self._session_id)

def get_data(self):
"""Persist data to avoid sending messages if result hasn't changed"""
ubuntu_pro_info = get_ubuntu_pro_info()

if self._persist.get("data") != ubuntu_pro_info:
self._persist.set("data", ubuntu_pro_info)
return json.dumps(ubuntu_pro_info, separators=(",", ":"))

def _reset(self):
"""Reset the persist."""
self._persist.remove("data")
return json.dumps(ubuntu_pro_info, separators=(",", ":"),
sort_keys=True)


def get_ubuntu_pro_info() -> dict:
Expand Down
1 change: 0 additions & 1 deletion landscape/client/monitor/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"SwiftUsage",
"CephUsage",
"ComputerTags",
"LivePatch",
"UbuntuProRebootRequired",
"SnapServicesMonitor",
]
Expand Down

0 comments on commit 708d166

Please sign in to comment.