Merge "Decontainerizing rvmc.py and Modular Integration in DC Repo"
This commit is contained in:
commit
87653b7890
@ -6,4 +6,4 @@ distributedcloud-dcdbsync
|
||||
distributedcloud-dcmanager
|
||||
distributedcloud-dcorch
|
||||
|
||||
|
||||
python3-redfish
|
||||
|
@ -1 +1,2 @@
|
||||
distributedcloud
|
||||
python/python3-redfish
|
||||
|
@ -121,10 +121,6 @@ class ImageNotInLocalRegistry(NotFound):
|
||||
"system registry-image-tags %(image_name)s")
|
||||
|
||||
|
||||
class SubcloudShutdownError(PlaybookExecutionFailed):
|
||||
message = _("Subcloud %(subcloud_name)s could not be shut down.")
|
||||
|
||||
|
||||
class ApiException(DCCommonException):
|
||||
message = _("%(endpoint)s failed with status code: %(rc)d")
|
||||
|
||||
@ -140,3 +136,12 @@ class SubcloudPeerGroupNotFound(NotFound):
|
||||
class SubcloudPeerGroupDeleteFailedAssociated(DCCommonException):
|
||||
message = _("Subcloud Peer Group %(peer_group_ref)s delete failed "
|
||||
"cause it is associated with a system peer.")
|
||||
|
||||
|
||||
class RvmcException(Exception):
|
||||
def __init__(self, message=None):
|
||||
super(RvmcException, self).__init__(message)
|
||||
|
||||
|
||||
class RvmcExit(DCCommonException):
|
||||
message = _("Rvmc failed with status code: %(rc)d")
|
||||
|
1692
distributedcloud/dccommon/rvmc.py
Executable file
1692
distributedcloud/dccommon/rvmc.py
Executable file
File diff suppressed because it is too large
Load Diff
@ -13,41 +13,31 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import socket
|
||||
import ssl
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from eventlet.green import subprocess
|
||||
import netaddr
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_log import log as logging
|
||||
from six.moves.urllib import error as urllib_error
|
||||
from six.moves.urllib import parse
|
||||
from six.moves.urllib import request
|
||||
import yaml
|
||||
|
||||
from dccommon import consts
|
||||
from dccommon.drivers.openstack.keystone_v3 import KeystoneClient
|
||||
from dccommon.drivers.openstack.sysinv_v1 import SysinvClient
|
||||
from dccommon import exceptions
|
||||
from dccommon import kubeoperator
|
||||
from dccommon import utils as dccommon_utils
|
||||
from dcmanager.common import consts as dcmanager_consts
|
||||
from dcmanager.common import utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
BOOT_MENU_TIMEOUT = '5'
|
||||
|
||||
# The RVMC_IMAGE_NAME:RVMC_IMAGE_TAG must align with the one specified
|
||||
# in system images in the ansible install/upgrade playbook
|
||||
RVMC_IMAGE_NAME = 'docker.io/starlingx/rvmc'
|
||||
RVMC_IMAGE_TAG = 'stx.8.0-v1.0.2'
|
||||
|
||||
SUBCLOUD_ISO_PATH = '/opt/platform/iso'
|
||||
SUBCLOUD_ISO_DOWNLOAD_PATH = '/var/www/pages/iso'
|
||||
SUBCLOUD_FEED_PATH = '/var/www/pages/feed'
|
||||
@ -60,104 +50,6 @@ NETWORK_INTERFACE_PREFIX = 'ifcfg'
|
||||
NETWORK_ROUTE_PREFIX = 'route'
|
||||
LOCAL_REGISTRY_PREFIX = 'registry.local:9001/'
|
||||
|
||||
# Redfish constants
|
||||
ACTION_URL = '/Actions/ComputerSystem.Reset'
|
||||
POWER_OFF_PAYLOAD = {'Action': 'Reset', 'ResetType': 'ForceOff'}
|
||||
REDFISH_HEADER = {'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'}
|
||||
REDFISH_SYSTEMS_URL = '/redfish/v1/Systems'
|
||||
SUCCESSFUL_STATUS_CODES = [200, 202, 204]
|
||||
|
||||
RVMC_LOCK_NAME = 'dc-rvmc-install'
|
||||
RVMC_NAMESPACE = 'rvmc'
|
||||
KUBE_SYSTEM_NAMESPACE = 'kube-system'
|
||||
DEFAULT_REGISTRY_KEY = 'default-registry-key'
|
||||
|
||||
|
||||
class SubcloudShutdown(object):
|
||||
"""Sends a shutdown signal to a Redfish controlled subcloud
|
||||
|
||||
Approach:
|
||||
|
||||
To shutdown a Redfish controlled subcloud, it's needed to first
|
||||
send a GET request to find the @odata.id of the member, and then
|
||||
send a POST request with the shutdown signal. Since this is
|
||||
intended as a way to turn off the subcloud during the deploy abort
|
||||
process, only the ForceOff option is considered.
|
||||
"""
|
||||
def __init__(self, subcloud_name):
|
||||
self.target = subcloud_name
|
||||
self.rvmc_data = self._get_subcloud_data()
|
||||
|
||||
def _get_subcloud_data(self):
|
||||
rvmc_config_file_path = os.path.join(consts.ANSIBLE_OVERRIDES_PATH,
|
||||
self.target, consts.RVMC_CONFIG_FILE_NAME)
|
||||
if not os.path.isfile(rvmc_config_file_path):
|
||||
raise Exception('Missing rvmc files for %s' % self.target)
|
||||
with open(os.path.abspath(rvmc_config_file_path), 'r') as f:
|
||||
rvmc_data = f.read()
|
||||
rvmc_config_values = yaml.load(rvmc_data, Loader=yaml.SafeLoader)
|
||||
base_url = "https://" + rvmc_config_values['bmc_address']
|
||||
bmc_username = rvmc_config_values['bmc_username']
|
||||
bmc_password = rvmc_config_values['bmc_password']
|
||||
credentials = ("%s:%s" % (bmc_username.rstrip(), bmc_password)).encode("utf-8")
|
||||
return {'base_url': base_url, 'credentials': credentials}
|
||||
|
||||
def _make_request(self, url, credentials, method, retry=5):
|
||||
if method == 'get':
|
||||
payload = None
|
||||
else:
|
||||
payload = json.dumps(POWER_OFF_PAYLOAD).encode('utf-8')
|
||||
|
||||
try:
|
||||
context = ssl._create_unverified_context()
|
||||
req = request.Request(url, headers=REDFISH_HEADER, method=method)
|
||||
req.add_header('Authorization', 'Basic %s' % credentials)
|
||||
response = request.urlopen(req, data=payload, context=context)
|
||||
status_code = response.getcode()
|
||||
|
||||
if status_code not in SUCCESSFUL_STATUS_CODES:
|
||||
if retry <= 0:
|
||||
raise exceptions.SubcloudShutdownError(
|
||||
subcloud_name=self.target)
|
||||
retry -= retry
|
||||
time.sleep(2)
|
||||
self._make_request(url, credentials, method, retry=retry)
|
||||
except urllib_error.URLError:
|
||||
# This occurs when the BMC is not available anymore,
|
||||
# so we just ignore it.
|
||||
return None
|
||||
except Exception as ex:
|
||||
raise ex
|
||||
|
||||
return response
|
||||
|
||||
def _get_data_id(self):
|
||||
base_url = self.rvmc_data['base_url']
|
||||
credentials = self.rvmc_data['credentials']
|
||||
url = base_url + REDFISH_SYSTEMS_URL
|
||||
response = self._make_request(url, credentials, method='GET')
|
||||
if not response:
|
||||
return None
|
||||
r = json.loads(response.read().decode())
|
||||
|
||||
for member in r['Members']:
|
||||
if member.get('@odata.id'):
|
||||
url_with_id = member['@odata.id']
|
||||
break
|
||||
|
||||
return url_with_id
|
||||
|
||||
def send_shutdown_signal(self):
|
||||
base_url = self.rvmc_data['base_url']
|
||||
credentials = self.rvmc_data['credentials']
|
||||
url_with_id = self._get_data_id()
|
||||
if not url_with_id:
|
||||
return None
|
||||
url = base_url + url_with_id + ACTION_URL
|
||||
response = self._make_request(url, credentials, method='POST')
|
||||
return response
|
||||
|
||||
|
||||
class SubcloudInstall(object):
|
||||
"""Class to encapsulate the subcloud install operations"""
|
||||
@ -256,17 +148,6 @@ class SubcloudInstall(object):
|
||||
|
||||
return "%s://%s:%s" % (protocol, self.get_oam_address(), port)
|
||||
|
||||
def check_image_exists(self, image_name, image_tag):
|
||||
tags = self.sysinv_client.get_registry_image_tags(image_name)
|
||||
if tags:
|
||||
if any(getattr(tag, 'tag') == image_tag for tag in tags):
|
||||
return
|
||||
msg = "Error: Image %s:%s not found in the local registry." % (
|
||||
image_name, image_tag)
|
||||
LOG.error(msg)
|
||||
raise exceptions.ImageNotInLocalRegistry(image_name=image_name,
|
||||
image_tag=image_tag)
|
||||
|
||||
@staticmethod
|
||||
def create_rvmc_config_file(override_path, payload):
|
||||
|
||||
@ -279,39 +160,17 @@ class SubcloudInstall(object):
|
||||
if k in consts.BMC_INSTALL_VALUES or k == 'image':
|
||||
f_out_rvmc_config_file.write(k + ': ' + v + '\n')
|
||||
|
||||
@lockutils.synchronized(RVMC_LOCK_NAME)
|
||||
def copy_default_registry_key(self):
|
||||
"""Copy default-registry-key secret for pulling rvmc image."""
|
||||
kube = kubeoperator.KubeOperator()
|
||||
try:
|
||||
if kube.kube_get_secret(DEFAULT_REGISTRY_KEY, RVMC_NAMESPACE) is None:
|
||||
if not kube.kube_get_namespace(RVMC_NAMESPACE):
|
||||
LOG.info("Creating rvmc namespace")
|
||||
kube.kube_create_namespace(RVMC_NAMESPACE)
|
||||
LOG.info("Copying default-registry-key secret to rvmc namespace")
|
||||
kube.kube_copy_secret(
|
||||
DEFAULT_REGISTRY_KEY, KUBE_SYSTEM_NAMESPACE, RVMC_NAMESPACE)
|
||||
except Exception as e:
|
||||
LOG.exception("Failed to copy default-registry-key secret")
|
||||
raise e
|
||||
|
||||
def create_install_override_file(self, override_path, payload):
|
||||
|
||||
LOG.debug("create install override file")
|
||||
self.check_image_exists(RVMC_IMAGE_NAME, RVMC_IMAGE_TAG)
|
||||
rvmc_image = LOCAL_REGISTRY_PREFIX + RVMC_IMAGE_NAME + ':' +\
|
||||
RVMC_IMAGE_TAG
|
||||
install_override_file = os.path.join(override_path,
|
||||
'install_values.yml')
|
||||
rvmc_name = "%s-%s" % (consts.RVMC_NAME_PREFIX, self.name)
|
||||
host_name = socket.gethostname()
|
||||
|
||||
with open(install_override_file, 'w') as f_out_override_file:
|
||||
f_out_override_file.write(
|
||||
'---'
|
||||
'\npassword_change: true'
|
||||
'\nrvmc_image: ' + rvmc_image +
|
||||
'\nrvmc_name: ' + rvmc_name +
|
||||
'\nhost_name: ' + host_name +
|
||||
'\nrvmc_config_dir: ' + override_path
|
||||
+ '\n'
|
||||
@ -687,9 +546,6 @@ class SubcloudInstall(object):
|
||||
# create the rvmc config file
|
||||
self.create_rvmc_config_file(override_path, payload)
|
||||
|
||||
# copy the default_registry_key secret to rvmc namespace
|
||||
self.copy_default_registry_key()
|
||||
|
||||
# remove the bmc values from the payload
|
||||
for k in consts.BMC_INSTALL_VALUES:
|
||||
if k in payload:
|
||||
|
@ -30,6 +30,7 @@ from dccommon import consts
|
||||
from dccommon import exceptions
|
||||
from dccommon.exceptions import PlaybookExecutionFailed
|
||||
from dccommon.exceptions import PlaybookExecutionTimeout
|
||||
from dccommon import rvmc
|
||||
from dccommon.subprocess_cleanup import kill_subprocess_group
|
||||
from dccommon.subprocess_cleanup import SubprocessCleanup
|
||||
from dcorch.common.i18n import _
|
||||
@ -356,3 +357,19 @@ def get_ssl_cert_ca_file():
|
||||
return os.path.join(
|
||||
consts.SSL_CERT_CA_DIR,
|
||||
consts.CERT_CA_FILE_DEBIAN if is_debian() else consts.CERT_CA_FILE_CENTOS)
|
||||
|
||||
|
||||
def send_subcloud_shutdown_signal(subcloud_name):
|
||||
"""Sends a shutdown signal to a Redfish controlled subcloud.
|
||||
|
||||
:param subcloud_name: the name of the subcloud to be shut down
|
||||
:type subcloud_name: str
|
||||
"""
|
||||
# All logs are expected to originate from the rvmc module,
|
||||
# so the log churn from the 'redfish.rest.v1' module is disabled.
|
||||
logging.getLogger('redfish.rest.v1').setLevel(logging.CRITICAL)
|
||||
|
||||
rvmc_config_file = os.path.join(consts.ANSIBLE_OVERRIDES_PATH,
|
||||
subcloud_name,
|
||||
consts.RVMC_CONFIG_FILE_NAME)
|
||||
rvmc.power_off(subcloud_name, rvmc_config_file, LOG)
|
||||
|
@ -266,6 +266,10 @@ ERR_MSG_DICT = {
|
||||
|
||||
"ping_bmc": "Check reachability to the BMC: ping <<BMC_URL>>",
|
||||
|
||||
"rvmc_process": "Ensure the previous RVMC process is terminated.",
|
||||
|
||||
"rvmc_timeout": "Please check the dcmanager ansible log for details.",
|
||||
|
||||
"dm_pod_failed": """- Ensure you are using the correct tarball that \
|
||||
corresponds to the image.
|
||||
- Check helm overrides files, ensure the deployment manager images exist in \
|
||||
|
@ -638,6 +638,16 @@ def validate_install_values(payload, subcloud=None):
|
||||
LOG.exception(e)
|
||||
pecan.abort(400, _("rd.net.timeout.ipv6dad invalid: %s") % e)
|
||||
|
||||
if 'rvmc_debug_level' in install_values:
|
||||
try:
|
||||
rvmc_debug_level = int(install_values['rvmc_debug_level'])
|
||||
if rvmc_debug_level < 0 or rvmc_debug_level > 4:
|
||||
pecan.abort(400, _("rvmc_debug_level must be an integer "
|
||||
"between 0 and 4."))
|
||||
except ValueError as e:
|
||||
LOG.exception(e)
|
||||
pecan.abort(400, _("Invalid value of rvmc_debug_level: %s") % e)
|
||||
|
||||
|
||||
def validate_k8s_version(payload):
|
||||
"""Validate k8s version.
|
||||
|
@ -46,9 +46,9 @@ from dccommon.exceptions import PlaybookExecutionFailed
|
||||
from dccommon.exceptions import SubcloudNotFound
|
||||
from dccommon import kubeoperator
|
||||
from dccommon.subcloud_install import SubcloudInstall
|
||||
from dccommon.subcloud_install import SubcloudShutdown
|
||||
from dccommon.utils import AnsiblePlaybook
|
||||
from dccommon.utils import LAST_SW_VERSION_IN_CENTOS
|
||||
from dccommon.utils import send_subcloud_shutdown_signal
|
||||
from dcmanager.audit import rpcapi as dcmanager_audit_rpc_client
|
||||
from dcmanager.common import consts
|
||||
from dcmanager.common.consts import INVENTORY_FILE_POSTFIX
|
||||
@ -291,7 +291,11 @@ class SubcloudManager(manager.Manager):
|
||||
"-e", "@%s" % dccommon_consts.ANSIBLE_OVERRIDES_PATH + "/" +
|
||||
subcloud_name + '/' + "install_values.yml",
|
||||
"-e", "install_release_version=%s" %
|
||||
software_version if software_version else SW_VERSION]
|
||||
software_version if software_version else SW_VERSION,
|
||||
"-e", "rvmc_config_file=%s" %
|
||||
os.path.join(dccommon_consts.ANSIBLE_OVERRIDES_PATH,
|
||||
subcloud_name,
|
||||
dccommon_consts.RVMC_CONFIG_FILE_NAME)]
|
||||
return install_command
|
||||
|
||||
def compose_bootstrap_command(self, subcloud_name,
|
||||
@ -1132,24 +1136,11 @@ class SubcloudManager(manager.Manager):
|
||||
return
|
||||
|
||||
if subcloud.deploy_status == consts.DEPLOY_STATE_ABORTING_INSTALL:
|
||||
# First delete the k8s job and pod, stopping the current
|
||||
# installation attempt if exists
|
||||
# Then send shutdown signal to subcloud
|
||||
kube = kubeoperator.KubeOperator()
|
||||
shutdown_subcloud = SubcloudShutdown(subcloud.name)
|
||||
namespace = dccommon_consts.RVMC_NAME_PREFIX
|
||||
jobname = '%s-%s' % (namespace, subcloud.name)
|
||||
pod_basename = '%s-' % jobname
|
||||
all_pods = kube.get_pods_by_namespace(namespace)
|
||||
desired_pod = next((s for s in all_pods if pod_basename in s), None)
|
||||
if desired_pod:
|
||||
kube.kube_delete_job(jobname, namespace)
|
||||
kube.kube_delete_pod(desired_pod, namespace)
|
||||
while kube.pod_exists(desired_pod, namespace):
|
||||
time.sleep(2)
|
||||
shutdown_subcloud.send_shutdown_signal()
|
||||
# Send shutdown signal to subcloud
|
||||
send_subcloud_shutdown_signal(subcloud.name)
|
||||
except Exception as ex:
|
||||
LOG.error("Subcloud deploy abort failed for subcloud %s" % subcloud.name)
|
||||
LOG.error("Subcloud deploy abort failed for subcloud %s: %s" %
|
||||
(subcloud.name, str(ex)))
|
||||
utils.update_abort_status(context, subcloud.id, subcloud.deploy_status,
|
||||
abort_failed=True)
|
||||
# exception is logged above
|
||||
|
@ -356,12 +356,17 @@ class UpgradingSimplexState(BaseState):
|
||||
utils.create_subcloud_inventory(install_values,
|
||||
ansible_subcloud_inventory_file)
|
||||
|
||||
rvmc_config_file = os.path.join(dccommon_consts.ANSIBLE_OVERRIDES_PATH,
|
||||
strategy_step.subcloud.name,
|
||||
dccommon_consts.RVMC_CONFIG_FILE_NAME)
|
||||
|
||||
# SubcloudInstall.prep creates data_install.yml (install overrides)
|
||||
install_command = [
|
||||
"ansible-playbook", dccommon_consts.ANSIBLE_SUBCLOUD_INSTALL_PLAYBOOK,
|
||||
"-i", ansible_subcloud_inventory_file,
|
||||
"-e", "@%s" % dccommon_consts.ANSIBLE_OVERRIDES_PATH + "/" +
|
||||
strategy_step.subcloud.name + '/' + "install_values.yml"
|
||||
strategy_step.subcloud.name + '/' + "install_values.yml",
|
||||
"-e", "rvmc_config_file=%s" % rvmc_config_file
|
||||
]
|
||||
|
||||
# Run the remote install playbook
|
||||
|
@ -1762,7 +1762,11 @@ class TestSubcloudManager(base.DCManagerTestCase):
|
||||
'-i', f'{dccommon_consts.ANSIBLE_OVERRIDES_PATH}/subcloud1_inventory.yml',
|
||||
'--limit', 'subcloud1',
|
||||
'-e', f"@{dccommon_consts.ANSIBLE_OVERRIDES_PATH}/subcloud1/install_values.yml",
|
||||
'-e', "install_release_version=%s" % FAKE_PREVIOUS_SW_VERSION
|
||||
'-e', "install_release_version=%s" % FAKE_PREVIOUS_SW_VERSION,
|
||||
'-e', "rvmc_config_file=%s" %
|
||||
os.path.join(dccommon_consts.ANSIBLE_OVERRIDES_PATH,
|
||||
'subcloud1',
|
||||
dccommon_consts.RVMC_CONFIG_FILE_NAME)
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -2,3 +2,4 @@ etc/logrotate.d/distcloud.conf
|
||||
etc/syslog-ng/conf.d/distcloud.conf
|
||||
usr/lib/python3/dist-packages/dccommon/*
|
||||
usr/lib/python3/dist-packages/distributedcloud-*.egg-info
|
||||
usr/local/bin/rvmc_install.py
|
@ -14,6 +14,9 @@ BIN_DIR = $(ROOT)/usr/bin
|
||||
%:
|
||||
dh $@ --with python3 --buildsystem=pybuild
|
||||
|
||||
override_dh_usrlocal:
|
||||
# skip running dh_usrlocal
|
||||
|
||||
override_dh_install:
|
||||
python3 setup.py install -f --install-layout=deb \
|
||||
--root=$(CURDIR)/debian/tmp
|
||||
@ -77,6 +80,10 @@ override_dh_install:
|
||||
--output-file ./dcdbsync/dcdbsync.conf.sample
|
||||
install -p -D -m 640 ./dcdbsync/dcdbsync.conf.sample $(SYS_CONF_DIR)/dcdbsync/dcdbsync.conf
|
||||
|
||||
# install rvmc_install.py script
|
||||
install -d $(ROOT)/usr/local/bin/
|
||||
install -p -D -m 700 scripts/rvmc_install.py $(ROOT)/usr/local/bin
|
||||
|
||||
rm -rf $(ROOT)/usr/lib/python3/dist-packages/dcmanagerclient/tests
|
||||
rm -rf $(ROOT)/usr/lib/python3/dist-packages/dccommon/tests
|
||||
rm -rf $(ROOT)/usr/lib/python3/dist-packages/dcmanager/tests
|
||||
|
@ -33,10 +33,11 @@ python-neutronclient>=6.3.0 # Apache-2.0
|
||||
python-cinderclient>=2.1.0 # Apache-2.0
|
||||
python-novaclient>=7.1.0 # Apache-2.0
|
||||
python-keystoneclient>=3.8.0 # Apache-2.0
|
||||
routes>=2.3.1 # MIT
|
||||
redfish # BSD
|
||||
requests!=2.12.2,!=2.13.0,>=2.10.0 # Apache-2.0
|
||||
requests_toolbelt # Apache-2.0
|
||||
retrying!=1.3.0,>=1.2.3 # Apache-2.0
|
||||
routes>=2.3.1 # MIT
|
||||
six>=1.9.0 # MIT
|
||||
sqlalchemy!=1.1.5,!=1.1.6,!=1.1.7,!=1.1.8,>=1.0.10 # MIT
|
||||
sqlalchemy-migrate>=0.11.0 # Apache-2.0
|
||||
|
311
distributedcloud/scripts/rvmc_install.py
Normal file
311
distributedcloud/scripts/rvmc_install.py
Normal file
@ -0,0 +1,311 @@
|
||||
#!/usr/bin/python3
|
||||
###############################################################################
|
||||
#
|
||||
# Copyright (c) 2019-2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Redfish Virtual Media Controller Installer Script"""
|
||||
#
|
||||
###############################################################################
|
||||
#
|
||||
# This Redfish Virtual Media Controller forces an install of a specified BMC's
|
||||
# host using the Redfish Platform management protocol.
|
||||
#
|
||||
# To do so the following Redfish operations are performed
|
||||
#
|
||||
# Step 1: Client Connect ... Establish a client connection to the BMC
|
||||
# Step 2: Root Query ... Learn Redfish Services offered by the BMC
|
||||
# Step 3: Find CD/DVD ... Locate the virtual media CD/DVD device
|
||||
# Step 4: Power Off Host ... Host power needs to be off
|
||||
# Step 5: Eject Iso ... Eject iso if needed
|
||||
# Step 6: Inject Iso ... Inject the URL based ISO image into CD/DVD
|
||||
# Step 7: Force DVD Boot ... Set Net boot device to be CD/DVD
|
||||
# Step 8: Power On Host ... Host will boot and install from DVD
|
||||
#
|
||||
# Note: All server starting state conditions such as the server running or
|
||||
# being stuck in POST, say at the grub prompt due to previous host boot
|
||||
# failure, the host needs to be in the powered off state for the ISO
|
||||
# Insertion and Set DVD for Next Boot steps.
|
||||
#
|
||||
# Mandatory Arguments:
|
||||
#
|
||||
# --config_file <A yaml file with BMC information>
|
||||
#
|
||||
# Config file is assumed to be a yaml file with the following format.
|
||||
#
|
||||
# bmc_address: <bmc ip address>
|
||||
# bmc_username: <the bmc username>
|
||||
# bmc_password: <base64 encoded password>
|
||||
# image: http://<ip>:<port>/<path>/bootimage.iso
|
||||
#
|
||||
# Real Example:
|
||||
#
|
||||
# bmc_address: 123.45.67.89
|
||||
# bmc_username: root
|
||||
# bmc_password: TGk2OW51eCo=
|
||||
# image: http://[2620:10a:a001:a103::81]:8080/iso/sub_cloud_bootimage.iso
|
||||
#
|
||||
# > rvmc_install.py --config_file
|
||||
# "/opt/dc-vault/ansible/subcloud1/rvmc-config.yaml"
|
||||
#
|
||||
# --subcloud_name <subcloud name>
|
||||
#
|
||||
# The subcloud name is used as the bmc target name, and used for tracking
|
||||
# the RVMC installation process ID. The process ID file is generated under
|
||||
# '/var/run/rvmc', eg. '/var/run/rvmc/subcloud1_rvmc.pid'.
|
||||
#
|
||||
# > rvmc_install.py --subcloud_name subcloud1 --config_file <config_file>
|
||||
#
|
||||
# Optional Argument:
|
||||
#
|
||||
# --debug <0 .. 4>
|
||||
#
|
||||
# Note: 0 no debug info (default value)
|
||||
# 1 = execution stage
|
||||
# 2 + http request logs
|
||||
# 3 + headers and payloads and misc other
|
||||
# 4 + json output of all command responses
|
||||
#
|
||||
# > rvmc_install.py --debug 4 --subcloud_name <subcloud name>
|
||||
# --config_file <config_file>
|
||||
###############################################################################
|
||||
#
|
||||
# Code structure: Note: any error causes error log, session close and exit.
|
||||
#
|
||||
# parse command line arguments
|
||||
#
|
||||
# create target control object
|
||||
#
|
||||
# execute:
|
||||
# _redfish_client_connect ... connect to bmc
|
||||
# _redfish_root_query ... get base url tree
|
||||
# _redfish_create_session ... authenticated session
|
||||
# _redfish_get_managers ... get managers urls
|
||||
# _redfish_get_systems_members .. get systems members info
|
||||
# _redfish_get_vm_url ... get cd/dvd vm url
|
||||
# _redfish_load_vm_actions ... get eject/insert action urls/info
|
||||
# _redfish_poweroff_host ... tell bmc to power-off the host
|
||||
# _redfish_eject_image ... eject current media if present
|
||||
# _redfish_insert_image ... insert and verify insertion of iso
|
||||
# _redfish_set_boot_override ... set boot from cd/dvd on next reset
|
||||
# _redfish_poweron_host ... tell bmc to power-on the host
|
||||
#
|
||||
# exit code:
|
||||
# 0 - Success
|
||||
# 1 - Retryable failures:
|
||||
# - Config file not found/opened
|
||||
# - BNC IP address ping failure
|
||||
# - Redfish GET query failures (managers, system members, URLs, status,
|
||||
# virtual media group, etc.)
|
||||
# - Power command not found
|
||||
# - Host power on/off failure
|
||||
# - Virtual media not supported by BMC
|
||||
# - CD/DVD virtual media type not found
|
||||
# - Eject target not found
|
||||
# - VM state query failure
|
||||
# - Image insertion/ejection timeout
|
||||
# - Media insertion failure
|
||||
# - Image verification failure
|
||||
# - Boot override set/verification failure
|
||||
# - ...
|
||||
# 2 - Non-retryable failures:
|
||||
# - Invalid credentials
|
||||
# - Script execution time out
|
||||
# - Failed to terminate the previous process
|
||||
###############################################################################
|
||||
|
||||
import argparse
|
||||
import eventlet
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
|
||||
from dccommon import rvmc
|
||||
|
||||
|
||||
# Constants
|
||||
# ---------
|
||||
FEATURE_NAME = 'Redfish Virtual Media Controller'
|
||||
VERSION_MAJOR = 3
|
||||
VERSION_MINOR = 1
|
||||
|
||||
# The path for RVMC PID file
|
||||
RVMC_PID_FILE_PATH = '/var/run/rvmc/'
|
||||
RVMC_PID_FILENAME_POSTFIX = '_rvmc.pid'
|
||||
|
||||
# The signals to be caught for abnormal termination
|
||||
EXIT_SIGNALS = [signal.SIGTERM, signal.SIGABRT, signal.SIGINT]
|
||||
|
||||
# Global variables
|
||||
# ------------------
|
||||
# The logging utility
|
||||
logging_util = None
|
||||
# The exit handler
|
||||
exit_handler = None
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
"""Parse command line arguments.
|
||||
|
||||
:returns argparse.Namespace: the arguments with name and value
|
||||
"""
|
||||
parser = argparse.ArgumentParser(description=FEATURE_NAME)
|
||||
|
||||
parser.add_argument("--debug", type=int, required=False, default=0,
|
||||
help="Optional debug level ; 0..4")
|
||||
|
||||
parser.add_argument("--subcloud_name", type=str, required=False,
|
||||
help="Subcloud name")
|
||||
|
||||
parser.add_argument("--config_file", type=str, required=True,
|
||||
help="RVMC config file")
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def prepare_execution(rvmc_pid_file):
|
||||
"""Terminate the previous RMVC process if it's still running.
|
||||
|
||||
:param rvmc_pid_file: The RVMC PID file
|
||||
:param type: str
|
||||
"""
|
||||
if not rvmc_pid_file:
|
||||
return
|
||||
|
||||
if not os.path.exists(RVMC_PID_FILE_PATH):
|
||||
os.makedirs(RVMC_PID_FILE_PATH)
|
||||
|
||||
# Check if the PID file exists.
|
||||
# Usually, it exists only when the parent process was manually killed.
|
||||
if os.path.exists(rvmc_pid_file):
|
||||
with open(rvmc_pid_file, 'r') as pid_file:
|
||||
pid = pid_file.read()
|
||||
# Attempt to kill the previous RVMC process using SIGTERM (15)
|
||||
if pid:
|
||||
try:
|
||||
os.kill(int(pid), 15)
|
||||
except ProcessLookupError:
|
||||
# Ignore the error if the process with this PID doesn't exit
|
||||
logging_util.ilog(
|
||||
"Process %s not found or already terminated." % pid)
|
||||
except Exception:
|
||||
logging_util.elog(
|
||||
"Failed to terminate the previous process %s," % pid)
|
||||
logging_util.alog(
|
||||
"Please terminate the previous process %s "
|
||||
"before running the RVMC script again." % pid)
|
||||
exit_handler.exit(2)
|
||||
# Give some time between reading and writing to the same PID file
|
||||
time.sleep(3)
|
||||
|
||||
# Get the current process ID
|
||||
current_pid = os.getpid()
|
||||
|
||||
# Write the PID to the file
|
||||
logging_util.dlog1("Save process ID %d to the file %s." %
|
||||
(current_pid, rvmc_pid_file))
|
||||
with open(rvmc_pid_file, 'w') as pid_file:
|
||||
pid_file.write(str(current_pid))
|
||||
|
||||
|
||||
def signal_handler():
|
||||
"""This function handles signals received by the script"""
|
||||
logging_util.elog("Received exit signal.")
|
||||
exit_handler.exit(1)
|
||||
|
||||
|
||||
class ExitHandler(rvmc.ExitHandler):
|
||||
"""A utility class for handling different exit scenarios in a process.
|
||||
|
||||
Provides methods to manage the process exit in various situations.
|
||||
"""
|
||||
def __init__(self, rvmc_pid_file):
|
||||
"""Handler object constructor.
|
||||
|
||||
:param rvmc_pid_file: the RVMC PID file.
|
||||
:type rvmc_pid_file: str.
|
||||
"""
|
||||
self.rvmc_pid_file = rvmc_pid_file
|
||||
|
||||
def exit(self, code):
|
||||
"""Early fault handling.
|
||||
|
||||
:param code: the exit status code
|
||||
:type code: int.
|
||||
"""
|
||||
|
||||
if self.rvmc_pid_file and os.path.exists(self.rvmc_pid_file):
|
||||
os.remove(self.rvmc_pid_file)
|
||||
sys.stdout.write("\n\n")
|
||||
sys.exit(code)
|
||||
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Main steps:
|
||||
# 1. Parse script arguments.
|
||||
# 2. Register the signal handler.
|
||||
# 3. Load BMC target info from config file.
|
||||
# 4. Insert BMC iso for the target through self.execute
|
||||
#
|
||||
##############################################################################
|
||||
if __name__ == "__main__":
|
||||
args = parse_arguments()
|
||||
|
||||
# get debug level
|
||||
debug = args.debug
|
||||
|
||||
# get subcloud name
|
||||
subcloud_name = args.subcloud_name
|
||||
|
||||
# get config file
|
||||
config_file = args.config_file
|
||||
|
||||
# RVMC PID file
|
||||
rvmc_pid_file = os.path.join(
|
||||
RVMC_PID_FILE_PATH, subcloud_name + RVMC_PID_FILENAME_POSTFIX)
|
||||
|
||||
# Set logging utility and exit handler
|
||||
logging_util = rvmc.LoggingUtil(debug_level=debug)
|
||||
exit_handler = ExitHandler(rvmc_pid_file)
|
||||
|
||||
logging_util.ilog("%s version %d.%d\n" %
|
||||
(FEATURE_NAME, VERSION_MAJOR, VERSION_MINOR))
|
||||
|
||||
# Register the signal handler
|
||||
for sig in EXIT_SIGNALS:
|
||||
signal.signal(sig, signal_handler)
|
||||
|
||||
config, target_object = rvmc.parse_config_file(
|
||||
subcloud_name, config_file, logging_util, exit_handler)
|
||||
|
||||
if target_object:
|
||||
prepare_execution(rvmc_pid_file)
|
||||
# TODO(lzhu1): support --timeout <value> option
|
||||
script_timeout = eventlet.timeout.Timeout(
|
||||
int(os.environ.get('RVMC_SCRIPT_TIMEOUT', 1800)))
|
||||
try:
|
||||
# Load the Iso for the target
|
||||
logging_util.ilog("BMC Target : %s" % target_object.target)
|
||||
logging_util.ilog("BMC IP Addr : %s" % target_object.ip)
|
||||
logging_util.ilog("Host Image : %s" % target_object.img)
|
||||
target_object.execute()
|
||||
except eventlet.timeout.Timeout as e:
|
||||
if e is not script_timeout:
|
||||
raise
|
||||
logging_util.elog("RVMC script execution timed out.")
|
||||
exit_handler.exit(2)
|
||||
except Exception as e:
|
||||
logging_util.elog("Got exception: %s" % e)
|
||||
exit_handler.exit(1)
|
||||
finally:
|
||||
script_timeout.cancel()
|
||||
else:
|
||||
logging_util.elog("Operation aborted ; no valid bmc information found")
|
||||
if config_file and config:
|
||||
logging_util.ilog("Config File :\n%s" % config)
|
||||
exit_handler.exit(1)
|
||||
|
||||
exit_handler.exit(0)
|
@ -7,6 +7,7 @@ coverage!=4.4,>=4.0 # Apache-2.0
|
||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||
mock>=2.0 # BSD
|
||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||
redfish # BSD
|
||||
requests-mock>=1.1 # Apache-2.0
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
testtools>=1.4.0 # MIT
|
||||
|
5
python/python3-redfish/debian/deb_folder/changelog
Normal file
5
python/python3-redfish/debian/deb_folder/changelog
Normal file
@ -0,0 +1,5 @@
|
||||
redfish (3.2.1) unstable; urgency=medium
|
||||
|
||||
* Initial release.
|
||||
|
||||
-- Li Zhu <li.zhu@windriver.com> Fri, 20 Oct 2023 10:14:40 -0300
|
18
python/python3-redfish/debian/deb_folder/control
Normal file
18
python/python3-redfish/debian/deb_folder/control
Normal file
@ -0,0 +1,18 @@
|
||||
Source: redfish
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Maintainer: StarlingX Developers <starlingx-discuss@lists.starlingx.io>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
dh-python,
|
||||
flake8,
|
||||
python3-setuptools,
|
||||
python3-all,
|
||||
python3-jsonpath-rw,
|
||||
python3-requests-unixsocket
|
||||
Standards-Version: 4.5.1
|
||||
|
||||
Package: python3-redfish
|
||||
Architecture: all
|
||||
Depends: ${python3:Depends}, ${misc:Depends}
|
||||
Description: Python library for Redfish operations
|
||||
This package provides the Redfish Python library.
|
48
python/python3-redfish/debian/deb_folder/copyright
Normal file
48
python/python3-redfish/debian/deb_folder/copyright
Normal file
@ -0,0 +1,48 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: python3-redfish
|
||||
Source: https://opendev.org/starlingx/distcloud/python/python3-redfish
|
||||
|
||||
Files: *
|
||||
Copyright: Copyright 2016-2022 DMTF. All rights reserved.
|
||||
License: BSD-3-clause
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of Distributed Management Task Force (DMTF) nor the names
|
||||
of its contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Files: debian/*
|
||||
Copyright: (c) 2023 Wind River Systems, Inc
|
||||
License: Apache-2
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
.
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
.
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
.
|
||||
On Debian-based systems the full text of the Apache version 2.0 license
|
||||
can be found in `/usr/share/common-licenses/Apache-2.0'.
|
17
python/python3-redfish/debian/deb_folder/rules
Executable file
17
python/python3-redfish/debian/deb_folder/rules
Executable file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
export DH_VERBOSE=1
|
||||
export PYBUILD_NAME=python3-redfish
|
||||
|
||||
%:
|
||||
dh $@ --with python3 --buildsystem=pybuild
|
||||
|
||||
override_dh_auto_clean:
|
||||
python3 setup.py clean -a
|
||||
find . -name \*.pyc -exec rm {} \;
|
||||
|
||||
override_dh_auto_build:
|
||||
python3 setup.py build --force
|
||||
|
||||
override_dh_auto_install:
|
||||
python3 setup.py install --force --root=debian/python3-redfish --no-compile -O0 --install-layout=deb
|
14
python/python3-redfish/debian/meta_data.yaml
Normal file
14
python/python3-redfish/debian/meta_data.yaml
Normal file
@ -0,0 +1,14 @@
|
||||
---
|
||||
debname: redfish
|
||||
debver: 3.2.1
|
||||
dl_path:
|
||||
name: redfish-3.2.1.tar.gz
|
||||
url: >
|
||||
https://files.pythonhosted.org/packages/ea/04/
|
||||
0e08ec3ad8af6de811edc864c7f3c505e22af14a7da735c45c512dfe9e9c/
|
||||
redfish-3.2.1.tar.gz
|
||||
md5sum: 8203bb777f2559e65302e988eee9f1cc
|
||||
sha256sum: c10b11ff5a4e6cab0888b577c51facae689f4c6002b7ce34b1741707290aca78
|
||||
revision:
|
||||
dist: $STX_DIST
|
||||
PKG_GITREVCOUNT: true
|
11
tox.ini
11
tox.ini
@ -51,10 +51,19 @@ commands =
|
||||
rm -rf api-ref/build
|
||||
sphinx-build -W -b html -d api-ref/build/doctrees api-ref/source api-ref/build/html
|
||||
|
||||
[bandit]
|
||||
# The following bandit tests are being skipped:
|
||||
# B605: Test for starting a process with a shell
|
||||
#
|
||||
# Note: 'skips' entry cannot be split across multiple lines
|
||||
#
|
||||
skips = B605
|
||||
exclude = tests
|
||||
|
||||
[testenv:bandit]
|
||||
description = Bandit code scan for *.py files under config folder
|
||||
deps = bandit
|
||||
commands = bandit -r {toxinidir}/ -x '**/.tox/**,**/.eggs/**' -lll
|
||||
commands = bandit --ini tox.ini -r {toxinidir}/ -x '**/.tox/**,**/.eggs/**' -lll
|
||||
|
||||
[testenv:linters]
|
||||
allowlist_externals = bash
|
||||
|
Loading…
x
Reference in New Issue
Block a user