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:
parent
cb2df3f712
commit
7d9b127369
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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'}
|
||||
)
|
||||
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue