Fix migration scripts execution sequence

The following changes have been introduced as a fix for this issue:

1. Changed the sorting on the migration script file names to be based
on the first number on the file name.
2. Added file name format validation: "nnn-*.*", where "nnn" string
shall contain only digits.
3. Fixed the name of two migration scripts that were not following the
correct format (not using "-" separator).
4. Added set of unit tests to test and validate the execution of
migration scripts code.

Manual upgrade testing to STX 5.0 has been executed.

Closes-Bug: 1887985
Signed-off-by: Adriano Oliveira <adriano.oliveira@windriver.com>
Change-Id: I04fdb8a3b3e177c609c4037825810a531954d99c
This commit is contained in:
Adriano Oliveira 2021-02-01 19:48:28 -05:00
parent ddf2dd3d55
commit 36a4ff4fd2
10 changed files with 220 additions and 8 deletions

View File

@ -12,6 +12,8 @@
- sysinv-tox-flake8 - sysinv-tox-flake8
- sysinv-tox-pylint - sysinv-tox-pylint
- sysinv-tox-bandit - sysinv-tox-bandit
- controllerconfig-tox-py27
- controllerconfig-tox-py36
- controllerconfig-tox-flake8 - controllerconfig-tox-flake8
- controllerconfig-tox-pylint - controllerconfig-tox-pylint
- cgtsclient-tox-py27 - cgtsclient-tox-py27
@ -26,6 +28,8 @@
- sysinv-tox-flake8 - sysinv-tox-flake8
- sysinv-tox-pylint - sysinv-tox-pylint
- sysinv-tox-bandit - sysinv-tox-bandit
- controllerconfig-tox-py27
- controllerconfig-tox-py36
- controllerconfig-tox-flake8 - controllerconfig-tox-flake8
- controllerconfig-tox-pylint - controllerconfig-tox-pylint
- cgtsclient-tox-py27 - cgtsclient-tox-py27
@ -105,6 +109,30 @@
tox_envlist: bandit tox_envlist: bandit
tox_extra_args: -c sysinv/sysinv/sysinv/tox.ini tox_extra_args: -c sysinv/sysinv/sysinv/tox.ini
- job:
name: controllerconfig-tox-py27
parent: tox
description: Run py27 tests for controllerconfig
required-projects:
- starlingx/fault
files:
- controllerconfig/*
vars:
tox_envlist: py27
tox_extra_args: -c controllerconfig/controllerconfig/tox.ini
- job:
name: controllerconfig-tox-py36
parent: tox
description: Run py36 tests for controllerconfig
required-projects:
- starlingx/fault
files:
- controllerconfig/*
vars:
tox_envlist: py36
tox_extra_args: -c controllerconfig/controllerconfig/tox.ini
- job: - job:
name: controllerconfig-tox-flake8 name: controllerconfig-tox-flake8
parent: tox parent: tox

View File

@ -0,0 +1,3 @@
[DEFAULT]
test_path=./controllerconfig/tests
top_dir=./controllerconfig

View File

@ -0,0 +1,5 @@
#
# Copyright (c) 2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#

View File

@ -0,0 +1,5 @@
#
# Copyright (c) 2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#

View File

@ -0,0 +1,144 @@
# Copyright (c) 2021 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""Base test code to test migration scripts
First, focus on the migration script name validation
Second, the validation script sequence call
"""
from mockproc import mockprocess
from os import listdir
from os.path import isfile
from os.path import join
from tempfile import mkdtemp
import os
import unittest
from controllerconfig.upgrades import utils
# The way to assert is to pass a script execution that writes the script file
# name into a file
# The content of the file will contain the sequence of the called scripts
script_body = '''#! /usr/bin/env python
with open('%s', 'a+') as f:
f.write("%s")
'''
from_release = "20.06"
to_release = "20.12"
action = "migrate"
# Lists to add scripts to be called, use a ":" separator for
# parsing/asserting
validScripts1 = ["71-bla1-bla2-bla3.sh", "8-bla1-bla2-bla3.py:",
"21-bla1-bla2-bla3.sh:"]
validScripts2 = ["75-deployment-ns-upgrade.py:", "65-k8s-app-upgrade.sh:",
"10-sysinv-adjust-partitions.py:",
"60-helm-releases-data-migration.py:",
"55-armada-helm-upgrade.py:",
"95-apply-mandatory-psp-policies.py:",
"10-sysinv-adjust-partitions.py:",
"85-update-sc-admin-endpoint-cert.py:",
"70-active-secured-etcd-after-upgrade.sh:",
"50-dcmanager-subcloud-status-migration.py:",
"45-sysinv-remove-identity-shared-service.py:",
"25-coredns-configmap.sh:",
"20-exempt-admin-from-lockout.sh:",
"115-foo-bar-test-ok.sh:", "299-foo-bar-test-ok.sh:",
"2123-foo-bar-test-ok.sh"]
invalidScripts1 = ["70-bla1-bla2-bla3.sh", "7-bla1-bla2-bla3.py:",
"20-bla1-bla2-bla3.sh:", "-20-bla1-bla2-bla3.sh"]
invalidScripts2 = ["95-apply-mandatory-psp-policies.py",
"10-sysinv-adjust-partitions.py:",
"85-update-sc-admin-endpoint-cert.py:",
"70_active-secured-etcd-after-upgrade.sh:"]
# Append scripts to be executed according to the passed list
def addScripts(self, scripts, output_filename):
for script in scripts:
self.scripts.append(script, returncode=0, script=script_body %
(output_filename, script))
# Test with the files under "controllerconfig/upgrade-scripts"
def addRealMigrationScripts(self, output_filename):
path = os.getcwd() + "/upgrade-scripts"
for f in listdir(path):
if isfile(join(path, f)):
self.scripts.append(f, returncode=0, script=script_body %
(output_filename, f))
def assertProperSorted(scripts):
output = False
sequence = []
for script in scripts:
sequence.append(int(script.split("-")[0]))
if sorted(sequence) == sequence:
output = True
return output
class TestMigrationScripts(unittest.TestCase):
def setUp(self):
self.scripts_dir = mkdtemp()
self.output_filename = mkdtemp() + "/output.txt"
# Re-create the file for each run
open(self.output_filename, 'w+').close()
self.scripts = mockprocess.MockProc(self.scripts_dir)
def test_migration_scripts_success_1(self):
addScripts(self, validScripts1, self.output_filename)
with self.scripts:
utils.execute_migration_scripts(from_release, to_release, action,
self.scripts_dir)
with open(self.output_filename, 'r') as f:
output = str(f.read())
if(assertProperSorted(output.split(':'))):
pass
def test_migration_scripts_success_2(self):
addScripts(self, validScripts2, self.output_filename)
with self.scripts:
utils.execute_migration_scripts(from_release, to_release, action,
self.scripts_dir)
with open(self.output_filename, 'r') as f:
output = str(f.read())
if(assertProperSorted(output.split(':'))):
pass
def test_real_migration_scripts(self):
addRealMigrationScripts(self, self.output_filename)
with self.scripts:
utils.execute_migration_scripts(from_release, to_release, action,
self.scripts_dir)
with open(self.output_filename, 'r') as f:
output = str(f.read())
if(assertProperSorted(output.split(':'))):
pass
def test_migration_scripts_validation_fail_1(self):
addScripts(self, invalidScripts1, self.output_filename)
with self.assertRaises(ValueError):
with self.scripts:
utils.execute_migration_scripts(from_release, to_release,
action, self.scripts_dir)
def test_migration_scripts_validation_fail_2(self):
addScripts(self, invalidScripts2, self.output_filename)
with self.assertRaises(ValueError):
with self.scripts:
utils.execute_migration_scripts(from_release, to_release,
action, self.scripts_dir)
def tearDown(self):
os.remove(self.output_filename)

View File

@ -1,5 +1,5 @@
# #
# Copyright (c) 2016-2020 Wind River Systems, Inc. # Copyright (c) 2016-2021 Wind River Systems, Inc.
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# #
@ -50,7 +50,8 @@ ACTION_MIGRATE = "migrate"
ACTION_ACTIVATE = "activate" ACTION_ACTIVATE = "activate"
def execute_migration_scripts(from_release, to_release, action): def execute_migration_scripts(from_release, to_release, action,
migration_script_dir="/etc/upgrade.d"):
""" Execute migration scripts with an action: """ Execute migration scripts with an action:
start: Prepare for upgrade on release N side. Called during start: Prepare for upgrade on release N side. Called during
"system upgrade-start". "system upgrade-start".
@ -60,8 +61,6 @@ def execute_migration_scripts(from_release, to_release, action):
devnull = open(os.devnull, 'w') devnull = open(os.devnull, 'w')
migration_script_dir = "/etc/upgrade.d"
LOG.info("Executing migration scripts with from_release: %s, " LOG.info("Executing migration scripts with from_release: %s, "
"to_release: %s, action: %s" % (from_release, to_release, action)) "to_release: %s, action: %s" % (from_release, to_release, action))
@ -70,7 +69,16 @@ def execute_migration_scripts(from_release, to_release, action):
files = [f for f in os.listdir(migration_script_dir) files = [f for f in os.listdir(migration_script_dir)
if os.path.isfile(os.path.join(migration_script_dir, f)) and if os.path.isfile(os.path.join(migration_script_dir, f)) and
os.access(os.path.join(migration_script_dir, f), os.X_OK)] os.access(os.path.join(migration_script_dir, f), os.X_OK)]
files.sort() # From file name, get the number to sort the calling sequence,
# abort when the file name format does not follow the pattern
# "nnn-*.*", where "nnn" string shall contain only digits, corresponding
# to a valid unsigned integer (first sequence of characters before "-")
try:
files.sort(key=lambda x: int(x.split("-")[0]))
except Exception:
LOG.exception("Migration script sequence validation failed, invalid "
"file name format")
raise
# Execute each migration script # Execute each migration script
for f in files: for f in files:

View File

@ -1,9 +1,10 @@
hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
pylint <=1.9.3;python_version<'3.0' pylint <=1.9.3;python_version<'3.0'
pytest pytest
mock mockproc>= 0.3.1 # BSD
coverage>=3.6 coverage>=3.6
PyYAML>=3.10.0 # MIT PyYAML>=3.10.0 # MIT
os-testr>=0.8.0 # Apache-2.0 os-testr>=0.8.0 # Apache-2.0
stestr>=1.0.0 # Apache-2.0
testresources>=0.2.4 # Apache-2.0/BSD testresources>=0.2.4 # Apache-2.0/BSD
testrepository>=0.0.18 # Apache-2.0/BSD testrepository>=0.0.18 # Apache-2.0/BSD

View File

@ -4,7 +4,7 @@
# and then run "tox" from this directory. # and then run "tox" from this directory.
[tox] [tox]
envlist = flake8, pylint envlist = flake8, pylint, py27, py36
# Tox does not work if the path to the workdir is too long, so move it to /tmp # Tox does not work if the path to the workdir is too long, so move it to /tmp
toxworkdir = /tmp/{env:USER}_cctox toxworkdir = /tmp/{env:USER}_cctox
stxdir = {toxinidir}/../../.. stxdir = {toxinidir}/../../..
@ -21,7 +21,9 @@ deps = -r{toxinidir}/requirements.txt
-e{[tox]stxdir}/fault/fm-api -e{[tox]stxdir}/fault/fm-api
-e{[tox]stxdir}/config/tsconfig/tsconfig -e{[tox]stxdir}/config/tsconfig/tsconfig
-e{[tox]stxdir}/config/sysinv/sysinv/sysinv -e{[tox]stxdir}/config/sysinv/sysinv/sysinv
-e{[tox]stxdir}/config/sysinv/cgts-client/cgts-client
commands =
find . -type f -name "*.pyc" -delete
[testenv:venv] [testenv:venv]
commands = {posargs} commands = {posargs}
@ -36,6 +38,22 @@ basepython = python2.7
deps = -r{toxinidir}/test-requirements.txt deps = -r{toxinidir}/test-requirements.txt
commands = flake8 {posargs} commands = flake8 {posargs}
[testenv:py27]
basepython = python2.7
deps = {[testenv]deps}
commands =
{[testenv]commands}
stestr run {posargs}
stestr slowest
[testenv:py36]
basepython = python3.6
deps = {[testenv]deps}
commands =
{[testenv]commands}
stestr run {posargs}
stestr slowest
[flake8] [flake8]
# H series are hacking # H series are hacking
# H101: Use TODO(NAME) # H101: Use TODO(NAME)