Greg Padgett has uploaded a new change for review. Change subject: agent, broker: api to retrieve host status ......................................................................
agent, broker: api to retrieve host status Add client library with api to retrieve host status from storage. This required some refactoring to move selected agent logic to a shared location in the source tree. Change-Id: I03997f51a4e512d5456f59d6794dce6ce8495287 Signed-off-by: Greg Padgett <[email protected]> --- M configure.ac M ovirt_hosted_engine_ha/Makefile.am M ovirt_hosted_engine_ha/agent/Makefile.am M ovirt_hosted_engine_ha/agent/constants.py.in M ovirt_hosted_engine_ha/agent/hosted_engine.py M ovirt_hosted_engine_ha/broker/constants.py.in M ovirt_hosted_engine_ha/broker/listener.py C ovirt_hosted_engine_ha/client/Makefile.am A ovirt_hosted_engine_ha/client/__init__.py A ovirt_hosted_engine_ha/client/client.py C ovirt_hosted_engine_ha/env/Makefile.am A ovirt_hosted_engine_ha/env/__init__.py R ovirt_hosted_engine_ha/env/config.py R ovirt_hosted_engine_ha/env/constants.py.in A ovirt_hosted_engine_ha/env/path.py M ovirt_hosted_engine_ha/lib/Makefile.am R ovirt_hosted_engine_ha/lib/brokerlink.py M ovirt_hosted_engine_ha/lib/exceptions.py A ovirt_hosted_engine_ha/lib/metadata.py M ovirt_hosted_engine_ha/lib/vds_client.py 20 files changed, 282 insertions(+), 82 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-hosted-engine-ha refs/changes/31/18731/1 diff --git a/configure.ac b/configure.ac index ed845c0..0441336 100644 --- a/configure.ac +++ b/configure.ac @@ -83,6 +83,8 @@ ovirt_hosted_engine_ha/agent/Makefile ovirt_hosted_engine_ha/broker/Makefile ovirt_hosted_engine_ha/broker/submonitors/Makefile + ovirt_hosted_engine_ha/client/Makefile + ovirt_hosted_engine_ha/env/Makefile ovirt_hosted_engine_ha/lib/Makefile sudoers/Makefile ]) diff --git a/ovirt_hosted_engine_ha/Makefile.am b/ovirt_hosted_engine_ha/Makefile.am index b8d97e7..3f6613b 100644 --- a/ovirt_hosted_engine_ha/Makefile.am +++ b/ovirt_hosted_engine_ha/Makefile.am @@ -26,6 +26,8 @@ SUBDIRS = \ agent \ broker \ + client \ + env \ lib \ $(NULL) diff --git a/ovirt_hosted_engine_ha/agent/Makefile.am b/ovirt_hosted_engine_ha/agent/Makefile.am index 45757b8..eab0c1d 100644 --- a/ovirt_hosted_engine_ha/agent/Makefile.am +++ b/ovirt_hosted_engine_ha/agent/Makefile.am @@ -39,8 +39,6 @@ dist_agent_PYTHON = \ __init__.py \ agent.py \ - brokerlink.py \ - config.py \ hosted_engine.py \ $(NULL) diff --git a/ovirt_hosted_engine_ha/agent/constants.py.in b/ovirt_hosted_engine_ha/agent/constants.py.in index a759512..7ab92aa 100644 --- a/ovirt_hosted_engine_ha/agent/constants.py.in +++ b/ovirt_hosted_engine_ha/agent/constants.py.in @@ -48,7 +48,6 @@ LOG_CONF_FILE = '@ENGINE_HA_CONFDIR@/agent-log.conf' LOG_FILE = '@ENGINE_HA_LOGDIR@/agent.log' PID_FILE = '@ENGINE_HA_RUNDIR@/agent.pid' -BROKER_SOCKET_FILE = '@ENGINE_HA_RUNDIR@/broker.socket' ENGINE_SETUP_CONF_FILE = '/etc/ovirt-hosted-engine/hosted-engine.conf' VM_CONF_FILE = '/etc/ovirt-hosted-engine/vm.conf' diff --git a/ovirt_hosted_engine_ha/agent/hosted_engine.py b/ovirt_hosted_engine_ha/agent/hosted_engine.py index 7f8aa0e..2b8aac2 100644 --- a/ovirt_hosted_engine_ha/agent/hosted_engine.py +++ b/ovirt_hosted_engine_ha/agent/hosted_engine.py @@ -26,11 +26,12 @@ import sanlock -from . import brokerlink -from . import config from . import constants +from ..env import config +from ..lib import brokerlink from ..lib import exceptions as ex from ..lib import log_filter +from ..lib import metadata from ..lib import vds_client as vdsc @@ -546,47 +547,15 @@ local_ts = time.time() for host_str, data in all_stats.iteritems(): try: - host_id = int(host_str) - except ValueError: - self._log.error("Malformed metadata:" - " host id '%s' not an integer", host_id) + md = metadata.parse_metadata_to_dict(host_str, data) + except ex.MetadataError as e: + self._log.error( + str(e), + extra=self._get_lf_args(self.LF_MD_ERROR)) continue - if len(data) < 512: - self._log.error("Malformed metadata for host %d:" - " received %d of %d expected bytes", - host_id, len(data), 512) - continue - data = data[:512].rstrip('\0') - tokens = data.split('|') - if len(tokens) < 7: - self._log.error("Malformed metadata for host %d:" - " received %d of %d expected tokens", - host_id, len(tokens), 7) - continue - - try: - md_parse_vers = int(tokens[0]) - except ValueError: - self._log.error("Malformed metadata for host %d:" - " non-parsable metadata version %s", - host_id, tokens[0]) - continue - - if md_parse_vers > constants.METADATA_FEATURE_VERSION: - self._log.error("Metadata version %s for host %s too new for" - " this agent (%s)", md_parse_vers, host_id, - constants.METADATA_FEATURE_VERSION, - extra=self._get_lf_args(self.LF_MD_ERROR)) - continue - - host_ts = int(tokens[2]) - score = int(tokens[4]) - engine_status = str(tokens[5]) # convert from bytearray - hostname = str(tokens[6]) # convert from bytearray - - if host_id not in self._all_host_stats: - self._all_host_stats[host_id] = { + if md['host-id'] not in self._all_host_stats: + self._all_host_stats[md['host-id']] = { 'first-update': True, 'last-update-local-ts': local_ts, 'last-update-host-ts': None, @@ -595,20 +564,25 @@ 'engine-status': None, 'hostname': '(unknown)'} - if host_ts != self._all_host_stats[host_id]['last-update-host-ts']: + if self._all_host_stats[md['host-id']]['last-update-host-ts'] \ + != md['host-ts']: # Track first update in order to accurately judge liveness. # If last-update-host-ts is 0, then first-update stays True # which indicates that we cannot use this last-update-local-ts # as an indication of host liveness. - if self._all_host_stats[host_id]['last-update-host-ts']: - self._all_host_stats[host_id]['first-update'] = False + if self._all_host_stats[md['host-id']]['last-update-host-ts']: + self._all_host_stats[md['host-id']]['first-update'] = False - self._all_host_stats[host_id]['last-update-host-ts'] = host_ts - self._all_host_stats[host_id]['last-update-local-ts'] = \ + self._all_host_stats[md['host-id']]['last-update-host-ts'] = \ + md['host-ts'] + self._all_host_stats[md['host-id']]['last-update-local-ts'] = \ local_ts - self._all_host_stats[host_id]['score'] = score - self._all_host_stats[host_id]['engine-status'] = engine_status - self._all_host_stats[host_id]['hostname'] = hostname + self._all_host_stats[md['host-id']]['score'] = \ + md['score'] + self._all_host_stats[md['host-id']]['engine-status'] = \ + md['engine-status'] + self._all_host_stats[md['host-id']]['hostname'] = \ + md['hostname'] # All updated, now determine if hosts are alive/updating for host_id, attr in self._all_host_stats.iteritems(): diff --git a/ovirt_hosted_engine_ha/broker/constants.py.in b/ovirt_hosted_engine_ha/broker/constants.py.in index de336e9..1ec3fff 100644 --- a/ovirt_hosted_engine_ha/broker/constants.py.in +++ b/ovirt_hosted_engine_ha/broker/constants.py.in @@ -29,7 +29,6 @@ LOG_CONF_FILE = '@ENGINE_HA_CONFDIR@/broker-log.conf' LOG_FILE = '@ENGINE_HA_LOGDIR@/broker.log' PID_FILE = '@ENGINE_HA_RUNDIR@/broker.pid' -SOCKET_FILE = '@ENGINE_HA_RUNDIR@/broker.socket' VDSM_USER = '@VDSM_USER@' VDSM_GROUP = '@VDSM_GROUP@' diff --git a/ovirt_hosted_engine_ha/broker/listener.py b/ovirt_hosted_engine_ha/broker/listener.py index bd729da..44085f9 100644 --- a/ovirt_hosted_engine_ha/broker/listener.py +++ b/ovirt_hosted_engine_ha/broker/listener.py @@ -24,7 +24,7 @@ import SocketServer import threading -from . import constants +from ..env import constants from ..lib import util from ..lib.exceptions import DisconnectionError from ..lib.exceptions import RequestError @@ -47,10 +47,10 @@ self._storage_broker_instance_access_lock = threading.Lock() self._need_exit = False - if os.path.exists(constants.SOCKET_FILE): - os.unlink(constants.SOCKET_FILE) + if os.path.exists(constants.BROKER_SOCKET_FILE): + os.unlink(constants.BROKER_SOCKET_FILE) self._server = ThreadedStreamServer( - constants.SOCKET_FILE, ConnectionHandler, True, self) + constants.BROKER_SOCKET_FILE, ConnectionHandler, True, self) self._log.info("SocketServer ready") diff --git a/ovirt_hosted_engine_ha/lib/constants.py.in b/ovirt_hosted_engine_ha/client/Makefile.am similarity index 67% copy from ovirt_hosted_engine_ha/lib/constants.py.in copy to ovirt_hosted_engine_ha/client/Makefile.am index c7e2301..cd123e2 100644 --- a/ovirt_hosted_engine_ha/lib/constants.py.in +++ b/ovirt_hosted_engine_ha/client/Makefile.am @@ -1,6 +1,6 @@ # # ovirt-hosted-engine-ha -- ovirt hosted engine high availability -# Copyright (C) 2013 Red Hat, Inc. +# Copyright (C) 2012-2013 Red Hat, Inc. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -17,8 +17,26 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # -"""Constants.""" +include $(top_srcdir)/build/python.inc -VDS_CLIENT_DIR = '/usr/share/vdsm' -VDS_CLIENT_SSL = True -VDS_CLIENT_MAX_RETRY = 3 +MAINTAINERCLEANFILES = \ + $(srcdir)/Makefile.in \ + $(NULL) +CLEANFILES = \ + $(NULL) + +haclientdir = $(engine_ha_libdir)/client + +dist_haclient_PYTHON = \ + __init__.py \ + client.py \ + $(NULL) + +clean-local: \ + python-clean \ + $(NULL) + +all-local: \ + $(DISTFILES) \ + python-syntax-check \ + $(NULL) diff --git a/ovirt_hosted_engine_ha/client/__init__.py b/ovirt_hosted_engine_ha/client/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ovirt_hosted_engine_ha/client/__init__.py diff --git a/ovirt_hosted_engine_ha/client/client.py b/ovirt_hosted_engine_ha/client/client.py new file mode 100644 index 0000000..ca27061 --- /dev/null +++ b/ovirt_hosted_engine_ha/client/client.py @@ -0,0 +1,56 @@ +# +# ovirt-hosted-engine-ha -- ovirt hosted engine high availability +# Copyright (C) 2013 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + +import logging + +from ..env import config +from ..env import constants +from ..env import path +from ..lib import brokerlink +from ..lib import metadata +from ..lib.exceptions import MetadataError + + +class HAClient(object): + def __init__(self): + self._log = logging.getLogger("HAClient") + self._config = config.Config() + + def get_all_host_stats(self): + """ + Connects to HA broker, reads stats for all hosts, and returns + them in a dictionary as {host_id: = {key: value, ...}} + """ + broker = brokerlink.BrokerLink() + broker.connect() + stats = broker.get_stats_from_storage( + path.get_metadata_path(self._config), + constants.SERVICE_TYPE) + broker.disconnect() + + output = {} + for host_str, data in stats.iteritems(): + try: + md = metadata.parse_metadata_to_dict(host_str, data) + except MetadataError as e: + self._log.error(str(e)) + continue + else: + output[md['host-id']] = md + return output diff --git a/ovirt_hosted_engine_ha/lib/constants.py.in b/ovirt_hosted_engine_ha/env/Makefile.am similarity index 60% copy from ovirt_hosted_engine_ha/lib/constants.py.in copy to ovirt_hosted_engine_ha/env/Makefile.am index c7e2301..2ccd1d0 100644 --- a/ovirt_hosted_engine_ha/lib/constants.py.in +++ b/ovirt_hosted_engine_ha/env/Makefile.am @@ -1,6 +1,6 @@ # # ovirt-hosted-engine-ha -- ovirt hosted engine high availability -# Copyright (C) 2013 Red Hat, Inc. +# Copyright (C) 2012-2013 Red Hat, Inc. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -17,8 +17,37 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # -"""Constants.""" +include $(top_srcdir)/build/python.inc +include $(top_srcdir)/build/var_subst.inc -VDS_CLIENT_DIR = '/usr/share/vdsm' -VDS_CLIENT_SSL = True -VDS_CLIENT_MAX_RETRY = 3 +MAINTAINERCLEANFILES = \ + $(srcdir)/Makefile.in \ + $(NULL) +CLEANFILES = \ + constants.py \ + $(NULL) + +haenvdir = $(engine_ha_libdir)/env + +dist_haenv_PYTHON = \ + __init__.py \ + config.py \ + path.py \ + $(NULL) + +haenv_PYTHON = \ + constants.py \ + $(NULL) + +EXTRA_DIST = \ + constants.py.in \ + $(NULL) + +clean-local: \ + python-clean \ + $(NULL) + +all-local: \ + $(DISTFILES) \ + python-syntax-check \ + $(NULL) diff --git a/ovirt_hosted_engine_ha/env/__init__.py b/ovirt_hosted_engine_ha/env/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ovirt_hosted_engine_ha/env/__init__.py diff --git a/ovirt_hosted_engine_ha/agent/config.py b/ovirt_hosted_engine_ha/env/config.py similarity index 100% rename from ovirt_hosted_engine_ha/agent/config.py rename to ovirt_hosted_engine_ha/env/config.py diff --git a/ovirt_hosted_engine_ha/lib/constants.py.in b/ovirt_hosted_engine_ha/env/constants.py.in similarity index 68% rename from ovirt_hosted_engine_ha/lib/constants.py.in rename to ovirt_hosted_engine_ha/env/constants.py.in index c7e2301..13e6694 100644 --- a/ovirt_hosted_engine_ha/lib/constants.py.in +++ b/ovirt_hosted_engine_ha/env/constants.py.in @@ -19,6 +19,21 @@ """Constants.""" +METADATA_FEATURE_VERSION = 1 +METADATA_PARSE_VERSION = 1 + +METADATA_BLOCK_BYTES = 512 +SERVICE_TYPE = 'hosted-engine' + +# See http://www.gnu.org/software/automake/manual/html_node/Scripts.html +BROKER_SOCKET_FILE = '@ENGINE_HA_RUNDIR@/broker.socket' + +ENGINE_SETUP_CONF_FILE = '/etc/ovirt-hosted-engine/hosted-engine.conf' +VM_CONF_FILE = '/etc/ovirt-hosted-engine/vm.conf' + +SD_MOUNT_PARENT = '/rhev/data-center/mnt' +SD_METADATA_DIR = 'ha_agent' + VDS_CLIENT_DIR = '/usr/share/vdsm' VDS_CLIENT_SSL = True VDS_CLIENT_MAX_RETRY = 3 diff --git a/ovirt_hosted_engine_ha/env/path.py b/ovirt_hosted_engine_ha/env/path.py new file mode 100644 index 0000000..7ee0904 --- /dev/null +++ b/ovirt_hosted_engine_ha/env/path.py @@ -0,0 +1,45 @@ +# +# ovirt-hosted-engine-ha -- ovirt hosted engine high availability +# Copyright (C) 2013 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + +import os + +from . import config +from . import constants + + +def get_domain_path(config_): + """ + Return path of storage domain holding engine vm + """ + sd_uuid = config_.get(config.ENGINE, config.SD_UUID) + parent = constants.SD_MOUNT_PARENT + for dname in os.listdir(parent): + path = os.path.join(parent, dname, sd_uuid) + if os.access(path, os.F_OK): + return path + raise Exception("path to storage domain {0} not found in {1}" + .format(sd_uuid, parent)) + + +def get_metadata_path(config_): + """ + Return path to ha agent metadata + """ + return os.path.join(get_domain_path(config_), + constants.SD_METADATA_DIR) diff --git a/ovirt_hosted_engine_ha/lib/Makefile.am b/ovirt_hosted_engine_ha/lib/Makefile.am index 8ebc2fc..a2a99ba 100644 --- a/ovirt_hosted_engine_ha/lib/Makefile.am +++ b/ovirt_hosted_engine_ha/lib/Makefile.am @@ -18,31 +18,21 @@ # include $(top_srcdir)/build/python.inc -include $(top_srcdir)/build/var_subst.inc MAINTAINERCLEANFILES = \ $(srcdir)/Makefile.in \ - $(NULL) -CLEANFILES = \ - constants.py \ $(NULL) halibdir = $(engine_ha_libdir)/lib dist_halib_PYTHON = \ __init__.py \ + brokerlink.py \ exceptions.py \ log_filter.py \ + metadata.py \ util.py \ vds_client.py \ - $(NULL) - -halib_PYTHON = \ - constants.py \ - $(NULL) - -EXTRA_DIST = \ - constants.py.in \ $(NULL) clean-local: \ diff --git a/ovirt_hosted_engine_ha/agent/brokerlink.py b/ovirt_hosted_engine_ha/lib/brokerlink.py similarity index 98% rename from ovirt_hosted_engine_ha/agent/brokerlink.py rename to ovirt_hosted_engine_ha/lib/brokerlink.py index 20ce7b7..ee2749c 100644 --- a/ovirt_hosted_engine_ha/agent/brokerlink.py +++ b/ovirt_hosted_engine_ha/lib/brokerlink.py @@ -21,7 +21,7 @@ import logging import socket -from . import constants +from ..env import constants from ..lib.exceptions import DisconnectionError from ..lib.exceptions import RequestError from ..lib import util @@ -114,7 +114,7 @@ tokens = result.split() ret = {} - # broker returns "<host 1>=<hex data 1> [<host 2>=...]" + # broker returns "<host_id 1>=<hex data 1> [<host_id 2>=...]" while tokens: (host_id, data) = tokens.pop(0).split('=', 1) ret[host_id] = base64.b16decode(data) diff --git a/ovirt_hosted_engine_ha/lib/exceptions.py b/ovirt_hosted_engine_ha/lib/exceptions.py index 3810930..c5563c0 100644 --- a/ovirt_hosted_engine_ha/lib/exceptions.py +++ b/ovirt_hosted_engine_ha/lib/exceptions.py @@ -37,3 +37,7 @@ def __init__(self, msg, detail): Exception.__init__(self, msg) self.detail = detail + + +class MetadataError(Exception): + pass diff --git a/ovirt_hosted_engine_ha/lib/metadata.py b/ovirt_hosted_engine_ha/lib/metadata.py new file mode 100644 index 0000000..f0746f7 --- /dev/null +++ b/ovirt_hosted_engine_ha/lib/metadata.py @@ -0,0 +1,69 @@ +# +# ovirt-hosted-engine-ha -- ovirt hosted engine high availability +# Copyright (C) 2013 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + +from ..env import constants +from exceptions import MetadataError + + +def parse_metadata_to_dict(host_str, data): + try: + host_id = int(host_str) + except ValueError: + raise MetadataError("Malformed metadata:" + " host id '{0}' not an integer" + .format(host_id)) + + if len(data) < 512: + raise MetadataError("Malformed metadata for host {0}:" + " received {1} of {2} expected bytes" + .format(host_id, len(data), 512)) + + tokens = data[:512].rstrip('\0').split('|') + if len(tokens) < 7: + raise MetadataError("Malformed metadata for host {0}:" + " received {1} of {2} expected tokens" + .format(host_id, len(tokens), 7)) + + try: + md_parse_vers = int(tokens[0]) + except ValueError: + raise MetadataError("Malformed metadata for host {0}:" + " non-parsable metadata version {1}" + .format(host_id, tokens[0])) + + if md_parse_vers > constants.METADATA_FEATURE_VERSION: + raise MetadataError("Metadata version {0} for host {1}" + " too new for this agent ({2})" + .format(md_parse_vers, host_id, + constants.METADATA_FEATURE_VERSION)) + + ret = { + 'host-id': host_id, + 'host-ts': int(tokens[2]), + 'score': int(tokens[4]), + 'engine-status': str(tokens[5]), + 'hostname': str(tokens[6]), + } + + # Add human-readable summary from bytes 512+ + extra = data[512:].rstrip('\0') + if len(extra): + ret['extra'] = extra + + return ret diff --git a/ovirt_hosted_engine_ha/lib/vds_client.py b/ovirt_hosted_engine_ha/lib/vds_client.py index d3d5ac7..d876ad4 100644 --- a/ovirt_hosted_engine_ha/lib/vds_client.py +++ b/ovirt_hosted_engine_ha/lib/vds_client.py @@ -24,8 +24,8 @@ from otopi import util from vdsm import vdscli -import constants from exceptions import DetailedError +from ..env import constants def run_vds_client_cmd(address, use_ssl, command, *args): -- To view, visit http://gerrit.ovirt.org/18731 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I03997f51a4e512d5456f59d6794dce6ce8495287 Gerrit-PatchSet: 1 Gerrit-Project: ovirt-hosted-engine-ha Gerrit-Branch: master Gerrit-Owner: Greg Padgett <[email protected]> _______________________________________________ Engine-patches mailing list [email protected] http://lists.ovirt.org/mailman/listinfo/engine-patches
