diff --git a/starlingx-dashboard/starlingx-dashboard/starlingx_dashboard/api/__init__.py b/starlingx-dashboard/starlingx-dashboard/starlingx_dashboard/api/__init__.py index 27d3824c..61914a83 100755 --- a/starlingx-dashboard/starlingx-dashboard/starlingx_dashboard/api/__init__.py +++ b/starlingx-dashboard/starlingx-dashboard/starlingx_dashboard/api/__init__.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. # -# Copyright (c) 2017-2019 Wind River Systems, Inc. +# Copyright (c) 2017-2024 Wind River Systems, Inc. # from starlingx_dashboard.api import base @@ -21,6 +21,7 @@ from starlingx_dashboard.api import fm from starlingx_dashboard.api import neutron from starlingx_dashboard.api import patch from starlingx_dashboard.api import sysinv +from starlingx_dashboard.api import usm from starlingx_dashboard.api import vim @@ -32,5 +33,6 @@ __all__ = [ "neutron", "patch", "sysinv", + "usm", "vim", ] diff --git a/starlingx-dashboard/starlingx-dashboard/starlingx_dashboard/api/usm.py b/starlingx-dashboard/starlingx-dashboard/starlingx_dashboard/api/usm.py new file mode 100755 index 00000000..d639f713 --- /dev/null +++ b/starlingx-dashboard/starlingx-dashboard/starlingx_dashboard/api/usm.py @@ -0,0 +1,227 @@ +# +# Copyright (c) 2023 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +from horizon import messages + +import logging + +from urllib.parse import urlparse + +import requests + +from openstack_dashboard.api import base +from requests_toolbelt import MultipartEncoder + +LOG = logging.getLogger(__name__) + +USM_API_SERVICENAME = "usm" + + +class Client(object): + def __init__(self, version, url, token_id): + self.version = version + self.url = url + self.token_id = token_id + + def _make_request(self, token_id, method, api_version, api_cmd, + encoder=None): + url = self.url + url += "%s/software/%s" % (api_version, api_cmd) + + headers = {"X-Auth-Token": token_id, + "Accept": "application/json"} + + if method == 'GET': + req = requests.get(url, headers=headers) + elif method == 'POST': + if encoder is not None: + headers['Content-Type'] = encoder.content_type + req = requests.post(url, headers=headers, data=encoder) + + resp = req.json() + + return resp + + def get_releases(self): + return self._make_request(self.token_id, "GET", self.version, + "query?show=all") + + def show_release(self, release_id): + return self._make_request(self.token_id, "GET", self.version, + "show/%s" % release_id) + + def get_hosts(self): + return self._make_request(self.token_id, "GET", self.version, + "host_list") + + def install_local(self): + return self._make_request(self.token_id, "GET", self.version, + "host_install") + + def upload(self, releasefile): + encoder = MultipartEncoder(fields=releasefile) + return self._make_request(self.token_id, "POST", self.version, + "upload", encoder=encoder) + + def delete(self, release_ids): + releases = "/".join(release_ids) + return self._make_request(self.token_id, "POST", self.version, + "delete/%s" % releases) + + def commit(self, release_ids): + releases = "/".join(release_ids) + return self._make_request(self.token_id, "POST", self.version, + "commit_patch/%s" % releases) + + +def _software_client(request): + o = urlparse(base.url_for(request, USM_API_SERVICENAME)) + url = "://".join((o.scheme, o.netloc)) + return Client("v1", url, token_id=request.user.token.id) + + +class Release(object): + _attrs = ['state', + 'sw_version', + 'status', + 'unremovable', + 'summary', + 'description', + 'install_instructions', + 'warnings', + 'reboot_required', + 'requires'] + + +class Host(object): + _attrs = ['hostname', + 'ip', + 'latest_sysroot_commit', + 'nodetype', + 'patch_current', + 'patch_failed', + 'requires_reboot', + 'secs_since_ack', + 'stale_details', + 'sw_version', + 'state', + 'allow_insvc_patching', + 'interim_state'] + + +def get_releases(request): + releases = [] + try: + info = _software_client(request).get_releases() + except Exception: + return releases + + if info: + for r in info['sd'].items(): + release = Release() + + for a in release._attrs: + if a == 'requires': + release.requires = [str(rs) for rs in r[1][a]] + continue + + if a == 'reboot_required': + # Default to "N" + setattr(release, a, str(r[1].get(a, "N"))) + continue + # Must handle older patches that have metadata that is missing + # newer attributes. Default missing attributes to "". + setattr(release, a, str(r[1].get(a, ""))) + release.release_id = str(r[0]) + + releases.append(release) + return releases + + +def get_release(request, release_id): + releases = get_releases(request) + release = next((r for r in releases if r.release_id == release_id), None) + + # add on release contents + data = _software_client(request).show_release(release_id) + + release.contents = {} + if "number_of_commits" in data['contents'][release_id] and \ + data['contents'][release_id]['number_of_commits'] != "": + release.contents["number_of_commits"] = \ + data['contents'][release_id]['number_of_commits'] + if "base" in data['contents'][release_id] and \ + data['contents'][release_id]['base']['commit'] != "": + release.contents["base_commit"] = \ + data['contents'][release_id]["base"]['commit'] + for i in range(int(data['contents'][release_id]['number_of_commits'])): + release.contents["commit%s" % (i + 1)] = \ + data['contents'][release_id]["commit%s" % (i + 1)]['commit'] + + return release + + +def get_hosts(request): + hosts = [] + default_value = None + try: + info = _software_client(request).get_hosts() + except Exception: + return hosts + + if info: + for h in info['data']: + host = Host() + for a in host._attrs: + # if host received doesn't have this attribute, + # add it with a default value + if hasattr(h, a): + setattr(host, a, h[a]) + else: + setattr(host, a, default_value) + LOG.debug("Attribute not found. Adding default:" + "%s", a) + hosts.append(host) + return hosts + + +def get_host(request, hostname): + phosts = get_hosts(request) + return next((phost for phost in phosts if phost.hostname == hostname), + None) + + +def get_message(request, data): + LOG.info("RESPONSE: %s", data) + if not data or ('error' in data and data["error"] != ""): + error_msg = data["error"] or "Invalid release file" + messages.error(request, error_msg) + LOG.error(error_msg) + if 'warning' in data and data["warning"] != "": + return data["warning"] + if 'info' in data and data["info"] != "": + return data["info"] + return "" + + +def host_install(request): + resp = _software_client(request).host_install() + return get_message(request, resp) + + +def upload_software(request, softwarefile, name): + _file = {'file': (name, softwarefile,)} + resp = _software_client(request).upload(_file) + return get_message(request, resp) + + +def software_delete_req(request, software_id): + resp = _software_client(request).delete(software_id) + return get_message(request, resp) + + +def software_commit_req(request, software_id): + resp = _software_client(request).commit(software_id) + return get_message(request, resp)