Validate commit_id sent by controller on deploy host

This commit adds a validation on software agent, to check if
the commit_id sent by software controller on deploy host matches
with the remote commit_id.

Test Plan:
PASS: run "deploy host" successfully
PASS: simulate commit_id divergence, observe deploy host
      is blocked, remote is removed and error message is logged

Story: 2010676
Task: 49904

Signed-off-by: Heitor Matsui <heitorvieira.matsui@windriver.com>
Change-Id: I75176ada3a9534ac2812ecdff366d925e438a836
This commit is contained in:
Heitor Matsui 2024-04-19 11:30:06 -03:00
parent 24016bd363
commit 60898e7ae2
3 changed files with 64 additions and 9 deletions

View File

@ -546,3 +546,37 @@ def add_ostree_remote(major_release, nodetype):
LOG.exception("Error adding %s ostree remote: %s" % (major_release, str(e)))
raise
return rel_name
def delete_ostree_remote(remote):
"""
Delete an ostree remote
:param remote: remote name to be deleted
"""
cmd = ["ostree", "remote", "delete", "--if-exists", remote]
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError as e:
LOG.exception("Error deleting %s ostree remote: %s" % (remote, str(e)))
def check_commit_id(remote, commit_id):
"""
Check if commit_id matches with the commit_id from the remote ostree_repo
:param remote: ostree remote name to be checked against
:param commit_id: commit_id sent by the controller to the agent
:return: boolean indicating if commit_id matches with remote commit_id
"""
commit_id_match = False
cmd = "ostree remote summary %s | grep 'Latest Commit' -A1" % remote
try:
# output should be similar to:
# Latest Commit (<n> bytes):
# <ostree_commit_id>
output = subprocess.check_output(cmd, shell=True, text=True,
stderr=subprocess.STDOUT).strip()
remote_commit_id = output.split("\n")[1].strip()
commit_id_match = commit_id == remote_commit_id
except subprocess.CalledProcessError as e:
LOG.exception("Error getting remote commit_id: %s: %s" % (str(e), e.stdout))
return commit_id_match

View File

@ -340,6 +340,7 @@ class PatchMessageAgentInstallReq(messages.PatchMessage):
messages.PatchMessage.__init__(self, messages.PATCHMSG_AGENT_INSTALL_REQ)
self.force = False
self.major_release = None
self.commit_id = None
def decode(self, data):
messages.PatchMessage.decode(self, data)
@ -347,13 +348,16 @@ class PatchMessageAgentInstallReq(messages.PatchMessage):
self.force = data['force']
if 'major_release' in data:
self.major_release = data['major_release']
if 'commit_id' in data:
self.commit_id = data['commit_id']
def encode(self):
# Nothing to add to the HELLO_AGENT, so just call the super class
messages.PatchMessage.encode(self)
def handle(self, sock, addr):
LOG.info("Handling host install request, force=%s, major_release=%s", self.force, self.major_release)
LOG.info("Handling host install request, force=%s, major_release=%s, commit_id=%s",
self.force, self.major_release, self.commit_id)
global pa
resp = PatchMessageAgentInstallResp()
@ -371,7 +375,7 @@ class PatchMessageAgentInstallReq(messages.PatchMessage):
resp.reject_reason = 'Node must be locked.'
resp.send(sock, addr)
return
resp.status = pa.handle_install(major_release=self.major_release)
resp.status = pa.handle_install(major_release=self.major_release, commit_id=self.commit_id)
resp.send(sock, addr)
def send(self, sock): # pylint: disable=unused-argument
@ -474,10 +478,8 @@ class PatchAgent(PatchService):
self.latest_sysroot_commit = active_sysroot_commit
self.last_repo_revision = active_sysroot_commit
# checks if this is a major release deployment operation
if major_release:
upgrade_feed_commit = ostree_utils.get_feed_latest_commit(major_release)
LOG.info("Major release deployment for %s with commit %s" % (major_release,
upgrade_feed_commit))
self.changes = True
return True
@ -504,7 +506,8 @@ class PatchAgent(PatchService):
verbose_to_stdout=False,
disallow_insvc_patch=False,
delete_older_deployments=False,
major_release=None):
major_release=None,
commit_id=None):
#
# The disallow_insvc_patch parameter is set when we're installing
# the patch during init. At that time, we don't want to deal with
@ -558,10 +561,23 @@ class PatchAgent(PatchService):
remote = None
ref = None
if major_release:
LOG.info("Major release deployment for %s with commit %s" % (major_release, commit_id))
# add remote
nodetype = utils.get_platform_conf("nodetype")
remote = ostree_utils.add_ostree_remote(major_release, nodetype)
ref = "%s:%s" % (remote, constants.OSTREE_REF)
LOG.info("OSTree remote added: %s" % remote)
# check if remote commit_id matches with the one sent by the controller
commit_id_match = ostree_utils.check_commit_id(remote, commit_id)
if not commit_id_match:
LOG.exception("The OSTree commit_id %s sent by the controller "
"doesn't match with the remote commit_id." % commit_id)
ostree_utils.delete_ostree_remote(remote)
LOG.info("OSTree remote deleted: %s" % remote)
return False
ref = "%s:%s" % (remote, constants.OSTREE_REF)
copy_target_release_pxeboot_files(major_release)
# Build up the install set

View File

@ -521,12 +521,14 @@ class PatchMessageAgentInstallReq(messages.PatchMessage):
self.ip = None
self.force = False
self.major_release = None
self.commit_id = None
def encode(self):
global sc
messages.PatchMessage.encode(self)
self.message['force'] = self.force
self.message['major_release'] = self.major_release
self.message['commit_id'] = self.commit_id
def handle(self, sock, addr):
LOG.error("Should not get here")
@ -2735,13 +2737,15 @@ class PatchController(PatchService):
# Check if there is a major release deployment in progress
# and set agent request parameters accordingly
major_release = None
commit_id = None
upgrade_in_progress = self.get_software_upgrade()
if upgrade_in_progress:
major_release = upgrade_in_progress["to_release"]
commit_id = ostree_utils.get_feed_latest_commit(major_release)
force = False
async_req = False
msg = "Running major release deployment, major_release=%s, force=%s, async_req=%s" % (
major_release, force, async_req)
msg = "Running major release deployment, major_release=%s, force=%s, async_req=%s, commit_id=%s" % (
major_release, force, async_req, commit_id)
msg_info += msg + "\n"
LOG.info(msg)
set_host_target_load(hostname, major_release)
@ -2757,6 +2761,7 @@ class PatchController(PatchService):
installreq.ip = ip
installreq.force = force
installreq.major_release = major_release
installreq.commit_id = commit_id
installreq.encode()
self.socket_lock.acquire()
installreq.send(self.sock_out)