config/sysinv/sysinv/sysinv/sysinv/tests/common/test_app_metadata.py

537 lines
22 KiB
Python

#
# 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")