Create unit tests for the metadata validation logic
These changes organize and add more unit tests to the Validate_metadata_file function. Improvements implemented: - A separate test file was created to be exclusive to the app_metadata.py file. - Data input was organized in external files. - Unit tests added individually to each key in the yaml file. Test plan: PASS: Run tox py39, pylint and verify that they are all passing. PASS: The output of the 'tox -e cover' was improved from 13% to 58%. Within the same file there are other functions to be tested that are not the scope of this demand. Story: 2010929 Task: 49834 Change-Id: If4bdb734990582f302b1e0d20179e02c524de546 Signed-off-by: David Bastos <david.barbosabastos@windriver.com>
This commit is contained in:
parent
3773c65f61
commit
fa5855845c
|
@ -1,5 +1,5 @@
|
||||||
#
|
#
|
||||||
# Copyright (c) 2023 Wind River Systems, Inc.
|
# Copyright (c) 2023-2024 Wind River Systems, Inc.
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
#
|
#
|
||||||
|
@ -125,7 +125,7 @@ def validate_metadata_file(path, metadata_file, upgrade_from_release=None):
|
||||||
if not error_message:
|
if not error_message:
|
||||||
error_message = _("Invalid boolean value: {}"
|
error_message = _("Invalid boolean value: {}"
|
||||||
.format(value))
|
.format(value))
|
||||||
raise exception.SysinvException(error_message)
|
raise exception.SysinvException(error_message)
|
||||||
|
|
||||||
def validate_dict(value, error_message=None):
|
def validate_dict(value, error_message=None):
|
||||||
"""Validate dictionary types"""
|
"""Validate dictionary types"""
|
||||||
|
@ -142,7 +142,7 @@ def validate_metadata_file(path, metadata_file, upgrade_from_release=None):
|
||||||
if not isinstance(value, list):
|
if not isinstance(value, list):
|
||||||
if not error_message:
|
if not error_message:
|
||||||
error_message = _("Invalid list: {}".format(value))
|
error_message = _("Invalid list: {}".format(value))
|
||||||
raise exception.SysinvException(error_message)
|
raise exception.SysinvException(error_message)
|
||||||
|
|
||||||
# Field-level validations:
|
# Field-level validations:
|
||||||
def validate_string_field(parent, key):
|
def validate_string_field(parent, key):
|
||||||
|
@ -426,8 +426,9 @@ def validate_metadata_file(path, metadata_file, upgrade_from_release=None):
|
||||||
six.string_types))
|
six.string_types))
|
||||||
for release, release_patches in supported_releases.items():
|
for release, release_patches in supported_releases.items():
|
||||||
validate_string(release, release_error_message)
|
validate_string(release, release_error_message)
|
||||||
validate_list_field(release_patches,
|
validate_list(release_patches,
|
||||||
release_patches_error_message)
|
release_patches_error_message)
|
||||||
|
|
||||||
for patch in release_patches:
|
for patch in release_patches:
|
||||||
validate_string(patch, patch_error_message)
|
validate_string(patch, patch_error_message)
|
||||||
if release == check_release:
|
if release == check_release:
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
app_name: sample-app
|
||||||
|
app_version: 1.2-3
|
||||||
|
supported_k8s_version:
|
||||||
|
minimum: 'v1.2.3'
|
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
app_name: sample-app
|
||||||
|
app_version: 1.2-3
|
||||||
|
supported_k8s_version:
|
||||||
|
minimum: 'v1.2.3'
|
||||||
|
behavior:
|
||||||
|
platform_managed_app: true
|
||||||
|
desired_state: applied
|
||||||
|
evaluate_reapply:
|
||||||
|
after:
|
||||||
|
- metrics-server.1
|
||||||
|
- vault.1
|
||||||
|
- oran.3
|
||||||
|
triggers:
|
||||||
|
- type: kube-upgrade-complete
|
||||||
|
filters:
|
||||||
|
- availability: services-enabled
|
||||||
|
- type: host-delete
|
||||||
|
filters:
|
||||||
|
- personality: controller
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
app_name: sample-app
|
||||||
|
app_version: 1.2-3
|
||||||
|
supported_k8s_version:
|
||||||
|
minimum: 'v1.2.3'
|
||||||
|
k8s_upgrades:
|
||||||
|
auto_update: true
|
||||||
|
timing: pre
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
app_name: sample-app
|
||||||
|
app_version: 1.2-3
|
||||||
|
maintain_user_overrides: true
|
||||||
|
supported_k8s_version:
|
||||||
|
minimum: 'v1.2.3'
|
||||||
|
maximum: 'v1.2.4'
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
app_name: sample-app
|
||||||
|
app_version: 1.2-3
|
||||||
|
maintain_user_overrides: true
|
||||||
|
supported_k8s_version:
|
||||||
|
minimum: 'v1.2.3'
|
||||||
|
repo: 'stx-platform'
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
app_name: sample-app
|
||||||
|
app_version: 1.2-3
|
||||||
|
supported_k8s_version:
|
||||||
|
minimum: 'v1.2.3'
|
||||||
|
supported_releases:
|
||||||
|
TEST.SW.VERSION:
|
||||||
|
- "patch_0001"
|
||||||
|
- "patch_0002"
|
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
app_name: sample-app
|
||||||
|
app_version: 1.2-3
|
||||||
|
supported_k8s_version:
|
||||||
|
minimum: 'v1.2.3'
|
||||||
|
upgrades:
|
||||||
|
auto_update: true
|
||||||
|
update_failure_no_rollback: true
|
||||||
|
from_version:
|
||||||
|
- 1
|
||||||
|
- 2
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
app_name: sample-app
|
||||||
|
app_version: 1.2-3
|
||||||
|
maintain_user_overrides: true
|
|
@ -0,0 +1,536 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2024 Wind River Systems, Inc.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Tests for app_metadata.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import io
|
||||||
|
import mock
|
||||||
|
import os.path
|
||||||
|
import testtools
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from sysinv.common import app_metadata
|
||||||
|
from sysinv.common import constants
|
||||||
|
from sysinv.common import exception
|
||||||
|
|
||||||
|
|
||||||
|
# these unit tests do not need to subclass base.TestCase
|
||||||
|
class Validate_metadata_file(testtools.TestCase):
|
||||||
|
|
||||||
|
def get_metadata_yaml_sample(self, yaml_file_name):
|
||||||
|
"""Help Funcition to import example yaml
|
||||||
|
|
||||||
|
:param yaml_file_name: path to yaml
|
||||||
|
"""
|
||||||
|
path = os.path.join(os.path.dirname(__file__),
|
||||||
|
"data", yaml_file_name)
|
||||||
|
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
yaml_content = f.read()
|
||||||
|
|
||||||
|
return yaml_content
|
||||||
|
|
||||||
|
def test_nofile(self):
|
||||||
|
"""Verify results of validate_metadata_file
|
||||||
|
|
||||||
|
when if no file is found, returns:
|
||||||
|
app_name = "", app_version = "", patches = []
|
||||||
|
"""
|
||||||
|
app_name, app_version, patches = \
|
||||||
|
app_metadata.validate_metadata_file("invalid_path",
|
||||||
|
"invalid_file",
|
||||||
|
upgrade_from_release=None)
|
||||||
|
# if the file is not loaded or has invalid contents
|
||||||
|
# validate_metadata_file returns two empty strings and
|
||||||
|
# an empty list ie: "","",[]
|
||||||
|
self.assertEqual(app_name, "")
|
||||||
|
self.assertEqual(app_version, "")
|
||||||
|
self.assertEqual(patches, [])
|
||||||
|
|
||||||
|
@mock.patch.object(io, 'open')
|
||||||
|
@mock.patch.object(os.path, 'isfile')
|
||||||
|
def test_file_validation(self, _mock_isfile, _mock_open):
|
||||||
|
"""This test mocks file operations
|
||||||
|
|
||||||
|
Returns static file contents to allow unit
|
||||||
|
testing the validation code
|
||||||
|
"""
|
||||||
|
|
||||||
|
_mock_isfile.return_value = "True"
|
||||||
|
yaml_content = self.get_metadata_yaml_sample('sample_metadata.yaml')
|
||||||
|
_mock_open.return_value = io.StringIO(yaml_content)
|
||||||
|
|
||||||
|
app_name, app_version, patches = \
|
||||||
|
app_metadata.validate_metadata_file("valid_path",
|
||||||
|
"valid_file",
|
||||||
|
upgrade_from_release=None)
|
||||||
|
self.assertEqual(app_name, "sample-app")
|
||||||
|
self.assertEqual(app_version, "1.2-3")
|
||||||
|
|
||||||
|
@mock.patch.object(io, 'open')
|
||||||
|
@mock.patch.object(os.path, 'isfile')
|
||||||
|
def test_file_validate_with_bad_contents(self,
|
||||||
|
_mock_isfile,
|
||||||
|
_mock_open):
|
||||||
|
"""This test mocks file operations with bad values
|
||||||
|
|
||||||
|
This test mocks file operations and verifies
|
||||||
|
failure handling in how the yaml is validated
|
||||||
|
"""
|
||||||
|
_mock_isfile.return_value = "True"
|
||||||
|
|
||||||
|
# bad_replacements is a list of atomic changes that
|
||||||
|
# will trigger a SysinvException in the validator
|
||||||
|
bad_replacements = [
|
||||||
|
# app_name cannot be None
|
||||||
|
{constants.APP_METADATA_NAME: None},
|
||||||
|
# app_version cannot be None
|
||||||
|
{constants.APP_METADATA_VERSION: None},
|
||||||
|
# minimum or maximum cannot be a boolean
|
||||||
|
{constants.APP_METADATA_SUPPORTED_K8S_VERSION: {
|
||||||
|
constants.APP_METADATA_MINIMUM: True}},
|
||||||
|
# minimum or maximum cannot be a number
|
||||||
|
{constants.APP_METADATA_SUPPORTED_K8S_VERSION: {
|
||||||
|
constants.APP_METADATA_MINIMUM: 2}},
|
||||||
|
]
|
||||||
|
|
||||||
|
# start each loop with valid contents and replace
|
||||||
|
# a certain section with bad contents so that the
|
||||||
|
# validator will raise a SysinvException
|
||||||
|
for bad_dict in bad_replacements:
|
||||||
|
contents = self.get_metadata_yaml_sample('sample_metadata.yaml')
|
||||||
|
contents = yaml.safe_load(contents)
|
||||||
|
|
||||||
|
for key, value in bad_dict.items():
|
||||||
|
contents[key] = value
|
||||||
|
|
||||||
|
bad_contents = yaml.dump(contents)
|
||||||
|
_mock_open.return_value = io.StringIO(bad_contents)
|
||||||
|
|
||||||
|
self.assertRaises(exception.SysinvException,
|
||||||
|
app_metadata.validate_metadata_file,
|
||||||
|
"valid_path",
|
||||||
|
"valid_file")
|
||||||
|
|
||||||
|
@mock.patch.object(io, 'open')
|
||||||
|
@mock.patch.object(os.path, 'isfile')
|
||||||
|
def test_file_validation_supported_k8s_version(self,
|
||||||
|
_mock_isfile,
|
||||||
|
_mock_open):
|
||||||
|
"""Validate supported_k8s_version
|
||||||
|
|
||||||
|
This test mocks file operations with supported_k8s_version key
|
||||||
|
and returns static file contents to allow unit
|
||||||
|
testing the validation code
|
||||||
|
"""
|
||||||
|
_mock_isfile.return_value = "True"
|
||||||
|
|
||||||
|
yaml_content = self.get_metadata_yaml_sample(
|
||||||
|
'sample_metadata_k8s_versions.yaml')
|
||||||
|
_mock_open.return_value = io.StringIO(yaml_content)
|
||||||
|
|
||||||
|
app_name, app_version, patches = \
|
||||||
|
app_metadata.validate_metadata_file("valid_path",
|
||||||
|
"valid_file",
|
||||||
|
upgrade_from_release=None)
|
||||||
|
|
||||||
|
self.assertEqual(app_name, "sample-app")
|
||||||
|
self.assertEqual(app_version, "1.2-3")
|
||||||
|
|
||||||
|
@mock.patch.object(io, 'open')
|
||||||
|
@mock.patch.object(os.path, 'isfile')
|
||||||
|
def test_file_validation_supported_k8s_version_bad_contents(self,
|
||||||
|
_mock_isfile,
|
||||||
|
_mock_open):
|
||||||
|
"""Validate supported_k8s_version with bad values
|
||||||
|
|
||||||
|
This test mocks file operations to supported_k8s_version key
|
||||||
|
and verifies failure handling in how the yaml is validated
|
||||||
|
"""
|
||||||
|
|
||||||
|
_mock_isfile.return_value = "True"
|
||||||
|
|
||||||
|
# bad_replacements is a list of atomic changes that
|
||||||
|
# will trigger a SysinvException in the validator
|
||||||
|
bad_replacements = [
|
||||||
|
# supported_k8s_version must be a dict
|
||||||
|
{constants.APP_METADATA_SUPPORTED_K8S_VERSION: None},
|
||||||
|
# minimum must be a string
|
||||||
|
{constants.APP_METADATA_SUPPORTED_K8S_VERSION: {
|
||||||
|
constants.APP_METADATA_MINIMUM: True}},
|
||||||
|
# minimum must be a string
|
||||||
|
{constants.APP_METADATA_SUPPORTED_K8S_VERSION: {
|
||||||
|
constants.APP_METADATA_MAXIMUM: 1}}
|
||||||
|
]
|
||||||
|
|
||||||
|
# start each loop with valid contents and replace
|
||||||
|
# a certain section with bad contents so that the
|
||||||
|
# validator will raise a SysinvException
|
||||||
|
for bad_dict in bad_replacements:
|
||||||
|
contents = self.get_metadata_yaml_sample(
|
||||||
|
'sample_metadata_k8s_versions.yaml')
|
||||||
|
contents = yaml.safe_load(contents)
|
||||||
|
|
||||||
|
for key, value in bad_dict.items():
|
||||||
|
contents[key] = value
|
||||||
|
|
||||||
|
bad_contents = yaml.dump(contents)
|
||||||
|
_mock_open.return_value = io.StringIO(bad_contents)
|
||||||
|
|
||||||
|
self.assertRaises(exception.SysinvException,
|
||||||
|
app_metadata.validate_metadata_file,
|
||||||
|
"valid_path",
|
||||||
|
"valid_file")
|
||||||
|
|
||||||
|
@mock.patch.object(io, 'open')
|
||||||
|
@mock.patch.object(os.path, 'isfile')
|
||||||
|
def test_file_validation_without_min_k8s_version(self,
|
||||||
|
_mock_isfile,
|
||||||
|
_mock_open):
|
||||||
|
"""Validate supported_k8s_version without supported_k8s_version
|
||||||
|
|
||||||
|
This test mocks file operations without supported_k8s_version
|
||||||
|
key and returns static file contents to allow unit testing the
|
||||||
|
validation code
|
||||||
|
"""
|
||||||
|
|
||||||
|
_mock_isfile.return_value = "True"
|
||||||
|
|
||||||
|
yaml_content = self.get_metadata_yaml_sample(
|
||||||
|
'sample_metadata_without_k8s_minimum_version.yaml')
|
||||||
|
_mock_open.return_value = io.StringIO(yaml_content)
|
||||||
|
|
||||||
|
self.assertRaises(exception.SysinvException,
|
||||||
|
app_metadata.validate_metadata_file,
|
||||||
|
"valid_path",
|
||||||
|
"valid_file")
|
||||||
|
|
||||||
|
@mock.patch.object(io, 'open')
|
||||||
|
@mock.patch.object(os.path, 'isfile')
|
||||||
|
def test_file_validation_with_upgrade_key(self,
|
||||||
|
_mock_isfile,
|
||||||
|
_mock_open):
|
||||||
|
"""Validate upgrades key
|
||||||
|
|
||||||
|
This test mocks file operations with upgrades key
|
||||||
|
and returns static file contents to allow unit testing
|
||||||
|
the validation code
|
||||||
|
"""
|
||||||
|
|
||||||
|
_mock_isfile.return_value = "True"
|
||||||
|
yaml_content = self.get_metadata_yaml_sample('sample_metadata_upgrades.yaml')
|
||||||
|
_mock_open.return_value = io.StringIO(yaml_content)
|
||||||
|
|
||||||
|
app_name, app_version, patches = \
|
||||||
|
app_metadata.validate_metadata_file("valid_path",
|
||||||
|
"valid_file",
|
||||||
|
upgrade_from_release=None)
|
||||||
|
self.assertEqual(app_name, "sample-app")
|
||||||
|
self.assertEqual(app_version, "1.2-3")
|
||||||
|
|
||||||
|
@mock.patch.object(io, 'open')
|
||||||
|
@mock.patch.object(os.path, 'isfile')
|
||||||
|
def test_file_validation_with_upgrade_key_bad_contents(self,
|
||||||
|
_mock_isfile,
|
||||||
|
_mock_open):
|
||||||
|
"""Validate upgrades key with bad values
|
||||||
|
|
||||||
|
This test mocks file operations to upgrades key and
|
||||||
|
verifies failure handling in how the yaml is validated
|
||||||
|
"""
|
||||||
|
|
||||||
|
_mock_isfile.return_value = "True"
|
||||||
|
|
||||||
|
# bad_replacements is a list of atomic changes that
|
||||||
|
# will trigger a SysinvException in the validator
|
||||||
|
bad_replacements = [
|
||||||
|
# upgrades must be a dict
|
||||||
|
{constants.APP_METADATA_UPGRADES: None},
|
||||||
|
# update_failure_no_rollback must be a boolean string like: <true/false/yes/no>
|
||||||
|
{constants.APP_METADATA_UPGRADES: {
|
||||||
|
constants.APP_METADATA_UPDATE_FAILURE_SKIP_RECOVERY: 123}},
|
||||||
|
# auto_update must be a boolean string like: <true/false/yes/no>
|
||||||
|
{constants.APP_METADATA_UPGRADES: {constants.APP_METADATA_AUTO_UPDATE: 123}},
|
||||||
|
# auto_update must be a list
|
||||||
|
{constants.APP_METADATA_UPGRADES: {constants.APP_METADATA_FROM_VERSIONS: {}}}
|
||||||
|
]
|
||||||
|
|
||||||
|
# start each loop with valid contents and replace
|
||||||
|
# a certain section with bad contents so that the
|
||||||
|
# validator will raise a SysinvException
|
||||||
|
for bad_dict in bad_replacements:
|
||||||
|
contents = self.get_metadata_yaml_sample('sample_metadata_upgrades.yaml')
|
||||||
|
contents = yaml.safe_load(contents)
|
||||||
|
|
||||||
|
for key, value in bad_dict.items():
|
||||||
|
contents[key] = value
|
||||||
|
|
||||||
|
bad_contents = yaml.dump(contents)
|
||||||
|
_mock_open.return_value = io.StringIO(bad_contents)
|
||||||
|
|
||||||
|
self.assertRaises(exception.SysinvException,
|
||||||
|
app_metadata.validate_metadata_file,
|
||||||
|
"valid_path",
|
||||||
|
"valid_file")
|
||||||
|
|
||||||
|
@mock.patch.object(io, 'open')
|
||||||
|
@mock.patch.object(os.path, 'isfile')
|
||||||
|
def test_file_validation_repo_key(self,
|
||||||
|
_mock_isfile,
|
||||||
|
_mock_open):
|
||||||
|
"""Validate repo key
|
||||||
|
|
||||||
|
This test mocks file operations with repo key and
|
||||||
|
returns static file contents to allow unit testing
|
||||||
|
the validation code
|
||||||
|
"""
|
||||||
|
|
||||||
|
_mock_isfile.return_value = "True"
|
||||||
|
yaml_content = self.get_metadata_yaml_sample('sample_metadata_repo.yaml')
|
||||||
|
_mock_open.return_value = io.StringIO(yaml_content)
|
||||||
|
|
||||||
|
app_name, app_version, patches = \
|
||||||
|
app_metadata.validate_metadata_file("valid_path",
|
||||||
|
"valid_file",
|
||||||
|
upgrade_from_release=None)
|
||||||
|
self.assertEqual(app_name, "sample-app")
|
||||||
|
self.assertEqual(app_version, "1.2-3")
|
||||||
|
|
||||||
|
@mock.patch.object(io, 'open')
|
||||||
|
@mock.patch.object(os.path, 'isfile')
|
||||||
|
def test_file_validation_behavior_key(self,
|
||||||
|
_mock_isfile,
|
||||||
|
_mock_open):
|
||||||
|
"""Validate behavior key
|
||||||
|
|
||||||
|
This test mocks file operations with behavior key and
|
||||||
|
returns static file contents to allow unit testing
|
||||||
|
the validation code
|
||||||
|
"""
|
||||||
|
|
||||||
|
_mock_isfile.return_value = "True"
|
||||||
|
yaml_content = self.get_metadata_yaml_sample('sample_metadata_behavior.yaml')
|
||||||
|
_mock_open.return_value = io.StringIO(yaml_content)
|
||||||
|
|
||||||
|
app_name, app_version, patches = \
|
||||||
|
app_metadata.validate_metadata_file("valid_path",
|
||||||
|
"valid_file",
|
||||||
|
upgrade_from_release=None)
|
||||||
|
self.assertEqual(app_name, "sample-app")
|
||||||
|
self.assertEqual(app_version, "1.2-3")
|
||||||
|
|
||||||
|
@mock.patch.object(io, 'open')
|
||||||
|
@mock.patch.object(os.path, 'isfile')
|
||||||
|
def test_file_validate_behavior_key_with_bad_contents(self,
|
||||||
|
_mock_isfile,
|
||||||
|
_mock_open):
|
||||||
|
"""Validate behavior key with bad values
|
||||||
|
|
||||||
|
This test mocks file operations to behavior key and
|
||||||
|
verifies failure handling in how the yaml is validated
|
||||||
|
"""
|
||||||
|
|
||||||
|
_mock_isfile.return_value = "True"
|
||||||
|
|
||||||
|
# bad_replacements is a list of atomic changes that
|
||||||
|
# will trigger a SysinvException in the validator
|
||||||
|
bad_replacements = [
|
||||||
|
# behavior must be a dict
|
||||||
|
{constants.APP_METADATA_BEHAVIOR: []},
|
||||||
|
# platform_managed_app must be a boolean string like: <true/false/yes/no>
|
||||||
|
{constants.APP_METADATA_BEHAVIOR: {
|
||||||
|
constants.APP_METADATA_PLATFORM_MANAGED_APP: "something"}},
|
||||||
|
# desired_state must be a dict
|
||||||
|
{constants.APP_METADATA_BEHAVIOR: {
|
||||||
|
constants.APP_METADATA_DESIRED_STATE: 2}},
|
||||||
|
# evaluate_reapply must be a dict
|
||||||
|
{constants.APP_METADATA_BEHAVIOR: {constants.APP_METADATA_EVALUATE_REAPPLY: []}},
|
||||||
|
# after must be a list
|
||||||
|
{constants.APP_METADATA_BEHAVIOR: {constants.APP_METADATA_EVALUATE_REAPPLY: {
|
||||||
|
constants.APP_METADATA_AFTER: {}}}},
|
||||||
|
# triggers must be a list
|
||||||
|
{constants.APP_METADATA_BEHAVIOR: {constants.APP_METADATA_EVALUATE_REAPPLY: {
|
||||||
|
constants.APP_METADATA_TRIGGERS: {}}}},
|
||||||
|
# type must be a string
|
||||||
|
{constants.APP_METADATA_BEHAVIOR: {
|
||||||
|
constants.APP_METADATA_EVALUATE_REAPPLY: {
|
||||||
|
constants.APP_METADATA_TRIGGERS: [{constants.APP_METADATA_TYPE: 1}]
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
# filter_field must be a string
|
||||||
|
{constants.APP_METADATA_BEHAVIOR: {
|
||||||
|
constants.APP_METADATA_EVALUATE_REAPPLY: {
|
||||||
|
constants.APP_METADATA_TRIGGERS: [{constants.APP_METADATA_FILTER_FIELD: 1}]
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
# filters must be a list
|
||||||
|
{constants.APP_METADATA_BEHAVIOR: {
|
||||||
|
constants.APP_METADATA_EVALUATE_REAPPLY: {
|
||||||
|
constants.APP_METADATA_TRIGGERS: [{constants.APP_METADATA_FILTERS: {}}]
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
]
|
||||||
|
|
||||||
|
# start each loop with valid contents and replace
|
||||||
|
# a certain section with bad contents so that the
|
||||||
|
# validator will raise a SysinvException
|
||||||
|
for bad_dict in bad_replacements:
|
||||||
|
contents = self.get_metadata_yaml_sample('sample_metadata_behavior.yaml')
|
||||||
|
contents = yaml.safe_load(contents)
|
||||||
|
|
||||||
|
for key, value in bad_dict.items():
|
||||||
|
contents[key] = value
|
||||||
|
|
||||||
|
bad_contents = yaml.dump(contents)
|
||||||
|
_mock_open.return_value = io.StringIO(bad_contents)
|
||||||
|
|
||||||
|
self.assertRaises(exception.SysinvException,
|
||||||
|
app_metadata.validate_metadata_file,
|
||||||
|
"valid_path",
|
||||||
|
"valid_file")
|
||||||
|
|
||||||
|
@mock.patch.object(io, 'open')
|
||||||
|
@mock.patch.object(os.path, 'isfile')
|
||||||
|
def test_file_validation_supported_releases(self,
|
||||||
|
_mock_isfile,
|
||||||
|
_mock_open):
|
||||||
|
"""Validate supported_releases key
|
||||||
|
|
||||||
|
This test mocks file operations with supported_releases
|
||||||
|
key and returns static file contents to allow unit testing
|
||||||
|
the validation code
|
||||||
|
"""
|
||||||
|
_mock_isfile.return_value = "True"
|
||||||
|
|
||||||
|
yaml_content = self.get_metadata_yaml_sample(
|
||||||
|
'sample_metadata_supported_releases.yaml')
|
||||||
|
_mock_open.return_value = io.StringIO(yaml_content)
|
||||||
|
|
||||||
|
app_name, app_version, patches = \
|
||||||
|
app_metadata.validate_metadata_file("valid_path",
|
||||||
|
"valid_file",
|
||||||
|
upgrade_from_release=None)
|
||||||
|
|
||||||
|
self.assertEqual(app_name, "sample-app")
|
||||||
|
self.assertEqual(app_version, "1.2-3")
|
||||||
|
self.assertEqual(patches, ["patch_0001", "patch_0002"])
|
||||||
|
|
||||||
|
@mock.patch.object(io, 'open')
|
||||||
|
@mock.patch.object(os.path, 'isfile')
|
||||||
|
def test_file_validation_supported_releases_bad_contents(self,
|
||||||
|
_mock_isfile,
|
||||||
|
_mock_open):
|
||||||
|
"""Validate supported_releases key with bad values
|
||||||
|
|
||||||
|
This test mocks file operations to supported_releases key
|
||||||
|
and verifies failure handling in how the yaml is validated
|
||||||
|
"""
|
||||||
|
_mock_isfile.return_value = "True"
|
||||||
|
|
||||||
|
# bad_replacements is a list of atomic changes that
|
||||||
|
# will trigger a SysinvException in the validator
|
||||||
|
bad_replacements = [
|
||||||
|
# supported_releases must be a dict
|
||||||
|
{constants.APP_METADATA_SUPPORTED_RELEASES: []},
|
||||||
|
# releases group must be a list
|
||||||
|
{constants.APP_METADATA_SUPPORTED_RELEASES: {"TEST.SW.VERSION": {}}},
|
||||||
|
# releases item must be a string
|
||||||
|
{constants.APP_METADATA_SUPPORTED_RELEASES: {"TEST.SW.VERSION": [1, True]}},
|
||||||
|
]
|
||||||
|
|
||||||
|
# start each loop with valid contents and replace
|
||||||
|
# a certain section with bad contents so that the
|
||||||
|
# validator will raise a SysinvException
|
||||||
|
for bad_dict in bad_replacements:
|
||||||
|
contents = self.get_metadata_yaml_sample(
|
||||||
|
'sample_metadata_supported_releases.yaml')
|
||||||
|
contents = yaml.safe_load(contents)
|
||||||
|
|
||||||
|
for key, value in bad_dict.items():
|
||||||
|
contents[key] = value
|
||||||
|
|
||||||
|
bad_contents = yaml.dump(contents)
|
||||||
|
_mock_open.return_value = io.StringIO(bad_contents)
|
||||||
|
|
||||||
|
self.assertRaises(exception.SysinvException,
|
||||||
|
app_metadata.validate_metadata_file,
|
||||||
|
"valid_path",
|
||||||
|
"valid_file")
|
||||||
|
|
||||||
|
@mock.patch.object(io, 'open')
|
||||||
|
@mock.patch.object(os.path, 'isfile')
|
||||||
|
def test_file_validation_k8s_upgrades(self,
|
||||||
|
_mock_isfile,
|
||||||
|
_mock_open):
|
||||||
|
"""Validate k8s_upgrades key
|
||||||
|
|
||||||
|
This test mocks file operations with k8s_upgrades
|
||||||
|
key and returns static file contents to allow unit testing
|
||||||
|
the validation code
|
||||||
|
"""
|
||||||
|
_mock_isfile.return_value = "True"
|
||||||
|
|
||||||
|
yaml_content = self.get_metadata_yaml_sample(
|
||||||
|
'sample_metadata_k8s_upgrades.yaml')
|
||||||
|
_mock_open.return_value = io.StringIO(yaml_content)
|
||||||
|
|
||||||
|
app_name, app_version, patches = \
|
||||||
|
app_metadata.validate_metadata_file("valid_path",
|
||||||
|
"valid_file",
|
||||||
|
upgrade_from_release=None)
|
||||||
|
|
||||||
|
self.assertEqual(app_name, "sample-app")
|
||||||
|
self.assertEqual(app_version, "1.2-3")
|
||||||
|
|
||||||
|
@mock.patch.object(io, 'open')
|
||||||
|
@mock.patch.object(os.path, 'isfile')
|
||||||
|
def test_file_validation_k8s_upgrades_bad_contents(self,
|
||||||
|
_mock_isfile,
|
||||||
|
_mock_open):
|
||||||
|
"""Validate k8s_upgrades key with bad values
|
||||||
|
|
||||||
|
This test mocks file operations to k8s_upgrades key
|
||||||
|
and verifies failure handling in how the yaml is validated
|
||||||
|
"""
|
||||||
|
_mock_isfile.return_value = "True"
|
||||||
|
|
||||||
|
# bad_replacements is a list of atomic changes that
|
||||||
|
# will trigger a SysinvException in the validator
|
||||||
|
bad_replacements = [
|
||||||
|
# k8s_upgrades must be a dict
|
||||||
|
{constants.APP_METADATA_K8S_UPGRADES: []},
|
||||||
|
# auto_update key must be a boolean string
|
||||||
|
# like: <true/false/yes/no>
|
||||||
|
{constants.APP_METADATA_K8S_UPGRADES: {
|
||||||
|
constants.APP_METADATA_AUTO_UPDATE: None,
|
||||||
|
}},
|
||||||
|
{constants.APP_METADATA_K8S_UPGRADES: {
|
||||||
|
constants.APP_METADATA_AUTO_UPDATE: True,
|
||||||
|
# if auto_update key exist timing key must exist and be a string
|
||||||
|
constants.APP_METADATA_TIMING: None
|
||||||
|
}},
|
||||||
|
]
|
||||||
|
|
||||||
|
# start each loop with valid contents and replace
|
||||||
|
# a certain section with bad contents so that the
|
||||||
|
# validator will raise a SysinvException
|
||||||
|
for bad_dict in bad_replacements:
|
||||||
|
contents = self.get_metadata_yaml_sample(
|
||||||
|
'sample_metadata_k8s_upgrades.yaml')
|
||||||
|
contents = yaml.safe_load(contents)
|
||||||
|
|
||||||
|
for key, value in bad_dict.items():
|
||||||
|
contents[key] = value
|
||||||
|
|
||||||
|
bad_contents = yaml.dump(contents)
|
||||||
|
_mock_open.return_value = io.StringIO(bad_contents)
|
||||||
|
|
||||||
|
self.assertRaises(exception.SysinvException,
|
||||||
|
app_metadata.validate_metadata_file,
|
||||||
|
"valid_path",
|
||||||
|
"valid_file")
|
|
@ -15,26 +15,22 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# Copyright (c) 2022 Wind River Systems, Inc.
|
# Copyright (c) 2022,2024 Wind River Systems, Inc.
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
#
|
#
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
import io
|
|
||||||
import mock
|
import mock
|
||||||
import netaddr
|
import netaddr
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import six.moves.builtins as __builtin__
|
import six.moves.builtins as __builtin__
|
||||||
import tempfile
|
import tempfile
|
||||||
import testtools
|
|
||||||
import string
|
import string
|
||||||
import yaml
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
from sysinv.common import app_metadata
|
|
||||||
from sysinv.common import constants
|
from sysinv.common import constants
|
||||||
from sysinv.common import exception
|
from sysinv.common import exception
|
||||||
from sysinv.common import utils
|
from sysinv.common import utils
|
||||||
|
@ -448,131 +444,3 @@ class IntLikeTestCase(base.TestCase):
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
utils.is_int_like("0cc3346e-9fef-4445-abe6-5d2b2690ec64"))
|
utils.is_int_like("0cc3346e-9fef-4445-abe6-5d2b2690ec64"))
|
||||||
self.assertFalse(utils.is_int_like("a1"))
|
self.assertFalse(utils.is_int_like("a1"))
|
||||||
|
|
||||||
|
|
||||||
# these unit tests do not need to subclass base.TestCase
|
|
||||||
class FindMetadataTestCase(testtools.TestCase):
|
|
||||||
|
|
||||||
sample_contents = """
|
|
||||||
app_name: sample-app
|
|
||||||
app_version: 1.2-3
|
|
||||||
helm_repo: stx-platform
|
|
||||||
maintain_user_overrides: true
|
|
||||||
supported_k8s_version:
|
|
||||||
minimum: 'v1.2.3'
|
|
||||||
maximum: 'v2.4.6'
|
|
||||||
behavior:
|
|
||||||
platform_managed_app: yes
|
|
||||||
desired_state: applied
|
|
||||||
evaluate_reapply:
|
|
||||||
triggers:
|
|
||||||
- type: runtime-apply-puppet # TODO(someuser): an inline comment
|
|
||||||
- type: host-availability-updated
|
|
||||||
- type: kube-upgrade-complete
|
|
||||||
filters:
|
|
||||||
- availability: services-enabled
|
|
||||||
- type: host-delete
|
|
||||||
filters:
|
|
||||||
- personality: controller
|
|
||||||
"""
|
|
||||||
|
|
||||||
bad_contents = """
|
|
||||||
app_name: sample-app
|
|
||||||
app_version: 1.2-3
|
|
||||||
helm_repo: stx-platform
|
|
||||||
maintain_user_overrides: true
|
|
||||||
supported_k8s_version:
|
|
||||||
minimum: 1 # must be a string, not a number
|
|
||||||
maximum: true # must be a string, not a boolean
|
|
||||||
behavior:
|
|
||||||
platform_managed_app: yes
|
|
||||||
desired_state: applied
|
|
||||||
evaluate_reapply:
|
|
||||||
triggers:
|
|
||||||
- type: runtime-apply-puppet # TODO(someuser): an inline comment
|
|
||||||
- type: host-availability-updated
|
|
||||||
- type: kube-upgrade-complete
|
|
||||||
filters:
|
|
||||||
- availability: services-enabled
|
|
||||||
- type: host-delete
|
|
||||||
filters:
|
|
||||||
- personality: controller
|
|
||||||
"""
|
|
||||||
|
|
||||||
def test_validate_metadata_file_nofile(self):
|
|
||||||
"""Verify results of validate_metadata_file
|
|
||||||
|
|
||||||
when if no file is found, returns:
|
|
||||||
app_name = "", app_version = "", patches = []
|
|
||||||
"""
|
|
||||||
app_name, app_version, patches = \
|
|
||||||
app_metadata.validate_metadata_file("invalid_path",
|
|
||||||
"invalid_file",
|
|
||||||
upgrade_from_release=None)
|
|
||||||
# if the file is not loaded or has invalid contents
|
|
||||||
# validate_metadata_file returns two empty strings and
|
|
||||||
# an empty list ie: "","",[]
|
|
||||||
self.assertEqual(app_name, "")
|
|
||||||
self.assertEqual(app_version, "")
|
|
||||||
self.assertEqual(patches, [])
|
|
||||||
|
|
||||||
@mock.patch.object(io, 'open')
|
|
||||||
@mock.patch.object(os.path, 'isfile')
|
|
||||||
def test_validate_metadata_file(self,
|
|
||||||
_mock_isfile,
|
|
||||||
_mock_open):
|
|
||||||
"""This test mocks file operations
|
|
||||||
and returns static file contents to allow unit
|
|
||||||
testing the validation code
|
|
||||||
"""
|
|
||||||
|
|
||||||
_mock_isfile.return_value = "True"
|
|
||||||
|
|
||||||
# load fake yaml file contents for: sample-app 1.2-3
|
|
||||||
_mock_open.return_value = io.StringIO(self.sample_contents)
|
|
||||||
|
|
||||||
app_name, app_version, patches = \
|
|
||||||
app_metadata.validate_metadata_file("valid_path",
|
|
||||||
"valid_file",
|
|
||||||
upgrade_from_release=None)
|
|
||||||
self.assertEqual(app_name, "sample-app")
|
|
||||||
self.assertEqual(app_version, "1.2-3")
|
|
||||||
|
|
||||||
@mock.patch.object(io, 'open')
|
|
||||||
@mock.patch.object(os.path, 'isfile')
|
|
||||||
def test_validate_metadata_file_bad_contents(self,
|
|
||||||
_mock_isfile,
|
|
||||||
_mock_open):
|
|
||||||
"""This test mocks file operations and verifies
|
|
||||||
failure handling in how the yaml is validated
|
|
||||||
"""
|
|
||||||
_mock_isfile.return_value = "True"
|
|
||||||
|
|
||||||
# bad_replacements is a list of atomic changes that
|
|
||||||
# will trigger a SysinvException in the validator
|
|
||||||
bad_replacements = [
|
|
||||||
# app_name cannot be None
|
|
||||||
{"app_name": None},
|
|
||||||
# app_version cannot be None
|
|
||||||
{"app_version": None},
|
|
||||||
# behavior must be a dictionary (not a list)
|
|
||||||
{"behavior": []},
|
|
||||||
# minimum or maximum cannot be a boolean
|
|
||||||
{"supported_k8s_version": {"minimum": True, "maximum": "2.4.6"}},
|
|
||||||
# minimum or maximum cannot be a number
|
|
||||||
{"supported_k8s_version": {"minimum": "1.2.3", "maximum": 2}},
|
|
||||||
]
|
|
||||||
|
|
||||||
# start each loop with valid contents and replace
|
|
||||||
# a certain section with bad contents so that the
|
|
||||||
# validator will raise a SysinvException
|
|
||||||
for bad_dict in bad_replacements:
|
|
||||||
contents = yaml.safe_load(self.sample_contents)
|
|
||||||
for key, value in bad_dict.items():
|
|
||||||
contents[key] = value
|
|
||||||
bad_contents = yaml.dump(contents)
|
|
||||||
_mock_open.return_value = io.StringIO(bad_contents)
|
|
||||||
self.assertRaises(exception.SysinvException,
|
|
||||||
app_metadata.validate_metadata_file,
|
|
||||||
"valid_path",
|
|
||||||
"valid_file")
|
|
||||||
|
|
Loading…
Reference in New Issue