Search
j0ke.net Open Build Service
>
Projects
>
internetx
:
projects
:
virtualization
>
rhevm-guest-agent
> 0012-agent-Implement-basics-for-API-Versioning.patch
Sign Up
|
Log In
Username
Password
Cancel
Overview
Repositories
Revisions
Requests
Users
Advanced
Attributes
Meta
File 0012-agent-Implement-basics-for-API-Versioning.patch of Package rhevm-guest-agent
From f2d1493440b562ad1d3b32189a3dae111a9a84b6 Mon Sep 17 00:00:00 2001 From: Vinzenz Feenstra <vfeenstr@redhat.com> Date: Wed, 17 Jul 2013 11:05:51 +0200 Subject: [PATCH 1/3] agent: Implement basics for API Versioning This commit introduces the basics needed to support API versioning between the ovirt-guest-agent and VDSM. How the negotiation works: - VDSM has as a default the apiVersion value set to _DISABLED_API_VALUE marking it as unsupported - The guest agent sends its max supported version with every `heartbeat` message - VDSM checks that `apiVersion` exists in the `heartbeat` message arguments - If it exists it retrieves the minimum common version and sends the `api-version` message with the common version as `apiVersion` argument - If it does NOT exist it and the apiVersion is not set to _DISABLED_API_VALUE it will set it to _DISABLED_API_VALUE, with the meaning that it is not supported at all by the guest agent on the other end. - The guest agent on receiving this message makes it own check and sets the `apiVersion` to the commonVersion - If VDSM sends the `refresh` command it also sends its `apiVersion` value however if it does not, capable guest agents are disabling the versioning support and will know it in pre-supported state as well. NOTE: VDSM will never send an api-version message without being triggered. This is to ensure backwards compatibility. Change-Id: I754d52009538914dd0143894b24ad48fbf13cb38 Signed-off-by: Vinzenz Feenstra <vfeenstr@redhat.com> --- ovirt-guest-agent/OVirtAgentLogic.py | 116 ++++++++++++++++++++++++++++------- tests/guest_agent_test.py | 10 +++ tests/message_validator.py | 68 +++++++++++++++++++- 3 files changed, 170 insertions(+), 24 deletions(-) diff --git a/ovirt-guest-agent/OVirtAgentLogic.py b/ovirt-guest-agent/OVirtAgentLogic.py index 52c3825..1c8fb30 100644 --- a/ovirt-guest-agent/OVirtAgentLogic.py +++ b/ovirt-guest-agent/OVirtAgentLogic.py @@ -24,6 +24,27 @@ import socket from threading import Event from VirtIoChannel import VirtIoChannel +_MAX_SUPPORTED_API_VERSION = 0 +_DISABLED_API_VALUE = 0 + +_MESSAGE_MIN_API_VERSION = { + 'active-user': 0, + 'applications': 0, + 'disks-usage': 0, + 'echo': 0, + 'fqdn': 0, + 'heartbeat': 0, + 'host-name': 0, + 'memory-stats': 0, + 'network-interfaces': 0, + 'os-version': 0, + 'session-lock': 0, + 'session-logoff': 0, + 'session-logon': 0, + 'session-shutdown': 0, + 'session-startup': 0, + 'session-unlock': 0} + # Return a safe (password masked) repr of the credentials block. def safe_creds_repr(creds): @@ -36,6 +57,7 @@ def safe_creds_repr(creds): class DataRetriverBase: def __init__(self): + self.apiVersion = _DISABLED_API_VALUE self.memStats = { 'mem_total': 0, 'mem_free': 0, @@ -45,6 +67,39 @@ class DataRetriverBase: 'pageflt': 0, 'majflt': 0} + def onAPIVersionUpdated(self, oldVersion, newVersion): + pass + + def getAPIVersion(self): + return self.apiVersion + + def setAPIVersion(self, version): + oldVersion = self.apiVersion + try: + version = int(version) + except ValueError: + logging.info("Invalid api version value '%s' set. Version value " + "not changed.", version) + return + + if _MAX_SUPPORTED_API_VERSION < version: + logging.debug("API version requested (%d) higher than known (%d). " + "Using max known version.", version, + _MAX_SUPPORTED_API_VERSION) + version = _MAX_SUPPORTED_API_VERSION + + if version == self.apiVersion: + logging.debug("API version %d already set, no update necessary", + version) + return + self.apiVersion = version + + logging.info("API Version updated from %d to %d", oldVersion, version) + try: + self.onAPIVersionUpdated(oldVersion, version) + except Exception: + logging.exception("onAPIVersionUpdated failed") + def getMachineName(self): pass @@ -91,6 +146,16 @@ class AgentLogicBase: self.dr = None self.commandHandler = None + def _send(self, name, arguments=None): + version = _MESSAGE_MIN_API_VERSION.get(name, None) + if version is None: + logging.error('Undocumented message "%s"', name) + elif version <= self.dr.getAPIVersion(): + self.vio.write(name, arguments or {}) + else: + logging.debug("Message %s not supported by api version %d.", + name, self.dr.getAPIVersion()) + def run(self): logging.debug("AgentLogicBase:: run() entered") thread.start_new_thread(self.doListen, ()) @@ -125,9 +190,10 @@ class AgentLogicBase: counter += 1 hbsecs -= 1 if hbsecs <= 0: - self.vio.write('heartbeat', - {'free-ram': self.dr.getAvailableRAM(), - 'memory-stat': self.dr.getMemoryStats()}) + self._send('heartbeat', + {'free-ram': self.dr.getAvailableRAM(), + 'memory-stat': self.dr.getMemoryStats(), + 'apiVersion': _MAX_SUPPORTED_API_VERSION}) hbsecs = self.heartBitRate usersecs -= 1 if usersecs <= 0: @@ -165,12 +231,17 @@ class AgentLogicBase: 'channel.') logging.debug("AgentLogicBase::doListen() - exiting") + def _onApiVersion(self, args): + self.dr.setAPIVersion(args['apiVersion']) + def parseCommand(self, command, args): logging.info("Received an external command: %s..." % (command)) if command == 'lock-screen': self.commandHandler.lock_screen() elif command == 'log-off': self.commandHandler.logoff() + elif command == 'api-version': + self._onApiVersion(args) elif command == 'shutdown': try: timeout = int(args['timeout']) @@ -201,6 +272,10 @@ class AgentLogicBase: % (safe_creds_repr(credentials))) self.commandHandler.login(credentials) elif command == 'refresh': + if not 'apiVersion' in args and self.dr.getAPIVersion() > 0: + logging.info('API versioning not supported by VDSM. Disabling ' + 'versioning support.') + self.dr.setAPIVersion(_DISABLED_API_VALUE) self.sendUserInfo(True) self.sendAppList() self.sendInfo() @@ -208,7 +283,7 @@ class AgentLogicBase: self.sendFQDN() elif command == 'echo': logging.debug("Echo: %s", args) - self.vio.write('echo', args) + self._send('echo', args) elif command == 'hibernate': state = args.get('state', 'disk') self.commandHandler.hibernate(state) @@ -217,31 +292,30 @@ class AgentLogicBase: % (command, args)) def sendFQDN(self): - self.vio.write('fqdn', {'fqdn': self.dr.getFQDN()}) + self._send('fqdn', {'fqdn': self.dr.getFQDN()}) def sendUserInfo(self, force=False): cur_user = str(self.dr.getActiveUser()) logging.debug("AgentLogicBase::sendUserInfo - cur_user = '%s'" % (cur_user)) if cur_user != self.activeUser or force: - self.vio.write('active-user', {'name': cur_user}) + self._send('active-user', {'name': cur_user}) self.activeUser = cur_user def sendInfo(self): - self.vio.write('host-name', {'name': self.dr.getMachineName()}) - self.vio.write('os-version', {'version': self.dr.getOsVersion()}) - self.vio.write('network-interfaces', - {'interfaces': self.dr.getAllNetworkInterfaces()}) + self._send('host-name', {'name': self.dr.getMachineName()}) + self._send('os-version', {'version': self.dr.getOsVersion()}) + self._send('network-interfaces', + {'interfaces': self.dr.getAllNetworkInterfaces()}) def sendAppList(self): - self.vio.write('applications', - {'applications': self.dr.getApplications()}) + self._send('applications', {'applications': self.dr.getApplications()}) def sendDisksUsages(self): - self.vio.write('disks-usage', {'disks': self.dr.getDisksUsage()}) + self._send('disks-usage', {'disks': self.dr.getDisksUsage()}) def sendMemoryStats(self): - self.vio.write('memory-stats', {'memory': self.dr.getMemoryStats()}) + self._send('memory-stats', {'memory': self.dr.getMemoryStats()}) def sessionLogon(self): logging.debug("AgentLogicBase::sessionLogon: user logs on the system.") @@ -252,29 +326,29 @@ class AgentLogicBase: cur_user = self.dr.getActiveUser() retries = retries + 1 self.sendUserInfo() - self.vio.write('session-logon') + self._send('session-logon') def sessionLogoff(self): logging.debug("AgentLogicBase::sessionLogoff: " "user logs off from the system.") self.activeUser = 'None' - self.vio.write('session-logoff') - self.vio.write('active-user', {'name': self.activeUser}) + self._send('session-logoff') + self._send('active-user', {'name': self.activeUser}) def sessionLock(self): logging.debug("AgentLogicBase::sessionLock: " "user locks the workstation.") - self.vio.write('session-lock') + self._send('session-lock') def sessionUnlock(self): logging.debug("AgentLogicBase::sessionUnlock: " "user unlocks the workstation.") - self.vio.write('session-unlock') + self._send('session-unlock') def sessionStartup(self): logging.debug("AgentLogicBase::sessionStartup: system starts up.") - self.vio.write('session-startup') + self._send('session-startup') def sessionShutdown(self): logging.debug("AgentLogicBase::sessionShutdown: system shuts down.") - self.vio.write('session-shutdown') + self._send('session-shutdown') diff --git a/tests/guest_agent_test.py b/tests/guest_agent_test.py index b3939ad..bd4a7f4 100644 --- a/tests/guest_agent_test.py +++ b/tests/guest_agent_test.py @@ -56,6 +56,10 @@ class FunctionalTest(GuestAgentTestCase): self.vdsAgent = agent_class(self._config) + def testRefresh(self): + self._validator.verifyRefreshReply(self.vdsAgent) + self._validator.verifyRefreshReply2(self.vdsAgent) + def testSendInfo(self): self._validator.verifySendInfo(self.vdsAgent) @@ -91,3 +95,9 @@ class FunctionalTest(GuestAgentTestCase): def testSessionShutdown(self): self._validator.verifySessionShutdown(self.vdsAgent) + + def testAPIVersion(self): + self._validator.verifyAPIVersion(self.vdsAgent) + + def testAPIVersion2(self): + self._validator.verifyAPIVersion2(self.vdsAgent) diff --git a/tests/message_validator.py b/tests/message_validator.py index 95c379d..20954b9 100644 --- a/tests/message_validator.py +++ b/tests/message_validator.py @@ -5,6 +5,7 @@ import test_port import json import logging +import OVirtAgentLogic class TestPortWriteBuffer(test_port.TestPort): @@ -23,19 +24,39 @@ class TestPortWriteBuffer(test_port.TestPort): self._buffer = '' +def _ensure_no_messages(f): + def fun(self, *args, **kwargs): + result = f(self, *args, **kwargs) + parsed = self._get_messages() + assert(len(parsed) == 0) + return result + return fun + + +def assertIn(m, n): + if not m in n: + raise Exception("%s not in %s" % (m, str(n))) + + +def assertEqual(a, b, msg=None): + if a != b: + raise Exception(msg or '%s != %s' % (str(a), str(b))) + + def _ensure_messages(*messages): def wrapped(f): def fun(self, *args, **kwargs): result = f(self, *args, **kwargs) names = [] parsed = self._get_messages() - assert(len(parsed) == len(messages)) for m in parsed: - assert('__name__' in m) + assertIn('__name__', m) names.append(m['__name__']) self._check(m) for m in messages: - assert(m in names) + assertIn(m, names) + for n in names: + assertIn(n, messages) return result return fun return wrapped @@ -69,6 +90,13 @@ def _name_and_one_str_param(msg_name, param_name): return wrapped +def _name_and_one_integral_param(msg_name, param_name): + def wrapped(o): + assert(o['__name__'] == msg_name) + assert_integral_param(o, param_name) + return wrapped + + def assert_is_string_list(o): assert(isinstance(o, list)) for s in o: @@ -134,6 +162,7 @@ _MSG_VALIDATORS = { 'session-shutdown': _name_only('session-shutdown'), 'session-startup': _name_only('session-startup'), 'session-unlock': _name_only('session-unlock'), + 'api-version': _name_and_one_integral_param('api-version', 'apiVersion') } @@ -208,3 +237,36 @@ class MessageValidator(object): @_ensure_messages('session-shutdown') def verifySessionShutdown(self, agent): agent.sessionShutdown() + + def verifyAPIVersion(self, agent): + # If not yet activated, monkey patch to support the versioning + if OVirtAgentLogic._MAX_SUPPORTED_API_VERSION == 0: + OVirtAgentLogic._MAX_SUPPORTED_API_VERSION = 1 + # Pretend VDSM told us it would support a higher version + useVersion = OVirtAgentLogic._MAX_SUPPORTED_API_VERSION + 1 + agent._onApiVersion({'apiVersion': useVersion}) + + def verifyAPIVersion2(self, agent): + if OVirtAgentLogic._MAX_SUPPORTED_API_VERSION == 0: + OVirtAgentLogic._MAX_SUPPORTED_API_VERSION = 1 + # Pretend VDSM told us nothing or 0 + agent.dr.setAPIVersion(0) + + @_ensure_messages('applications', 'host-name', 'os-version', 'active-user', + 'network-interfaces', 'disks-usage', 'fqdn') + def verifyRefreshReply(self, agent): + # If not yet activated, monkey patch to support the versioning + if OVirtAgentLogic._MAX_SUPPORTED_API_VERSION == 0: + OVirtAgentLogic._MAX_SUPPORTED_API_VERSION = 1 + agent.dr.setAPIVersion(1) + assert(agent.dr.getAPIVersion() == 1) + agent.parseCommand('refresh', {'apiVersion': 1}) + assert(agent.dr.getAPIVersion() == 1) + + @_ensure_messages('applications', 'host-name', 'os-version', 'active-user', + 'network-interfaces', 'disks-usage', 'fqdn') + def verifyRefreshReply2(self, agent): + agent.dr.setAPIVersion(1) + assert(agent.dr.getAPIVersion() == 1) + agent.parseCommand('refresh', {}) + assert(agent.dr.getAPIVersion() == 0) -- 1.9.0