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:
Guilherme Schons 2023-02-24 12:26:46 -03:00 committed by Al Bailey
parent d85ef8a4f1
commit 587adfe010
5 changed files with 399 additions and 20 deletions

View File

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

View 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, [])

View File

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

View File

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

View File

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