"software install-local" implementation for USM

This commit enables "software install-local" in a Debian
env for a patch.

Test Plan:
[PASS] Verify software install-local installs the patch
[PASS] Verify state machine for install-local workflow

Story: 2010676
Task: 48607
Change-Id: I6fd44653210cecdc0e3e1131bfd4619a756556c8
Signed-off-by: Jessica Castelino <jessica.castelino@windriver.com>
This commit is contained in:
Jessica Castelino 2023-08-14 14:13:46 +00:00
parent a4bf3a1dc8
commit 0eb68e8416
3 changed files with 139 additions and 10 deletions

View File

@ -96,6 +96,16 @@ class SoftwareAPIController(object):
return result
@expose('json')
@expose('query.xml', content_type='application/xml')
def install_local(self):
try:
result = sc.software_install_local_api()
except SoftwareError as e:
return dict(error="Error: %s" % str(e))
return result
@expose('json')
def is_completed(self, *args):
return sc.is_completed(list(args))

View File

@ -12,6 +12,7 @@ import os
import re
import requests
import signal
import software.constants as constants
import subprocess
import sys
import textwrap
@ -20,8 +21,6 @@ import time
from requests_toolbelt import MultipartEncoder
from urllib.parse import urlparse
import software.constants as constants
from tsconfig.tsconfig import SW_VERSION as RUNNING_SW_VERSION
api_addr = "127.0.0.1:5493"
@ -716,6 +715,24 @@ def drop_host(args):
return check_rc(req)
def install_local(args): # pylint: disable=unused-argument
# Ignore interrupts during this function
signal.signal(signal.SIGINT, signal.SIG_IGN)
url = "http://%s/software/install_local" % (api_addr)
headers = {}
append_auth_token_if_required(headers)
req = requests.get(url, headers=headers)
if args.debug:
print_result_debug(req)
else:
print_software_op_result(req)
return check_rc(req)
def release_upload_dir_req(args):
# arg.release is a list
release_dirs = args.release
@ -1248,6 +1265,16 @@ def setup_argparse():
nargs="+", # accepts a list
help='Release ID to delete')
# -- software install-local ---------------
cmd = commands.add_parser(
'install-local',
help='Trigger patch install/remove on the local host. ' +
'This command can only be used for patch installation ' +
'prior to initial configuration.'
)
cmd.set_defaults(cmd='install-local')
cmd.set_defaults(func=install_local)
# --- software list ---------------------------
cmd = commands.add_parser(
'list',

View File

@ -55,6 +55,7 @@ from software.software_functions import package_dir
from software.software_functions import repo_dir
from software.software_functions import root_scripts_dir
from software.software_functions import semantics_dir
from software.software_functions import SW_VERSION
from software.software_functions import LOG
from software.software_functions import audit_log_info
@ -69,6 +70,7 @@ import software.messages as messages
import software.constants as constants
from tsconfig.tsconfig import INITIAL_CONFIG_COMPLETE_FLAG
from tsconfig.tsconfig import INITIAL_CONTROLLER_CONFIG_COMPLETE
CONF = oslo_cfg.CONF
@ -81,6 +83,9 @@ app_dependency_filename = "%s/%s" % (constants.SOFTWARE_STORAGE_DIR, app_depende
insvc_patch_restart_controller = "/run/software/.restart.software-controller"
ETC_HOSTS_FILE_PATH = "/etc/hosts"
ETC_HOSTS_BACKUP_FILE_PATH = "/etc/hosts.patchbak"
stale_hosts = []
pending_queries = []
@ -897,10 +902,89 @@ class PatchController(PatchService):
LOG.exception(msg)
raise SoftwareFail(msg)
def software_install_local_api(self):
"""
Trigger patch installation prior to configuration
:return: dict of info, warning and error messages
"""
msg_info = ""
msg_warning = ""
msg_error = ""
# Check to see if initial configuration has completed
if os.path.isfile(INITIAL_CONTROLLER_CONFIG_COMPLETE):
# Disallow the install
msg = "This command can only be used before initial system configuration."
LOG.exception(msg)
raise SoftwareFail(msg)
update_hosts_file = False
# Check to see if the controller hostname is already known.
if not utils.gethostbyname(constants.CONTROLLER_FLOATING_HOSTNAME):
update_hosts_file = True
# To allow software installation to occur before configuration, we need
# to alias controller to localhost
# There is a HOSTALIASES feature that would be preferred here, but it
# unfortunately requires dnsmasq to be running, which it is not at this point.
if update_hosts_file:
# Make a backup of /etc/hosts
try:
shutil.copy2(ETC_HOSTS_FILE_PATH, ETC_HOSTS_BACKUP_FILE_PATH)
except Exception:
msg = f"Error occurred while copying {ETC_HOSTS_FILE_PATH}."
LOG.exception(msg)
raise SoftwareFail(msg)
# Update /etc/hosts
with open(ETC_HOSTS_FILE_PATH, 'a') as f:
f.write("127.0.0.1 controller\n")
# Run the software install
try:
# Use the restart option of the sw-patch init script, which will
# install patches but won't automatically reboot if the RR flag is set
subprocess.check_output(['/etc/init.d/sw-patch', 'restart'])
except subprocess.CalledProcessError:
msg = "Failed to install patches."
LOG.exception(msg)
raise SoftwareFail(msg)
if update_hosts_file:
# Restore /etc/hosts
os.rename(ETC_HOSTS_BACKUP_FILE_PATH, ETC_HOSTS_FILE_PATH)
for release in sorted(list(self.release_data.metadata)):
if self.release_data.metadata[release]["state"] == constants.DEPLOYING_START:
self.release_data.metadata[release]["state"] = constants.DEPLOYED
try:
shutil.move("%s/%s-metadata.xml" % (deploying_start_dir, release),
"%s/%s-metadata.xml" % (deployed_dir, release))
except shutil.Error:
msg = "Failed to move the metadata for %s" % release
LOG.exception(msg)
raise MetadataFail(msg)
elif self.release_data.metadata[release]["state"] == constants.REMOVING:
self.release_data.metadata[release]["state"] = constants.AVAILABLE
try:
shutil.move("%s/%s-metadata.xml" % (removing_dir, release),
"%s/%s-metadata.xml" % (available_dir, release))
except shutil.Error:
msg = "Failed to move the metadata for %s" % release
LOG.exception(msg)
raise MetadataFail(msg)
msg_info += "Software installation is complete.\n"
msg_info += "Please reboot before continuing with configuration."
return dict(info=msg_info, warning=msg_warning, error=msg_error)
def software_release_upload(self, release_files):
"""
Upload software release files
:return:
:return: dict of info, warning and error messages
"""
msg_info = ""
msg_warning = ""
@ -1015,7 +1099,9 @@ class PatchController(PatchService):
msg_info += "%s is now uploaded\n" % release_id
self.release_data.add_release(thisrelease)
if len(self.hosts) > 0:
if not os.path.isfile(INITIAL_CONTROLLER_CONFIG_COMPLETE):
self.release_data.metadata[release_id]["state"] = constants.AVAILABLE
elif len(self.hosts) > 0:
self.release_data.metadata[release_id]["state"] = constants.AVAILABLE
else:
self.release_data.metadata[release_id]["state"] = constants.UNKNOWN
@ -1067,7 +1153,7 @@ class PatchController(PatchService):
def software_release_delete_api(self, release_ids):
"""
Delete release(s)
:return:
:return: dict of info, warning and error messages
"""
msg_info = ""
msg_warning = ""
@ -1145,7 +1231,7 @@ class PatchController(PatchService):
def patch_init_release_api(self, release):
"""
Create an empty repo for a new release
:return:
:return: dict of info, warning and error messages
"""
msg_info = ""
msg_warning = ""
@ -1204,7 +1290,7 @@ class PatchController(PatchService):
def patch_query_what_requires(self, patch_ids):
"""
Query the known patches to see which have dependencies on the specified patches
:return:
:return: dict of info, warning and error messages
"""
msg_info = ""
msg_warning = ""
@ -1763,7 +1849,9 @@ class PatchController(PatchService):
LOG.exception(msg)
raise MetadataFail(msg)
if len(self.hosts) > 0:
if not os.path.isfile(INITIAL_CONTROLLER_CONFIG_COMPLETE):
self.release_data.metadata[release]["state"] = constants.DEPLOYING_START
elif len(self.hosts) > 0:
self.release_data.metadata[release]["state"] = constants.DEPLOYING_START
else:
self.release_data.metadata[release]["state"] = constants.UNKNOWN
@ -1875,7 +1963,9 @@ class PatchController(PatchService):
raise MetadataFail(msg)
# update state
if len(self.hosts) > 0:
if not os.path.isfile(INITIAL_CONTROLLER_CONFIG_COMPLETE):
self.release_data.metadata[release]["state"] = constants.REMOVING
elif len(self.hosts) > 0:
self.release_data.metadata[release]["state"] = constants.REMOVING
else:
self.release_data.metadata[release]["state"] = constants.UNKNOWN
@ -1901,7 +1991,9 @@ class PatchController(PatchService):
raise MetadataFail(msg)
# update state
if len(self.hosts) > 0:
if not os.path.isfile(INITIAL_CONTROLLER_CONFIG_COMPLETE):
self.release_data.metadata[deployment]["state"] = constants.DEPLOYING_START
elif len(self.hosts) > 0:
self.release_data.metadata[deployment]["state"] = constants.DEPLOYING_START
else:
self.release_data.metadata[deployment]["state"] = constants.UNKNOWN