Add inactive param for import-load on cgts-client
This change added a parameter called 'inactive' to load-import workflow on cgts-client to allow import a previous release (ISO). Test Plan: PASS: (AIO-SX) failed to import the current version PASS: (AIO-SX) failed to import the current version with active param PASS: (AIO-SX) import the new version PASS: (AIO-SX) import new version with local param PASS: (AIO-SX) failed to import the previous release PASS: (AIO-SX) import the previous release with inactive param PASS: DC (--os-region-name SystemController) success to import currently version with active param PASS: DC (--os-region-name SystemController) failed to import currently version PASS: DC (--os-region-name SystemController) import new version PASS: DC (--os-region-name SystemController) import new version with local param PASS: DC (--os-region-name SystemController) import previous release with inactive param PASS: DC (--os-region-name SystemController) failed to import previous release PASS: DC (--os-region-name SystemController) extracted ISO files to the controller (/var/www/pages/feed/rel-version) Story: 2010611 Task: 47509 Depends-On: https://review.opendev.org/c/starlingx/config/+/875186 Signed-off-by: Guilherme Schons <guilherme.dossantosschons@windriver.com> Change-Id: I053b0f29ffb347e109143181c6f60988706e6d29
This commit is contained in:
parent
d85ef8a4f1
commit
587adfe010
@ -47,6 +47,22 @@ class FakeAPI(object):
|
||||
fixture = self._request(*args, **kwargs)
|
||||
return FakeResponse(fixture[0]), fixture[1]
|
||||
|
||||
def upload_request_with_multipart(self, *args, **kwargs):
|
||||
# TODO(gdossant): add 'data' parameter to _request method.
|
||||
# It will impact more than 40 tests and must be done in
|
||||
# a specific commit.
|
||||
|
||||
kwargs.pop('check_exceptions')
|
||||
data = kwargs.pop('data')
|
||||
|
||||
fixture = self._request(*args, **kwargs)
|
||||
|
||||
call = list(self.calls[0])
|
||||
call.append(data)
|
||||
self.calls[0] = tuple(call)
|
||||
|
||||
return fixture[1]
|
||||
|
||||
|
||||
class FakeResponse(object):
|
||||
def __init__(self, headers, body=None, version=None):
|
||||
|
132
sysinv/cgts-client/cgts-client/cgtsclient/tests/v1/test_load.py
Normal file
132
sysinv/cgts-client/cgts-client/cgtsclient/tests/v1/test_load.py
Normal file
@ -0,0 +1,132 @@
|
||||
#
|
||||
# Copyright (c) 2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
import testtools
|
||||
|
||||
from cgtsclient.exc import InvalidAttribute
|
||||
from cgtsclient.tests import utils
|
||||
from cgtsclient.v1.load import Load
|
||||
from cgtsclient.v1.load import LoadManager
|
||||
|
||||
|
||||
class LoadManagerTest(testtools.TestCase):
|
||||
def setUp(self):
|
||||
super(LoadManagerTest, self).setUp()
|
||||
|
||||
self.load = {
|
||||
'id': '1',
|
||||
'uuid': 'c0d71e4c-f327-45a7-8349-11821a9d44df',
|
||||
'state': 'IMPORTED',
|
||||
'software_version': '6.0',
|
||||
'compatible_version': '6.0',
|
||||
'required_patches': '',
|
||||
}
|
||||
fixtures = {
|
||||
'/v1/loads/import_load':
|
||||
{
|
||||
'POST': (
|
||||
{},
|
||||
self.load,
|
||||
),
|
||||
},
|
||||
}
|
||||
self.api = utils.FakeAPI(fixtures)
|
||||
self.mgr = LoadManager(self.api)
|
||||
|
||||
|
||||
class LoadImportTest(LoadManagerTest):
|
||||
def setUp(self):
|
||||
super(LoadImportTest, self).setUp()
|
||||
|
||||
self.load_patch = {
|
||||
'path_to_iso': '/home/bootimage.iso',
|
||||
'path_to_sig': '/home/bootimage.sig',
|
||||
'inactive': False,
|
||||
'active': False,
|
||||
'local': False,
|
||||
}
|
||||
self.load_patch_request_body = {
|
||||
'path_to_iso': '/home/bootimage.iso',
|
||||
'path_to_sig': '/home/bootimage.sig',
|
||||
}
|
||||
|
||||
def test_load_import(self):
|
||||
expected = [
|
||||
(
|
||||
'POST', '/v1/loads/import_load',
|
||||
{},
|
||||
self.load_patch_request_body,
|
||||
{'active': 'false', 'inactive': 'false'},
|
||||
)
|
||||
]
|
||||
|
||||
load = self.mgr.import_load(**self.load_patch)
|
||||
|
||||
self.assertEqual(self.api.calls, expected)
|
||||
self.assertIsInstance(load, Load)
|
||||
|
||||
def test_load_import_active(self):
|
||||
self.load_patch['active'] = True
|
||||
|
||||
expected = [
|
||||
(
|
||||
'POST', '/v1/loads/import_load',
|
||||
{},
|
||||
self.load_patch_request_body,
|
||||
{'active': 'true', 'inactive': 'false'},
|
||||
)
|
||||
]
|
||||
|
||||
load = self.mgr.import_load(**self.load_patch)
|
||||
|
||||
self.assertEqual(self.api.calls, expected)
|
||||
self.assertIsInstance(load, Load)
|
||||
|
||||
def test_load_import_local(self):
|
||||
self.load_patch['local'] = True
|
||||
self.load_patch_request_body['active'] = 'false'
|
||||
self.load_patch_request_body['inactive'] = 'false'
|
||||
|
||||
expected = [
|
||||
(
|
||||
'POST', '/v1/loads/import_load',
|
||||
{},
|
||||
self.load_patch_request_body,
|
||||
)
|
||||
]
|
||||
|
||||
load = self.mgr.import_load(**self.load_patch)
|
||||
|
||||
self.assertEqual(self.api.calls, expected)
|
||||
self.assertIsInstance(load, Load)
|
||||
|
||||
def test_load_import_inactive(self):
|
||||
self.load_patch['inactive'] = True
|
||||
|
||||
expected = [
|
||||
(
|
||||
'POST', '/v1/loads/import_load',
|
||||
{},
|
||||
self.load_patch_request_body,
|
||||
{'active': 'false', 'inactive': 'true'}
|
||||
)
|
||||
]
|
||||
|
||||
load = self.mgr.import_load(**self.load_patch)
|
||||
|
||||
self.assertEqual(self.api.calls, expected)
|
||||
self.assertIsInstance(load, Load)
|
||||
|
||||
def test_load_import_invalid_attribute(self):
|
||||
self.load_patch['foo'] = 'bar'
|
||||
|
||||
self.assertRaises(
|
||||
InvalidAttribute,
|
||||
self.mgr.import_load,
|
||||
**self.load_patch
|
||||
)
|
||||
|
||||
self.assertEqual(self.api.calls, [])
|
@ -0,0 +1,215 @@
|
||||
#
|
||||
# Copyright (c) 2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
from mock import patch
|
||||
|
||||
from cgtsclient.exc import CommandError
|
||||
from cgtsclient.tests import test_shell
|
||||
from cgtsclient.v1.load import Load
|
||||
|
||||
|
||||
class LoadImportShellTest(test_shell.ShellTest):
|
||||
def setUp(self):
|
||||
super(LoadImportShellTest, self).setUp()
|
||||
|
||||
load_import = patch('cgtsclient.v1.load.LoadManager.import_load')
|
||||
self.mock_load_import = load_import.start()
|
||||
self.addCleanup(load_import.stop)
|
||||
|
||||
load_show = patch('cgtsclient.v1.load_shell._print_load_show')
|
||||
self.mock_load_show = load_show.start()
|
||||
self.addCleanup(load_show.stop)
|
||||
|
||||
load_list = patch('cgtsclient.v1.load.LoadManager.list')
|
||||
self.mock_load_list = load_list.start()
|
||||
self.addCleanup(load_list.stop)
|
||||
|
||||
load_resource = {
|
||||
'software_version': '6.0',
|
||||
'compatible_version': '5.0',
|
||||
'required_patches': '',
|
||||
}
|
||||
self.load_resouce = Load(
|
||||
manager=None,
|
||||
info=load_resource,
|
||||
loaded=True,
|
||||
)
|
||||
|
||||
self.mock_load_import.return_value = self.load_resouce
|
||||
self.mock_load_list.return_value = []
|
||||
self.mock_load_show.return_value = {}
|
||||
|
||||
self.patch_expected = {
|
||||
'path_to_iso': '/home/bootimage.iso',
|
||||
'path_to_sig': '/home/bootimage.sig',
|
||||
'active': False,
|
||||
'local': False,
|
||||
'inactive': False,
|
||||
}
|
||||
|
||||
@patch('os.path.isfile', lambda x: True)
|
||||
def test_load_import(self):
|
||||
self.make_env()
|
||||
|
||||
cmd = 'load-import /home/bootimage.iso /home/bootimage.sig'
|
||||
self.shell(cmd)
|
||||
|
||||
self.mock_load_import.assert_called_once()
|
||||
self.mock_load_list.assert_called_once()
|
||||
self.mock_load_show.assert_called_once()
|
||||
|
||||
self.mock_load_import.assert_called_with(**self.patch_expected)
|
||||
|
||||
@patch('os.path.abspath')
|
||||
@patch('os.path.isfile', lambda x: True)
|
||||
def test_load_import_relative_path(self, mock_abspath):
|
||||
self.make_env()
|
||||
|
||||
mock_abspath.side_effect = [
|
||||
'/home/bootimage.iso',
|
||||
'/home/bootimage.sig',
|
||||
]
|
||||
|
||||
cmd = 'load-import bootimage.iso bootimage.sig'
|
||||
self.shell(cmd)
|
||||
|
||||
self.mock_load_import.assert_called_once()
|
||||
self.mock_load_list.assert_called_once()
|
||||
self.mock_load_show.assert_called_once()
|
||||
|
||||
self.mock_load_import.assert_called_with(**self.patch_expected)
|
||||
|
||||
@patch('os.path.isfile', lambda x: True)
|
||||
def test_load_import_active(self):
|
||||
self.make_env()
|
||||
|
||||
self.patch_expected['active'] = True
|
||||
|
||||
cmd = '''
|
||||
load-import --active
|
||||
/home/bootimage.iso
|
||||
/home/bootimage.sig
|
||||
'''
|
||||
self.shell(cmd)
|
||||
|
||||
self.mock_load_import.assert_called_once()
|
||||
self.mock_load_show.assert_called_once()
|
||||
|
||||
self.mock_load_import.assert_called_with(**self.patch_expected)
|
||||
|
||||
self.mock_load_list.assert_not_called()
|
||||
|
||||
@patch('os.path.isfile', lambda x: True)
|
||||
def test_load_import_active_short_form(self):
|
||||
self.make_env()
|
||||
|
||||
self.patch_expected['active'] = True
|
||||
|
||||
cmd = '''
|
||||
load-import -a
|
||||
/home/bootimage.iso
|
||||
/home/bootimage.sig
|
||||
'''
|
||||
self.shell(cmd)
|
||||
|
||||
self.mock_load_import.assert_called_once()
|
||||
self.mock_load_show.assert_called_once()
|
||||
|
||||
self.mock_load_import.assert_called_with(**self.patch_expected)
|
||||
|
||||
self.mock_load_list.assert_not_called()
|
||||
|
||||
@patch('os.path.isfile', lambda x: True)
|
||||
def test_load_import_local(self):
|
||||
self.make_env()
|
||||
|
||||
self.patch_expected['local'] = True
|
||||
|
||||
cmd = '''
|
||||
load-import --local
|
||||
/home/bootimage.iso
|
||||
/home/bootimage.sig
|
||||
'''
|
||||
self.shell(cmd)
|
||||
|
||||
self.mock_load_import.assert_called_once()
|
||||
self.mock_load_list.assert_called_once()
|
||||
self.mock_load_show.assert_called_once()
|
||||
|
||||
self.mock_load_import.assert_called_with(**self.patch_expected)
|
||||
|
||||
@patch('os.path.isfile', lambda x: True)
|
||||
def test_load_import_inactive(self):
|
||||
self.make_env()
|
||||
|
||||
self.patch_expected['inactive'] = True
|
||||
|
||||
cmd = '''
|
||||
load-import --inactive
|
||||
/home/bootimage.iso
|
||||
/home/bootimage.sig
|
||||
'''
|
||||
self.shell(cmd)
|
||||
|
||||
self.mock_load_import.assert_called_once()
|
||||
self.mock_load_show.assert_called_once()
|
||||
self.mock_load_list.assert_not_called()
|
||||
|
||||
self.mock_load_import.assert_called_with(**self.patch_expected)
|
||||
|
||||
@patch('os.path.isfile', lambda x: True)
|
||||
def test_load_import_inactive_short_form(self):
|
||||
self.make_env()
|
||||
|
||||
self.patch_expected['inactive'] = True
|
||||
|
||||
cmd = '''
|
||||
load-import -i
|
||||
/home/bootimage.iso
|
||||
/home/bootimage.sig
|
||||
'''
|
||||
self.shell(cmd)
|
||||
|
||||
self.mock_load_import.assert_called_once()
|
||||
self.mock_load_show.assert_called_once()
|
||||
self.mock_load_list.assert_not_called()
|
||||
|
||||
self.mock_load_import.assert_called_with(**self.patch_expected)
|
||||
|
||||
@patch('os.path.isfile', lambda x: True)
|
||||
def test_load_import_max_imported(self):
|
||||
self.make_env()
|
||||
|
||||
self.mock_load_list.return_value = [
|
||||
{
|
||||
'id': 1,
|
||||
'state': 'ACTIVE',
|
||||
'software_version': '5',
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'state': 'IMPORTED',
|
||||
'software_version': '6',
|
||||
},
|
||||
]
|
||||
|
||||
cmd = 'load-import bootimage.iso bootimage.sig'
|
||||
self.assertRaises(CommandError, self.shell, cmd)
|
||||
|
||||
self.mock_load_list.assert_called_once()
|
||||
|
||||
self.mock_load_import.assert_not_called()
|
||||
self.mock_load_show.assert_not_called()
|
||||
|
||||
def test_load_import_invalid_path(self):
|
||||
self.make_env()
|
||||
|
||||
cmd = 'load-import bootimage.iso bootimage.sig'
|
||||
self.assertRaises(CommandError, self.shell, cmd)
|
||||
|
||||
self.mock_load_import.assert_not_called()
|
||||
self.mock_load_list.assert_not_called()
|
||||
self.mock_load_show.assert_not_called()
|
@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2015-2022 Wind River Systems, Inc.
|
||||
# Copyright (c) 2015-2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -11,7 +11,8 @@ from cgtsclient import exc
|
||||
CREATION_ATTRIBUTES = ['software_version', 'compatible_version',
|
||||
'required_patches']
|
||||
|
||||
IMPORT_ATTRIBUTES = ['path_to_iso', 'path_to_sig', 'active', 'local']
|
||||
IMPORT_ATTRIBUTES = ['path_to_iso', 'path_to_sig', 'active', 'local',
|
||||
'inactive']
|
||||
|
||||
|
||||
class Load(base.Resource):
|
||||
@ -48,27 +49,33 @@ class LoadManager(base.Manager):
|
||||
|
||||
def import_load(self, **kwargs):
|
||||
path = '/v1/loads/import_load'
|
||||
|
||||
active = None
|
||||
local = False
|
||||
local = kwargs.pop('local')
|
||||
load_info = {}
|
||||
for (key, value) in kwargs.items():
|
||||
|
||||
for key, value in kwargs.items():
|
||||
if key in IMPORT_ATTRIBUTES:
|
||||
if key == 'active':
|
||||
active = value
|
||||
elif key == 'local':
|
||||
local = value
|
||||
if isinstance(value, bool):
|
||||
load_info[key] = str(value).lower()
|
||||
else:
|
||||
load_info[key] = value
|
||||
else:
|
||||
raise exc.InvalidAttribute(key)
|
||||
|
||||
if local is True:
|
||||
load_info['active'] = active
|
||||
if local:
|
||||
return self._create(path, body=load_info)
|
||||
|
||||
data = {
|
||||
'active': load_info.pop('active', 'false'),
|
||||
'inactive': load_info.pop('inactive', 'false'),
|
||||
}
|
||||
|
||||
json_data = self._upload_multipart(
|
||||
path, body=load_info, data={'active': active}, check_exceptions=True)
|
||||
path,
|
||||
body=load_info,
|
||||
data=data,
|
||||
check_exceptions=True,
|
||||
)
|
||||
|
||||
return self.resource_class(self, json_data)
|
||||
|
||||
def delete(self, load_id):
|
||||
|
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright (c) 2015-2022 Wind River Systems, Inc.
|
||||
# Copyright (c) 2015-2023 Wind River Systems, Inc.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
@ -73,6 +73,11 @@ def do_load_delete(cc, args):
|
||||
help=("Perform an active load import operation. "
|
||||
"Applicable only for SystemController to allow import of "
|
||||
"an active load for subcloud install"))
|
||||
@utils.arg('-i', '--inactive',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=("Perform an inactive load import operation. "
|
||||
"Import a previous release load for subcloud install"))
|
||||
@utils.arg('--local',
|
||||
action='store_true',
|
||||
default=False,
|
||||
@ -84,6 +89,8 @@ def do_load_import(cc, args):
|
||||
"""Import a load."""
|
||||
|
||||
local = args.local
|
||||
active = args.active
|
||||
inactive = args.inactive
|
||||
|
||||
# If absolute path is not specified, we assume it is the relative path.
|
||||
# args.isopath will then be set to the absolute path
|
||||
@ -99,10 +106,7 @@ def do_load_import(cc, args):
|
||||
if not os.path.isfile(args.sigpath):
|
||||
raise exc.CommandError(_("File %s does not exist." % args.sigpath))
|
||||
|
||||
active = None
|
||||
if args.active is True:
|
||||
active = 'true'
|
||||
else:
|
||||
if not active and not inactive:
|
||||
# The following logic is taken from sysinv api as it takes a while for
|
||||
# this large POST request to reach the server.
|
||||
#
|
||||
@ -114,8 +118,13 @@ def do_load_import(cc, args):
|
||||
"Max number of loads (2) reached. Please remove the "
|
||||
"old or unused load before importing a new one."))
|
||||
|
||||
patch = {'path_to_iso': args.isopath, 'path_to_sig': args.sigpath,
|
||||
'active': active, 'local': local}
|
||||
patch = {
|
||||
'path_to_iso': args.isopath,
|
||||
'path_to_sig': args.sigpath,
|
||||
'inactive': inactive,
|
||||
'active': active,
|
||||
'local': local,
|
||||
}
|
||||
|
||||
try:
|
||||
print("This operation will take a while. Please wait.")
|
||||
|
Loading…
Reference in New Issue
Block a user