Vinzenz Feenstra has uploaded a new change for review.
Change subject: agent: Implement basics for API Versioning
......................................................................
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 <[email protected]>
---
M ovirt-guest-agent/OVirtAgentLogic.py
M tests/guest_agent_test.py
M tests/message_validator.py
3 files changed, 170 insertions(+), 24 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/ovirt-guest-agent
refs/changes/31/27031/1
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 @@
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 @@
class DataRetriverBase:
def __init__(self):
+ self.apiVersion = _DISABLED_API_VALUE
self.memStats = {
'mem_total': 0,
'mem_free': 0,
@@ -44,6 +66,39 @@
'swap_out': 0,
'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 @@
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 @@
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 @@
'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 @@
% (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 @@
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 @@
% (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 @@
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 @@
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 @@
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 @@
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
@@ -66,6 +87,13 @@
def wrapped(o):
assert(o['__name__'] == msg_name)
assert_string_param(o, 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
@@ -134,6 +162,7 @@
'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 @@
@_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)
--
To view, visit http://gerrit.ovirt.org/27031
To unsubscribe, visit http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I754d52009538914dd0143894b24ad48fbf13cb38
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-guest-agent
Gerrit-Branch: ovirt-3.4
Gerrit-Owner: Vinzenz Feenstra <[email protected]>
_______________________________________________
Engine-patches mailing list
[email protected]
http://lists.ovirt.org/mailman/listinfo/engine-patches