Add deletion constraint
This change adds checks before deleting software releases: 1. software release is available or unavailable 2. When it is on a system controller, the release is not being used by a subcloud This change also update the following: 1. removed the exception handling in controller level, moved to exception hook 2. CLI code to display HTTP error, only handles 500 status code, by displaying message from API, all other 4xx, 5xx status code display HTTP error directly. 3. ensure CLI return 1 for unsuccessful requets (status code 500) 4. fixed some minor issues Story: 2010676 Task: 49657 TCs: passed: observe delection rejected because of release not found, release is not in available or unavailable state. passed: delete an available release passed: on system controller, successfully delete scenarios passed: (simulated) on system controller with subcloud, delete release used by subcloud is rejected Change-Id: I306b1d8604113b92d907384844e8e8107835a463 Signed-off-by: Bin Qian <bin.qian@windriver.com>
This commit is contained in:
parent
2c27382eba
commit
229acb985f
@ -4,7 +4,6 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
from keystoneauth1 import loading
|
||||
from oslo_utils import importutils
|
||||
|
||||
from software_client import exc
|
||||
@ -18,6 +17,7 @@ API_ENDPOINT = "http://127.0.0.1:" + API_PORT
|
||||
|
||||
|
||||
def _make_session(**kwargs):
|
||||
from keystoneauth1 import loading
|
||||
"""Construct a session based on authentication information
|
||||
|
||||
:param kwargs: keyword args containing credentials, either:
|
||||
|
49
software-client/software_client/common/http_errors.py
Normal file
49
software-client/software_client/common/http_errors.py
Normal file
@ -0,0 +1,49 @@
|
||||
"""
|
||||
Copyright (c) 2024 Wind River Systems, Inc.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
"""
|
||||
|
||||
# HTTP ERRORS, display message when corresponding status code
|
||||
# is received.
|
||||
# status code 500, will be handled separatedly, as it returns
|
||||
# API request related information.
|
||||
HTTP_ERRORS = {
|
||||
400: "Bad Request",
|
||||
401: "Unauthorized",
|
||||
403: "Forbidden",
|
||||
404: "Not Found",
|
||||
405: "Method Not Allowed",
|
||||
406: "Not Acceptable",
|
||||
407: "Proxy Authentication Required",
|
||||
408: "Request Timeout",
|
||||
409: "Conflict",
|
||||
410: "Gone",
|
||||
411: "Length Required",
|
||||
412: "Precondition Failed",
|
||||
413: "Content Too Large",
|
||||
414: "URI Too Long",
|
||||
415: "Unsupported Media Type",
|
||||
416: "Range Not Satisfiable",
|
||||
417: "Expectation Failed",
|
||||
418: "I'm a teapot",
|
||||
421: "Misdirected Request",
|
||||
422: "Unprocessable Content",
|
||||
423: "Locked",
|
||||
424: "Failed Dependency",
|
||||
425: "Too Early",
|
||||
426: "Upgrade Required",
|
||||
428: "Precondition Required",
|
||||
429: "Too Many Requests",
|
||||
501: "Not Implemented",
|
||||
502: "Bad Gateway",
|
||||
503: "Service Unavailable",
|
||||
504: "Gateway Timeout",
|
||||
505: "HTTP Version Not Support",
|
||||
506: "Variant Also Negotiates",
|
||||
507: "Insufficient Storage",
|
||||
508: "Loop Detected",
|
||||
509: "Not Extended",
|
||||
511: "Network Authentication Required"
|
||||
}
|
@ -25,6 +25,8 @@ import textwrap
|
||||
from oslo_utils import importutils
|
||||
from six.moves import zip
|
||||
|
||||
from software_client.common.http_errors import HTTP_ERRORS
|
||||
|
||||
|
||||
TERM_WIDTH = 72
|
||||
|
||||
@ -116,6 +118,46 @@ def check_rc(req, data):
|
||||
return rc
|
||||
|
||||
|
||||
def _display_info(text):
|
||||
''' display the basic info json object '''
|
||||
try:
|
||||
data = json.loads(text)
|
||||
except Exception:
|
||||
print(f"Invalid response format: {text}")
|
||||
return
|
||||
|
||||
if "error" in data and data["error"] != "":
|
||||
print("Error:\n%s" % data["error"])
|
||||
elif "warning" in data and data["warning"] != "":
|
||||
print("Warning:\n%s" % data["warning"])
|
||||
elif "info" in data and data["info"] != "":
|
||||
print(data["info"])
|
||||
|
||||
|
||||
def display_info(resp):
|
||||
'''
|
||||
This function displays basic REST API return, w/ info json object:
|
||||
{
|
||||
"info":"",
|
||||
"warning":"",
|
||||
"error":"",
|
||||
}
|
||||
'''
|
||||
|
||||
status_code = resp.status_code
|
||||
text = resp.text
|
||||
|
||||
if resp.status_code == 500:
|
||||
# all 500 error comes with basic info json object
|
||||
_display_info(text)
|
||||
elif resp.status_code in HTTP_ERRORS:
|
||||
# any 4xx and 5xx errors does not contain API information.
|
||||
print("Error:\n%s", HTTP_ERRORS[status_code])
|
||||
else:
|
||||
# print out the basic info json object
|
||||
_display_info(text)
|
||||
|
||||
|
||||
def print_result_list(header_data_list, data_list, has_error, sort_key=0):
|
||||
"""
|
||||
Print a list of data in a simple table format
|
||||
|
@ -392,12 +392,17 @@ class SoftwareClientShell(object):
|
||||
args.os_endpoint_type = endpoint_type
|
||||
client = sclient.get_client(api_version, auth_mode, **(args.__dict__))
|
||||
|
||||
return args.func(client, args)
|
||||
# TODO(bqian) reenable below once Exception classes are defined
|
||||
"""
|
||||
try:
|
||||
args.func(client, args)
|
||||
except exc.Unauthorized:
|
||||
raise exc.CommandError("Invalid Identity credentials.")
|
||||
except exc.HTTPForbidden:
|
||||
raise exc.CommandError("Error: Forbidden")
|
||||
"""
|
||||
|
||||
|
||||
def do_bash_completion(self, args):
|
||||
"""Prints all of the commands and options to stdout.
|
||||
@ -435,7 +440,7 @@ class HelpFormatter(argparse.HelpFormatter):
|
||||
|
||||
def main():
|
||||
try:
|
||||
SoftwareClientShell().main(sys.argv[1:])
|
||||
return SoftwareClientShell().main(sys.argv[1:])
|
||||
|
||||
except KeyboardInterrupt as e:
|
||||
print(('caught: %r, aborting' % (e)), file=sys.stderr)
|
||||
@ -451,4 +456,4 @@ def main():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
sys.exit(main())
|
||||
|
@ -83,7 +83,7 @@ class ReleaseManager(base.Manager):
|
||||
|
||||
path = '/v1/software/upload'
|
||||
if is_local:
|
||||
to_upload_filenames = json.dumps(valid_files)
|
||||
to_upload_filenames = valid_files
|
||||
headers = {'Content-Type': 'text/plain'}
|
||||
return self._create(path, body=to_upload_filenames, headers=headers)
|
||||
else:
|
||||
|
@ -192,9 +192,5 @@ def do_upload_dir(cc, args):
|
||||
def do_delete(cc, args):
|
||||
"""Delete the software release"""
|
||||
resp, body = cc.release.release_delete(args.release)
|
||||
if args.debug:
|
||||
utils.print_result_debug(resp, body)
|
||||
else:
|
||||
utils.print_software_op_result(resp, body)
|
||||
|
||||
utils.display_info(resp)
|
||||
return utils.check_rc(resp, body)
|
||||
|
@ -14,6 +14,7 @@ from pecan import Response
|
||||
import shutil
|
||||
|
||||
from software.exceptions import SoftwareError
|
||||
from software.exceptions import SoftwareServiceError
|
||||
from software.software_controller import sc
|
||||
import software.utils as utils
|
||||
import software.constants as constants
|
||||
@ -26,32 +27,20 @@ class SoftwareAPIController(object):
|
||||
|
||||
@expose('json')
|
||||
def commit_patch(self, *args):
|
||||
try:
|
||||
result = sc.patch_commit(list(args))
|
||||
except SoftwareError as e:
|
||||
return dict(error=str(e))
|
||||
|
||||
result = sc.patch_commit(list(args))
|
||||
sc.software_sync()
|
||||
|
||||
return result
|
||||
|
||||
@expose('json')
|
||||
def commit_dry_run(self, *args):
|
||||
try:
|
||||
result = sc.patch_commit(list(args), dry_run=True)
|
||||
except SoftwareError as e:
|
||||
return dict(error=str(e))
|
||||
|
||||
result = sc.patch_commit(list(args), dry_run=True)
|
||||
return result
|
||||
|
||||
@expose('json')
|
||||
@expose('query.xml', content_type='application/xml')
|
||||
def delete(self, *args):
|
||||
try:
|
||||
result = sc.software_release_delete_api(list(args))
|
||||
except SoftwareError as e:
|
||||
return dict(error="Error: %s" % str(e))
|
||||
|
||||
result = sc.software_release_delete_api(list(args))
|
||||
sc.software_sync()
|
||||
|
||||
return result
|
||||
@ -60,13 +49,9 @@ class SoftwareAPIController(object):
|
||||
@expose('query.xml', content_type='application/xml')
|
||||
def deploy_activate(self, *args):
|
||||
if sc.any_patch_host_installing():
|
||||
return dict(error="Rejected: One or more nodes are installing a release.")
|
||||
|
||||
try:
|
||||
result = sc.software_deploy_activate_api(list(args)[0])
|
||||
except SoftwareError as e:
|
||||
return dict(error="Error: %s" % str(e))
|
||||
raise SoftwareServiceError(error="Rejected: One or more nodes are installing a release.")
|
||||
|
||||
result = sc.software_deploy_activate_api(list(args)[0])
|
||||
sc.software_sync()
|
||||
return result
|
||||
|
||||
@ -74,12 +59,9 @@ class SoftwareAPIController(object):
|
||||
@expose('query.xml', content_type='application/xml')
|
||||
def deploy_complete(self, *args):
|
||||
if sc.any_patch_host_installing():
|
||||
return dict(error="Rejected: One or more nodes are installing a release.")
|
||||
raise SoftwareServiceError(error="Rejected: One or more nodes are installing a release.")
|
||||
|
||||
try:
|
||||
result = sc.software_deploy_complete_api(list(args)[0])
|
||||
except SoftwareError as e:
|
||||
return dict(error="Error: %s" % str(e))
|
||||
result = sc.software_deploy_complete_api(list(args)[0])
|
||||
|
||||
sc.software_sync()
|
||||
return result
|
||||
@ -93,10 +75,7 @@ class SoftwareAPIController(object):
|
||||
if len(list(args)) > 1 and 'force' in list(args)[1:]:
|
||||
force = True
|
||||
|
||||
try:
|
||||
result = sc.software_deploy_host_api(list(args)[0], force, async_req=True)
|
||||
except SoftwareError as e:
|
||||
return dict(error="Error: %s" % str(e))
|
||||
result = sc.software_deploy_host_api(list(args)[0], force, async_req=True)
|
||||
|
||||
return result
|
||||
|
||||
@ -107,10 +86,7 @@ class SoftwareAPIController(object):
|
||||
if 'force' in list(args):
|
||||
force = True
|
||||
|
||||
try:
|
||||
result = sc.software_deploy_precheck_api(list(args)[0], force, **kwargs)
|
||||
except SoftwareError as e:
|
||||
return dict(error="Error: %s" % str(e))
|
||||
result = sc.software_deploy_precheck_api(list(args)[0], force, **kwargs)
|
||||
|
||||
return result
|
||||
|
||||
@ -121,12 +97,9 @@ class SoftwareAPIController(object):
|
||||
force = 'force' in list(args)
|
||||
|
||||
if sc.any_patch_host_installing():
|
||||
return dict(error="Rejected: One or more nodes are installing releases.")
|
||||
raise SoftwareServiceError(error="Rejected: One or more nodes are installing a release.")
|
||||
|
||||
try:
|
||||
result = sc.software_deploy_start_api(list(args)[0], force, **kwargs)
|
||||
except SoftwareError as e:
|
||||
return dict(error="Error: %s" % str(e))
|
||||
result = sc.software_deploy_start_api(list(args)[0], force, **kwargs)
|
||||
|
||||
sc.send_latest_feed_commit_to_agent()
|
||||
sc.software_sync()
|
||||
@ -144,10 +117,7 @@ class SoftwareAPIController(object):
|
||||
@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))
|
||||
result = sc.software_install_local_api()
|
||||
|
||||
return result
|
||||
|
||||
@ -166,10 +136,7 @@ class SoftwareAPIController(object):
|
||||
@expose('json')
|
||||
@expose('show.xml', content_type='application/xml')
|
||||
def show(self, *args):
|
||||
try:
|
||||
result = sc.software_release_query_specific_cached(list(args))
|
||||
except SoftwareError as e:
|
||||
return dict(error="Error: %s" % str(e))
|
||||
result = sc.software_release_query_specific_cached(list(args))
|
||||
|
||||
return result
|
||||
|
||||
@ -212,8 +179,6 @@ class SoftwareAPIController(object):
|
||||
# Process uploaded files
|
||||
return sc.software_release_upload(uploaded_files)
|
||||
|
||||
except Exception as e:
|
||||
return dict(error=str(e))
|
||||
finally:
|
||||
# Remove all uploaded files from /scratch dir
|
||||
sc.software_sync()
|
||||
@ -223,10 +188,7 @@ class SoftwareAPIController(object):
|
||||
@expose('json')
|
||||
@expose('query.xml', content_type='application/xml')
|
||||
def query(self, **kwargs):
|
||||
try:
|
||||
sd = sc.software_release_query_cached(**kwargs)
|
||||
except SoftwareError as e:
|
||||
return dict(error="Error: %s" % str(e))
|
||||
sd = sc.software_release_query_cached(**kwargs)
|
||||
|
||||
return dict(sd=sd)
|
||||
|
||||
|
@ -21,6 +21,9 @@ ADDRESS_VERSION_IPV4 = 4
|
||||
ADDRESS_VERSION_IPV6 = 6
|
||||
CONTROLLER_FLOATING_HOSTNAME = "controller"
|
||||
|
||||
DISTRIBUTED_CLOUD_ROLE_SYSTEMCONTROLLER = 'systemcontroller'
|
||||
SYSTEM_CONTROLLER_REGION = 'SystemController'
|
||||
|
||||
SOFTWARE_STORAGE_DIR = "/opt/software"
|
||||
SOFTWARE_CONFIG_FILE_LOCAL = "/etc/software/software.conf"
|
||||
|
||||
@ -64,7 +67,8 @@ UNAVAILABLE = 'unavailable'
|
||||
DEPLOYING = 'deploying'
|
||||
DEPLOYED = 'deployed'
|
||||
REMOVING = 'removing'
|
||||
UNKNOWN = 'n/a'
|
||||
|
||||
DELETABLE_STATE = [AVAILABLE, UNAVAILABLE]
|
||||
|
||||
# TODO(bqian) states to be removed once current references are removed
|
||||
ABORTING = 'aborting'
|
||||
@ -182,6 +186,7 @@ class DEPLOY_STATES(Enum):
|
||||
HOST_DONE = 'host-done'
|
||||
HOST_FAILED = 'host-failed'
|
||||
|
||||
|
||||
class DEPLOY_HOST_STATES(Enum):
|
||||
DEPLOYED = 'deployed'
|
||||
DEPLOYING = 'deploying'
|
||||
|
118
software/software/dc_utils.py
Normal file
118
software/software/dc_utils.py
Normal file
@ -0,0 +1,118 @@
|
||||
"""
|
||||
Copyright (c) 2024 Wind River Systems, Inc.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
from keystoneauth1 import exceptions
|
||||
from keystoneauth1 import identity
|
||||
from keystoneauth1 import session
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import encodeutils
|
||||
from six.moves.urllib.request import Request
|
||||
from six.moves.urllib.request import urlopen
|
||||
|
||||
from software import utils
|
||||
from software.constants import SYSTEM_CONTROLLER_REGION
|
||||
|
||||
|
||||
LOG = logging.getLogger('main_logger')
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def get_token_endpoint(service_type, region_name=None, interface="internal"):
|
||||
config = CONF.get('keystone_authtoken')
|
||||
if region_name is None:
|
||||
region_name = config.region_name
|
||||
|
||||
try:
|
||||
auth = identity.Password(
|
||||
auth_url=config.auth_url,
|
||||
username=config.username,
|
||||
password=config.password,
|
||||
project_name=config.project_name,
|
||||
user_domain_name=config.user_domain_name,
|
||||
project_domain_name=config.project_domain_name
|
||||
)
|
||||
sess = session.Session(auth=auth)
|
||||
token = sess.get_token()
|
||||
endpoint = sess.get_endpoint(service_type=service_type,
|
||||
region_name=region_name,
|
||||
interface=interface)
|
||||
except exceptions.http.Unauthorized:
|
||||
raise Exception("Failed to authenticate to Keystone. Request unauthorized")
|
||||
except Exception as e:
|
||||
msg = "Failed to get token and endpoint. Error: %s", str(e)
|
||||
raise Exception(msg)
|
||||
return token, endpoint
|
||||
|
||||
|
||||
def rest_api_request(token, method, api_cmd,
|
||||
api_cmd_payload=None, timeout=45):
|
||||
"""
|
||||
Make a rest-api request
|
||||
Returns: response as a dictionary
|
||||
"""
|
||||
api_cmd_headers = dict()
|
||||
api_cmd_headers['Content-type'] = "application/json"
|
||||
api_cmd_headers['User-Agent'] = "usm/1.0"
|
||||
|
||||
request_info = Request(api_cmd)
|
||||
request_info.get_method = lambda: method
|
||||
if token:
|
||||
request_info.add_header("X-Auth-Token", token)
|
||||
request_info.add_header("Accept", "application/json")
|
||||
|
||||
if api_cmd_headers is not None:
|
||||
for header_type, header_value in api_cmd_headers.items():
|
||||
request_info.add_header(header_type, header_value)
|
||||
|
||||
if api_cmd_payload is not None:
|
||||
request_info.data = encodeutils.safe_encode(api_cmd_payload)
|
||||
|
||||
request = None
|
||||
try:
|
||||
request = urlopen(request_info, timeout=timeout)
|
||||
response = request.read()
|
||||
finally:
|
||||
if request:
|
||||
request.close()
|
||||
|
||||
if response == "":
|
||||
response = json.loads("{}")
|
||||
else:
|
||||
response = json.loads(response)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def get_subclouds_from_dcmanager():
|
||||
token, api_url = get_token_endpoint("dcmanager", region_name=SYSTEM_CONTROLLER_REGION)
|
||||
|
||||
api_cmd = api_url + '/subclouds'
|
||||
LOG.debug('api_cmd %s' % api_cmd)
|
||||
data = rest_api_request(token, "GET", api_cmd)
|
||||
if 'subclouds' in data:
|
||||
return data['subclouds']
|
||||
raise Exception(f"Incorrect response from dcmanager for querying subclouds {data}")
|
||||
|
||||
|
||||
def get_subcloud_groupby_version():
|
||||
subclouds = get_subclouds_from_dcmanager()
|
||||
grouped_subclouds = {}
|
||||
for subcloud in subclouds:
|
||||
major_ver = utils.get_major_release_version(subcloud['software_version'])
|
||||
if major_ver not in grouped_subclouds:
|
||||
grouped_subclouds[major_ver] = [subcloud]
|
||||
else:
|
||||
grouped_subclouds[major_ver].append(subcloud)
|
||||
|
||||
msg = "total %s subclouds." % len(subclouds)
|
||||
for ver in grouped_subclouds:
|
||||
msg = msg + " %s: %s subclouds." % (ver, len(grouped_subclouds[ver]))
|
||||
|
||||
LOG.info(msg)
|
||||
return grouped_subclouds
|
@ -5,11 +5,13 @@
|
||||
#
|
||||
|
||||
import os
|
||||
from packaging import version
|
||||
import shutil
|
||||
from software import constants
|
||||
from software.exceptions import FileSystemError
|
||||
from software.exceptions import InternalError
|
||||
from software.software_functions import LOG
|
||||
from software import utils
|
||||
|
||||
|
||||
class SWRelease(object):
|
||||
@ -19,6 +21,7 @@ class SWRelease(object):
|
||||
self._id = rel_id
|
||||
self._metadata = metadata
|
||||
self._contents = contents
|
||||
self._sw_version = None
|
||||
|
||||
@property
|
||||
def metadata(self):
|
||||
@ -83,9 +86,17 @@ class SWRelease(object):
|
||||
raise InternalError(error)
|
||||
|
||||
@property
|
||||
def sw_version(self):
|
||||
def sw_release(self):
|
||||
'''3 sections MM.mm.pp release version'''
|
||||
return self.metadata['sw_version']
|
||||
|
||||
@property
|
||||
def sw_version(self):
|
||||
'''2 sections MM.mm software version'''
|
||||
if self._sw_version is None:
|
||||
self._sw_version = utils.get_major_release_version(self.sw_release)
|
||||
return self._sw_version
|
||||
|
||||
def _get_latest_commit(self):
|
||||
num_commits = self.contents['number_of_commits']
|
||||
if int(num_commits) > 0:
|
||||
@ -156,6 +167,16 @@ class SWRelease(object):
|
||||
# latest commit
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_ga_release(self):
|
||||
ver = version.parse(self.sw_release)
|
||||
_, _, pp = ver.release
|
||||
return pp == 0
|
||||
|
||||
@property
|
||||
def is_deletable(self):
|
||||
return self.state in constants.DELETABLE_STATE
|
||||
|
||||
|
||||
class SWReleaseCollection(object):
|
||||
'''SWReleaseCollection encapsulates aggregated software release collection
|
||||
|
@ -31,6 +31,7 @@ from software.api import app
|
||||
from software.authapi import app as auth_app
|
||||
from software.constants import DEPLOY_STATES
|
||||
from software.base import PatchService
|
||||
from software.dc_utils import get_subcloud_groupby_version
|
||||
from software.exceptions import APTOSTreeCommandFail
|
||||
from software.exceptions import InternalError
|
||||
from software.exceptions import MetadataFail
|
||||
@ -66,6 +67,7 @@ from software.release_verify import verify_files
|
||||
import software.config as cfg
|
||||
import software.utils as utils
|
||||
from software.sysinv_utils import get_k8s_ver
|
||||
from software.sysinv_utils import is_system_controller
|
||||
|
||||
from software.db.api import get_instance
|
||||
|
||||
@ -1148,7 +1150,7 @@ class PatchController(PatchService):
|
||||
max_major_releases = 2
|
||||
major_releases = []
|
||||
for rel in self.release_collection.iterate_releases():
|
||||
major_rel = utils.get_major_release_version(rel.sw_version)
|
||||
major_rel = rel.sw_version
|
||||
if major_rel not in major_releases:
|
||||
major_releases.append(major_rel)
|
||||
|
||||
@ -1470,7 +1472,43 @@ class PatchController(PatchService):
|
||||
msg_error = ""
|
||||
|
||||
# Protect against duplications
|
||||
release_list = sorted(list(set(release_ids)))
|
||||
full_list = sorted(list(set(release_ids)))
|
||||
|
||||
not_founds = []
|
||||
cannot_del = []
|
||||
used_by_subcloud = []
|
||||
release_list = []
|
||||
for rel_id in full_list:
|
||||
rel = self.release_collection.get_release_by_id(rel_id)
|
||||
if rel is None:
|
||||
not_founds.append(rel_id)
|
||||
else:
|
||||
if not rel.is_deletable:
|
||||
cannot_del.append(rel_id)
|
||||
elif rel.is_ga_release and is_system_controller():
|
||||
subcloud_by_sw_version = get_subcloud_groupby_version()
|
||||
if rel.sw_version in subcloud_by_sw_version:
|
||||
used_by_subcloud.append(rel_id)
|
||||
else:
|
||||
release_list.append(rel_id)
|
||||
else:
|
||||
release_list.append(rel_id)
|
||||
|
||||
err_msg = ""
|
||||
if len(not_founds) > 0:
|
||||
list_str = ','.join(not_founds)
|
||||
err_msg = f"Releases {list_str} can not be found\n"
|
||||
|
||||
if len(cannot_del) > 0:
|
||||
list_str = ','.join(cannot_del)
|
||||
err_msg = err_msg + f"Releases {list_str} are not ready to delete\n"
|
||||
|
||||
if len(used_by_subcloud) > 0:
|
||||
list_str = ','.join(used_by_subcloud)
|
||||
err_msg = err_msg + f"Releases {list_str} are still used by subcloud(s)"
|
||||
|
||||
if len(err_msg) > 0:
|
||||
raise SoftwareServiceError(error=err_msg)
|
||||
|
||||
msg = "Deleting releases: %s" % ",".join(release_list)
|
||||
LOG.info(msg)
|
||||
@ -2915,7 +2953,6 @@ class PatchController(PatchService):
|
||||
return None
|
||||
deploy = deploy[0]
|
||||
|
||||
|
||||
deploy_host_list = []
|
||||
for host in deploy_hosts:
|
||||
state = host.get("state")
|
||||
|
@ -276,8 +276,8 @@ class DeployHandler(Deploy):
|
||||
"""
|
||||
super().query(from_release, to_release)
|
||||
for deploy in self.data.get("deploy", []):
|
||||
if (deploy.get("from_release") == from_release
|
||||
and deploy.get("to_release") == to_release):
|
||||
if (deploy.get("from_release") == from_release and
|
||||
deploy.get("to_release") == to_release):
|
||||
return deploy
|
||||
return []
|
||||
|
||||
@ -325,7 +325,7 @@ class DeployHostHandler(DeployHosts):
|
||||
super().__init__()
|
||||
self.data = get_software_filesystem_data()
|
||||
|
||||
def create(self, hostname, state:DEPLOY_HOST_STATES=None):
|
||||
def create(self, hostname, state: DEPLOY_HOST_STATES = None):
|
||||
super().create(hostname, state)
|
||||
deploy = self.query(hostname)
|
||||
if deploy:
|
||||
|
@ -1178,7 +1178,6 @@ def create_deploy_hosts():
|
||||
raise err
|
||||
|
||||
|
||||
|
||||
def collect_current_load_for_hosts():
|
||||
load_data = {
|
||||
"current_loads": []
|
||||
|
@ -5,8 +5,10 @@ SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
"""
|
||||
import logging
|
||||
import software.utils as utils
|
||||
|
||||
from software.exceptions import SysinvClientNotInitialized
|
||||
from software import constants
|
||||
from software import utils
|
||||
|
||||
|
||||
LOG = logging.getLogger('main_logger')
|
||||
@ -41,6 +43,7 @@ def get_k8s_ver():
|
||||
return k8s_ver.version
|
||||
raise Exception("Failed to get current k8s version")
|
||||
|
||||
|
||||
def get_ihost_list():
|
||||
try:
|
||||
token, endpoint = utils.get_endpoints_token()
|
||||
@ -49,3 +52,18 @@ def get_ihost_list():
|
||||
except Exception as err:
|
||||
LOG.error("Error getting ihost list: %s", err)
|
||||
raise
|
||||
|
||||
|
||||
def get_dc_role():
|
||||
try:
|
||||
token, endpoint = utils.get_endpoints_token()
|
||||
sysinv_client = get_sysinv_client(token=token, endpoint=endpoint)
|
||||
system = sysinv_client.isystem.list()[0]
|
||||
return system.distributed_cloud_role
|
||||
except Exception as err:
|
||||
LOG.error("Error getting DC role: %s", err)
|
||||
raise
|
||||
|
||||
|
||||
def is_system_controller():
|
||||
return get_dc_role() == constants.DISTRIBUTED_CLOUD_ROLE_SYSTEMCONTROLLER
|
||||
|
@ -42,10 +42,10 @@ class ExceptionHook(hooks.PecanHook):
|
||||
|
||||
if isinstance(e, SoftwareServiceError):
|
||||
LOG.warning("An issue is detected. Signature [%s]" % signature)
|
||||
# TODO(bqian) remove the logging after it is stable
|
||||
LOG.exception(e)
|
||||
|
||||
data = dict(info=e.info, warning=e.warning, error=e.error)
|
||||
data['error'] = data['error'] + " Error signature [%s]" % signature
|
||||
else:
|
||||
err_msg = "Internal error occurred. Error signature [%s]" % signature
|
||||
LOG.error(err_msg)
|
||||
|
Loading…
Reference in New Issue
Block a user