Update USM Rest API

Updating software client to reflect changes in the USM's
new REST API release. Adjusting other files due to method
name and return value modifications.

Test Plan:
- Manage a subcloud and check if usm_sync_status is in-sync
- Use the software_client to perform the following commands:
  - list, show, delete, commit, deploy precheck

Story: 2010676
Task: 50015

Change-Id: Ifa15b50a3d163c981a72adbaeffd462102f7c42d
Signed-off-by: Hugo Brito <hugo.brito@windriver.com>
This commit is contained in:
Hugo Brito 2024-04-30 19:39:02 -03:00
parent cb2df3f712
commit 7d9b127369
8 changed files with 173 additions and 491 deletions

View File

@ -3,11 +3,9 @@
# SPDX-License-Identifier: Apache-2.0
#
import os
from oslo_log import log
import requests
from requests_toolbelt import MultipartEncoder
from dccommon import consts
from dccommon.drivers import base
@ -20,12 +18,9 @@ ABORTING = 'aborting'
AVAILABLE = 'available'
COMMITTED = 'committed'
DEPLOYED = 'deployed'
DEPLOYING_ACTIVATE = 'deploying-activate'
DEPLOYING_COMPLETE = 'deploying-complete'
DEPLOYING_HOST = 'deploying-host'
DEPLOYING_START = 'deploying-start'
REMOVING = 'removing'
UNAVAILABLE = 'unavailable'
REST_DEFAULT_TIMEOUT = 900
@ -45,204 +40,51 @@ class SoftwareClient(base.DriverBase):
# The usm systemcontroller endpoint ends with a slash but the regionone
# and the subcloud endpoint don't. The slash is removed to standardize
# with the other endpoints.
self.endpoint = self.endpoint.rstrip('/') + '/v1/software'
self.endpoint = self.endpoint.rstrip('/') + '/v1'
self.token = session.get_token()
self.headers = {"X-Auth-Token": self.token}
def query(self, state='all', release=None, timeout=REST_DEFAULT_TIMEOUT):
"""Query releases"""
extra_opts = ""
if release:
extra_opts = "&release=%s" % release
url = self.endpoint + '/query?show=%s%s' % (state, extra_opts)
def list(self, timeout=REST_DEFAULT_TIMEOUT):
"""List releases"""
url = self.endpoint + '/release'
response = requests.get(url, headers=self.headers, timeout=timeout)
return self._handle_response(response, operation="List")
headers = {"X-Auth-Token": self.token}
response = requests.get(url, headers=headers, timeout=timeout)
if response.status_code != 200:
LOG.error("Query failed with RC: %d" % response.status_code)
raise exceptions.ApiException(endpoint="Query",
rc=response.status_code)
data = response.json()
if data.get('error'):
message = "Query failed with error: %s" % data["error"]
LOG.error(message)
raise Exception(message)
return data.get('sd', [])
def show(self, release, timeout=REST_DEFAULT_TIMEOUT):
"""Show release"""
url = self.endpoint + f"/release/{release}"
response = requests.get(url, headers=self.headers, timeout=timeout)
return self._handle_response(response, operation="Show")
def delete(self, releases, timeout=REST_DEFAULT_TIMEOUT):
"""Delete"""
"""Delete release"""
release_str = "/".join(releases)
url = self.endpoint + '/delete/%s' % release_str
headers = {"X-Auth-Token": self.token}
response = requests.post(url, headers=headers, timeout=timeout)
url = self.endpoint + f'/release/{release_str}'
response = requests.delete(url, headers=self.headers, timeout=timeout)
return self._handle_response(response, operation="Delete")
if response.status_code != 200:
LOG.error("Delete failed with RC: %d" % response.status_code)
raise exceptions.ApiException(endpoint="Delete",
rc=response.status_code)
data = response.json()
if data.get('error'):
message = "Delete failed with error: %s" % data["error"]
LOG.error(message)
raise Exception(message)
return data.get('sd', [])
def deploy_activate(self, deployment, timeout=REST_DEFAULT_TIMEOUT):
"""Deploy activate"""
url = self.endpoint + '/deploy_activate/%s' % deployment
headers = {"X-Auth-Token": self.token}
response = requests.post(url, headers=headers, timeout=timeout)
if response.status_code != 200:
LOG.error("Deploy activate failed with RC: %d" % response.status_code)
raise exceptions.ApiException(endpoint="Deploy activate",
rc=response.status_code)
data = response.json()
if data.get('error'):
message = "Deploy activate failed with error: %s" % data["error"]
LOG.error(message)
raise Exception(message)
return data.get('sd', [])
def deploy_complete(self, deployment, timeout=REST_DEFAULT_TIMEOUT):
"""Deploy complete"""
url = self.endpoint + '/deploy_complete/%s' % deployment
headers = {"X-Auth-Token": self.token}
response = requests.post(url, headers=headers, timeout=timeout)
if response.status_code != 200:
LOG.error("Deploy complete failed with RC: %d" % response.status_code)
raise exceptions.ApiException(endpoint="Deploy complete",
rc=response.status_code)
data = response.json()
if data.get('error'):
message = "Deploy complete failed with error: %s" % data["error"]
LOG.error(message)
raise Exception(message)
return data.get('sd', [])
def deploy_start(self, deployment, timeout=REST_DEFAULT_TIMEOUT):
"""Deploy start"""
url = self.endpoint + '/deploy_start/%s' % deployment
headers = {"X-Auth-Token": self.token}
response = requests.post(url, headers=headers, timeout=timeout)
if response.status_code != 200:
LOG.error("Deploy start failed with RC: %d" % response.status_code)
raise exceptions.ApiException(endpoint="Deploy start",
rc=response.status_code)
data = response.json()
if data.get('error'):
message = "Deploy start failed with error: %s" % data["error"]
LOG.error(message)
raise Exception(message)
return data.get('sd', [])
def deploy_host(self, host, timeout=REST_DEFAULT_TIMEOUT):
"""Deploy host"""
url = self.endpoint + '/deploy_host/%s' % host
headers = {"X-Auth-Token": self.token}
response = requests.post(url, headers=headers, timeout=timeout)
if response.status_code != 200:
LOG.error("Deploy host failed with RC: %d" % response.status_code)
raise exceptions.ApiException(endpoint="Deploy host",
rc=response.status_code)
data = response.json()
if data.get('error'):
message = "Deploy host failed with error: %s" % data["error"]
LOG.error(message)
raise Exception(message)
return data.get('sd', [])
def upload_dir(self, release_dirs, timeout=REST_DEFAULT_TIMEOUT):
"""Upload dir"""
dirlist = {}
i = 0
for d in sorted(set(release_dirs)):
dirlist["dir%d" % i] = os.path.abspath(d)
i += 1
url = self.endpoint + '/upload_dir'
headers = {"X-Auth-Token": self.token}
response = requests.post(
url, params=dirlist, headers=headers, timeout=timeout)
if response.status_code != 200:
LOG.error("Upload dir failed with RC: %d" % response.status_code)
raise exceptions.ApiException(endpoint="Upload dir",
rc=response.status_code)
data = response.json()
if data.get('error'):
message = "Upload dir failed with error: %s" % data["error"]
LOG.error(message)
raise Exception(message)
return data.get('sd', [])
def upload(self, releases, timeout=REST_DEFAULT_TIMEOUT):
"""Upload"""
to_upload_files = {}
for software_file in sorted(set(releases)):
if os.path.isdir(software_file):
message = ("Error: %s is a directory. Please use upload-dir" %
software_file)
LOG.error(message)
raise IsADirectoryError(message)
if not os.path.isfile(software_file):
message = "Error: %s doesn't exist" % software_file
LOG.error(message)
raise FileNotFoundError(message)
to_upload_files[software_file] = (
software_file, open(software_file, 'rb')
)
enc = MultipartEncoder(fields=to_upload_files)
url = self.endpoint + '/upload'
headers = {"X-Auth-Token": self.token, "Content-Type": enc.content_type}
response = requests.post(url,
data=enc,
headers=headers,
timeout=timeout)
if response.status_code != 200:
LOG.error("Upload failed with RC: %d" % response.status_code)
raise exceptions.ApiException(endpoint="Upload",
rc=response.status_code)
data = response.json()
if data.get('error'):
message = "Upload failed with error: %s" % data["error"]
LOG.error(message)
raise Exception(message)
return data.get('sd', [])
def query_hosts(self, timeout=REST_DEFAULT_TIMEOUT):
"""Query hosts"""
url = self.endpoint + '/query_hosts'
headers = {"X-Auth-Token": self.token}
response = requests.get(url, headers=headers, timeout=timeout)
if response.status_code != 200:
LOG.error("Query hosts failed with RC: %d" % response.status_code)
raise exceptions.ApiException(endpoint="Query hosts",
rc=response.status_code)
data = response.json()
if not data.get('data'):
message = "Invalid data returned: %s" % data
LOG.error(message)
raise Exception(message)
return data.get('data', [])
def deploy_precheck(self, deployment, timeout=REST_DEFAULT_TIMEOUT):
"""Deploy precheck"""
url = self.endpoint + f"/deploy/{deployment}/precheck"
response = requests.post(url, headers=self.headers, timeout=timeout)
return self._handle_response(response, operation="Deploy precheck")
def commit_patch(self, releases, timeout=REST_DEFAULT_TIMEOUT):
"""Commit patch"""
release_str = "/".join(releases)
url = self.endpoint + '/commit_patch/%s' % release_str
headers = {"X-Auth-Token": self.token}
response = requests.post(url, headers=headers, timeout=timeout)
url = self.endpoint + f'/commit_patch/{release_str}'
response = requests.post(url, headers=self.headers, timeout=timeout)
return self._handle_response(response, operation="Commit patch")
def _handle_response(self, response, operation):
if response.status_code != 200:
LOG.error("Commit patch failed with RC: %d" % response.status_code)
raise exceptions.ApiException(endpoint="Commit patch",
LOG.error(f"{operation} failed with RC: {response.status_code}")
raise exceptions.ApiException(endpoint=operation,
rc=response.status_code)
data = response.json()
if data.get('error'):
message = "Commit patch failed with error: %s" % data["error"]
# Data response could be a dict with an error key or a list
if isinstance(data, dict) and data.get('error'):
message = f"{operation} failed with error: {data.get('error')}"
LOG.error(message)
raise Exception(message)
return data.get('sd', [])
return data

View File

@ -1,11 +1,10 @@
#
# Copyright (c) 2023 Wind River Systems, Inc.
# Copyright (c) 2023-2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import json
import os
import mock
import requests
@ -19,70 +18,57 @@ from dccommon.tests import utils
FAKE_ENDPOINT = "endpoint"
QUERY_RESPONSE = {
"sd": {
"22.12_NRR_INSVC": {
"state": "available",
"sw_version": "22.12",
"status": "DEV",
"unremovable": "N",
"summary": "Debian patch test",
"description": "In service patch",
"install_instructions": "Sample instructions",
"restart_script": "22.12_NRR_INSVC_example-restart",
"warnings": "Sample warning",
"reboot_required": "N",
"requires": []}}
}
QUERY_HOSTS_RESPONSE = {
"data": [{
"ip": "192.168.101.3",
"hostname": "controller-0",
"deployed": True,
"secs_since_ack": 26,
"patch_failed": False,
"stale_details": False,
"latest_sysroot_commit": "4b8bcc6f53",
"nodetype": "controller",
"subfunctions": ["controller", "worker"],
"sw_version": "22.12",
"state": "idle",
"requires_reboot": False,
"allow_insvc_patching": True,
"interim_state": False}]
}
CLIENT_QUERY_HOSTS_RESPONSE = [{
"allow_insvc_patching": True,
"deployed": True,
"hostname": "controller-0",
"interim_state": False,
"ip": "192.168.101.3",
"latest_sysroot_commit": "4b8bcc6f53",
"nodetype": "controller",
"patch_failed": False,
"requires_reboot": False,
"secs_since_ack": 26,
"stale_details": False,
"state": "idle",
"subfunctions": ["controller", "worker"],
"sw_version": "22.12"
}]
CLIENT_QUERY_RESPONSE = {
"22.12_NRR_INSVC": {
"description": "In service patch",
"install_instructions": "Sample instructions",
"reboot_required": "N",
LIST_RESPONSE = [
{
"release_id": "starlingx-24.03.0",
"state": "deployed",
"sw_version": "24.03.0",
"component": None,
"status": "REL",
"unremovable": True,
"summary": "STX 24.03 GA release",
"description": "STX 24.03 major GA release",
"install_instructions": "",
"warnings": "",
"reboot_required": True,
"requires": [],
"restart_script": "22.12_NRR_INSVC_example-restart",
"state": "available",
"status": "DEV",
"summary": "Debian patch test",
"sw_version": "22.12",
"unremovable": "N",
"warnings": "Sample warning"}}
"packages": [],
}
]
CLIENT_LIST_RESPONSE = [
{
"release_id": "starlingx-24.03.0",
"state": "deployed",
"sw_version": "24.03.0",
"component": None,
"status": "REL",
"unremovable": True,
"summary": "STX 24.03 GA release",
"description": "STX 24.03 major GA release",
"install_instructions": "",
"warnings": "",
"reboot_required": True,
"requires": [],
"packages": [],
}
]
SHOW_RESPONSE = {
"release_id": "starlingx-24.03.0",
"state": "deployed",
"sw_version": "24.03.0",
"component": None,
"status": "REL",
"unremovable": True,
"summary": "STX 24.03 GA release",
"description": "STX 24.03 major GA release",
"install_instructions": "",
"warnings": "",
"reboot_required": True,
"requires": [],
"packages": [],
}
ERROR_RESPONSE = {
"error": "something went wrong"
@ -93,13 +79,7 @@ INFO_RESPONSE = {
}
URLS = [
"/upload",
"/upload_dir",
"/deploy_complete",
"/deploy_activate",
"/deploy_start",
"/deploy_host",
"/delete",
"/deploy",
"/commit_patch"
]
@ -107,10 +87,12 @@ URLS = [
def mocked_requests_success(*args, **kwargs):
response_content = None
if args[0].endswith('/query_hosts'):
response_content = json.dumps(QUERY_HOSTS_RESPONSE)
elif args[0].endswith('/query?show=all'):
response_content = json.dumps(QUERY_RESPONSE)
if args[0].endswith('/release'):
response_content = json.dumps(LIST_RESPONSE)
elif args[0].endswith("/release/DC.1"):
response_content = json.dumps(SHOW_RESPONSE)
elif args[0].endswith('/release/DC.1/DC.2'):
response_content = json.dumps(INFO_RESPONSE)
elif any([url in args[0] for url in URLS]):
response_content = json.dumps(INFO_RESPONSE)
response = requests.Response()
@ -121,14 +103,7 @@ def mocked_requests_success(*args, **kwargs):
def mocked_requests_failure(*args, **kwargs):
response_content = None
if args[0].endswith('/query_hosts'):
response_content = json.dumps(ERROR_RESPONSE)
elif args[0].endswith('/query?show=all'):
response_content = json.dumps(ERROR_RESPONSE)
elif any([url in args[0] for url in URLS]):
response_content = json.dumps(ERROR_RESPONSE)
response_content = json.dumps(ERROR_RESPONSE)
response = requests.Response()
response.status_code = 500
response._content = str.encode(response_content)
@ -144,191 +119,65 @@ class TestSoftwareClient(base.DCCommonTestCase):
self.software_client = SoftwareClient(
region=dccommon_consts.DEFAULT_REGION_NAME,
endpoint=FAKE_ENDPOINT,
session=mock.MagicMock()
session=mock.MagicMock(),
)
@mock.patch('requests.get')
def test_query_success(self, mock_get):
def test_list_success(self, mock_get):
mock_get.side_effect = mocked_requests_success
response = self.software_client.query()
self.assertEqual(response, CLIENT_QUERY_RESPONSE)
response = self.software_client.list()
self.assertEqual(response, CLIENT_LIST_RESPONSE)
@mock.patch('requests.get')
def test_query_failure(self, mock_get):
def test_list_failure(self, mock_get):
mock_get.side_effect = mocked_requests_failure
e = self.assertRaises(exceptions.ApiException,
self.software_client.query)
exc = self.assertRaises(exceptions.ApiException, self.software_client.list)
self.assertTrue("List failed with status code: 500" in str(exc))
self.assertTrue('Query failed with status code: 500'
in str(e))
@mock.patch("requests.get")
def test_show_success(self, mock_get):
mock_get.side_effect = mocked_requests_success
release = "DC.1"
response = self.software_client.show(release)
self.assertEqual(response, SHOW_RESPONSE)
@mock.patch('requests.post')
def test_delete_success(self, mock_post):
mock_post.side_effect = mocked_requests_success
@mock.patch("requests.get")
def test_show_failure(self, mock_get):
mock_get.side_effect = mocked_requests_failure
release = 'DC.1'
exc = self.assertRaises(
exceptions.ApiException, self.software_client.show, release
)
self.assertTrue("Show failed with status code: 500" in str(exc))
@mock.patch('requests.delete')
def test_delete_success(self, mock_delete):
mock_delete.side_effect = mocked_requests_success
releases = ['DC.1', 'DC.2']
response = self.software_client.delete(releases)
self.assertEqual(response, [])
self.assertEqual(response, INFO_RESPONSE)
@mock.patch('requests.post')
def test_delete_failure(self, mock_post):
mock_post.side_effect = mocked_requests_failure
@mock.patch("requests.delete")
def test_delete_failure(self, mock_delete):
mock_delete.side_effect = mocked_requests_failure
releases = ['DC.1', 'DC.2']
e = self.assertRaises(exceptions.ApiException,
self.software_client.delete,
releases)
self.assertTrue('Delete failed with status code: 500'
in str(e))
@mock.patch('requests.get')
def test_query_hosts_success(self, mock_get):
mock_get.side_effect = mocked_requests_success
response = self.software_client.query_hosts()
self.assertEqual(response, CLIENT_QUERY_HOSTS_RESPONSE)
@mock.patch('requests.get')
def test_query_hosts_failure(self, mock_get):
mock_get.side_effect = mocked_requests_failure
e = self.assertRaises(exceptions.ApiException,
self.software_client.query_hosts)
self.assertTrue('Query hosts failed with status code: 500'
in str(e))
@mock.patch.object(os.path, 'isdir')
@mock.patch.object(os.path, 'isfile')
@mock.patch('requests.post')
@mock.patch('builtins.open', new_callable=mock.mock_open())
def test_upload_success(self, mock_open_file,
mock_post, mock_isfile, mock_isdir):
mock_open_file.return_value = str.encode('patch')
mock_post.side_effect = mocked_requests_success
releases = ['DC1.patch']
mock_isfile.return_value = True
mock_isdir.return_value = False
response = self.software_client.upload(releases)
self.assertEqual(response, [])
@mock.patch.object(os.path, 'isdir')
@mock.patch.object(os.path, 'isfile')
@mock.patch('requests.post')
@mock.patch('builtins.open', new_callable=mock.mock_open())
def test_upload_failure(self, mock_open_file, mock_post,
mock_isfile, mock_isdir):
mock_open_file.return_value = str.encode('patch')
mock_post.side_effect = mocked_requests_success
releases = ['DC5', 'DC4']
mock_isfile.return_value = False
mock_isdir.return_value = True
e = self.assertRaises(IsADirectoryError,
self.software_client.upload,
releases)
self.assertTrue('Error: DC4 is a directory. Please use upload-dir'
in str(e))
@mock.patch('requests.post')
def test_upload_dir_success(self, mock_post):
mock_post.side_effect = mocked_requests_success
releases = ['DC5', 'DC4']
response = self.software_client.upload_dir(releases)
self.assertEqual(response, [])
@mock.patch('requests.post')
def test_upload_dir_failure(self, mock_post):
mock_post.side_effect = mocked_requests_failure
releases = ['DC1.patch']
e = self.assertRaises(exceptions.ApiException,
self.software_client.upload_dir,
releases)
self.assertEqual('Upload dir failed with status code: 500', str(e))
@mock.patch('requests.post')
def test_deploy_activate_success(self, mock_post):
mock_post.side_effect = mocked_requests_success
release = 'DC.1'
response = self.software_client.deploy_activate(release)
self.assertEqual(response, [])
@mock.patch('requests.post')
def test_deploy_activate_failure(self, mock_post):
mock_post.side_effect = mocked_requests_failure
release = 'DC.1'
e = self.assertRaises(exceptions.ApiException,
self.software_client.deploy_activate,
release)
self.assertTrue('Deploy activate failed with status code: 500'
in str(e))
@mock.patch('requests.post')
def test_deploy_complete_success(self, mock_post):
mock_post.side_effect = mocked_requests_success
release = 'DC.1'
response = self.software_client.deploy_complete(release)
self.assertEqual(response, [])
@mock.patch('requests.post')
def test_deploy_complete_failure(self, mock_post):
mock_post.side_effect = mocked_requests_failure
release = 'DC.1'
e = self.assertRaises(exceptions.ApiException,
self.software_client.deploy_complete,
release)
self.assertTrue('Deploy complete failed with status code: 500'
in str(e))
@mock.patch('requests.post')
def test_deploy_start_success(self, mock_post):
mock_post.side_effect = mocked_requests_success
release = 'DC.1'
response = self.software_client.deploy_start(release)
self.assertEqual(response, [])
@mock.patch('requests.post')
def test_deploy_start_failure(self, mock_post):
mock_post.side_effect = mocked_requests_failure
release = 'DC.1'
e = self.assertRaises(exceptions.ApiException,
self.software_client.deploy_start,
release)
self.assertTrue('Deploy start failed with status code: 500'
in str(e))
@mock.patch('requests.post')
def test_deploy_host_success(self, mock_post):
mock_post.side_effect = mocked_requests_success
host = 'controller-0'
response = self.software_client.deploy_host(host)
self.assertEqual(response, [])
@mock.patch('requests.post')
def test_deploy_host_failure(self, mock_post):
mock_post.side_effect = mocked_requests_failure
host = 'controller-0'
e = self.assertRaises(exceptions.ApiException,
self.software_client.deploy_host,
host)
self.assertTrue('Deploy host failed with status code: 500'
in str(e))
exc = self.assertRaises(
exceptions.ApiException, self.software_client.delete, releases
)
self.assertTrue("Delete failed with status code: 500" in str(exc))
@mock.patch('requests.post')
def test_commit_patch_success(self, mock_post):
mock_post.side_effect = mocked_requests_success
releases = ['DC.1', 'DC.2']
response = self.software_client.commit_patch(releases)
self.assertEqual(response, [])
self.assertEqual(response, INFO_RESPONSE)
@mock.patch('requests.post')
def test_commit_patch_failure(self, mock_post):
mock_post.side_effect = mocked_requests_failure
releases = ['DC.1', 'DC.2']
e = self.assertRaises(exceptions.ApiException,
self.software_client.commit_patch,
releases)
self.assertTrue('Commit patch failed with status code: 500'
in str(e))
exc = self.assertRaises(
exceptions.ApiException, self.software_client.commit_patch, releases
)
self.assertTrue("Commit patch failed with status code: 500" in str(exc))

View File

@ -90,36 +90,24 @@ class SoftwareAudit(object):
return None
# First query RegionOne to determine what releases should be deployed
# to the system.
regionone_releases = software_client.query()
regionone_releases = software_client.list()
LOG.debug(f"regionone_releases: {regionone_releases}")
# Build lists of releases that should be deployed or committed in all
# subclouds, based on their state in RegionOne.
deployed_release_ids = list()
committed_release_ids = list()
for release_id in regionone_releases.keys():
if regionone_releases[release_id]["state"] == software_v1.DEPLOYED:
deployed_release_ids.append(release_id)
elif regionone_releases[release_id]["state"] == software_v1.COMMITTED:
committed_release_ids.append(release_id)
for release in regionone_releases:
if release["state"] == software_v1.DEPLOYED:
deployed_release_ids.append(release["release_id"])
elif release["state"] == software_v1.COMMITTED:
committed_release_ids.append(release["release_id"])
LOG.debug(f"RegionOne deployed_release_ids: {deployed_release_ids}")
LOG.debug(f"RegionOne committed_release_ids: {committed_release_ids}")
return SoftwareAuditData(
regionone_releases, deployed_release_ids, committed_release_ids
)
# TODO(nicodemos): This method will be removed once the USM feature is
# fully complete. The USM endpoints are not working properly, so we are
# always returning 'in-sync' to avoid regression failures.
def subcloud_software_audit(self, subcloud_name, subcloud_region, _audit_data):
self._update_subcloud_sync_status(
subcloud_name,
subcloud_region,
dccommon_consts.ENDPOINT_TYPE_SOFTWARE,
dccommon_consts.SYNC_STATUS_IN_SYNC,
)
LOG.info(f"Software audit completed for: {subcloud_name}.")
def _subcloud_software_audit(self, subcloud_name, subcloud_region, audit_data):
def subcloud_software_audit(self, subcloud_name, subcloud_region, audit_data):
LOG.info(f"Triggered software audit for: {subcloud_name}.")
try:
sc_os_client = sdk_platform.OpenStackDriver(
@ -146,7 +134,7 @@ class SoftwareAudit(object):
# Retrieve all the releases that are present in this subcloud.
try:
subcloud_releases = software_client.query()
subcloud_releases = software_client.list()
LOG.debug(f"Releases for subcloud {subcloud_name}: {subcloud_releases}")
except Exception:
LOG.warn(
@ -163,13 +151,14 @@ class SoftwareAudit(object):
# Check that all releases in this subcloud are in the correct
# state, based on the state of the release in RegionOne. For the
# subcloud.
for release_id in subcloud_releases.keys():
if subcloud_releases[release_id]["state"] == software_v1.DEPLOYED:
for release in subcloud_releases:
release_id = release.get("release_id")
if release["state"] == software_v1.DEPLOYED:
if release_id not in audit_data.deployed_release_ids:
if release_id not in audit_data.committed_release_ids:
LOG.debug(
f"Release {release_id} should not be deployed "
f"in {subcloud_name}."
f"Release {release_id} should not be deployed"
f" in {subcloud_name}."
)
else:
LOG.debug(
@ -177,7 +166,7 @@ class SoftwareAudit(object):
f"in {subcloud_name}."
)
out_of_sync = True
elif subcloud_releases[release_id]["state"] == software_v1.COMMITTED:
elif release["state"] == software_v1.COMMITTED:
if (
release_id not in audit_data.committed_release_ids
and release_id not in audit_data.deployed_release_ids

View File

@ -364,7 +364,7 @@ class SubcloudAuditManager(manager.Manager):
patch_audit_data = self.patch_audit.get_regionone_audit_data()
if audit_software:
# Query RegionOne releases
software_audit_data = self.patch_audit.get_regionone_audit_data()
software_audit_data = self.software_audit.get_regionone_audit_data()
if audit_firmware:
# Query RegionOne firmware
firmware_audit_data = self.firmware_audit.get_regionone_audit_data()

View File

@ -58,13 +58,13 @@ REGION_ONE_SYSTEM_INFO_CACHE_SPECIFICATION = CacheSpecification(
lambda: clients.get_sysinv_client().get_system())
REGION_ONE_RELEASE_USM_CACHE_SPECIFICATION = CacheSpecification(
lambda: clients.get_software_client().query(),
# Filter results by patching state, if any is given
lambda patches, **filter_params: {
patch_id: patch for patch_id, patch in patches.items()
lambda: clients.get_software_client().list(),
# Filter results by release state, if any is given
lambda patches, **filter_params: [
patch for patch in patches
if filter_params.get('state') is None
or patch.get('state') == filter_params.get('state')
},
],
{'state'}
)

View File

@ -37,7 +37,7 @@ class FinishStrategyState(BaseState):
try:
software_client = self.get_software_client(self.region_name)
subcloud_releases = software_client.query()
subcloud_releases = software_client.list()
except Exception:
message = ("Cannot retrieve subcloud releases. Please see logs for "
"details.")

View File

@ -21,23 +21,26 @@ from dcmanager.orchestrator.states.software.cache.shared_cache_repository import
SharedCacheRepository
from dcmanager.tests import base
SOFTWARE_CLIENT_QUERY_RETURN = {
"stx_23.09.0": {
SOFTWARE_CLIENT_QUERY_RETURN = [
{
"sw_version": "23.09.0",
"state": "available",
"reboot_required": "N",
"release_id": "stx_23.09.0",
},
"stx_23.09.1": {
{
"sw_version": "23.09.1",
"state": "available",
"reboot_required": "N",
"release_id": "stx_23.09.1",
},
"stx_23.09.2": {
{
"sw_version": "23.09.2",
"state": "unavailable",
"reboot_required": "N",
}
}
"release_id": "stx_23.09.2",
},
]
class TestSharedCacheRepository(base.DCManagerTestCase):
@ -55,7 +58,7 @@ class TestSharedCacheRepository(base.DCManagerTestCase):
)
self.shared_cache_repository.initialize_caches()
self.software_client().query.return_value = SOFTWARE_CLIENT_QUERY_RETURN
self.software_client().list.return_value = SOFTWARE_CLIENT_QUERY_RETURN
def _mock_software_client(self):
mock_patch = mock.patch.object(clients, 'SoftwareClient')
@ -76,16 +79,16 @@ class TestSharedCacheRepository(base.DCManagerTestCase):
self.mock_sysinv_client().get_system.return_value = 'fake system info'
response = \
self.shared_cache_repository.read(REGION_ONE_SYSTEM_INFO_CACHE_TYPE)
response = self.shared_cache_repository.read(
REGION_ONE_SYSTEM_INFO_CACHE_TYPE)
self.assertEqual(response, 'fake system info')
def test_read_succeeds_with_release_usm_cache_type(self):
"""Test read cache succeeds when using REGION_ONE_RELEASE_USM_CACHE_TYPE"""
response = \
self.shared_cache_repository.read(REGION_ONE_RELEASE_USM_CACHE_TYPE)
response = self.shared_cache_repository.read(
REGION_ONE_RELEASE_USM_CACHE_TYPE)
self.assertEqual(response, SOFTWARE_CLIENT_QUERY_RETURN)
@ -101,8 +104,7 @@ class TestSharedCacheRepository(base.DCManagerTestCase):
def test_read_fails_when_openstack_driver_raises_exception(self):
"""Test read cache fails when the OpenStackDriver raises an Exception"""
self.mock_openstack_driver.side_effect = \
keystone_exceptions.ConnectFailure()
self.mock_openstack_driver.side_effect = keystone_exceptions.ConnectFailure()
self.assertRaises(
keystone_exceptions.ConnectFailure,
@ -119,7 +121,7 @@ class TestSharedCacheRepository(base.DCManagerTestCase):
)
expected_response = copy.copy(SOFTWARE_CLIENT_QUERY_RETURN)
del expected_response["stx_23.09.2"]
del expected_response[2]
self.assertEqual(response, expected_response)

View File

@ -74,7 +74,7 @@ class TestFinishStrategyState(TestSoftwareOrchestrator):
# Add mock API endpoints for software client calls
# invoked by this state
self.software_client.query = mock.MagicMock()
self.software_client.list = mock.MagicMock()
self.software_client.delete = mock.MagicMock()
self.software_client.commit_patch = mock.MagicMock()
self._read_from_cache = mock.MagicMock()
@ -84,7 +84,7 @@ class TestFinishStrategyState(TestSoftwareOrchestrator):
self.mock_read_from_cache.return_value = REGION_ONE_RELEASES
self.software_client.query.side_effect = [SUBCLOUD_RELEASES]
self.software_client.list.side_effect = [SUBCLOUD_RELEASES]
# invoke the strategy state operation on the orch thread
self.worker.perform_state_action(self.strategy_step)
@ -104,7 +104,7 @@ class TestFinishStrategyState(TestSoftwareOrchestrator):
self.mock_read_from_cache.return_value = REGION_ONE_RELEASES
self.software_client.query.side_effect = [REGION_ONE_RELEASES]
self.software_client.list.side_effect = [REGION_ONE_RELEASES]
# invoke the strategy state operation on the orch thread
self.worker.perform_state_action(self.strategy_step)
@ -122,7 +122,7 @@ class TestFinishStrategyState(TestSoftwareOrchestrator):
self.mock_read_from_cache.return_value = REGION_ONE_RELEASES
self.software_client.query.side_effect = Exception()
self.software_client.list.side_effect = Exception()
self.worker.perform_state_action(self.strategy_step)
@ -135,7 +135,7 @@ class TestFinishStrategyState(TestSoftwareOrchestrator):
self.mock_read_from_cache.return_value = REGION_ONE_RELEASES
self.software_client.query.side_effect = [SUBCLOUD_RELEASES]
self.software_client.list.side_effect = [SUBCLOUD_RELEASES]
self.software_client.delete.side_effect = Exception()
self.worker.perform_state_action(self.strategy_step)
@ -149,7 +149,7 @@ class TestFinishStrategyState(TestSoftwareOrchestrator):
"""Test finish strategy fails when stopped"""
self.mock_read_from_cache.return_value = REGION_ONE_RELEASES
self.software_client.query.side_effect = [SUBCLOUD_RELEASES]
self.software_client.list.side_effect = [SUBCLOUD_RELEASES]
mock_base_stopped.return_value = True
@ -167,7 +167,7 @@ class TestFinishStrategyState(TestSoftwareOrchestrator):
self.mock_read_from_cache.return_value = REGION_ONE_RELEASES
self.software_client.query.side_effect = [SUBCLOUD_RELEASES]
self.software_client.list.side_effect = [SUBCLOUD_RELEASES]
self.software_client.commit_patch.side_effect = Exception()
self.worker.perform_state_action(self.strategy_step)