Create USM Horizon Internal API

This first commit adds a new internal API for starlingx dashboard that
will be referenced by multiple screens.
The 'software' endpoint will then replace the patching and upgrading
operations.

Test plan:
PASS: The starlingx-dashboard package is built successfully.
PASS: Browse Horizon, check that the other services are still working
      (e.g. sysinv, fault management, vim).

Story: 2010676
Task: 48997

Signed-off-by: Agustin Carranza <agustin.carranza@windriver.com>
Change-Id: I391eff6875e6f3b051051a6532bd2d0982a80d98
This commit is contained in:
Agustin Carranza 2023-10-26 13:21:00 -03:00 committed by Italo Lemos
parent 8121f7d24d
commit ce0da259f1
2 changed files with 230 additions and 1 deletions

View File

@ -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",
]

View File

@ -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)