New USM REST API
Updated the USM REST Api as agreed Also applied the standardized CLI output w/ tabulate for CLI commands. Fixed a few things: 1. software upload column header change to "Release" 2. use region_name from usm.conf to replace default "RegionOne" as local region. 3. temporarily skip the ParsableErrorMiddleware. 4. do not recreate http exceptions based on the http status_code on the client side, use common display function to display the http error. 5. expose webob.exc.HTTPClientError to the client side. 6. updated --debug output to include CLI output at the end. Test Cases: passed all CLI commands, verify the endpoints and request body. passed verify CLI requests compiled with defined REST Api. Story: 2010676 Task: 49905 Change-Id: I5ab971b455aed527b7b1a21396b97334ba1e05ab
This commit is contained in:
parent
1d6add41a2
commit
e0415cd65b
|
@ -7,7 +7,7 @@
|
||||||
from oslo_utils import importutils
|
from oslo_utils import importutils
|
||||||
|
|
||||||
from software_client import exc
|
from software_client import exc
|
||||||
from software_client.constants import TOKEN, KEYSTONE, LOCAL_ROOT
|
from software_client.constants import LOCAL_ROOT
|
||||||
|
|
||||||
|
|
||||||
SERVICE_NAME = 'usm'
|
SERVICE_NAME = 'usm'
|
||||||
|
@ -121,8 +121,7 @@ def get_client(api_version, auth_mode, session=None, service_type=SERVICE_TYPE,
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = ('Failed to get openstack endpoint')
|
msg = ('Failed to get openstack endpoint')
|
||||||
raise exc.EndpointException(
|
raise exc.EndpointException(
|
||||||
('%(message)s, error was: %(error)s') %
|
('%(message)s, error was: %(error)s') % {'message': msg, 'error': e})
|
||||||
{'message': msg, 'error': e})
|
|
||||||
elif local_root:
|
elif local_root:
|
||||||
endpoint = API_ENDPOINT
|
endpoint = API_ENDPOINT
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -36,6 +36,9 @@ class Manager(object):
|
||||||
def _create_multipart(self, url, **kwargs):
|
def _create_multipart(self, url, **kwargs):
|
||||||
return self.api.multipart_request('POST', url, **kwargs)
|
return self.api.multipart_request('POST', url, **kwargs)
|
||||||
|
|
||||||
|
def _post(self, url, **kwargs):
|
||||||
|
return self.api.json_request('POST', url, **kwargs)
|
||||||
|
|
||||||
def _list(self, url, response_key=None, obj_class=None, body=None):
|
def _list(self, url, response_key=None, obj_class=None, body=None):
|
||||||
resp, body = self.api.json_request('GET', url)
|
resp, body = self.api.json_request('GET', url)
|
||||||
if response_key:
|
if response_key:
|
||||||
|
@ -48,6 +51,14 @@ class Manager(object):
|
||||||
|
|
||||||
return resp, data
|
return resp, data
|
||||||
|
|
||||||
|
def _fetch(self, url):
|
||||||
|
resp, body = self.api.json_request('GET', url)
|
||||||
|
data = body
|
||||||
|
return resp, data
|
||||||
|
|
||||||
|
def _delete(self, url):
|
||||||
|
return self.api.json_request('DELETE', url)
|
||||||
|
|
||||||
|
|
||||||
class Resource(object):
|
class Resource(object):
|
||||||
"""A resource represents a particular instance of an object (tenant, user,
|
"""A resource represents a particular instance of an object (tenant, user,
|
||||||
|
|
|
@ -176,13 +176,9 @@ class SessionClient(adapter.LegacyJsonAdapter):
|
||||||
|
|
||||||
resp = self.session.request(url, method,
|
resp = self.session.request(url, method,
|
||||||
raise_exc=False, **kwargs)
|
raise_exc=False, **kwargs)
|
||||||
if 400 <= resp.status_code < 600:
|
# NOTE (bqian) Do not recreate and raise exceptions. Let the
|
||||||
error_json = _extract_error_json(resp.content, resp)
|
# display_error utility function to handle the well formatted
|
||||||
raise exceptions.from_response(
|
# response for webob.exc.HTTPClientError
|
||||||
resp, error_json.get('faultstring'),
|
|
||||||
error_json.get('debuginfo'), method, url)
|
|
||||||
elif resp.status_code in (300, 301, 302, 305):
|
|
||||||
raise exceptions.from_response(resp, method=method, url=url)
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def json_request(self, method, url, **kwargs):
|
def json_request(self, method, url, **kwargs):
|
||||||
|
@ -270,8 +266,8 @@ class SessionClient(adapter.LegacyJsonAdapter):
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
err_message = _extract_error_json(response.text, response)
|
err_message = _extract_error_json(response.text, response)
|
||||||
fault_text = (
|
fault_text = (
|
||||||
err_message.get("faultstring")
|
err_message.get("faultstring") or
|
||||||
or "Unknown error in SessionClient while uploading request with multipart"
|
"Unknown error in SessionClient while uploading request with multipart"
|
||||||
)
|
)
|
||||||
raise exceptions.HTTPBadRequest(fault_text)
|
raise exceptions.HTTPBadRequest(fault_text)
|
||||||
|
|
||||||
|
@ -475,7 +471,6 @@ class HTTPClient(httplib2.Http):
|
||||||
def multipart_request(self, method, url, **kwargs):
|
def multipart_request(self, method, url, **kwargs):
|
||||||
return self.upload_request_with_multipart(method, url, **kwargs)
|
return self.upload_request_with_multipart(method, url, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def raw_request(self, method, url, **kwargs):
|
def raw_request(self, method, url, **kwargs):
|
||||||
if not self.local_root:
|
if not self.local_root:
|
||||||
self.authenticate_and_fetch_endpoint_url()
|
self.authenticate_and_fetch_endpoint_url()
|
||||||
|
@ -539,8 +534,8 @@ class HTTPClient(httplib2.Http):
|
||||||
'tenantName': self.tenant_name, }, }
|
'tenantName': self.tenant_name, }, }
|
||||||
|
|
||||||
resp, resp_body = self._cs_request(token_url, "POST",
|
resp, resp_body = self._cs_request(token_url, "POST",
|
||||||
body=json.dumps(body),
|
body=json.dumps(body),
|
||||||
content_type="application/json")
|
content_type="application/json")
|
||||||
status_code = self.get_status_code(resp)
|
status_code = self.get_status_code(resp)
|
||||||
if status_code != 200:
|
if status_code != 200:
|
||||||
raise exceptions.HTTPUnauthorized(resp_body)
|
raise exceptions.HTTPUnauthorized(resp_body)
|
||||||
|
@ -570,7 +565,7 @@ class HTTPClient(httplib2.Http):
|
||||||
body_json = json.loads(body)
|
body_json = json.loads(body)
|
||||||
if 'error' in body_json:
|
if 'error' in body_json:
|
||||||
error_json = {'faultstring': body_json.get('error'),
|
error_json = {'faultstring': body_json.get('error'),
|
||||||
'debuginfo': body_json.get('info')}
|
'debuginfo': body_json.get('info')}
|
||||||
elif 'error_message' in body_json:
|
elif 'error_message' in body_json:
|
||||||
raw_msg = body_json['error_message']
|
raw_msg = body_json['error_message']
|
||||||
error_json = json.loads(raw_msg)
|
error_json = json.loads(raw_msg)
|
||||||
|
|
|
@ -20,32 +20,11 @@ import argparse
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import textwrap
|
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
from oslo_utils import importutils
|
from oslo_utils import importutils
|
||||||
from six.moves import zip
|
|
||||||
|
|
||||||
from software_client.common.http_errors import HTTP_ERRORS
|
from software_client.common.http_errors import HTTP_ERRORS
|
||||||
|
|
||||||
# TODO(bqian) remove below overrides when switching to
|
|
||||||
# system command style CLI display for USM CLI is ready
|
|
||||||
from tabulate import _table_formats
|
|
||||||
from tabulate import TableFormat
|
|
||||||
from tabulate import Line
|
|
||||||
from tabulate import DataRow
|
|
||||||
|
|
||||||
simple = TableFormat(
|
|
||||||
lineabove=Line("", "-", " ", ""),
|
|
||||||
linebelowheader=Line("", "=", " ", ""),
|
|
||||||
linebetweenrows=None,
|
|
||||||
linebelow=Line("", "-", " ", ""),
|
|
||||||
headerrow=DataRow("", " ", ""),
|
|
||||||
datarow=DataRow("", " ", ""),
|
|
||||||
padding=0,
|
|
||||||
with_header_hide=["lineabove", "linebelow"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# _table_formats['pretty'] = simple
|
|
||||||
#####################################################
|
#####################################################
|
||||||
|
|
||||||
TERM_WIDTH = 72
|
TERM_WIDTH = 72
|
||||||
|
@ -140,7 +119,7 @@ def check_rc(req, data):
|
||||||
|
|
||||||
|
|
||||||
def _display_info(text):
|
def _display_info(text):
|
||||||
''' display the basic info json object '''
|
'''display the basic info json object '''
|
||||||
try:
|
try:
|
||||||
data = json.loads(text)
|
data = json.loads(text)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -155,6 +134,27 @@ def _display_info(text):
|
||||||
print(data["info"])
|
print(data["info"])
|
||||||
|
|
||||||
|
|
||||||
|
def _display_error(status_code, text):
|
||||||
|
try:
|
||||||
|
data = json.loads(text)
|
||||||
|
except Exception:
|
||||||
|
print("Error:\n%s", HTTP_ERRORS[status_code])
|
||||||
|
return
|
||||||
|
|
||||||
|
if "code" in data:
|
||||||
|
print("Status: %s." % data["code"], end="")
|
||||||
|
else:
|
||||||
|
print("Status: %s." % status_code, end="")
|
||||||
|
|
||||||
|
if "description" in data:
|
||||||
|
print(" " + data["description"])
|
||||||
|
elif "title" in data:
|
||||||
|
print(" " + data["title"])
|
||||||
|
else:
|
||||||
|
# any 4xx and 5xx errors does not contain API information.
|
||||||
|
print(HTTP_ERRORS[status_code])
|
||||||
|
|
||||||
|
|
||||||
def display_info(resp):
|
def display_info(resp):
|
||||||
'''
|
'''
|
||||||
This function displays basic REST API return, w/ info json object:
|
This function displays basic REST API return, w/ info json object:
|
||||||
|
@ -163,17 +163,21 @@ def display_info(resp):
|
||||||
"warning":"",
|
"warning":"",
|
||||||
"error":"",
|
"error":"",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
or an webob exception:
|
||||||
|
{"code": 404, "title": "", "description": ""}
|
||||||
|
|
||||||
|
or default message based on status code
|
||||||
'''
|
'''
|
||||||
|
|
||||||
status_code = resp.status_code
|
status_code = resp.status_code
|
||||||
text = resp.text
|
text = resp.text
|
||||||
|
|
||||||
if resp.status_code == 500:
|
if status_code == 500:
|
||||||
# all 500 error comes with basic info json object
|
# all 500 error comes with basic info json object
|
||||||
_display_info(text)
|
_display_info(text)
|
||||||
elif resp.status_code in HTTP_ERRORS:
|
elif status_code in HTTP_ERRORS:
|
||||||
# any 4xx and 5xx errors does not contain API information.
|
_display_error(status_code, text)
|
||||||
print("Error:\n%s", HTTP_ERRORS[status_code])
|
|
||||||
else:
|
else:
|
||||||
# print out the basic info json object
|
# print out the basic info json object
|
||||||
_display_info(text)
|
_display_info(text)
|
||||||
|
@ -214,205 +218,6 @@ def display_detail_result(data):
|
||||||
print(tabulate(table, header, tablefmt='pretty', colalign=("left", "left")))
|
print(tabulate(table, header, tablefmt='pretty', colalign=("left", "left")))
|
||||||
|
|
||||||
|
|
||||||
def print_result_list(header_data_list, data_list, has_error, sort_key=0):
|
|
||||||
"""
|
|
||||||
Print a list of data in a simple table format
|
|
||||||
:param header_data_list: Array of header data
|
|
||||||
:param data_list: Array of data
|
|
||||||
:param has_error: Boolean indicating if the request has error message
|
|
||||||
:param sort_key: Sorting key for the list
|
|
||||||
"""
|
|
||||||
|
|
||||||
if has_error:
|
|
||||||
return
|
|
||||||
|
|
||||||
if data_list is None or len(data_list) == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Find the longest header string in each column
|
|
||||||
header_lengths = [len(str(x)) for x in header_data_list]
|
|
||||||
# Find the longest content string in each column
|
|
||||||
content_lengths = [max(len(str(x[i])) for x in data_list)
|
|
||||||
for i in range(len(header_data_list))]
|
|
||||||
# Find the max of the two for each column
|
|
||||||
col_lengths = [(x if x > y else y) for x, y in zip(header_lengths, content_lengths)]
|
|
||||||
|
|
||||||
print(' '.join(f"{x.center(col_lengths[i])}" for i, x in enumerate(header_data_list)))
|
|
||||||
print(' '.join('=' * length for length in col_lengths))
|
|
||||||
for item in sorted(data_list, key=lambda d: d[sort_key]):
|
|
||||||
print(' '.join(f"{str(x).center(col_lengths[i])}" for i, x in enumerate(item)))
|
|
||||||
print("\n")
|
|
||||||
|
|
||||||
|
|
||||||
def print_software_deploy_host_list_result(req, data):
|
|
||||||
if req.status_code == 200:
|
|
||||||
if not data:
|
|
||||||
print("No deploy in progress.\n")
|
|
||||||
return
|
|
||||||
|
|
||||||
# Calculate column widths
|
|
||||||
hdr_hn = "Hostname"
|
|
||||||
hdr_rel = "Software Release"
|
|
||||||
hdr_tg_rel = "Target Release"
|
|
||||||
hdr_rr = "Reboot Required"
|
|
||||||
hdr_state = "Host State"
|
|
||||||
|
|
||||||
width_hn = len(hdr_hn)
|
|
||||||
width_rel = len(hdr_rel)
|
|
||||||
width_tg_rel = len(hdr_tg_rel)
|
|
||||||
width_rr = len(hdr_rr)
|
|
||||||
width_state = len(hdr_state)
|
|
||||||
|
|
||||||
for agent in sorted(data, key=lambda a: a["hostname"]):
|
|
||||||
if agent.get("host_state") is None:
|
|
||||||
agent["host_state"] = "No active deployment"
|
|
||||||
if agent.get("target_release") is None:
|
|
||||||
agent["target_release"] = "N/A"
|
|
||||||
if len(agent["hostname"]) > width_hn:
|
|
||||||
width_hn = len(agent["hostname"])
|
|
||||||
if len(agent["software_release"]) > width_rel:
|
|
||||||
width_rel = len(agent["software_release"])
|
|
||||||
if len(agent["target_release"]) > width_tg_rel:
|
|
||||||
width_tg_rel = len(agent["target_release"])
|
|
||||||
if len(agent["host_state"]) > width_state:
|
|
||||||
width_state = len(agent["host_state"])
|
|
||||||
|
|
||||||
print("{0:^{width_hn}} {1:^{width_rel}} {2:^{width_tg_rel}} {3:^{width_rr}} {4:^{width_state}}".format(
|
|
||||||
hdr_hn, hdr_rel, hdr_tg_rel, hdr_rr, hdr_state,
|
|
||||||
width_hn=width_hn, width_rel=width_rel, width_tg_rel=width_tg_rel, width_rr=width_rr, width_state=width_state))
|
|
||||||
|
|
||||||
print("{0} {1} {2} {3} {4}".format(
|
|
||||||
'=' * width_hn, '=' * width_rel, '=' * width_tg_rel, '=' * width_rr, '=' * width_state))
|
|
||||||
|
|
||||||
for agent in sorted(data, key=lambda a: a["hostname"]):
|
|
||||||
print("{0:<{width_hn}} {1:^{width_rel}} {2:^{width_tg_rel}} {3:^{width_rr}} {4:^{width_state}}".format(
|
|
||||||
agent["hostname"],
|
|
||||||
agent["software_release"],
|
|
||||||
agent["target_release"],
|
|
||||||
"Yes" if agent.get("reboot_required", None) else "No",
|
|
||||||
agent["host_state"],
|
|
||||||
width_hn=width_hn, width_rel=width_rel, width_tg_rel=width_tg_rel, width_rr=width_rr, width_state=width_state))
|
|
||||||
|
|
||||||
elif req.status_code == 500:
|
|
||||||
print("An internal error has occurred. Please check /var/log/software.log for details")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def print_release_show_result(req, data, list_packages=False):
|
|
||||||
if req.status_code == 200:
|
|
||||||
|
|
||||||
if 'metadata' in data:
|
|
||||||
sd = data['metadata']
|
|
||||||
contents = data['contents']
|
|
||||||
for release_id in sorted(list(sd)):
|
|
||||||
print("%s:" % release_id)
|
|
||||||
|
|
||||||
if "sw_version" in sd[release_id] and sd[release_id]["sw_version"] != "":
|
|
||||||
print(textwrap.fill(" {0:<15} ".format("Version:") + sd[release_id]["sw_version"],
|
|
||||||
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
||||||
|
|
||||||
if "state" in sd[release_id] and sd[release_id]["state"] != "":
|
|
||||||
print(textwrap.fill(" {0:<15} ".format("State:") + sd[release_id]["state"],
|
|
||||||
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
||||||
|
|
||||||
if "status" in sd[release_id] and sd[release_id]["status"] != "":
|
|
||||||
print(textwrap.fill(" {0:<15} ".format("Status:") + sd[release_id]["status"],
|
|
||||||
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
||||||
|
|
||||||
if "unremovable" in sd[release_id] and sd[release_id]["unremovable"] != "":
|
|
||||||
print(textwrap.fill(" {0:<15} ".format("Unremovable:") + sd[release_id]["unremovable"],
|
|
||||||
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
||||||
|
|
||||||
if "reboot_required" in sd[release_id] and sd[release_id]["reboot_required"] != "":
|
|
||||||
print(textwrap.fill(" {0:<15} ".format("RR:") + sd[release_id]["reboot_required"],
|
|
||||||
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
||||||
|
|
||||||
if "apply_active_release_only" in sd[release_id] and sd[release_id]["apply_active_release_only"] != "":
|
|
||||||
print(textwrap.fill(" {0:<15} ".format("Apply Active Release Only:") + sd[release_id]["apply_active_release_only"],
|
|
||||||
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
||||||
|
|
||||||
if "summary" in sd[release_id] and sd[release_id]["summary"] != "":
|
|
||||||
print(textwrap.fill(" {0:<15} ".format("Summary:") + sd[release_id]["summary"],
|
|
||||||
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
||||||
|
|
||||||
if "description" in sd[release_id] and sd[release_id]["description"] != "":
|
|
||||||
first_line = True
|
|
||||||
for line in sd[release_id]["description"].split('\n'):
|
|
||||||
if first_line:
|
|
||||||
print(textwrap.fill(" {0:<15} ".format("Description:") + line,
|
|
||||||
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
||||||
first_line = False
|
|
||||||
else:
|
|
||||||
print(textwrap.fill(line,
|
|
||||||
width=TERM_WIDTH, subsequent_indent=' ' * 20,
|
|
||||||
initial_indent=' ' * 20))
|
|
||||||
|
|
||||||
if "install_instructions" in sd[release_id] and sd[release_id]["install_instructions"] != "":
|
|
||||||
print(" Install Instructions:")
|
|
||||||
for line in sd[release_id]["install_instructions"].split('\n'):
|
|
||||||
print(textwrap.fill(line,
|
|
||||||
width=TERM_WIDTH, subsequent_indent=' ' * 20,
|
|
||||||
initial_indent=' ' * 20))
|
|
||||||
|
|
||||||
if "warnings" in sd[release_id] and sd[release_id]["warnings"] != "":
|
|
||||||
first_line = True
|
|
||||||
for line in sd[release_id]["warnings"].split('\n'):
|
|
||||||
if first_line:
|
|
||||||
print(textwrap.fill(" {0:<15} ".format("Warnings:") + line,
|
|
||||||
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
||||||
first_line = False
|
|
||||||
else:
|
|
||||||
print(textwrap.fill(line,
|
|
||||||
width=TERM_WIDTH, subsequent_indent=' ' * 20,
|
|
||||||
initial_indent=' ' * 20))
|
|
||||||
|
|
||||||
if "requires" in sd[release_id] and len(sd[release_id]["requires"]) > 0:
|
|
||||||
print(" Requires:")
|
|
||||||
for req_patch in sorted(sd[release_id]["requires"]):
|
|
||||||
print(' ' * 20 + req_patch)
|
|
||||||
|
|
||||||
if "contents" in data and release_id in data["contents"]:
|
|
||||||
print(" Contents:\n")
|
|
||||||
if "number_of_commits" in contents[release_id] and \
|
|
||||||
contents[release_id]["number_of_commits"] != "":
|
|
||||||
print(textwrap.fill(" {0:<15} ".format("No. of commits:") +
|
|
||||||
contents[release_id]["number_of_commits"],
|
|
||||||
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
||||||
if "base" in contents[release_id] and \
|
|
||||||
contents[release_id]["base"]["commit"] != "":
|
|
||||||
print(textwrap.fill(" {0:<15} ".format("Base commit:") +
|
|
||||||
contents[release_id]["base"]["commit"],
|
|
||||||
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
||||||
if "number_of_commits" in contents[release_id] and \
|
|
||||||
contents[release_id]["number_of_commits"] != "":
|
|
||||||
for i in range(int(contents[release_id]["number_of_commits"])):
|
|
||||||
print(textwrap.fill(" {0:<15} ".format("Commit%s:" % (i + 1)) +
|
|
||||||
contents[release_id]["commit%s" % (i + 1)]["commit"],
|
|
||||||
width=TERM_WIDTH, subsequent_indent=' ' * 20))
|
|
||||||
|
|
||||||
if list_packages:
|
|
||||||
if "packages" in sd[release_id] and len(sd[release_id]["packages"]):
|
|
||||||
print(" Packages:")
|
|
||||||
for package in sorted(sd[release_id]["packages"]):
|
|
||||||
print(" " * 20 + package)
|
|
||||||
|
|
||||||
print("\n")
|
|
||||||
|
|
||||||
if 'info' in data and data["info"] != "":
|
|
||||||
print(data["info"])
|
|
||||||
|
|
||||||
if 'warning' in data and data["warning"] != "":
|
|
||||||
print("Warning:")
|
|
||||||
print(data["warning"])
|
|
||||||
|
|
||||||
if 'error' in data and data["error"] != "":
|
|
||||||
print("Error:")
|
|
||||||
print(data["error"])
|
|
||||||
|
|
||||||
elif req.status_code == 500:
|
|
||||||
print("An internal error has occurred. Please check /var/log/software.log for details")
|
|
||||||
|
|
||||||
|
|
||||||
def print_software_op_result(resp, data):
|
def print_software_op_result(resp, data):
|
||||||
if resp.status_code == 200:
|
if resp.status_code == 200:
|
||||||
if 'sd' in data:
|
if 'sd' in data:
|
||||||
|
|
|
@ -22,7 +22,9 @@ import software_client
|
||||||
from software_client import client as sclient
|
from software_client import client as sclient
|
||||||
from software_client import exc
|
from software_client import exc
|
||||||
from software_client.common import utils
|
from software_client.common import utils
|
||||||
from software_client.constants import TOKEN, KEYSTONE, LOCAL_ROOT
|
from software_client.constants import LOCAL_ROOT
|
||||||
|
from software_client.constants import KEYSTONE
|
||||||
|
from software_client.constants import TOKEN
|
||||||
|
|
||||||
|
|
||||||
VIRTUAL_REGION = 'SystemController'
|
VIRTUAL_REGION = 'SystemController'
|
||||||
|
@ -251,7 +253,6 @@ class SoftwareClientShell(object):
|
||||||
default=utils.env('OS_PROJECT_DOMAIN_NAME'),
|
default=utils.env('OS_PROJECT_DOMAIN_NAME'),
|
||||||
help='Defaults to env[OS_PROJECT_DOMAIN_NAME].')
|
help='Defaults to env[OS_PROJECT_DOMAIN_NAME].')
|
||||||
|
|
||||||
|
|
||||||
# All commands are considered restricted, unless explicitly set to False
|
# All commands are considered restricted, unless explicitly set to False
|
||||||
parser.set_defaults(restricted=True)
|
parser.set_defaults(restricted=True)
|
||||||
# All functions are initially defined as 'not implemented yet'
|
# All functions are initially defined as 'not implemented yet'
|
||||||
|
|
|
@ -15,7 +15,7 @@ from testtools import matchers
|
||||||
import keystoneauth1
|
import keystoneauth1
|
||||||
|
|
||||||
from software_client import exc
|
from software_client import exc
|
||||||
from software_client import software_client
|
from software_client.software_client import SoftwareClientShell
|
||||||
from software_client.tests import utils
|
from software_client.tests import utils
|
||||||
|
|
||||||
FAKE_ENV = {'OS_USERNAME': 'username',
|
FAKE_ENV = {'OS_USERNAME': 'username',
|
||||||
|
@ -49,7 +49,7 @@ class ShellTest(utils.BaseTestCase):
|
||||||
orig = sys.stdout
|
orig = sys.stdout
|
||||||
try:
|
try:
|
||||||
sys.stdout = StringIO()
|
sys.stdout = StringIO()
|
||||||
_shell = software_client.SoftwareClientShell()
|
_shell = SoftwareClientShell()
|
||||||
_shell.main(argstr.split())
|
_shell.main(argstr.split())
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
|
|
|
@ -131,26 +131,6 @@ class SoftwareClientHelpTestCase(SoftwareClientTestCase, SoftwareClientNonRootMi
|
||||||
print_help is invoked when there is a failure.
|
print_help is invoked when there is a failure.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@mock.patch('software_client.software_client.check_for_os_region_name')
|
|
||||||
@mock.patch('argparse.ArgumentParser.print_help')
|
|
||||||
@mock.patch('argparse.ArgumentParser.print_usage')
|
|
||||||
def test_main_no_args(self, mock_usage, mock_help, mock_check):
|
|
||||||
"""When no arguments are called, it should call print_usage"""
|
|
||||||
shell_args = [self.PROG, ]
|
|
||||||
self._test_method(shell_args=shell_args)
|
|
||||||
mock_help.assert_called()
|
|
||||||
mock_check.assert_not_called()
|
|
||||||
|
|
||||||
@mock.patch('software_client.software_client.check_for_os_region_name')
|
|
||||||
@mock.patch('argparse.ArgumentParser.print_help')
|
|
||||||
@mock.patch('argparse.ArgumentParser.print_usage')
|
|
||||||
def test_main_help(self, mock_usage, mock_help, mock_check):
|
|
||||||
"""When -h is passed in, this should invoke print_help"""
|
|
||||||
shell_args = [self.PROG, "-h"]
|
|
||||||
self._test_method(shell_args=shell_args)
|
|
||||||
mock_help.assert_called()
|
|
||||||
mock_check.assert_not_called()
|
|
||||||
|
|
||||||
@mock.patch('software_client.software_client.check_for_os_region_name')
|
@mock.patch('software_client.software_client.check_for_os_region_name')
|
||||||
@mock.patch('argparse.ArgumentParser.print_help')
|
@mock.patch('argparse.ArgumentParser.print_help')
|
||||||
@mock.patch('argparse.ArgumentParser.print_usage')
|
@mock.patch('argparse.ArgumentParser.print_usage')
|
||||||
|
|
|
@ -20,75 +20,86 @@ import software_client.v1.deploy
|
||||||
import software_client.v1.deploy_shell
|
import software_client.v1.deploy_shell
|
||||||
|
|
||||||
|
|
||||||
HOST_LIST = {'data': [{
|
HOST_LIST = {
|
||||||
'ip': '192.168.204.2',
|
'data': [
|
||||||
'hostname': 'controller-0',
|
{
|
||||||
'deployed': True,
|
'ip': '192.168.204.2',
|
||||||
'secs_since_ack': 20,
|
'hostname': 'controller-0',
|
||||||
'patch_failed': True,
|
'deployed': True,
|
||||||
'stale_details': False,
|
'secs_since_ack': 20,
|
||||||
'latest_sysroot_commit': '95139a5067',
|
'patch_failed': True,
|
||||||
'nodetype': 'controller',
|
'stale_details': False,
|
||||||
'subfunctions': ['controller', 'worker'],
|
'latest_sysroot_commit': '95139a5067',
|
||||||
'sw_version': '24.03',
|
'nodetype': 'controller',
|
||||||
'state': 'install-failed',
|
'subfunctions': ['controller', 'worker'],
|
||||||
'allow_insvc_patching': True,
|
'sw_version': '24.03',
|
||||||
'interim_state': False,
|
'state': 'install-failed',
|
||||||
'reboot_required': False}]
|
'allow_insvc_patching': True,
|
||||||
|
'interim_state': False,
|
||||||
|
'reboot_required': False
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fixtures = {
|
fixtures = {
|
||||||
'/v1/software/host_list':
|
'/v1/deploy_host':
|
||||||
{
|
{
|
||||||
'GET': (
|
'GET': (
|
||||||
{},
|
{},
|
||||||
HOST_LIST,
|
HOST_LIST,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
'/v1/software/deploy_show':
|
'/v1/deploy':
|
||||||
{
|
{
|
||||||
'GET': (
|
'GET': (
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
'/v1/software/deploy_precheck/1':
|
'/v1/deploy/precheck/1':
|
||||||
{
|
{
|
||||||
'GET': (
|
'GET': (
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
'/v1/software/deploy_precheck/1/force?region_name=RegionOne':
|
'/v1/deploy/1/precheck':
|
||||||
{
|
{
|
||||||
'POST': (
|
'POST': (
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
'/v1/software/deploy_start/1/force':
|
'/v1/deploy/1/start':
|
||||||
{
|
{
|
||||||
'POST': (
|
'POST': (
|
||||||
{},
|
{},
|
||||||
{},
|
{'force': 'true'},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
'/v1/software/deploy_host/1/force':
|
'/v1/deploy_host/controller-1':
|
||||||
{
|
{
|
||||||
'POST': (
|
'POST': (
|
||||||
{},
|
{},
|
||||||
{"error": True},
|
{"error": True},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
'/v1/software/deploy_activate/1':
|
'/v1/deploy_host/controller-1/force':
|
||||||
|
{
|
||||||
|
'POST': (
|
||||||
|
{},
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/deploy/activate':
|
||||||
{
|
{
|
||||||
'POST': (
|
'POST': (
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
'/v1/software/deploy_complete/1':
|
'/v1/deploy/complete':
|
||||||
{
|
{
|
||||||
'POST': (
|
'POST': (
|
||||||
{},
|
{},
|
||||||
|
@ -97,6 +108,7 @@ fixtures = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Args:
|
class Args:
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
for key, value in kwargs.items():
|
for key, value in kwargs.items():
|
||||||
|
@ -116,7 +128,7 @@ class DeployManagerTest(testtools.TestCase):
|
||||||
def test_host_list(self):
|
def test_host_list(self):
|
||||||
hosts = self.mgr.host_list()
|
hosts = self.mgr.host_list()
|
||||||
expect = [
|
expect = [
|
||||||
('GET', '/v1/software/host_list', {}, None),
|
('GET', '/v1/deploy_host', {}, None),
|
||||||
]
|
]
|
||||||
self.assertEqual(self.api.calls, expect)
|
self.assertEqual(self.api.calls, expect)
|
||||||
self.assertEqual(len(hosts), 2)
|
self.assertEqual(len(hosts), 2)
|
||||||
|
@ -124,9 +136,9 @@ class DeployManagerTest(testtools.TestCase):
|
||||||
HOST_LIST['data'][0]['hostname'])
|
HOST_LIST['data'][0]['hostname'])
|
||||||
|
|
||||||
def test_show(self):
|
def test_show(self):
|
||||||
deploy = self.mgr.show()
|
self.mgr.show()
|
||||||
expect = [
|
expect = [
|
||||||
('GET', '/v1/software/deploy_show', {}, None),
|
('GET', '/v1/deploy', {}, None),
|
||||||
]
|
]
|
||||||
self.assertEqual(self.api.calls, expect)
|
self.assertEqual(self.api.calls, expect)
|
||||||
|
|
||||||
|
@ -135,46 +147,46 @@ class DeployManagerTest(testtools.TestCase):
|
||||||
args = Args(**input)
|
args = Args(**input)
|
||||||
check = self.mgr.precheck(args)
|
check = self.mgr.precheck(args)
|
||||||
expect = [
|
expect = [
|
||||||
('POST', '/v1/software/deploy_precheck/1/force?region_name=RegionOne', {}, {}),
|
('POST', '/v1/deploy/1/precheck', {}, {'force': 'true', 'region_name': 'RegionOne'}),
|
||||||
]
|
]
|
||||||
self.assertEqual(self.api.calls, expect)
|
self.assertEqual(self.api.calls, expect)
|
||||||
self.assertEqual(len(check), 2)
|
self.assertEqual(len(check), 2)
|
||||||
|
|
||||||
def test_start(self):
|
def test_start(self):
|
||||||
input = {'deployment': '1', 'force': 1}
|
input = {'deployment': '1', 'force': 'True'}
|
||||||
args = Args(**input)
|
args = Args(**input)
|
||||||
resp = self.mgr.start(args)
|
resp = self.mgr.start(args)
|
||||||
expect = [
|
expect = [
|
||||||
('POST', '/v1/software/deploy_start/1/force', {}, {}),
|
('POST', '/v1/deploy/1/start', {}, {'force': 'true'}),
|
||||||
]
|
]
|
||||||
self.assertEqual(self.api.calls, expect)
|
self.assertEqual(self.api.calls, expect)
|
||||||
self.assertEqual(len(resp), 2)
|
self.assertEqual(len(resp), 2)
|
||||||
|
|
||||||
def test_host(self):
|
def test_host(self):
|
||||||
input = {'agent': '1', 'force': 1}
|
input = {'host': 'controller-1', 'force': False}
|
||||||
args = Args(**input)
|
args = Args(**input)
|
||||||
resp = self.mgr.host(args)
|
self.mgr.host(args)
|
||||||
expect = [
|
expect = [
|
||||||
('POST', '/v1/software/deploy_host/1/force', {}, {}),
|
('POST', '/v1/deploy_host/controller-1', {}, None),
|
||||||
]
|
]
|
||||||
self.assertEqual(self.api.calls, expect)
|
self.assertEqual(self.api.calls, expect)
|
||||||
|
|
||||||
def test_activate(self):
|
def test_activate(self):
|
||||||
input = {'deployment': '1'}
|
input = {}
|
||||||
args = Args(**input)
|
args = Args(**input)
|
||||||
resp = self.mgr.activate(args)
|
resp = self.mgr.activate(args)
|
||||||
expect = [
|
expect = [
|
||||||
('POST', '/v1/software/deploy_activate/1', {}, {}),
|
('POST', '/v1/deploy/activate', {}, {}),
|
||||||
]
|
]
|
||||||
self.assertEqual(self.api.calls, expect)
|
self.assertEqual(self.api.calls, expect)
|
||||||
self.assertEqual(len(resp), 2)
|
self.assertEqual(len(resp), 2)
|
||||||
|
|
||||||
def test_complete(self):
|
def test_complete(self):
|
||||||
input = {'deployment': '1'}
|
input = {}
|
||||||
args = Args(**input)
|
args = Args(**input)
|
||||||
resp = self.mgr.complete(args)
|
resp = self.mgr.complete(args)
|
||||||
expect = [
|
expect = [
|
||||||
('POST', '/v1/software/deploy_complete/1', {}, {}),
|
('POST', '/v1/deploy/complete', {}, {}),
|
||||||
]
|
]
|
||||||
self.assertEqual(self.api.calls, expect)
|
self.assertEqual(self.api.calls, expect)
|
||||||
self.assertEqual(len(resp), 2)
|
self.assertEqual(len(resp), 2)
|
||||||
|
|
|
@ -20,70 +20,68 @@ from software_client.tests import utils
|
||||||
import software_client.v1.release
|
import software_client.v1.release
|
||||||
|
|
||||||
RELEASE = {
|
RELEASE = {
|
||||||
'sd':
|
'sd': {
|
||||||
{'starlingx-24.03.0': {
|
'starlingx-24.03.0': {
|
||||||
'state': 'deployed',
|
'state': 'deployed',
|
||||||
'sw_version': '24.03.0',
|
'sw_version': '24.03.0',
|
||||||
'status': 'REL',
|
'status': 'REL',
|
||||||
'unremovable': 'Y',
|
'unremovable': 'Y',
|
||||||
'summary': 'STX 24.03 GA release',
|
'summary': 'STX 24.03 GA release',
|
||||||
'description': 'STX 24.03 major GA release',
|
'description': 'STX 24.03 major GA release',
|
||||||
'install_instructions': '',
|
'install_instructions': '',
|
||||||
'warnings': '',
|
'warnings': '',
|
||||||
'apply_active_release_only': '',
|
'apply_active_release_only': '',
|
||||||
'reboot_required': 'Y',
|
'reboot_required': 'Y',
|
||||||
'requires': [],
|
'requires': [],
|
||||||
'packages': []
|
'packages': []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fixtures = {
|
fixtures = {
|
||||||
'/v1/software/query?show=all':
|
'/v1/release?show=all':
|
||||||
{
|
{
|
||||||
'GET': (
|
'GET': (
|
||||||
{},
|
{},
|
||||||
{'sd': RELEASE['sd']},
|
{'sd': RELEASE['sd']},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
'/v1/software/show/1':
|
'/v1/release/1':
|
||||||
{
|
{
|
||||||
'POST': (
|
'DELETE': (
|
||||||
|
{},
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
'GET': (
|
||||||
{},
|
{},
|
||||||
True,
|
True,
|
||||||
),
|
),
|
||||||
|
|
||||||
},
|
},
|
||||||
'/v1/software/delete/1':
|
'/v1/release/1/is_available':
|
||||||
{
|
|
||||||
'POST': (
|
|
||||||
{},
|
|
||||||
{},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
'/v1/software/is_available/1':
|
|
||||||
{
|
|
||||||
'POST': (
|
|
||||||
{},
|
|
||||||
True,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
'/v1/software/is_deployed/1':
|
|
||||||
{
|
|
||||||
'POST': (
|
|
||||||
{},
|
|
||||||
False,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
'/v1/software/is_committed/1':
|
|
||||||
{
|
|
||||||
'POST': (
|
|
||||||
{},
|
|
||||||
False,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
'/v1/software/install_local':
|
|
||||||
{
|
{
|
||||||
'GET': (
|
'GET': (
|
||||||
|
{},
|
||||||
|
True,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/release/1/is_deployed':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/release/1/is_committed':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/deploy/install_local':
|
||||||
|
{
|
||||||
|
'POST': (
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
|
@ -112,7 +110,7 @@ class ReleaseManagerTest(testtools.TestCase):
|
||||||
args = Args(**input)
|
args = Args(**input)
|
||||||
release = self.mgr.list(args)
|
release = self.mgr.list(args)
|
||||||
expect = [
|
expect = [
|
||||||
('GET', '/v1/software/query?show=all', {}, None),
|
('GET', '/v1/release?show=all', {}, None),
|
||||||
]
|
]
|
||||||
self.assertEqual(self.api.calls, expect)
|
self.assertEqual(self.api.calls, expect)
|
||||||
self.assertEqual(len(release), 2)
|
self.assertEqual(len(release), 2)
|
||||||
|
@ -122,7 +120,7 @@ class ReleaseManagerTest(testtools.TestCase):
|
||||||
args = Args(**input)
|
args = Args(**input)
|
||||||
release = self.mgr.show(args)
|
release = self.mgr.show(args)
|
||||||
expect = [
|
expect = [
|
||||||
('POST', '/v1/software/show/1', {}, {}),
|
('GET', '/v1/release/1', {}, None),
|
||||||
]
|
]
|
||||||
self.assertEqual(self.api.calls, expect)
|
self.assertEqual(self.api.calls, expect)
|
||||||
self.assertEqual(len(release), 2)
|
self.assertEqual(len(release), 2)
|
||||||
|
@ -130,7 +128,7 @@ class ReleaseManagerTest(testtools.TestCase):
|
||||||
def test_release_delete(self):
|
def test_release_delete(self):
|
||||||
response = self.mgr.release_delete("1")
|
response = self.mgr.release_delete("1")
|
||||||
expect = [
|
expect = [
|
||||||
('POST', '/v1/software/delete/1', {}, {}),
|
('DELETE', '/v1/release/1', {}, None),
|
||||||
]
|
]
|
||||||
self.assertEqual(self.api.calls, expect)
|
self.assertEqual(self.api.calls, expect)
|
||||||
self.assertEqual(len(response), 2)
|
self.assertEqual(len(response), 2)
|
||||||
|
@ -138,7 +136,7 @@ class ReleaseManagerTest(testtools.TestCase):
|
||||||
def test_is_available(self):
|
def test_is_available(self):
|
||||||
response = self.mgr.is_available('1')
|
response = self.mgr.is_available('1')
|
||||||
expect = [
|
expect = [
|
||||||
('POST', '/v1/software/is_available/1', {}, {}),
|
('GET', '/v1/release/1/is_available', {}, None),
|
||||||
]
|
]
|
||||||
self.assertEqual(self.api.calls, expect)
|
self.assertEqual(self.api.calls, expect)
|
||||||
self.assertTrue(response[1], True)
|
self.assertTrue(response[1], True)
|
||||||
|
@ -146,7 +144,7 @@ class ReleaseManagerTest(testtools.TestCase):
|
||||||
def test_is_deployed(self):
|
def test_is_deployed(self):
|
||||||
response = self.mgr.is_deployed('1')
|
response = self.mgr.is_deployed('1')
|
||||||
expect = [
|
expect = [
|
||||||
('POST', '/v1/software/is_deployed/1', {}, {}),
|
('GET', '/v1/release/1/is_deployed', {}, None),
|
||||||
]
|
]
|
||||||
self.assertEqual(self.api.calls, expect)
|
self.assertEqual(self.api.calls, expect)
|
||||||
self.assertFalse(response[1], False)
|
self.assertFalse(response[1], False)
|
||||||
|
@ -154,7 +152,7 @@ class ReleaseManagerTest(testtools.TestCase):
|
||||||
def test_is_committed(self):
|
def test_is_committed(self):
|
||||||
response = self.mgr.is_committed('1')
|
response = self.mgr.is_committed('1')
|
||||||
expect = [
|
expect = [
|
||||||
('POST', '/v1/software/is_committed/1', {}, {}),
|
('GET', '/v1/release/1/is_committed', {}, None),
|
||||||
]
|
]
|
||||||
self.assertEqual(self.api.calls, expect)
|
self.assertEqual(self.api.calls, expect)
|
||||||
self.assertFalse(response[1], True)
|
self.assertFalse(response[1], True)
|
||||||
|
@ -164,7 +162,7 @@ class ReleaseManagerTest(testtools.TestCase):
|
||||||
args = Args(**input)
|
args = Args(**input)
|
||||||
response = self.mgr.upload(args)
|
response = self.mgr.upload(args)
|
||||||
expect = [
|
expect = [
|
||||||
('POST', '/v1/software/upload', {}, {}),
|
('POST', '/v1/release', {}, {}),
|
||||||
]
|
]
|
||||||
self.assertNotEqual(self.api.calls, expect)
|
self.assertNotEqual(self.api.calls, expect)
|
||||||
self.assertEqual(response, 0)
|
self.assertEqual(response, 0)
|
||||||
|
@ -174,7 +172,7 @@ class ReleaseManagerTest(testtools.TestCase):
|
||||||
args = Args(**input)
|
args = Args(**input)
|
||||||
response = self.mgr.upload_dir(args)
|
response = self.mgr.upload_dir(args)
|
||||||
expect = [
|
expect = [
|
||||||
('POST', '/v1/software/upload', {}, {}),
|
('POST', '/v1/release', {}, {}),
|
||||||
]
|
]
|
||||||
self.assertNotEqual(self.api.calls, expect)
|
self.assertNotEqual(self.api.calls, expect)
|
||||||
self.assertEqual(response, 0)
|
self.assertEqual(response, 0)
|
||||||
|
@ -182,15 +180,12 @@ class ReleaseManagerTest(testtools.TestCase):
|
||||||
def test_install_local(self):
|
def test_install_local(self):
|
||||||
self.mgr.install_local()
|
self.mgr.install_local()
|
||||||
expect = [
|
expect = [
|
||||||
('GET', '/v1/software/install_local', {}, None),
|
('POST', '/v1/deploy/install_local', {}, None),
|
||||||
]
|
]
|
||||||
self.assertEqual(self.api.calls, expect)
|
self.assertEqual(self.api.calls, expect)
|
||||||
|
|
||||||
def test_commit_patch(self):
|
def test_commit_patch(self):
|
||||||
input = {'sw_version': '1', 'all': ''}
|
|
||||||
args = Args(**input)
|
|
||||||
kernel = self.mgr.commit_patch(args)
|
|
||||||
expect = [
|
expect = [
|
||||||
('GET', '/v1/software/commit_patch/1', {}, None),
|
('POST', '/v1/release/1/commit_patch', {}, None),
|
||||||
]
|
]
|
||||||
self.assertNotEqual(self.api.calls, expect)
|
self.assertNotEqual(self.api.calls, expect)
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
import signal
|
import signal
|
||||||
import sys
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from software_client.common import base
|
from software_client.common import base
|
||||||
|
@ -27,15 +26,16 @@ class DeployManager(base.Manager):
|
||||||
# args.deployment is a string
|
# args.deployment is a string
|
||||||
deployment = args.deployment
|
deployment = args.deployment
|
||||||
|
|
||||||
# args.region is a string
|
path = "/v1/deploy/%s/precheck" % (deployment)
|
||||||
region_name = args.region_name
|
body = {}
|
||||||
|
|
||||||
path = "/v1/software/deploy_precheck/%s" % (deployment)
|
|
||||||
if args.force:
|
if args.force:
|
||||||
path += "/force"
|
body["force"] = "true"
|
||||||
path += "?region_name=%s" % region_name
|
|
||||||
|
|
||||||
return self._create(path, body={})
|
if args.region_name:
|
||||||
|
body["region_name"] = args.region_name
|
||||||
|
|
||||||
|
res = self._post(path, body=body)
|
||||||
|
return res
|
||||||
|
|
||||||
def start(self, args):
|
def start(self, args):
|
||||||
# args.deployment is a string
|
# args.deployment is a string
|
||||||
|
@ -45,51 +45,31 @@ class DeployManager(base.Manager):
|
||||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||||
|
|
||||||
# Issue deploy_start request
|
# Issue deploy_start request
|
||||||
|
path = "/v1/deploy/%s/start" % (deployment)
|
||||||
|
body = {}
|
||||||
if args.force:
|
if args.force:
|
||||||
path = "/v1/software/deploy_start/%s/force" % (deployment)
|
body["force"] = "true"
|
||||||
else:
|
|
||||||
path = "/v1/software/deploy_start/%s" % (deployment)
|
|
||||||
|
|
||||||
return self._create(path, body={})
|
return self._post(path, body=body)
|
||||||
|
|
||||||
def host(self, args):
|
def host(self, args):
|
||||||
# args.deployment is a string
|
# args.deployment is a string
|
||||||
hostname = args.host
|
hostname = args.host
|
||||||
|
|
||||||
# Issue deploy_host request and poll for results
|
# Issue deploy_host request and poll for results
|
||||||
path = "/v1/software/deploy_host/%s" % (hostname)
|
path = "/v1/deploy_host/%s" % (hostname)
|
||||||
|
|
||||||
if args.force:
|
if args.force:
|
||||||
path += "/force"
|
path += "/force"
|
||||||
|
|
||||||
req, data = self._create(path, body={})
|
return self._create(path)
|
||||||
if req.status_code == 200:
|
|
||||||
if 'error' in data and data["error"] != "":
|
|
||||||
print("Error:")
|
|
||||||
print(data["error"])
|
|
||||||
rc = 1
|
|
||||||
else:
|
|
||||||
# NOTE(bqian) should consider return host_list instead.
|
|
||||||
rc = self.wait_for_install_complete(hostname)
|
|
||||||
elif req.status_code == 500:
|
|
||||||
print("An internal error has occurred. "
|
|
||||||
"Please check /var/log/software.log for details")
|
|
||||||
rc = 1
|
|
||||||
else:
|
|
||||||
m = re.search("(Error message:.*)", req.text, re.MULTILINE)
|
|
||||||
if m:
|
|
||||||
print(m.group(0))
|
|
||||||
else:
|
|
||||||
print("%s %s" % (req.status_code, req.reason))
|
|
||||||
rc = 1
|
|
||||||
return rc
|
|
||||||
|
|
||||||
def activate(self, args):
|
def activate(self, args):
|
||||||
# Ignore interrupts during this function
|
# Ignore interrupts during this function
|
||||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||||
|
|
||||||
# Issue deploy_start request
|
# Issue deploy_start request
|
||||||
path = "/v1/software/deploy_activate"
|
path = "/v1/deploy/activate"
|
||||||
|
|
||||||
return self._create(path, body={})
|
return self._create(path, body={})
|
||||||
|
|
||||||
|
@ -98,20 +78,20 @@ class DeployManager(base.Manager):
|
||||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||||
|
|
||||||
# Issue deploy_start request
|
# Issue deploy_start request
|
||||||
path = "/v1/software/deploy_complete/"
|
path = "/v1/deploy/complete"
|
||||||
|
|
||||||
return self._create(path, body={})
|
return self._create(path, body={})
|
||||||
|
|
||||||
def host_list(self):
|
def host_list(self):
|
||||||
path = '/v1/software/host_list'
|
path = '/v1/deploy_host'
|
||||||
return self._list(path, "")
|
return self._list(path, "")
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
path = '/v1/software/deploy'
|
path = '/v1/deploy'
|
||||||
return self._list(path, "")
|
return self._list(path)
|
||||||
|
|
||||||
def wait_for_install_complete(self, hostname):
|
def wait_for_install_complete(self, hostname):
|
||||||
url = "/v1/software/host_list"
|
url = "/v1/deploy_host"
|
||||||
rc = 0
|
rc = 0
|
||||||
|
|
||||||
max_retries = 4
|
max_retries = 4
|
||||||
|
|
|
@ -24,23 +24,41 @@ def do_show(cc, args):
|
||||||
resp, data = cc.deploy.show()
|
resp, data = cc.deploy.show()
|
||||||
if args.debug:
|
if args.debug:
|
||||||
utils.print_result_debug(resp, data)
|
utils.print_result_debug(resp, data)
|
||||||
|
|
||||||
|
rc = utils.check_rc(resp, data)
|
||||||
|
if rc == 0:
|
||||||
|
if len(data) == 0:
|
||||||
|
print("No deploy in progress")
|
||||||
|
else:
|
||||||
|
header_data_list = {"From Release": "from_release",
|
||||||
|
"To Release": "to_release",
|
||||||
|
"RR": "reboot_required",
|
||||||
|
"State": "state"}
|
||||||
|
utils.display_result_list(header_data_list, data)
|
||||||
else:
|
else:
|
||||||
header_data_list = {"From Release": "from_release", "To Release": "to_release", "RR": "reboot_required", "State": "state"}
|
utils.display_info(resp)
|
||||||
utils.display_result_list(header_data_list, data)
|
|
||||||
|
|
||||||
return utils.check_rc(resp, data)
|
|
||||||
|
|
||||||
|
return rc
|
||||||
|
|
||||||
def do_host_list(cc, args):
|
def do_host_list(cc, args):
|
||||||
"""List of hosts for software deployment """
|
"""List of hosts for software deployment """
|
||||||
resp, data = cc.deploy.host_list()
|
resp, data = cc.deploy.host_list()
|
||||||
if args.debug:
|
if args.debug:
|
||||||
utils.print_result_debug(resp, data)
|
utils.print_result_debug(resp, data)
|
||||||
else:
|
|
||||||
header_data_list = {"Host": "hostname", "From Release": "software_release", "To Release": "target_release", "RR": "reboot_required", "State": "host_state"}
|
|
||||||
utils.display_result_list(header_data_list, data)
|
|
||||||
|
|
||||||
return utils.check_rc(resp, data)
|
rc = utils.check_rc(resp, data)
|
||||||
|
if rc == 0:
|
||||||
|
if len(data) == 0:
|
||||||
|
print("No deploy in progress")
|
||||||
|
else:
|
||||||
|
header_data_list = {"Host": "hostname", "From Release": "software_release",
|
||||||
|
"To Release": "target_release", "RR": "reboot_required",
|
||||||
|
"State": "host_state"}
|
||||||
|
utils.display_result_list(header_data_list, data)
|
||||||
|
else:
|
||||||
|
utils.display_info(resp)
|
||||||
|
|
||||||
|
return rc
|
||||||
|
|
||||||
|
|
||||||
@utils.arg('deployment',
|
@utils.arg('deployment',
|
||||||
|
@ -51,18 +69,18 @@ def do_host_list(cc, args):
|
||||||
required=False,
|
required=False,
|
||||||
help='Allow bypassing non-critical checks')
|
help='Allow bypassing non-critical checks')
|
||||||
@utils.arg('--region_name',
|
@utils.arg('--region_name',
|
||||||
default='RegionOne',
|
default=None,
|
||||||
required=False,
|
required=False,
|
||||||
help='Run precheck against a subcloud')
|
help='Run precheck against a subcloud')
|
||||||
def do_precheck(cc, args):
|
def do_precheck(cc, args):
|
||||||
"""Verify whether prerequisites for installing the software deployment are satisfied"""
|
"""Verify whether prerequisites for installing the software deployment are satisfied"""
|
||||||
req, data = cc.deploy.precheck(args)
|
resp, data = cc.deploy.precheck(args)
|
||||||
if args.debug:
|
if args.debug:
|
||||||
utils.print_result_debug(req, data)
|
utils.print_result_debug(resp, data)
|
||||||
else:
|
|
||||||
utils.print_software_op_result(req, data)
|
|
||||||
|
|
||||||
return utils.check_rc(req, data)
|
utils.display_info(resp)
|
||||||
|
|
||||||
|
return utils.check_rc(resp, data)
|
||||||
|
|
||||||
|
|
||||||
@utils.arg('deployment',
|
@utils.arg('deployment',
|
||||||
|
@ -77,8 +95,8 @@ def do_start(cc, args):
|
||||||
resp, data = cc.deploy.start(args)
|
resp, data = cc.deploy.start(args)
|
||||||
if args.debug:
|
if args.debug:
|
||||||
utils.print_result_debug(resp, data)
|
utils.print_result_debug(resp, data)
|
||||||
else:
|
|
||||||
utils.display_info(resp)
|
utils.display_info(resp)
|
||||||
|
|
||||||
return utils.check_rc(resp, data)
|
return utils.check_rc(resp, data)
|
||||||
|
|
||||||
|
@ -92,25 +110,34 @@ def do_start(cc, args):
|
||||||
help="Force deploy host")
|
help="Force deploy host")
|
||||||
def do_host(cc, args):
|
def do_host(cc, args):
|
||||||
"""Deploy prestaged software deployment to the host"""
|
"""Deploy prestaged software deployment to the host"""
|
||||||
return cc.deploy.host(args)
|
resp, data = cc.deploy.host(args)
|
||||||
|
|
||||||
|
if args.debug:
|
||||||
|
utils.print_result_debug(resp, data)
|
||||||
|
|
||||||
|
utils.display_info(resp)
|
||||||
|
|
||||||
|
return utils.check_rc(resp, data)
|
||||||
|
|
||||||
|
|
||||||
def do_activate(cc, args):
|
def do_activate(cc, args):
|
||||||
"""Activate the software deployment"""
|
"""Activate the software deployment"""
|
||||||
req, data = cc.deploy.activate(args)
|
resp, data = cc.deploy.activate(args)
|
||||||
if args.debug:
|
if args.debug:
|
||||||
utils.print_result_debug(req, data)
|
utils.print_result_debug(resp, data)
|
||||||
else:
|
|
||||||
utils.print_software_op_result(req, data)
|
utils.display_info(resp)
|
||||||
|
|
||||||
|
return utils.check_rc(resp, data)
|
||||||
|
|
||||||
return utils.check_rc(req, data)
|
|
||||||
|
|
||||||
def do_complete(cc, args):
|
def do_complete(cc, args):
|
||||||
"""Complete the software deployment"""
|
"""Complete the software deployment"""
|
||||||
req, data = cc.deploy.complete(args)
|
resp, data = cc.deploy.complete(args)
|
||||||
if args.debug:
|
|
||||||
utils.print_result_debug(req, data)
|
|
||||||
else:
|
|
||||||
utils.print_software_op_result(req, data)
|
|
||||||
|
|
||||||
return utils.check_rc(req, data)
|
if args.debug:
|
||||||
|
utils.print_result_debug(resp, data)
|
||||||
|
|
||||||
|
utils.display_info(resp)
|
||||||
|
|
||||||
|
return utils.check_rc(resp, data)
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
#
|
#
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
@ -27,27 +26,40 @@ class ReleaseManager(base.Manager):
|
||||||
resource_class = Release
|
resource_class = Release
|
||||||
|
|
||||||
def list(self, args):
|
def list(self, args):
|
||||||
state = args.state # defaults to "all"
|
path = "/v1/release"
|
||||||
extra_opts = ""
|
state = args.state
|
||||||
|
additions = []
|
||||||
|
if state:
|
||||||
|
additions.append("show=%s" % state)
|
||||||
|
|
||||||
if args.release:
|
if args.release:
|
||||||
extra_opts = "&release=%s" % args.release
|
additions.append("release=%s" % args.release)
|
||||||
path = "/v1/software/query?show=%s%s" % (state, extra_opts)
|
|
||||||
|
if len(additions) > 0:
|
||||||
|
path = path + "?" + "&".join(additions)
|
||||||
|
|
||||||
return self._list(path, "")
|
return self._list(path, "")
|
||||||
|
|
||||||
|
def show(self, args):
|
||||||
|
releases = "/".join(args.release)
|
||||||
|
|
||||||
|
path = "/v1/release/%s" % (releases)
|
||||||
|
return self._fetch(path)
|
||||||
|
|
||||||
def is_available(self, release):
|
def is_available(self, release):
|
||||||
releases = "/".join(release)
|
releases = "/".join(release)
|
||||||
path = '/v1/software/is_available/%s' % (releases)
|
path = '/v1/release/%s/is_available' % (releases)
|
||||||
return self._create(path, body={})
|
return self._fetch(path)
|
||||||
|
|
||||||
def is_deployed(self, release):
|
def is_deployed(self, release):
|
||||||
releases = "/".join(release)
|
releases = "/".join(release)
|
||||||
path = '/v1/software/is_deployed/%s' % (releases)
|
path = '/v1/release/%s/is_deployed' % (releases)
|
||||||
return self._create(path, body={})
|
return self._fetch(path)
|
||||||
|
|
||||||
def is_committed(self, release):
|
def is_committed(self, release):
|
||||||
releases = "/".join(release)
|
releases = "/".join(release)
|
||||||
path = '/v1/software/is_committed/%s' % (releases)
|
path = '/v1/release/%s/is_committed' % (releases)
|
||||||
return self._create(path, body={})
|
return self._fetch(path)
|
||||||
|
|
||||||
def upload(self, args):
|
def upload(self, args):
|
||||||
rc = 0
|
rc = 0
|
||||||
|
@ -81,7 +93,7 @@ class ReleaseManager(base.Manager):
|
||||||
print("No file to be uploaded.")
|
print("No file to be uploaded.")
|
||||||
return rc
|
return rc
|
||||||
|
|
||||||
path = '/v1/software/upload'
|
path = '/v1/release'
|
||||||
if is_local:
|
if is_local:
|
||||||
to_upload_filenames = valid_files
|
to_upload_filenames = valid_files
|
||||||
headers = {'Content-Type': 'text/plain'}
|
headers = {'Content-Type': 'text/plain'}
|
||||||
|
@ -136,7 +148,6 @@ class ReleaseManager(base.Manager):
|
||||||
temp_sig_files, file=sys.stderr)
|
temp_sig_files, file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
for software_file in sorted(set(all_raw_files)):
|
for software_file in sorted(set(all_raw_files)):
|
||||||
_, ext = os.path.splitext(software_file)
|
_, ext = os.path.splitext(software_file)
|
||||||
if ext in constants.SUPPORTED_UPLOAD_FILE_EXT:
|
if ext in constants.SUPPORTED_UPLOAD_FILE_EXT:
|
||||||
|
@ -146,22 +157,8 @@ class ReleaseManager(base.Manager):
|
||||||
|
|
||||||
encoder = MultipartEncoder(fields=to_upload_files)
|
encoder = MultipartEncoder(fields=to_upload_files)
|
||||||
headers = {'Content-Type': encoder.content_type}
|
headers = {'Content-Type': encoder.content_type}
|
||||||
path = '/v1/software/upload'
|
path = '/v1/release'
|
||||||
req, data = self._create_multipart(path, body=encoder, headers=headers)
|
return self._create_multipart(path, body=encoder, headers=headers)
|
||||||
if args.debug:
|
|
||||||
utils.print_result_debug(req, data)
|
|
||||||
else:
|
|
||||||
utils.print_software_op_result(req, data)
|
|
||||||
data = json.loads(req.text)
|
|
||||||
data_list = [(lambda key, value: (key, value["id"]))(k, v)
|
|
||||||
for d in data["upload_info"]
|
|
||||||
for k, v in d.items()
|
|
||||||
if not k.endswith(".sig")]
|
|
||||||
|
|
||||||
header_data_list = ["Uploaded File", "Id"]
|
|
||||||
has_error = 'error' in data and data["error"]
|
|
||||||
utils.print_result_list(header_data_list, data_list, has_error)
|
|
||||||
return utils.check_rc(req, data)
|
|
||||||
|
|
||||||
def commit_patch(self, args):
|
def commit_patch(self, args):
|
||||||
# Ignore interrupts during this function
|
# Ignore interrupts during this function
|
||||||
|
@ -179,7 +176,7 @@ class ReleaseManager(base.Manager):
|
||||||
elif args.all:
|
elif args.all:
|
||||||
# Get a list of all patches
|
# Get a list of all patches
|
||||||
extra_opts = "&release=%s" % relopt
|
extra_opts = "&release=%s" % relopt
|
||||||
url = "/v1/software/query?show=patch%s" % (extra_opts)
|
url = "/v1/release?show=patch%s" % (extra_opts)
|
||||||
|
|
||||||
resp, body = self._list(url, "")
|
resp, body = self._list(url, "")
|
||||||
|
|
||||||
|
@ -265,16 +262,10 @@ class ReleaseManager(base.Manager):
|
||||||
# Ignore interrupts during this function
|
# Ignore interrupts during this function
|
||||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||||
|
|
||||||
path = "/v1/software/install_local"
|
path = "/v1/deploy/install_local"
|
||||||
return self._list(path, "")
|
return self._post(path)
|
||||||
|
|
||||||
def show(self, args):
|
|
||||||
releases = "/".join(args.release)
|
|
||||||
|
|
||||||
path = "/v1/software/show/%s" % (releases)
|
|
||||||
return self._create(path, body={})
|
|
||||||
|
|
||||||
def release_delete(self, release_id):
|
def release_delete(self, release_id):
|
||||||
release_ids = "/".join(release_id)
|
release_ids = "/".join(release_id)
|
||||||
path = '/v1/software/delete/%s' % release_ids
|
path = '/v1/release/%s' % release_ids
|
||||||
return self._create(path, body={})
|
return self._delete(path)
|
||||||
|
|
|
@ -13,19 +13,23 @@ from software_client.common import utils
|
||||||
help='filter against a release ID')
|
help='filter against a release ID')
|
||||||
# --state is an optional argument. default: "all"
|
# --state is an optional argument. default: "all"
|
||||||
@utils.arg('--state',
|
@utils.arg('--state',
|
||||||
default="all",
|
default=None,
|
||||||
required=False,
|
required=False,
|
||||||
help='filter against a release state')
|
help='filter against a release state')
|
||||||
def do_list(cc, args):
|
def do_list(cc, args):
|
||||||
"""List the software releases"""
|
"""List the software releases"""
|
||||||
req, data = cc.release.list(args)
|
resp, data = cc.release.list(args)
|
||||||
if args.debug:
|
if args.debug:
|
||||||
utils.print_result_debug(req, data)
|
utils.print_result_debug(resp, data)
|
||||||
else:
|
|
||||||
|
rc = utils.check_rc(resp, data)
|
||||||
|
if rc == 0:
|
||||||
header_data_list = {"Release": "release_id", "RR": "reboot_required", "State": "state"}
|
header_data_list = {"Release": "release_id", "RR": "reboot_required", "State": "state"}
|
||||||
utils.display_result_list(header_data_list, data)
|
utils.display_result_list(header_data_list, data)
|
||||||
|
else:
|
||||||
|
utils.display_info(resp)
|
||||||
|
|
||||||
return utils.check_rc(req, data)
|
return rc
|
||||||
|
|
||||||
|
|
||||||
@utils.arg('release',
|
@utils.arg('release',
|
||||||
|
@ -38,17 +42,20 @@ def do_list(cc, args):
|
||||||
help='list packages contained in the release')
|
help='list packages contained in the release')
|
||||||
def do_show(cc, args):
|
def do_show(cc, args):
|
||||||
"""Show the software release"""
|
"""Show the software release"""
|
||||||
list_packages = args.packages
|
resp, data = cc.release.show(args)
|
||||||
req, data = cc.release.show(args)
|
|
||||||
if args.debug:
|
if args.debug:
|
||||||
utils.print_result_debug(req, data)
|
utils.print_result_debug(resp, data)
|
||||||
|
|
||||||
|
rc = utils.check_rc(resp, data)
|
||||||
|
if rc == 0:
|
||||||
|
utils.display_detail_result(data)
|
||||||
else:
|
else:
|
||||||
for d in data:
|
utils.display_info(resp)
|
||||||
utils.display_detail_result(d)
|
|
||||||
|
|
||||||
return utils.check_rc(req, data)
|
return rc
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE(bqian) need to review the commit patch CLI
|
||||||
@utils.arg('patch',
|
@utils.arg('patch',
|
||||||
nargs="+", # accepts a list
|
nargs="+", # accepts a list
|
||||||
help='Patch ID/s to commit')
|
help='Patch ID/s to commit')
|
||||||
|
@ -72,24 +79,27 @@ def do_commit_patch(cc, args):
|
||||||
|
|
||||||
|
|
||||||
def do_install_local(cc, args):
|
def do_install_local(cc, args):
|
||||||
""" Trigger patch install/remove on the local host.
|
"""Trigger patch install/remove on the local host.
|
||||||
This command can only be used for patch installation
|
This command can only be used for patch installation
|
||||||
prior to initial configuration."""
|
prior to initial configuration.
|
||||||
req, data = cc.release.install_local()
|
"""
|
||||||
|
resp, data = cc.release.install_local()
|
||||||
if args.debug:
|
if args.debug:
|
||||||
utils.print_result_debug(req, data)
|
utils.print_result_debug(resp, data)
|
||||||
else:
|
|
||||||
utils.print_software_op_result(req, data)
|
|
||||||
|
|
||||||
return utils.check_rc(req, data)
|
utils.display_info(resp)
|
||||||
|
|
||||||
|
return utils.check_rc(resp, data)
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE (bqian) verify this CLI is needed
|
||||||
@utils.arg('release',
|
@utils.arg('release',
|
||||||
nargs="+", # accepts a list
|
nargs="+", # accepts a list
|
||||||
help='List of releases')
|
help='List of releases')
|
||||||
def do_is_available(cc, args):
|
def do_is_available(cc, args):
|
||||||
"""Query Available state for list of releases.
|
"""Query Available state for list of releases.
|
||||||
Returns True if all are Available, False otherwise."""
|
Returns True if all are Available, False otherwise.
|
||||||
|
"""
|
||||||
req, result = cc.release.is_available(args.release)
|
req, result = cc.release.is_available(args.release)
|
||||||
rc = 1
|
rc = 1
|
||||||
if req.status_code == 200:
|
if req.status_code == 200:
|
||||||
|
@ -103,12 +113,14 @@ def do_is_available(cc, args):
|
||||||
return rc
|
return rc
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE (bqian) verify this CLI is needed
|
||||||
@utils.arg('release',
|
@utils.arg('release',
|
||||||
nargs="+", # accepts a list
|
nargs="+", # accepts a list
|
||||||
help='List of releases')
|
help='List of releases')
|
||||||
def do_is_deployed(cc, args):
|
def do_is_deployed(cc, args):
|
||||||
"""Query Deployed state for list of releases.
|
"""Query Deployed state for list of releases.
|
||||||
Returns True if all are Deployed, False otherwise."""
|
Returns True if all are Deployed, False otherwise.
|
||||||
|
"""
|
||||||
req, result = cc.release.is_deployed(args.release)
|
req, result = cc.release.is_deployed(args.release)
|
||||||
rc = 1
|
rc = 1
|
||||||
if req.status_code == 200:
|
if req.status_code == 200:
|
||||||
|
@ -122,12 +134,14 @@ def do_is_deployed(cc, args):
|
||||||
return rc
|
return rc
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE (bqian) verify this CLI is needed
|
||||||
@utils.arg('release',
|
@utils.arg('release',
|
||||||
nargs="+", # accepts a list
|
nargs="+", # accepts a list
|
||||||
help='List of releases')
|
help='List of releases')
|
||||||
def do_is_committed(cc, args):
|
def do_is_committed(cc, args):
|
||||||
"""Query Committed state for list of releases.
|
"""Query Committed state for list of releases.
|
||||||
Returns True if all are Committed, False otherwise."""
|
Returns True if all are Committed, False otherwise.
|
||||||
|
"""
|
||||||
req, result = cc.release.is_committed(args.release)
|
req, result = cc.release.is_committed(args.release)
|
||||||
rc = 1
|
rc = 1
|
||||||
if req.status_code == 200:
|
if req.status_code == 200:
|
||||||
|
@ -141,6 +155,25 @@ def do_is_committed(cc, args):
|
||||||
return rc
|
return rc
|
||||||
|
|
||||||
|
|
||||||
|
def _print_upload_result(resp, data, debug):
|
||||||
|
if debug:
|
||||||
|
utils.print_result_debug(resp, data)
|
||||||
|
|
||||||
|
rc = utils.check_rc(resp, data)
|
||||||
|
|
||||||
|
utils.display_info(resp)
|
||||||
|
if rc == 0:
|
||||||
|
if data["upload_info"]:
|
||||||
|
upload_info = data["upload_info"]
|
||||||
|
data_list = [{"file": k, "release": v["id"]}
|
||||||
|
for d in upload_info for k, v in d.items()
|
||||||
|
if not k.endswith(".sig")]
|
||||||
|
|
||||||
|
header_data_list = {"Uploaded File": "file", "Release": "release"}
|
||||||
|
utils.display_result_list(header_data_list, data_list)
|
||||||
|
return rc
|
||||||
|
|
||||||
|
|
||||||
@utils.arg('release',
|
@utils.arg('release',
|
||||||
metavar='(iso + sig) | patch',
|
metavar='(iso + sig) | patch',
|
||||||
nargs="+", # accepts a list
|
nargs="+", # accepts a list
|
||||||
|
@ -153,23 +186,10 @@ def do_is_committed(cc, args):
|
||||||
action='store_true')
|
action='store_true')
|
||||||
def do_upload(cc, args):
|
def do_upload(cc, args):
|
||||||
"""Upload a software release"""
|
"""Upload a software release"""
|
||||||
req, data = cc.release.upload(args)
|
resp, data = cc.release.upload(args)
|
||||||
if args.debug:
|
_print_upload_result(resp, data, args.debug)
|
||||||
utils.print_result_debug(req, data)
|
|
||||||
else:
|
|
||||||
utils.print_software_op_result(req, data)
|
|
||||||
data_list = [(k, v["id"])
|
|
||||||
for d in data["upload_info"] for k, v in d.items()
|
|
||||||
if not k.endswith(".sig")]
|
|
||||||
|
|
||||||
header_data_list = ["Uploaded File", "Id"]
|
return utils.check_rc(resp, data)
|
||||||
has_error = 'error' in data and data["error"]
|
|
||||||
utils.print_result_list(header_data_list, data_list, has_error)
|
|
||||||
rc = 0
|
|
||||||
if utils.check_rc(req, data) != 0:
|
|
||||||
# We hit a failure. Update rc but keep looping
|
|
||||||
rc = 1
|
|
||||||
return rc
|
|
||||||
|
|
||||||
|
|
||||||
@utils.arg('release',
|
@utils.arg('release',
|
||||||
|
@ -182,7 +202,10 @@ def do_upload(cc, args):
|
||||||
'ONE pair of (iso + sig)'))
|
'ONE pair of (iso + sig)'))
|
||||||
def do_upload_dir(cc, args):
|
def do_upload_dir(cc, args):
|
||||||
"""Upload a software release dir"""
|
"""Upload a software release dir"""
|
||||||
return cc.release.upload_dir(args)
|
resp, data = cc.release.upload_dir(args)
|
||||||
|
_print_upload_result(resp, data, args.debug)
|
||||||
|
|
||||||
|
return utils.check_rc(resp, data)
|
||||||
|
|
||||||
|
|
||||||
@utils.arg('release',
|
@utils.arg('release',
|
||||||
|
@ -190,6 +213,9 @@ def do_upload_dir(cc, args):
|
||||||
help='Release ID to delete')
|
help='Release ID to delete')
|
||||||
def do_delete(cc, args):
|
def do_delete(cc, args):
|
||||||
"""Delete the software release"""
|
"""Delete the software release"""
|
||||||
resp, body = cc.release.release_delete(args.release)
|
resp, data = cc.release.release_delete(args.release)
|
||||||
|
if args.debug:
|
||||||
|
utils.print_result_debug(resp, data)
|
||||||
|
|
||||||
utils.display_info(resp)
|
utils.display_info(resp)
|
||||||
return utils.check_rc(resp, body)
|
return utils.check_rc(resp, data)
|
||||||
|
|
|
@ -6,3 +6,4 @@ coverage
|
||||||
httplib2
|
httplib2
|
||||||
pylint
|
pylint
|
||||||
stestr
|
stestr
|
||||||
|
tabulate
|
||||||
|
|
|
@ -91,7 +91,7 @@ class HealthCheck(object):
|
||||||
:return: boolean indicating success/failure and list of patches
|
:return: boolean indicating success/failure and list of patches
|
||||||
that are not in the 'deployed' state
|
that are not in the 'deployed' state
|
||||||
"""
|
"""
|
||||||
url = self._software_endpoint + '/query?show=deployed'
|
url = self._software_endpoint + '/release?show=deployed'
|
||||||
headers = {"X-Auth-Token": self._software_token}
|
headers = {"X-Auth-Token": self._software_token}
|
||||||
response = requests.get(url, headers=headers, timeout=10)
|
response = requests.get(url, headers=headers, timeout=10)
|
||||||
|
|
||||||
|
@ -233,7 +233,7 @@ class PatchHealthCheck(HealthCheck):
|
||||||
|
|
||||||
def _get_required_patches(self):
|
def _get_required_patches(self):
|
||||||
"""Get required patches for a target release"""
|
"""Get required patches for a target release"""
|
||||||
url = self._software_endpoint + '/query'
|
url = self._software_endpoint + '/release'
|
||||||
headers = {"X-Auth-Token": self._software_token}
|
headers = {"X-Auth-Token": self._software_token}
|
||||||
response = requests.get(url, headers=headers, timeout=10)
|
response = requests.get(url, headers=headers, timeout=10)
|
||||||
|
|
||||||
|
@ -242,9 +242,9 @@ class PatchHealthCheck(HealthCheck):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
required_patches = []
|
required_patches = []
|
||||||
for release, values in response.json()["sd"].items():
|
for release in response.json():
|
||||||
if values["sw_version"] == self._target_release:
|
if release["sw_version"] == self._target_release:
|
||||||
required_patches.extend(values["requires"])
|
required_patches.extend(release["requires"])
|
||||||
break
|
break
|
||||||
|
|
||||||
return required_patches
|
return required_patches
|
||||||
|
|
|
@ -53,7 +53,7 @@ def get_token_endpoint(config, service_type="platform"):
|
||||||
raise Exception("Failed to get token and endpoint. Error: %s", str(e))
|
raise Exception("Failed to get token and endpoint. Error: %s", str(e))
|
||||||
|
|
||||||
if service_type == "usm":
|
if service_type == "usm":
|
||||||
endpoint += "/v1/software"
|
endpoint += "/v1"
|
||||||
|
|
||||||
return token, endpoint
|
return token, endpoint
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,10 @@ from wsme import types as wtypes
|
||||||
|
|
||||||
from software.api.controllers.v1 import base
|
from software.api.controllers.v1 import base
|
||||||
from software.api.controllers.v1 import link
|
from software.api.controllers.v1 import link
|
||||||
from software.api.controllers.v1 import software
|
from software.api.controllers.v1.software import SoftwareAPIController
|
||||||
|
from software.api.controllers.v1.release import ReleaseController
|
||||||
|
from software.api.controllers.v1.deploy import DeployController
|
||||||
|
from software.api.controllers.v1.deploy_host import DeployHostController
|
||||||
|
|
||||||
|
|
||||||
class MediaType(base.APIBase):
|
class MediaType(base.APIBase):
|
||||||
|
@ -49,6 +52,9 @@ class V1(base.APIBase):
|
||||||
"Links that point to a specific URL for this version and documentation"
|
"Links that point to a specific URL for this version and documentation"
|
||||||
|
|
||||||
software = [link.Link]
|
software = [link.Link]
|
||||||
|
release = [link.Link]
|
||||||
|
deploy = [link.Link]
|
||||||
|
deploy_host = [link.Link]
|
||||||
"Links to the software resource"
|
"Links to the software resource"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -56,18 +62,38 @@ class V1(base.APIBase):
|
||||||
v1 = V1()
|
v1 = V1()
|
||||||
v1.id = "v1"
|
v1.id = "v1"
|
||||||
v1.links = [link.Link.make_link('self', pecan.request.host_url,
|
v1.links = [link.Link.make_link('self', pecan.request.host_url,
|
||||||
'v1', '', bookmark=True),
|
'v1', '', bookmark=True)]
|
||||||
]
|
|
||||||
v1.media_types = [MediaType('application/json',
|
v1.media_types = [MediaType('application/json',
|
||||||
'application/vnd.openstack.software.v1+json')]
|
'application/vnd.openstack.software.v1+json')]
|
||||||
|
|
||||||
|
v1.release = [link.Link.make_link('self', pecan.request.host_url,
|
||||||
|
'release', ''),
|
||||||
|
link.Link.make_link('bookmark',
|
||||||
|
pecan.request.host_url,
|
||||||
|
'release', '',
|
||||||
|
bookmark=True)]
|
||||||
|
|
||||||
|
v1.deploy = [link.Link.make_link('self', pecan.request.host_url,
|
||||||
|
'deploy', ''),
|
||||||
|
link.Link.make_link('bookmark',
|
||||||
|
pecan.request.host_url,
|
||||||
|
'deploy', '',
|
||||||
|
bookmark=True)]
|
||||||
|
|
||||||
|
v1.deploy_host = [link.Link.make_link('self', pecan.request.host_url,
|
||||||
|
'deploy_host', ''),
|
||||||
|
link.Link.make_link('bookmark',
|
||||||
|
pecan.request.host_url,
|
||||||
|
'deploy_host', '',
|
||||||
|
bookmark=True)]
|
||||||
|
|
||||||
v1.software = [link.Link.make_link('self', pecan.request.host_url,
|
v1.software = [link.Link.make_link('self', pecan.request.host_url,
|
||||||
'software', ''),
|
'software', ''),
|
||||||
link.Link.make_link('bookmark',
|
link.Link.make_link('bookmark',
|
||||||
pecan.request.host_url,
|
pecan.request.host_url,
|
||||||
'software', '',
|
'software', '',
|
||||||
bookmark=True)
|
bookmark=True)]
|
||||||
]
|
|
||||||
|
|
||||||
return v1
|
return v1
|
||||||
|
|
||||||
|
@ -75,7 +101,10 @@ class V1(base.APIBase):
|
||||||
class Controller(rest.RestController):
|
class Controller(rest.RestController):
|
||||||
"""Version 1 API controller root."""
|
"""Version 1 API controller root."""
|
||||||
|
|
||||||
software = software.SoftwareAPIController()
|
software = SoftwareAPIController()
|
||||||
|
release = ReleaseController()
|
||||||
|
deploy = DeployController()
|
||||||
|
deploy_host = DeployHostController()
|
||||||
|
|
||||||
@wsme_pecan.wsexpose(V1)
|
@wsme_pecan.wsexpose(V1)
|
||||||
def get(self):
|
def get(self):
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
"""
|
||||||
|
Copyright (c) 2024 Wind River Systems, Inc.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from pecan import expose
|
||||||
|
from pecan import request
|
||||||
|
from pecan.rest import RestController
|
||||||
|
|
||||||
|
from software.exceptions import SoftwareServiceError
|
||||||
|
from software.release_data import reload_release_data
|
||||||
|
from software.software_controller import sc
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger('main_logger')
|
||||||
|
|
||||||
|
|
||||||
|
class DeployController(RestController):
|
||||||
|
_custom_actions = {
|
||||||
|
'activate': ['POST'],
|
||||||
|
'precheck': ['POST'],
|
||||||
|
'start': ['POST'],
|
||||||
|
'complete': ['POST'],
|
||||||
|
'is_sync_controller': ['GET'],
|
||||||
|
'software_upgrade': ['GET'],
|
||||||
|
}
|
||||||
|
|
||||||
|
@expose(method='POST', template='json')
|
||||||
|
def activate(self):
|
||||||
|
reload_release_data()
|
||||||
|
|
||||||
|
result = sc.software_deploy_activate_api()
|
||||||
|
sc.software_sync()
|
||||||
|
return result
|
||||||
|
|
||||||
|
@expose(method='POST', template='json')
|
||||||
|
def complete(self):
|
||||||
|
reload_release_data()
|
||||||
|
|
||||||
|
result = sc.software_deploy_complete_api()
|
||||||
|
sc.software_sync()
|
||||||
|
return result
|
||||||
|
|
||||||
|
@expose(method='POST', template='json')
|
||||||
|
def precheck(self, release, force=None, region_name=None):
|
||||||
|
_force = force is not None
|
||||||
|
reload_release_data()
|
||||||
|
|
||||||
|
result = sc.software_deploy_precheck_api(release, _force, region_name)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@expose(method='POST', template='json')
|
||||||
|
def start(self, release, force=None):
|
||||||
|
reload_release_data()
|
||||||
|
_force = force is not None
|
||||||
|
|
||||||
|
if sc.any_patch_host_installing():
|
||||||
|
raise SoftwareServiceError(error="Rejected: One or more nodes are installing a release.")
|
||||||
|
|
||||||
|
result = sc.software_deploy_start_api(release, _force)
|
||||||
|
|
||||||
|
sc.send_latest_feed_commit_to_agent()
|
||||||
|
sc.software_sync()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@expose(method='GET', template='json')
|
||||||
|
def get_all(self):
|
||||||
|
reload_release_data()
|
||||||
|
from_release = request.GET.get("from_release")
|
||||||
|
to_release = request.GET.get("to_release")
|
||||||
|
result = sc.software_deploy_show_api(from_release, to_release)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@expose(method='GET', template='json')
|
||||||
|
def in_sync_controller(self):
|
||||||
|
return sc.in_sync_controller_api()
|
||||||
|
|
||||||
|
@expose(method='GET', template='json')
|
||||||
|
def software_upgrade(self):
|
||||||
|
return sc.get_software_upgrade()
|
|
@ -0,0 +1,46 @@
|
||||||
|
"""
|
||||||
|
Copyright (c) 2024 Wind River Systems, Inc.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from pecan import expose
|
||||||
|
from pecan.rest import RestController
|
||||||
|
|
||||||
|
from software.release_data import reload_release_data
|
||||||
|
from software.software_controller import sc
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger('main_logger')
|
||||||
|
|
||||||
|
|
||||||
|
class DeployHostController(RestController):
|
||||||
|
_custom_actions = {
|
||||||
|
'install_local': ['POST'],
|
||||||
|
}
|
||||||
|
|
||||||
|
@expose(method='GET', template='json')
|
||||||
|
def get_all(self):
|
||||||
|
reload_release_data()
|
||||||
|
result = sc.deploy_host_list()
|
||||||
|
return result
|
||||||
|
|
||||||
|
@expose(method='POST', template='json')
|
||||||
|
def post(self, *args):
|
||||||
|
reload_release_data()
|
||||||
|
if len(list(args)) == 0:
|
||||||
|
return dict(error="Host must be specified for install")
|
||||||
|
force = False
|
||||||
|
if len(list(args)) > 1 and 'force' in list(args)[1:]:
|
||||||
|
force = True
|
||||||
|
|
||||||
|
result = sc.software_deploy_host_api(list(args)[0], force, async_req=True)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@expose(method='POST', template='json')
|
||||||
|
def install_local(self):
|
||||||
|
reload_release_data()
|
||||||
|
result = sc.software_install_local_api()
|
||||||
|
return result
|
|
@ -0,0 +1,130 @@
|
||||||
|
"""
|
||||||
|
Copyright (c) 2024 Wind River Systems, Inc.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
"""
|
||||||
|
import cgi
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from pecan import expose
|
||||||
|
from pecan import request
|
||||||
|
from pecan.rest import RestController
|
||||||
|
import shutil
|
||||||
|
import webob
|
||||||
|
|
||||||
|
from software import constants
|
||||||
|
from software.exceptions import SoftwareServiceError
|
||||||
|
from software.release_data import reload_release_data
|
||||||
|
from software.software_controller import sc
|
||||||
|
from software import utils
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger('main_logger')
|
||||||
|
|
||||||
|
|
||||||
|
class ReleaseController(RestController):
|
||||||
|
_custom_actions = {
|
||||||
|
'commit': ['POST'],
|
||||||
|
'commit_dry_run': ['POST'],
|
||||||
|
'is_available': ['GET'],
|
||||||
|
'is_committed': ['GET'],
|
||||||
|
'is_deployed': ['GET'],
|
||||||
|
}
|
||||||
|
|
||||||
|
@expose(method='GET', template='json')
|
||||||
|
def get_all(self, **kwargs):
|
||||||
|
reload_release_data()
|
||||||
|
sd = sc.software_release_query_cached(**kwargs)
|
||||||
|
return sd
|
||||||
|
|
||||||
|
@expose(method='GET', template='json')
|
||||||
|
def get_one(self, release):
|
||||||
|
reload_release_data()
|
||||||
|
result = sc.software_release_query_specific_cached([release])
|
||||||
|
if len(result) == 1:
|
||||||
|
return result[0]
|
||||||
|
msg = f"Release {release} not found"
|
||||||
|
raise webob.exc.HTTPNotFound(msg)
|
||||||
|
|
||||||
|
@expose(method='POST', template='json')
|
||||||
|
def post(self):
|
||||||
|
reload_release_data()
|
||||||
|
is_local = False
|
||||||
|
temp_dir = None
|
||||||
|
uploaded_files = []
|
||||||
|
request_data = []
|
||||||
|
local_files = []
|
||||||
|
|
||||||
|
# --local option only sends a list of file names
|
||||||
|
if (request.content_type == "text/plain"):
|
||||||
|
local_files = list(json.loads(request.body))
|
||||||
|
is_local = True
|
||||||
|
else:
|
||||||
|
request_data = list(request.POST.items())
|
||||||
|
temp_dir = os.path.join(constants.SCRATCH_DIR, 'upload_files')
|
||||||
|
|
||||||
|
try:
|
||||||
|
if len(request_data) == 0 and len(local_files) == 0:
|
||||||
|
raise SoftwareServiceError(error="No files uploaded")
|
||||||
|
|
||||||
|
if is_local:
|
||||||
|
uploaded_files = local_files
|
||||||
|
LOG.info("Uploaded local files: %s", uploaded_files)
|
||||||
|
else:
|
||||||
|
# Protect against duplications
|
||||||
|
uploaded_files = sorted(set(request_data))
|
||||||
|
# Save all uploaded files to /scratch/upload_files dir
|
||||||
|
for file_item in uploaded_files:
|
||||||
|
assert isinstance(file_item[1], cgi.FieldStorage)
|
||||||
|
utils.save_temp_file(file_item[1], temp_dir)
|
||||||
|
|
||||||
|
# Get all uploaded files from /scratch dir
|
||||||
|
uploaded_files = utils.get_all_files(temp_dir)
|
||||||
|
LOG.info("Uploaded files: %s", uploaded_files)
|
||||||
|
|
||||||
|
# Process uploaded files
|
||||||
|
return sc.software_release_upload(uploaded_files)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Remove all uploaded files from /scratch dir
|
||||||
|
sc.software_sync()
|
||||||
|
if temp_dir:
|
||||||
|
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
@expose(method='DELETE', template='json')
|
||||||
|
def delete(self, *args):
|
||||||
|
reload_release_data()
|
||||||
|
result = sc.software_release_delete_api(list(args))
|
||||||
|
sc.software_sync()
|
||||||
|
return result
|
||||||
|
|
||||||
|
@expose(method='POST', template='json')
|
||||||
|
def commit(self, *args):
|
||||||
|
reload_release_data()
|
||||||
|
result = sc.patch_commit(list(args))
|
||||||
|
sc.software_sync()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@expose(method='POST', template='json')
|
||||||
|
def commit_dry_run(self, *args):
|
||||||
|
reload_release_data()
|
||||||
|
result = sc.patch_commit(list(args), dry_run=True)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@expose(method='GET', template='json')
|
||||||
|
def is_available(self, *args):
|
||||||
|
reload_release_data()
|
||||||
|
return sc.is_available(list(args))
|
||||||
|
|
||||||
|
@expose(method='GET', template='json')
|
||||||
|
def is_committed(self, *args):
|
||||||
|
reload_release_data()
|
||||||
|
return sc.is_committed(list(args))
|
||||||
|
|
||||||
|
@expose(method='GET', template='json')
|
||||||
|
def is_deployed(self, *args):
|
||||||
|
reload_release_data()
|
||||||
|
return sc.is_deployed(list(args))
|
|
@ -4,222 +4,16 @@ Copyright (c) 2023-2024 Wind River Systems, Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import cgi
|
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
from pecan import expose
|
from pecan import expose
|
||||||
from pecan import request
|
from pecan.rest import RestController
|
||||||
import shutil
|
|
||||||
|
|
||||||
from software import constants
|
|
||||||
from software.exceptions import SoftwareError
|
|
||||||
from software.exceptions import SoftwareServiceError
|
|
||||||
from software.release_data import reload_release_data
|
|
||||||
from software.software_controller import sc
|
from software.software_controller import sc
|
||||||
from software import utils
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger('main_logger')
|
LOG = logging.getLogger('main_logger')
|
||||||
|
|
||||||
|
|
||||||
class SoftwareAPIController(object):
|
class SoftwareAPIController(RestController):
|
||||||
|
|
||||||
@expose('json')
|
|
||||||
def commit_patch(self, *args):
|
|
||||||
reload_release_data()
|
|
||||||
result = sc.patch_commit(list(args))
|
|
||||||
sc.software_sync()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
@expose('json')
|
|
||||||
def commit_dry_run(self, *args):
|
|
||||||
reload_release_data()
|
|
||||||
result = sc.patch_commit(list(args), dry_run=True)
|
|
||||||
return result
|
|
||||||
|
|
||||||
@expose('json')
|
|
||||||
@expose('query.xml', content_type='application/xml')
|
|
||||||
def delete(self, *args):
|
|
||||||
reload_release_data()
|
|
||||||
result = sc.software_release_delete_api(list(args))
|
|
||||||
sc.software_sync()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
@expose('json')
|
|
||||||
@expose('query.xml', content_type='application/xml')
|
|
||||||
def deploy_activate(self):
|
|
||||||
reload_release_data()
|
|
||||||
|
|
||||||
result = sc.software_deploy_activate_api()
|
|
||||||
sc.software_sync()
|
|
||||||
return result
|
|
||||||
|
|
||||||
@expose('json')
|
|
||||||
@expose('query.xml', content_type='application/xml')
|
|
||||||
def deploy_complete(self):
|
|
||||||
reload_release_data()
|
|
||||||
|
|
||||||
result = sc.software_deploy_complete_api()
|
|
||||||
sc.software_sync()
|
|
||||||
return result
|
|
||||||
|
|
||||||
@expose('json')
|
|
||||||
@expose('query.xml', content_type='application/xml')
|
|
||||||
def deploy_host(self, *args):
|
|
||||||
reload_release_data()
|
|
||||||
if len(list(args)) == 0:
|
|
||||||
return dict(error="Host must be specified for install")
|
|
||||||
force = False
|
|
||||||
if len(list(args)) > 1 and 'force' in list(args)[1:]:
|
|
||||||
force = True
|
|
||||||
|
|
||||||
result = sc.software_deploy_host_api(list(args)[0], force, async_req=True)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
@expose('json')
|
|
||||||
@expose('query.xml', content_type='application/xml')
|
|
||||||
def deploy_precheck(self, *args, **kwargs):
|
|
||||||
reload_release_data()
|
|
||||||
force = False
|
|
||||||
if 'force' in list(args):
|
|
||||||
force = True
|
|
||||||
|
|
||||||
result = sc.software_deploy_precheck_api(list(args)[0], force, **kwargs)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
@expose('json')
|
|
||||||
@expose('query.xml', content_type='application/xml')
|
|
||||||
def deploy_start(self, *args, **kwargs):
|
|
||||||
reload_release_data()
|
|
||||||
# if --force is provided
|
|
||||||
force = 'force' in list(args)
|
|
||||||
|
|
||||||
if sc.any_patch_host_installing():
|
|
||||||
raise SoftwareServiceError(error="Rejected: One or more nodes are installing a release.")
|
|
||||||
|
|
||||||
result = sc.software_deploy_start_api(list(args)[0], force, **kwargs)
|
|
||||||
|
|
||||||
sc.send_latest_feed_commit_to_agent()
|
|
||||||
sc.software_sync()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
@expose('json', method="GET")
|
|
||||||
def deploy(self):
|
|
||||||
reload_release_data()
|
|
||||||
from_release = request.GET.get("from_release")
|
|
||||||
to_release = request.GET.get("to_release")
|
|
||||||
result = sc.software_deploy_show_api(from_release, to_release)
|
|
||||||
return result
|
|
||||||
|
|
||||||
@expose('json')
|
|
||||||
@expose('query.xml', content_type='application/xml')
|
|
||||||
def install_local(self):
|
|
||||||
reload_release_data()
|
|
||||||
result = sc.software_install_local_api()
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
@expose('json')
|
|
||||||
def is_available(self, *args):
|
|
||||||
reload_release_data()
|
|
||||||
return sc.is_available(list(args))
|
|
||||||
|
|
||||||
@expose('json')
|
|
||||||
def is_committed(self, *args):
|
|
||||||
reload_release_data()
|
|
||||||
return sc.is_committed(list(args))
|
|
||||||
|
|
||||||
@expose('json')
|
|
||||||
def is_deployed(self, *args):
|
|
||||||
reload_release_data()
|
|
||||||
return sc.is_deployed(list(args))
|
|
||||||
|
|
||||||
@expose('json')
|
|
||||||
@expose('show.xml', content_type='application/xml')
|
|
||||||
def show(self, *args):
|
|
||||||
reload_release_data()
|
|
||||||
result = sc.software_release_query_specific_cached(list(args))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
@expose('json')
|
|
||||||
@expose('query.xml', content_type='application/xml')
|
|
||||||
def upload(self):
|
|
||||||
reload_release_data()
|
|
||||||
is_local = False
|
|
||||||
temp_dir = None
|
|
||||||
uploaded_files = []
|
|
||||||
request_data = []
|
|
||||||
local_files = []
|
|
||||||
|
|
||||||
# --local option only sends a list of file names
|
|
||||||
if (request.content_type == "text/plain"):
|
|
||||||
local_files = list(json.loads(request.body))
|
|
||||||
is_local = True
|
|
||||||
else:
|
|
||||||
request_data = list(request.POST.items())
|
|
||||||
temp_dir = os.path.join(constants.SCRATCH_DIR, 'upload_files')
|
|
||||||
|
|
||||||
try:
|
|
||||||
if len(request_data) == 0 and len(local_files) == 0:
|
|
||||||
raise SoftwareError("No files uploaded")
|
|
||||||
|
|
||||||
if is_local:
|
|
||||||
uploaded_files = local_files
|
|
||||||
LOG.info("Uploaded local files: %s", uploaded_files)
|
|
||||||
else:
|
|
||||||
# Protect against duplications
|
|
||||||
uploaded_files = sorted(set(request_data))
|
|
||||||
# Save all uploaded files to /scratch/upload_files dir
|
|
||||||
for file_item in uploaded_files:
|
|
||||||
assert isinstance(file_item[1], cgi.FieldStorage)
|
|
||||||
utils.save_temp_file(file_item[1], temp_dir)
|
|
||||||
|
|
||||||
# Get all uploaded files from /scratch dir
|
|
||||||
uploaded_files = utils.get_all_files(temp_dir)
|
|
||||||
LOG.info("Uploaded files: %s", uploaded_files)
|
|
||||||
|
|
||||||
# Process uploaded files
|
|
||||||
return sc.software_release_upload(uploaded_files)
|
|
||||||
|
|
||||||
finally:
|
|
||||||
# Remove all uploaded files from /scratch dir
|
|
||||||
sc.software_sync()
|
|
||||||
if temp_dir:
|
|
||||||
shutil.rmtree(temp_dir, ignore_errors=True)
|
|
||||||
|
|
||||||
@expose('json')
|
|
||||||
@expose('query.xml', content_type='application/xml')
|
|
||||||
def query(self, **kwargs):
|
|
||||||
reload_release_data()
|
|
||||||
sd = sc.software_release_query_cached(**kwargs)
|
|
||||||
return sd
|
|
||||||
|
|
||||||
@expose('json', method="GET")
|
|
||||||
def host_list(self):
|
|
||||||
reload_release_data()
|
|
||||||
result = sc.deploy_host_list()
|
|
||||||
return result
|
|
||||||
|
|
||||||
@expose(method='GET', template='json')
|
@expose(method='GET', template='json')
|
||||||
def in_sync_controller(self):
|
def in_sync_controller(self):
|
||||||
return sc.in_sync_controller_api()
|
return sc.in_sync_controller_api()
|
||||||
|
|
||||||
@expose(method='GET', template='json')
|
|
||||||
def software_upgrade(self):
|
|
||||||
return sc.get_software_upgrade()
|
|
||||||
|
|
||||||
@expose(method='GET', template='json')
|
|
||||||
def software_host_upgrade(self, *args):
|
|
||||||
args_list = list(args)
|
|
||||||
if not args_list:
|
|
||||||
return sc.get_all_software_host_upgrade()
|
|
||||||
|
|
||||||
hostname = args_list[0]
|
|
||||||
return sc.get_one_software_host_upgrade(hostname)
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ from software.authapi import acl
|
||||||
from software.authapi import config
|
from software.authapi import config
|
||||||
from software.authapi import hooks
|
from software.authapi import hooks
|
||||||
from software.authapi import policy
|
from software.authapi import policy
|
||||||
from software.parsable_error import ParsableErrorMiddleware
|
|
||||||
from software.utils import ExceptionHook
|
from software.utils import ExceptionHook
|
||||||
|
|
||||||
auth_opts = [
|
auth_opts = [
|
||||||
|
@ -56,7 +55,6 @@ def setup_app(pecan_config=None, extra_hooks=None):
|
||||||
debug=False,
|
debug=False,
|
||||||
force_canonical=getattr(pecan_config.app, 'force_canonical', True),
|
force_canonical=getattr(pecan_config.app, 'force_canonical', True),
|
||||||
hooks=app_hooks,
|
hooks=app_hooks,
|
||||||
wrap_app=ParsableErrorMiddleware,
|
|
||||||
guess_content_type_from_ext=False, # Avoid mime-type lookup
|
guess_content_type_from_ext=False, # Avoid mime-type lookup
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1134,7 +1134,7 @@ class PatchController(PatchService):
|
||||||
# Disallow the install
|
# Disallow the install
|
||||||
msg = "This command can only be used before initial system configuration."
|
msg = "This command can only be used before initial system configuration."
|
||||||
LOG.exception(msg)
|
LOG.exception(msg)
|
||||||
raise SoftwareFail(msg)
|
raise SoftwareServiceError(error=msg)
|
||||||
|
|
||||||
update_hosts_file = False
|
update_hosts_file = False
|
||||||
|
|
||||||
|
@ -1346,7 +1346,7 @@ class PatchController(PatchService):
|
||||||
|
|
||||||
# Get the release_id from the patch's metadata
|
# Get the release_id from the patch's metadata
|
||||||
# and check to see if it's already uploaded
|
# and check to see if it's already uploaded
|
||||||
release_id = get_release_from_patch(patch_file,'id')
|
release_id = get_release_from_patch(patch_file, 'id')
|
||||||
|
|
||||||
release = self.release_collection.get_release_by_id(release_id)
|
release = self.release_collection.get_release_by_id(release_id)
|
||||||
|
|
||||||
|
@ -2218,15 +2218,18 @@ class PatchController(PatchService):
|
||||||
|
|
||||||
return dict(info=msg_info, warning=msg_warning, error=msg_error, system_healthy=system_healthy)
|
return dict(info=msg_info, warning=msg_warning, error=msg_error, system_healthy=system_healthy)
|
||||||
|
|
||||||
def software_deploy_precheck_api(self, deployment: str, force: bool = False, **kwargs) -> dict:
|
def software_deploy_precheck_api(self, deployment: str, force: bool = False, region_name=None) -> dict:
|
||||||
"""
|
"""
|
||||||
Verify if system satisfy the requisites to upgrade to a specified deployment.
|
Verify if system satisfy the requisites to upgrade to a specified deployment.
|
||||||
:param deployment: full release name, e.g. starlingx-MM.mm.pp
|
:param deployment: full release name, e.g. starlingx-MM.mm.pp
|
||||||
:param force: if True will ignore minor alarms during precheck
|
:param force: if True will ignore minor alarms during precheck
|
||||||
:return: dict of info, warning and error messages
|
:return: dict of info, warning and error messages
|
||||||
"""
|
"""
|
||||||
|
|
||||||
release = self._release_basic_checks(deployment)
|
release = self._release_basic_checks(deployment)
|
||||||
region_name = kwargs["region_name"]
|
if region_name is None:
|
||||||
|
region_name = utils.get_local_region_name()
|
||||||
|
|
||||||
release_version = release.sw_release
|
release_version = release.sw_release
|
||||||
patch = not utils.is_upgrade_deploy(SW_VERSION, release_version)
|
patch = not utils.is_upgrade_deploy(SW_VERSION, release_version)
|
||||||
return self._deploy_precheck(release_version, force, region_name, patch)
|
return self._deploy_precheck(release_version, force, region_name, patch)
|
||||||
|
|
|
@ -1356,7 +1356,7 @@ def deploy_host_validations(hostname):
|
||||||
validate_host_deploy_order(hostname)
|
validate_host_deploy_order(hostname)
|
||||||
if not is_host_locked_and_online(hostname):
|
if not is_host_locked_and_online(hostname):
|
||||||
msg = f"Host {hostname} must be {constants.ADMIN_LOCKED}."
|
msg = f"Host {hostname} must be {constants.ADMIN_LOCKED}."
|
||||||
raise SoftwareServiceError(msg)
|
raise SoftwareServiceError(error=msg)
|
||||||
|
|
||||||
|
|
||||||
def validate_host_deploy_order(hostname):
|
def validate_host_deploy_order(hostname):
|
||||||
|
@ -1391,7 +1391,7 @@ def validate_host_deploy_order(hostname):
|
||||||
if host.get("state") == states.DEPLOY_HOST_STATES.DEPLOYED.value:
|
if host.get("state") == states.DEPLOY_HOST_STATES.DEPLOYED.value:
|
||||||
ordered_list.remove(host.get("hostname"))
|
ordered_list.remove(host.get("hostname"))
|
||||||
if not ordered_list:
|
if not ordered_list:
|
||||||
raise SoftwareServiceError("All hosts are already in deployed state.")
|
raise SoftwareServiceError(error="All hosts are already in deployed state.")
|
||||||
# If there is only workers nodes there is no order to deploy
|
# If there is only workers nodes there is no order to deploy
|
||||||
if hostname == ordered_list[0] or (ordered_list[0] in workers_list and hostname in workers_list):
|
if hostname == ordered_list[0] or (ordered_list[0] in workers_list and hostname in workers_list):
|
||||||
return
|
return
|
||||||
|
@ -1399,5 +1399,6 @@ def validate_host_deploy_order(hostname):
|
||||||
elif is_patch_release and ordered_list[0] in controllers_list and hostname in controllers_list:
|
elif is_patch_release and ordered_list[0] in controllers_list and hostname in controllers_list:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
raise SoftwareServiceError(f"{hostname.capitalize()} do not satisfy the right order of deployment "
|
errmsg = f"{hostname} does not satisfy the right order of deployment " + \
|
||||||
f"should be {ordered_list[0]}")
|
f"should be {ordered_list[0]}"
|
||||||
|
raise SoftwareServiceError(error=errmsg)
|
||||||
|
|
|
@ -52,6 +52,9 @@ class ExceptionHook(hooks.PecanHook):
|
||||||
LOG.exception(e)
|
LOG.exception(e)
|
||||||
|
|
||||||
data = dict(info=e.info, warning=e.warning, error=e.error)
|
data = dict(info=e.info, warning=e.warning, error=e.error)
|
||||||
|
elif isinstance(e, webob.exc.HTTPClientError):
|
||||||
|
LOG.warning("%s. Signature [%s]" % (str(e), signature))
|
||||||
|
raise e
|
||||||
else:
|
else:
|
||||||
# with an exception that is not pre-categorized as "expected", it is a
|
# with an exception that is not pre-categorized as "expected", it is a
|
||||||
# bug. Or not properly categorizing the exception itself is a bug.
|
# bug. Or not properly categorizing the exception itself is a bug.
|
||||||
|
@ -276,6 +279,12 @@ def get_all_files(temp_dir=constants.SCRATCH_DIR):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def get_local_region_name():
|
||||||
|
config = CONF.get('keystone_authtoken')
|
||||||
|
region_name = config.region_name
|
||||||
|
return region_name
|
||||||
|
|
||||||
|
|
||||||
def get_auth_token_and_endpoint(user: dict, service_type: str, region_name: str, interface: str):
|
def get_auth_token_and_endpoint(user: dict, service_type: str, region_name: str, interface: str):
|
||||||
"""Get the auth token and endpoint for a service
|
"""Get the auth token and endpoint for a service
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue