blob: 784678ee62be61549788b26eeb841343bf47543f [file]
# SPDX-License-Identifier: Apache-2.0
#
# Modifications by Apache Solr contributors; see git log for details.
# Licensed under the Apache License, Version 2.0.
#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.
# Modifications Copyright OpenSearch Contributors. See
# GitHub history for details.
# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
import configparser
from unittest import TestCase
from solrorbit import config, exceptions
class MockInput:
def __init__(self, inputs):
self.inputs = iter(inputs)
def __call__(self, *args, **kwargs):
null_output(*args, **kwargs)
v = next(self.inputs)
null_output(v)
return v
def null_output(*args, **kwargs):
# print(*args, **kwargs)
pass
class InMemoryConfigStore:
def __init__(self, config_name, config=None, backup_created=False, present=False):
self.config_name = config_name
# support initialization from a dict
if config:
self.config = configparser.ConfigParser()
self.config.read_dict(config)
else:
self.config = config
self.backup_created = backup_created
self.present = present
self.location = "in-memory"
self.config_dir = "in-memory"
def backup(self):
self.backup_created = True
def store_default_config(self):
self.store({
"distributions": {
"release.url": "https://acme.com/releases",
"release.cache": "true",
},
"system": {
"env.name": "existing-unit-test-config"
},
"meta": {
"config.version": config.Config.CURRENT_CONFIG_VERSION
},
"benchmarks": {
"local.dataset.cache": "/tmp/benchmark/data"
}
})
def store(self, c):
self.present = True
self.config = configparser.ConfigParser()
self.config.read_dict(c)
def load(self, interpolation=None):
# interpolation is not supported in tests, we just mimic the interface
return self.config
class ConfigTests(TestCase):
def test_load_non_existing_config(self):
cfg = config.Config(config_file_class=InMemoryConfigStore)
self.assertFalse(cfg.config_present())
# standard properties are still available
self.assertEqual("benchmark-node", cfg.opts("provisioning", "node.name.prefix"))
def test_load_existing_config(self):
cfg = config.Config(config_file_class=InMemoryConfigStore)
self.assertFalse(cfg.config_present())
sample_config = {
"tests": {
"sample.key": "value"
},
"meta": {
"config.version": config.Config.CURRENT_CONFIG_VERSION
}
}
cfg.config_file.store(sample_config)
self.assertTrue(cfg.config_present())
cfg.load_config()
# standard properties are still available
self.assertEqual("benchmark-node", cfg.opts("provisioning", "node.name.prefix"))
self.assertEqual("value", cfg.opts("tests", "sample.key"))
# we can also override values
cfg.add(config.Scope.applicationOverride, "tests", "sample.key", "override")
self.assertEqual("override", cfg.opts("tests", "sample.key"))
def test_load_all_opts_in_section(self):
cfg = config.Config(config_file_class=InMemoryConfigStore)
self.assertFalse(cfg.config_present())
sample_config = {
"distributions": {
"release.url": "https://acme.com/releases",
"release.cache": "true",
"snapshot.url": "https://acme.com/snapshots",
"snapshot.cache": "false"
},
"system": {
"env.name": "local"
},
"meta": {
"config.version": config.Config.CURRENT_CONFIG_VERSION
}
}
cfg.config_file.store(sample_config)
self.assertTrue(cfg.config_present())
cfg.load_config()
# override a value so we can see that the scoping logic still works. Default is scope "application"
cfg.add(config.Scope.applicationOverride, "distributions", "snapshot.cache", "true")
self.assertEqual({
"release.url": "https://acme.com/releases",
"release.cache": "true",
"snapshot.url": "https://acme.com/snapshots",
# overridden!
"snapshot.cache": "true"
}, cfg.all_opts("distributions"))
def test_add_all_in_section(self):
source_cfg = config.Config(config_file_class=InMemoryConfigStore)
sample_config = {
"tests": {
"sample.key": "value",
"sample.key2": "value"
},
"no_copy": {
"other.key": "value"
},
"meta": {
"config.version": config.Config.CURRENT_CONFIG_VERSION
}
}
source_cfg.config_file.store(sample_config)
source_cfg.load_config()
target_cfg = config.Config(config_file_class=InMemoryConfigStore)
self.assertIsNone(target_cfg.opts("tests", "sample.key", mandatory=False))
target_cfg.add_all(source=source_cfg, section="tests")
self.assertEqual("value", target_cfg.opts("tests", "sample.key"))
self.assertIsNone(target_cfg.opts("no_copy", "other.key", mandatory=False))
# nonexisting key will not throw an error
target_cfg.add_all(source=source_cfg, section="this section does not exist")
class AutoLoadConfigTests(TestCase):
def test_can_create_non_existing_config(self):
base_cfg = config.Config(config_name="unittest", config_file_class=InMemoryConfigStore)
base_cfg.add(config.Scope.application, "meta", "config.version", config.Config.CURRENT_CONFIG_VERSION)
base_cfg.add(config.Scope.application, "benchmarks", "local.dataset.cache", "/base-config/data-set-cache")
base_cfg.add(config.Scope.application, "reporting", "datastore.type", "opensearch")
base_cfg.add(config.Scope.application, "workloads", "metrics.url", "http://github.com/org/metrics")
base_cfg.add(config.Scope.application, "cluster_configs", "private.url", "http://github.com/org/cluster_configs")
base_cfg.add(config.Scope.application, "distributions", "release.cache", False)
base_cfg.add(config.Scope.application, "defaults", "preserve_benchmark_candidate", True)
cfg = config.auto_load_local_config(base_cfg, config_file_class=InMemoryConfigStore)
self.assertTrue(cfg.config_file.present)
# did not just copy base config
self.assertNotEqual(base_cfg.opts("benchmarks", "local.dataset.cache"), cfg.opts("benchmarks", "local.dataset.cache"))
# copied sections from base config
self.assert_equals_base_config(base_cfg, cfg, "reporting", "datastore.type")
self.assert_equals_base_config(base_cfg, cfg, "workloads", "metrics.url")
self.assert_equals_base_config(base_cfg, cfg, "cluster_configs", "private.url")
self.assert_equals_base_config(base_cfg, cfg, "distributions", "release.cache")
self.assert_equals_base_config(base_cfg, cfg, "defaults", "preserve_benchmark_candidate")
def test_can_load_and_amend_existing_config(self):
base_cfg = config.Config(config_name="unittest", config_file_class=InMemoryConfigStore)
base_cfg.add(config.Scope.application, "meta", "config.version", config.Config.CURRENT_CONFIG_VERSION)
base_cfg.add(config.Scope.application, "benchmarks", "local.dataset.cache", "/base-config/data-set-cache")
base_cfg.add(config.Scope.application, "unit-test", "sample.property", "let me copy you")
cfg = config.auto_load_local_config(base_cfg, additional_sections=["unit-test"],
config_file_class=InMemoryConfigStore, present=True, config={
"distributions": {
"release.url": "https://acme.com/releases",
"release.cache": "true",
},
"system": {
"env.name": "existing-unit-test-config"
},
"meta": {
"config.version": config.Config.CURRENT_CONFIG_VERSION
},
"benchmarks": {
"local.dataset.cache": "/tmp/benchmark/data"
}
})
self.assertTrue(cfg.config_file.present)
# did not just copy base config
self.assertNotEqual(base_cfg.opts("benchmarks", "local.dataset.cache"), cfg.opts("benchmarks", "local.dataset.cache"))
# keeps config properties
self.assertEqual("existing-unit-test-config", cfg.opts("system", "env.name"))
# copies additional properties
self.assert_equals_base_config(base_cfg, cfg, "unit-test", "sample.property")
def test_can_migrate_outdated_config(self):
base_cfg = config.Config(config_name="unittest", config_file_class=InMemoryConfigStore)
base_cfg.add(config.Scope.application, "meta", "config.version", config.Config.CURRENT_CONFIG_VERSION)
base_cfg.add(config.Scope.application, "benchmarks", "local.dataset.cache", "/base-config/data-set-cache")
base_cfg.add(config.Scope.application, "unit-test", "sample.property", "let me copy you")
cfg = config.auto_load_local_config(base_cfg, additional_sections=["unit-test"],
config_file_class=InMemoryConfigStore, present=True, config={
"distributions": {
"release.url": "https://acme.com/releases",
"release.cache": "true",
},
"system": {
"env.name": "existing-unit-test-config"
},
# outdated
"meta": {
# ensure we don't attempt to migrate if that version is unsupported
"config.version": max(config.Config.CURRENT_CONFIG_VERSION - 1, config.Config.EARLIEST_SUPPORTED_VERSION)
},
"benchmarks": {
"local.dataset.cache": "/tmp/benchmark/data"
},
"runtime": {
"java8.home": "/opt/jdk8"
}
})
self.assertTrue(cfg.config_file.present)
# did not just copy base config
self.assertNotEqual(base_cfg.opts("benchmarks", "local.dataset.cache"), cfg.opts("benchmarks", "local.dataset.cache"))
# migrated existing config
self.assertEqual(config.Config.CURRENT_CONFIG_VERSION, int(cfg.opts("meta", "config.version")))
def assert_equals_base_config(self, base_config, local_config, section, key):
self.assertEqual(base_config.opts(section, key), local_config.opts(section, key))
class ConfigMigrationTests(TestCase):
def test_does_not_migrate_outdated_config(self):
config_file = InMemoryConfigStore("test")
sample_config = {
"system": {
"root.dir": "in-memory"
},
"provisioning": {
},
"build": {
"maven.bin": "/usr/local/mvn"
},
"benchmarks": {
"metrics.stats.disk.device": "/dev/hdd1"
},
"reporting": {
"results.base.dir": "/tests/benchmark/reporting",
"output.html.results.filename": "index.html"
},
"runtime": {
"java8.home": "/opt/jdk/8",
}
}
config_file.store(sample_config)
with self.assertRaisesRegex(exceptions.ConfigError,
"The config file.*is too old. Please delete it and reconfigure from scratch"):
config.migrate(config_file, config.Config.EARLIEST_SUPPORTED_VERSION - 1, config.Config.CURRENT_CONFIG_VERSION, out=null_output)
# catch all test, migrations are checked in more detail in the other tests
def test_migrate_from_earliest_supported_to_latest(self):
config_file = InMemoryConfigStore("test")
sample_config = {
"meta": {
"config.version": config.Config.EARLIEST_SUPPORTED_VERSION
},
"system": {
"root.dir": "in-memory"
},
"provisioning": {
},
"build": {
"maven.bin": "/usr/local/mvn"
},
"benchmarks": {
"metrics.stats.disk.device": "/dev/hdd1"
},
"reporting": {
"results.base.dir": "/tests/benchmark/reporting",
"output.html.results.filename": "index.html"
},
"runtime": {
"java8.home": "/opt/jdk/8",
},
"distributions": {
"release.url": "https://artifacts.opensearch.org/releases/bundle/opensearch/{{VERSION}}/opensearch-"
"{{VERSION}}-linux-x64.tar.gz"
}
}
config_file.store(sample_config)
config.migrate(config_file, config.Config.EARLIEST_SUPPORTED_VERSION, config.Config.CURRENT_CONFIG_VERSION, out=null_output)
if config.Config.EARLIEST_SUPPORTED_VERSION < config.Config.CURRENT_CONFIG_VERSION:
self.assertTrue(config_file.backup_created)
self.assertEqual(str(config.Config.CURRENT_CONFIG_VERSION), config_file.config["meta"]["config.version"])