From 1ae4be91077978e324f2e463eaa97dcccfdb7057 Mon Sep 17 00:00:00 2001
From: Ivan Devat <idevat@redhat.com>
Date: Thu, 20 Jun 2019 11:44:46 +0200
Subject: [PATCH 2/3] squash bz1657166 Updating a bundle is a bit cumber
disallow to specify container type in bundle reset
fix id conflict in bundle reset
---
pcs/cli/resource/parse_args.py | 21 ++
pcs/lib/cib/resource/bundle.py | 216 +++++++-------
pcs/lib/commands/resource.py | 41 +--
pcs/lib/xml_tools.py | 15 +
pcs/pcs.8 | 2 +-
pcs/resource.py | 35 ++-
pcs/usage.py | 2 +-
pcs_test/tier0/cib_resource/test_bundle.py | 2 +-
.../lib/commands/resource/bundle_common.py | 4 +-
.../commands/resource/test_bundle_create.py | 6 +-
.../commands/resource/test_bundle_reset.py | 263 +++++++++++++++---
11 files changed, 402 insertions(+), 205 deletions(-)
diff --git a/pcs/cli/resource/parse_args.py b/pcs/cli/resource/parse_args.py
index 88da12a6..ff86f477 100644
--- a/pcs/cli/resource/parse_args.py
+++ b/pcs/cli/resource/parse_args.py
@@ -101,6 +101,27 @@ def parse_bundle_create_options(arg_list):
}
return parts
+def parse_bundle_reset_options(arg_list):
+ """
+ Commandline options: no options
+ """
+ groups = _parse_bundle_groups(arg_list)
+ container_options = groups.get("container", [])
+ parts = {
+ "container": prepare_options(container_options),
+ "network": prepare_options(groups.get("network", [])),
+ "port_map": [
+ prepare_options(port_map)
+ for port_map in groups.get("port-map", [])
+ ],
+ "storage_map": [
+ prepare_options(storage_map)
+ for storage_map in groups.get("storage-map", [])
+ ],
+ "meta": prepare_options(groups.get("meta", []))
+ }
+ return parts
+
def _split_bundle_map_update_op_and_options(
map_arg_list, result_parts, map_name
):
diff --git a/pcs/lib/cib/resource/bundle.py b/pcs/lib/cib/resource/bundle.py
index 5c2910c8..2b80608e 100644
--- a/pcs/lib/cib/resource/bundle.py
+++ b/pcs/lib/cib/resource/bundle.py
@@ -18,6 +18,7 @@ from pcs.lib.xml_tools import (
append_when_useful,
get_sub_element,
update_attributes_remove_empty,
+ reset_element,
)
TAG = "bundle"
@@ -84,15 +85,13 @@ def validate_new(
id_provider=id_provider
).validate({"id": bundle_id})
+
- validate_reset(
- id_provider,
- container_type,
- container_options,
- network_options,
- port_map,
- storage_map,
- force_options
- )
+ _validate_container(container_type, container_options, force_options)
+ +
+ _validate_network_options_new(network_options, force_options)
+ +
+ _validate_port_map_list(port_map, id_provider, force_options)
+ +
+ _validate_storage_map_list(storage_map, id_provider, force_options)
)
def append_new(
@@ -130,14 +129,14 @@ def append_new(
return bundle_element
def validate_reset(
- id_provider, container_type, container_options, network_options,
- port_map, storage_map, force_options=False
+ id_provider, bundle_el, container_options, network_options, port_map,
+ storage_map, force_options=False
):
"""
Validate bundle parameters, return list of report items
IdProvider id_provider -- elements' ids generator and uniqueness checker
- string container_type -- bundle container type
+ etree bundle_el -- the bundle to be reset
dict container_options -- container options
dict network_options -- network options
list of dict port_map -- list of port mapping options
@@ -145,7 +144,7 @@ def validate_reset(
bool force_options -- return warnings instead of forceable errors
"""
return (
- _validate_container(container_type, container_options, force_options)
+ _validate_container_reset(bundle_el, container_options, force_options)
+
_validate_network_options_new(network_options, force_options)
+
@@ -154,72 +153,46 @@ def validate_reset(
_validate_storage_map_list(storage_map, id_provider, force_options)
)
-def reset(
- bundle_element, id_provider, bundle_id, container_type, container_options,
- network_options, port_map, storage_map, meta_attributes
-):
+def validate_reset_to_minimal(bundle_element):
"""
- Remove configuration of bundle_element and create new one.
+ Validate removing configuration of bundle_element and keep the minimal one.
etree bundle_element -- the bundle element that will be reset
- IdProvider id_provider -- elements' ids generator
- string bundle_id -- id of the bundle
- string container_type -- bundle container type
- dict container_options -- container options
- dict network_options -- network options
- list of dict port_map -- list of port mapping options
- list of dict storage_map -- list of storage mapping options
- dict meta_attributes -- meta attributes
"""
- # pylint: disable=too-many-arguments
+ if not _is_supported_container(_get_container_element(bundle_element)):
+ return [_get_report_unsupported_container(bundle_element)]
+ return []
- # Old bundle configuration is removed and re-created. We aren't trying
- # to keep ids:
- # * It doesn't make sense to reference these ids.
- # * Newly created ids are based on (are prefixed by) the bundle element id,
- # which does not change. Therefore, it is VERY HIGHLY probable the newly
- # created ids will be the same as the original ones.
- elements_without_reset_impact = []
+def reset_to_minimal(bundle_element):
+ """
+ Remove configuration of bundle_element and keep the minimal one.
+ etree bundle_element -- the bundle element that will be reset
+ """
# Elements network, storage and meta_attributes must be kept even if they
# are without children.
# See https://bugzilla.redhat.com/show_bug.cgi?id=1642514
- #
- # The only scenario that makes sense is that these elements are empty
- # and no attributes or children are requested for them. So we collect only
- # deleted tags and we will ensure creation minimal relevant elements at
- # least.
- indelible_tags = []
- for child in list(bundle_element):
- if child.tag in ["network", "storage", META_ATTRIBUTES_TAG]:
- indelible_tags.append(child.tag)
- elif child.tag not in list(GENERIC_CONTAINER_TYPES):
- # Only primitive should be found here, currently.
- # The order of various element tags has no practical impact so we
- # don't care about it here.
- elements_without_reset_impact.append(child)
- bundle_element.remove(child)
+ # Element of container type is required.
- _append_container(bundle_element, container_type, container_options)
- if network_options or port_map or "network" in indelible_tags:
- _append_network(
- bundle_element,
- id_provider,
- bundle_id,
- network_options,
- port_map,
- )
- if storage_map or "storage" in indelible_tags:
- _append_storage(bundle_element, id_provider, bundle_id, storage_map)
- if meta_attributes or META_ATTRIBUTES_TAG in indelible_tags:
- append_new_meta_attributes(
- bundle_element,
- meta_attributes,
- id_provider,
- enforce_append=True,
- )
- for element in elements_without_reset_impact:
- bundle_element.append(element)
+ # There can be other elements beside bundle configuration (e.g. primitive).
+ # These elements stay untouched.
+ # Like any function that manipulates with cib, this also assumes prior
+ # validation that container is supported.
+ for child in list(bundle_element):
+ if child.tag in ["network", "storage"]:
+ reset_element(child)
+ if child.tag == META_ATTRIBUTES_TAG:
+ reset_element(child, keep_attrs=["id"])
+ if child.tag in list(GENERIC_CONTAINER_TYPES):
+ # GENERIC_CONTAINER_TYPES elements require the "image" attribute to
+ # be set.
+ reset_element(child, keep_attrs=["image"])
+
+def _get_report_unsupported_container(bundle_el):
+ return reports.resource_bundle_unsupported_container_type(
+ bundle_el.get("id"),
+ GENERIC_CONTAINER_TYPES,
+ )
def validate_update(
id_provider, bundle_el, container_options, network_options,
@@ -240,65 +213,26 @@ def validate_update(
list of string storage_map_remove -- list of storage mapping ids to remove
bool force_options -- return warnings instead of forceable errors
"""
- report_list = []
-
- # validate container options only if they are being updated
- if container_options:
- container_el = _get_container_element(bundle_el)
- if (
- container_el is not None
- and
- container_el.tag in GENERIC_CONTAINER_TYPES
- ):
- report_list.extend(
- _validate_generic_container_options_update(
- container_el,
- container_options,
- force_options
- )
- )
- else:
- report_list.append(
- reports.resource_bundle_unsupported_container_type(
- bundle_el.get("id"), GENERIC_CONTAINER_TYPES
- )
- )
-
- network_el = bundle_el.find("network")
- if network_el is None:
- report_list.extend(
- _validate_network_options_new(network_options, force_options)
- )
- else:
- report_list.extend(
- _validate_network_options_update(
- bundle_el,
- network_el,
- network_options,
- force_options
- )
- )
-
# TODO It will probably be needed to split the following validators to
# create and update variants. It should be done once the need exists and
# not sooner.
- report_list.extend(
+ return (
+ _validate_container_update(bundle_el, container_options, force_options)
+ +
+ _validate_network_update(bundle_el, network_options, force_options)
+ +
_validate_port_map_list(port_map_add, id_provider, force_options)
- )
- report_list.extend(
+ +
_validate_storage_map_list(storage_map_add, id_provider, force_options)
- )
- report_list.extend(
+ +
_validate_map_ids_exist(
bundle_el, "port-mapping", "port-map", port_map_remove
)
- )
- report_list.extend(
+ +
_validate_map_ids_exist(
bundle_el, "storage-mapping", "storage-map", storage_map_remove
)
)
- return report_list
def update(
id_provider, bundle_el, container_options, network_options,
@@ -420,8 +354,15 @@ def get_inner_resource(bundle_el):
return resources[0]
return None
+def _is_supported_container(container_el):
+ return (
+ container_el is not None
+ and
+ container_el.tag in GENERIC_CONTAINER_TYPES
+ )
+
def _validate_container(container_type, container_options, force_options=False):
- if not container_type in GENERIC_CONTAINER_TYPES:
+ if container_type not in GENERIC_CONTAINER_TYPES:
return [
reports.invalid_option_value(
"container type",
@@ -429,7 +370,10 @@ def _validate_container(container_type, container_options, force_options=False):
GENERIC_CONTAINER_TYPES,
)
]
+ return _validate_generic_container_options(container_options, force_options)
+
+def _validate_generic_container_options(container_options, force_options=False):
validators = [
validate.NamesIn(
GENERIC_CONTAINER_OPTIONS,
@@ -463,8 +407,32 @@ def _validate_container(container_type, container_options, force_options=False):
deprecation_reports
)
+def _validate_container_reset(bundle_el, container_options, force_options):
+ # Unlike in the case of update, in reset empty options are not necessary
+ # valid - user MUST set everything (including required options e.g. image).
+ if (
+ container_options
+ and
+ not _is_supported_container(_get_container_element(bundle_el))
+ ):
+ return [_get_report_unsupported_container(bundle_el)]
+ return _validate_generic_container_options(container_options, force_options)
+
+def _validate_container_update(bundle_el, options, force_options):
+ # Validate container options only if they are being updated. Empty options
+ # are valid - user DOESN'T NEED to change anything.
+ if not options:
+ return []
+
+ container_el = _get_container_element(bundle_el)
+ if not _is_supported_container(container_el):
+ return [_get_report_unsupported_container(bundle_el)]
+ return _validate_generic_container_options_update(
+ container_el, options, force_options
+ )
+
def _validate_generic_container_options_update(
- docker_el, options, force_options
+ container_el, options, force_options
):
validators_optional_options = [
validate.ValueNonnegativeInteger("masters"),
@@ -517,7 +485,7 @@ def _validate_generic_container_options_update(
if (
options.get("masters")
and
- docker_el.get("promoted-max") and options.get("promoted-max") != ""
+ container_el.get("promoted-max") and options.get("promoted-max") != ""
):
deprecation_reports.append(
reports.prerequisite_option_must_not_be_set(
@@ -527,7 +495,7 @@ def _validate_generic_container_options_update(
if (
options.get("promoted-max")
and
- docker_el.get("masters") and options.get("masters") != ""
+ container_el.get("masters") and options.get("masters") != ""
):
deprecation_reports.append(
reports.prerequisite_option_must_not_be_set(
@@ -571,6 +539,14 @@ def _is_pcmk_remote_acccessible_after_update(network_el, options):
return not (case1 or case2 or case3)
+def _validate_network_update(bundle_el, options, force_options):
+ network_el = bundle_el.find("network")
+ if network_el is None:
+ return _validate_network_options_new(options, force_options)
+ return _validate_network_options_update(
+ bundle_el, network_el, options, force_options
+ )
+
def _validate_network_options_update(
bundle_el, network_el, options, force_options
):
diff --git a/pcs/lib/commands/resource.py b/pcs/lib/commands/resource.py
index 89e7f225..af648022 100644
--- a/pcs/lib/commands/resource.py
+++ b/pcs/lib/commands/resource.py
@@ -180,7 +180,9 @@ def _check_special_cases(
_find_bundle = partial(find_element_by_tag_and_id, resource.bundle.TAG)
-def _get_required_cib_version_for_container(container_type, container_options):
+def _get_required_cib_version_for_container(
+ container_options, container_type=None
+):
if container_type == "podman":
return Version(3, 2, 0)
@@ -567,8 +569,8 @@ def bundle_create(
resource.common.are_meta_disabled(meta_attributes)
),
required_cib_version=_get_required_cib_version_for_container(
+ container_options,
container_type,
- container_options
),
) as resources_section:
# no need to run validations related to remote and guest nodes as those
@@ -602,7 +604,7 @@ def bundle_create(
resource.common.disable(bundle_element, id_provider)
def bundle_reset(
- env, bundle_id, container_type, container_options=None,
+ env, bundle_id, container_options=None,
network_options=None, port_map=None, storage_map=None, meta_attributes=None,
force_options=False,
ensure_disabled=False,
@@ -614,7 +616,6 @@ def bundle_reset(
LibraryEnvironment env -- provides communication with externals
string bundle_id -- id of the bundle to reset
- string container_type -- container engine name (docker, lxc...)
dict container_options -- container options
dict network_options -- network options
list of dict port_map -- a list of port mapping options
@@ -640,15 +641,20 @@ def bundle_reset(
resource.common.are_meta_disabled(meta_attributes)
),
required_cib_version=_get_required_cib_version_for_container(
- container_type,
container_options
),
) as resources_section:
+ bundle_element = _find_bundle(resources_section, bundle_id)
+ env.report_processor.process_list(
+ resource.bundle.validate_reset_to_minimal(bundle_element)
+ )
+ resource.bundle.reset_to_minimal(bundle_element)
+
id_provider = IdProvider(resources_section)
env.report_processor.process_list(
resource.bundle.validate_reset(
id_provider,
- container_type,
+ bundle_element,
container_options,
network_options,
port_map,
@@ -658,23 +664,21 @@ def bundle_reset(
)
)
- bundle_element = _find_bundle(resources_section, bundle_id)
- resource.bundle.reset(
- bundle_element,
+ resource.bundle.update(
id_provider,
- bundle_id,
- container_type,
+ bundle_element,
container_options,
network_options,
- port_map,
- storage_map,
- meta_attributes,
+ port_map_add=port_map,
+ port_map_remove=[],
+ storage_map_add=storage_map,
+ storage_map_remove=[],
+ meta_attributes=meta_attributes,
)
if ensure_disabled:
resource.common.disable(bundle_element, id_provider)
-
def bundle_update(
env, bundle_id, container_options=None, network_options=None,
port_map_add=None, port_map_remove=None, storage_map_add=None,
@@ -706,14 +710,13 @@ def bundle_update(
storage_map_remove = storage_map_remove or []
meta_attributes = meta_attributes or {}
- required_cib_version = Version(2, 8, 0)
- if "promoted-max" in container_options:
- required_cib_version = Version(3, 0, 0)
with resource_environment(
env,
wait,
[bundle_id],
- required_cib_version=required_cib_version
+ required_cib_version=_get_required_cib_version_for_container(
+ container_options
+ ),
) as resources_section:
# no need to run validations related to remote and guest nodes as those
# nodes can only be created from primitive resources
diff --git a/pcs/lib/xml_tools.py b/pcs/lib/xml_tools.py
index 43cde3b5..c058c288 100644
--- a/pcs/lib/xml_tools.py
+++ b/pcs/lib/xml_tools.py
@@ -154,3 +154,18 @@ def remove_when_pointless(element, attribs_important=True):
"""
if not is_element_useful(element, attribs_important):
element.getparent().remove(element)
+
+def reset_element(element, keep_attrs=None):
+ """
+ Remove all subelements and all attributes (except mentioned in keep_attrs)
+ of given element.
+
+ lxml.etree.element element -- element to reset
+ list keep_attrs -- names of attributes thas should be kept
+ """
+ keep_attrs = keep_attrs or []
+ for child in list(element):
+ element.remove(child)
+ for key in element.attrib.keys():
+ if key not in keep_attrs:
+ del element.attrib[key]
diff --git a/pcs/pcs.8 b/pcs/pcs.8
index 1e794c60..4ae646e2 100644
--- a/pcs/pcs.8
+++ b/pcs/pcs.8
@@ -191,7 +191,7 @@ Remove the clone which contains the specified group or resource (the resource or
bundle create <bundle id> container <container type> [<container options>] [network <network options>] [port\-map <port options>]... [storage\-map <storage options>]... [meta <meta options>] [\fB\-\-disabled\fR] [\fB\-\-wait\fR[=n]]
Create a new bundle encapsulating no resources. The bundle can be used either as it is or a resource may be put into it at any time. If \fB\-\-disabled\fR is specified, the bundle is not started automatically. If \fB\-\-wait\fR is specified, pcs will wait up to 'n' seconds for the bundle to start and then return 0 on success or 1 on error. If 'n' is not specified it defaults to 60 minutes.
.TP
-bundle reset <bundle id> container <container type> [<container options>] [network <network options>] [port\-map <port options>]... [storage\-map <storage options>]... [meta <meta options>] [\fB\-\-disabled\fR] [\fB\-\-wait\fR[=n]]
+bundle reset <bundle id> [container <container options>] [network <network options>] [port\-map <port options>]... [storage\-map <storage options>]... [meta <meta options>] [\fB\-\-disabled\fR] [\fB\-\-wait\fR[=n]]
Configure specified bundle with given options. Unlike bundle update, this command resets the bundle according given options - no previous options are kept. Resources inside the bundle are kept as they are. If \fB\-\-disabled\fR is specified, the bundle is not started automatically. If \fB\-\-wait\fR is specified, pcs will wait up to 'n' seconds for the bundle to start and then return 0 on success or 1 on error. If 'n' is not specified it defaults to 60 minutes.
.TP
bundle update <bundle id> [container <container options>] [network <network options>] [port\-map (add <port options>) | (delete | remove <id>...)]... [storage\-map (add <storage options>) | (delete | remove <id>...)]... [meta <meta options>] [\fB\-\-wait\fR[=n]]
diff --git a/pcs/resource.py b/pcs/resource.py
index a29ace5f..973f9e64 100644
--- a/pcs/resource.py
+++ b/pcs/resource.py
@@ -22,6 +22,7 @@ from pcs.cli.common.parse_args import (
)
from pcs.cli.resource.parse_args import (
parse_bundle_create_options,
+ parse_bundle_reset_options,
parse_bundle_update_options,
parse_create as parse_create_args,
)
@@ -2929,19 +2930,26 @@ def resource_bundle_create_cmd(lib, argv, modifiers):
* --wait
* -f - CIB file
"""
- _resource_bundle_configure(lib.resource.bundle_create, argv, modifiers)
+ modifiers.ensure_only_supported("--force", "--disabled", "--wait", "-f")
+ if not argv:
+ raise CmdLineInputError()
-def resource_bundle_reset_cmd(lib, argv, modifiers):
- """
- Options:
- * --force - allow unknown options
- * --disabled - create as a stopped bundle
- * --wait
- * -f - CIB file
- """
- _resource_bundle_configure(lib.resource.bundle_reset, argv, modifiers)
+ bundle_id = argv[0]
+ parts = parse_bundle_create_options(argv[1:])
+ lib.resource.bundle_create(
+ bundle_id,
+ parts["container_type"],
+ container_options=parts["container"],
+ network_options=parts["network"],
+ port_map=parts["port_map"],
+ storage_map=parts["storage_map"],
+ meta_attributes=parts["meta"],
+ force_options=modifiers.get("--force"),
+ ensure_disabled=modifiers.get("--disabled"),
+ wait=modifiers.get("--wait"),
+ )
-def _resource_bundle_configure(call_lib, argv, modifiers):
+def resource_bundle_reset_cmd(lib, argv, modifiers):
"""
Options:
* --force - allow unknown options
@@ -2954,10 +2962,9 @@ def _resource_bundle_configure(call_lib, argv, modifiers):
raise CmdLineInputError()
bundle_id = argv[0]
- parts = parse_bundle_create_options(argv[1:])
- call_lib(
+ parts = parse_bundle_reset_options(argv[1:])
+ lib.resource.bundle_reset(
bundle_id,
- parts["container_type"],
container_options=parts["container"],
network_options=parts["network"],
port_map=parts["port_map"],
diff --git a/pcs/usage.py b/pcs/usage.py
index 2566d522..c0c6d712 100644
--- a/pcs/usage.py
+++ b/pcs/usage.py
@@ -476,7 +476,7 @@ Commands:
to start and then return 0 on success or 1 on error. If 'n' is not
specified it defaults to 60 minutes.
- bundle reset <bundle id> container <container type> [<container options>]
+ bundle reset <bundle id> [container <container options>]
[network <network options>] [port-map <port options>]...
[storage-map <storage options>]... [meta <meta options>]
[--disabled] [--wait[=n]]
diff --git a/pcs_test/tier0/cib_resource/test_bundle.py b/pcs_test/tier0/cib_resource/test_bundle.py
index b80b0606..96fcc082 100644
--- a/pcs_test/tier0/cib_resource/test_bundle.py
+++ b/pcs_test/tier0/cib_resource/test_bundle.py
@@ -79,7 +79,7 @@ class BundleReset(BundleCreateCommon):
"resource bundle create B2 container docker image=pcs:test"
)
self.assert_effect(
- "resource bundle reset B1 container docker image=pcs:new",
+ "resource bundle reset B1 container image=pcs:new",
"""
<resources>
<bundle id="B1">
diff --git a/pcs_test/tier0/lib/commands/resource/bundle_common.py b/pcs_test/tier0/lib/commands/resource/bundle_common.py
index 4fbf00e4..5d7a4b42 100644
--- a/pcs_test/tier0/lib/commands/resource/bundle_common.py
+++ b/pcs_test/tier0/lib/commands/resource/bundle_common.py
@@ -50,13 +50,13 @@ class SetUpMixin:
)
class UpgradeMixin(FixturesMixin):
- upgraded_cib_filename = None
+ old_version_cib_filename = None
def test_cib_upgrade(self):
(self.config
.runner.cib.load(
name="load_cib_old_version",
- filename=self.upgraded_cib_filename,
+ filename=self.old_version_cib_filename,
before="runner.cib.load"
)
.runner.cib.upgrade(before="runner.cib.load")
diff --git a/pcs_test/tier0/lib/commands/resource/test_bundle_create.py b/pcs_test/tier0/lib/commands/resource/test_bundle_create.py
index 0bc9dc65..ba1ee0ac 100644
--- a/pcs_test/tier0/lib/commands/resource/test_bundle_create.py
+++ b/pcs_test/tier0/lib/commands/resource/test_bundle_create.py
@@ -82,16 +82,16 @@ class CreateParametrizedContainerMixin(
class CreateDocker(CreateParametrizedContainerMixin, TestCase):
container_type = "docker"
- upgraded_cib_filename = "cib-empty-2.0.xml"
+ old_version_cib_filename = "cib-empty-2.0.xml"
class CreatePodman(CreateParametrizedContainerMixin, TestCase):
container_type = "podman"
- upgraded_cib_filename = "cib-empty-3.1.xml"
+ old_version_cib_filename = "cib-empty-3.1.xml"
class CreateRkt(CreateParametrizedContainerMixin, TestCase):
container_type = "rkt"
- upgraded_cib_filename = "cib-empty-2.9.xml"
+ old_version_cib_filename = "cib-empty-2.9.xml"
class CreateWithNetwork(CreateCommandMixin, NetworkMixin, TestCase):
container_type = "docker"
diff --git a/pcs_test/tier0/lib/commands/resource/test_bundle_reset.py b/pcs_test/tier0/lib/commands/resource/test_bundle_reset.py
index e5d6ef16..cfc84500 100644
--- a/pcs_test/tier0/lib/commands/resource/test_bundle_reset.py
+++ b/pcs_test/tier0/lib/commands/resource/test_bundle_reset.py
@@ -12,6 +12,7 @@ from pcs_test.tier0.lib.commands.resource.bundle_common import(
AllOptionsMixin,
WaitMixin,
)
+from pcs_test.tools import fixture
from pcs.common import report_codes
from pcs.lib.commands.resource import bundle_reset
@@ -26,16 +27,13 @@ class BaseMixin(FixturesMixin):
def initial_resources(self):
return self.fixture_resources_bundle_simple
- def bundle_reset(
- self, bundle_id=None, container_type=None, **params
- ):
+ def bundle_reset(self, bundle_id=None, **params):
if "container_options" not in params:
params["container_options"] = {"image": self.image}
bundle_reset(
self.env_assist.get_env(),
bundle_id=bundle_id or self.bundle_id,
- container_type=container_type or self.container_type,
**params
)
@@ -44,10 +42,11 @@ class BaseMixin(FixturesMixin):
class MinimalMixin(BaseMixin, SetUpMixin):
container_type = None
- new_container_type = None
initial_cib_filename = "cib-empty-3.2.xml"
def test_success_zero_change(self):
+ # Resets a bundle with only an image set to a bundle with the same
+ # image set and no other options.
self.config.env.push_cib(resources=self.initial_resources)
self.bundle_reset()
@@ -63,13 +62,12 @@ class MinimalMixin(BaseMixin, SetUpMixin):
"""
.format(
bundle_id=self.bundle_id,
- container_type=self.new_container_type,
+ container_type=self.container_type,
image=new_image,
)
,
})
self.bundle_reset(
- container_type=self.new_container_type,
container_options={"image": new_image},
)
@@ -92,9 +90,20 @@ class MinimalMixin(BaseMixin, SetUpMixin):
expected_in_processor=False,
)
+ def test_no_options_set(self):
+ self.env_assist.assert_raise_library_error(
+ lambda: bundle_reset(self.env_assist.get_env(), self.bundle_id),
+ [
+ fixture.error(
+ report_codes.REQUIRED_OPTIONS_ARE_MISSING,
+ option_names=["image"],
+ option_type="container",
+ ),
+ ]
+ )
+
class FullMixin(SetUpMixin, BaseMixin):
container_type = None
- new_container_type = None
fixture_primitive = """
<primitive class="ocf" id="A" provider="heartbeat" type="Dummy"/>
"""
@@ -104,25 +113,12 @@ class FullMixin(SetUpMixin, BaseMixin):
return """
<resources>
<bundle id="{bundle_id}">
- <meta_attributes id="{bundle_id}-meta_attributes">
- <nvpair id="{bundle_id}-meta_attributes-target-role"
- name="target-role"
- value="Stopped"
- />
- </meta_attributes>
<{container_type}
image="{image}"
promoted-max="0"
- replicas="0"
- replicas-per-host="0"
+ replicas="1"
+ replicas-per-host="1"
/>
- <meta_attributes id="{bundle_id}-meta_attributes">
- <nvpair
- id="{bundle_id}-meta_attributes-is-managed"
- name="is-managed"
- value="false"
- />
- </meta_attributes>
<network
control-port="12345"
host-interface="eth0"
@@ -147,6 +143,13 @@ class FullMixin(SetUpMixin, BaseMixin):
target-dir="/tmp/{container_type}2b"
/>
</storage>
+ <meta_attributes id="{bundle_id}-meta_attributes">
+ <nvpair
+ id="{bundle_id}-meta_attributes-target-role"
+ name="target-role"
+ value="Stopped"
+ />
+ </meta_attributes>
{fixture_primitive}
</bundle>
</resources>
@@ -174,7 +177,7 @@ class FullMixin(SetUpMixin, BaseMixin):
</bundle>
"""
.format(
- container_type=self.new_container_type,
+ container_type=self.container_type,
bundle_id=self.bundle_id,
fixture_primitive=self.fixture_primitive,
image=new_image,
@@ -183,7 +186,6 @@ class FullMixin(SetUpMixin, BaseMixin):
})
self.bundle_reset(
- container_type=self.new_container_type,
container_options={"image": new_image},
)
@@ -220,12 +222,13 @@ class FullMixin(SetUpMixin, BaseMixin):
<storage-mapping
id="{bundle_id}-storage-map"
options="extra options 2"
- source-dir="/tmp/{container_type}2a"
- target-dir="/tmp/{container_type}2b"
+ source-dir="/tmp/{container_type}2aa"
+ target-dir="/tmp/{container_type}2bb"
/>
</storage>
<meta_attributes id="{bundle_id}-meta_attributes">
- <nvpair id="{bundle_id}-meta_attributes-target-role"
+ <nvpair
+ id="{bundle_id}-meta_attributes-target-role"
name="target-role"
value="Started"
/>
@@ -234,7 +237,7 @@ class FullMixin(SetUpMixin, BaseMixin):
</bundle>
"""
.format(
- container_type=self.new_container_type,
+ container_type=self.container_type,
bundle_id=self.bundle_id,
fixture_primitive=self.fixture_primitive,
image=new_image,
@@ -242,7 +245,6 @@ class FullMixin(SetUpMixin, BaseMixin):
,
})
self.bundle_reset(
- container_type=self.new_container_type,
container_options={
"image": new_image,
"promoted-max": "1",
@@ -262,8 +264,8 @@ class FullMixin(SetUpMixin, BaseMixin):
storage_map=[
{
"options": "extra options 2",
- "source-dir": f"/tmp/{self.new_container_type}2a",
- "target-dir": f"/tmp/{self.new_container_type}2b",
+ "source-dir": f"/tmp/{self.container_type}2aa",
+ "target-dir": f"/tmp/{self.container_type}2bb",
},
],
meta_attributes={
@@ -271,6 +273,80 @@ class FullMixin(SetUpMixin, BaseMixin):
}
)
+ def test_success_keep_map_ids(self):
+ self.config.env.push_cib(replace={
+ ".//resources/bundle/network":
+ f"""
+ <network
+ control-port="12345"
+ host-interface="eth0"
+ host-netmask="24"
+ ip-range-start="192.168.100.200"
+ >
+ <port-mapping
+ id="{self.bundle_id}-port-map-1001"
+ internal-port="3002"
+ port="3000"
+ />
+ <port-mapping
+ id="{self.bundle_id}-port-map-3000-3300"
+ range="4000-4400"
+ />
+ </network>
+ """
+ ,
+ ".//resources/bundle/storage":
+ f"""
+ <storage>
+ <storage-mapping
+ id="{self.bundle_id}-storage-map"
+ options="extra options 2"
+ source-dir="/tmp/{self.container_type}2aa"
+ target-dir="/tmp/{self.container_type}2bb"
+ />
+ </storage>
+ """
+ ,
+ })
+
+ # Every value is kept as before except port_map and storage_map.
+ self.bundle_reset(
+ container_options={
+ "image": self.image,
+ "promoted-max": "0",
+ "replicas": "1",
+ "replicas-per-host": "1",
+ },
+ network_options={
+ "control-port": "12345",
+ "host-interface": "eth0",
+ "host-netmask": "24",
+ "ip-range-start": "192.168.100.200",
+ },
+ port_map=[
+ {
+ "id": f"{self.bundle_id}-port-map-1001",
+ "internal-port": "3002",
+ "port": "3000",
+ },
+ {
+ "id": f"{self.bundle_id}-port-map-3000-3300",
+ "range": "4000-4400",
+ },
+ ],
+ storage_map=[
+ {
+ "id": f"{self.bundle_id}-storage-map",
+ "options": "extra options 2",
+ "source-dir": f"/tmp/{self.container_type}2aa",
+ "target-dir": f"/tmp/{self.container_type}2bb",
+ },
+ ],
+ meta_attributes={
+ "target-role": "Stopped",
+ }
+ )
+
class ResetParametrizedContainerMixin(
BaseMixin, ParametrizedContainerMixin, UpgradeMixin
):
@@ -278,39 +354,33 @@ class ResetParametrizedContainerMixin(
class MinimalRkt(MinimalMixin, TestCase):
container_type = "rkt"
- new_container_type = "docker"
class MinimalPodman(MinimalMixin, TestCase):
container_type = "podman"
- new_container_type = "rkt"
class MinimalDocker(MinimalMixin, TestCase):
container_type = "docker"
- new_container_type = "rkt"
class FullRkt(FullMixin, TestCase):
container_type = "rkt"
- new_container_type = "docker"
class FullPodman(FullMixin, TestCase):
container_type = "podman"
- new_container_type = "rkt"
class FullDocker(FullMixin, TestCase):
container_type = "docker"
- new_container_type = "docker"
-class CreateParametrizedPodman(ResetParametrizedContainerMixin, TestCase):
+class ResetParametrizedPodman(ResetParametrizedContainerMixin, TestCase):
container_type = "podman"
- upgraded_cib_filename = "cib-empty-3.1.xml"
+ old_version_cib_filename = "cib-empty-2.6.xml"
-class CreateParametrizedDocker(ResetParametrizedContainerMixin, TestCase):
+class ResetParametrizedDocker(ResetParametrizedContainerMixin, TestCase):
container_type = "docker"
- upgraded_cib_filename = "cib-empty-2.0.xml"
+ old_version_cib_filename = "cib-empty-2.0.xml"
-class CreateParametrizedRkt(ResetParametrizedContainerMixin, TestCase):
+class ResetParametrizedRkt(ResetParametrizedContainerMixin, TestCase):
container_type = "rkt"
- upgraded_cib_filename = "cib-empty-2.9.xml"
+ old_version_cib_filename = "cib-empty-2.6.xml"
class ResetWithNetwork(BaseMixin, NetworkMixin, TestCase):
container_type = "docker"
@@ -323,9 +393,114 @@ class ResetWithStorageMap(BaseMixin, StorageMapMixin, TestCase):
class ResetWithMetaMap(BaseMixin, MetaMixin, TestCase):
container_type = "docker"
+ def test_success(self):
+ # When there is no meta attributes the new one are put on the first
+ # possition (since reset now uses update internally). This is the reason
+ # for reimplementation of this MetaMixin test.
+ self.config.env.push_cib(
+ resources="""
+ <resources>
+ <bundle id="{bundle_id}">
+ <meta_attributes id="{bundle_id}-meta_attributes">
+ <nvpair id="{bundle_id}-meta_attributes-is-managed"
+ name="is-managed" value="false" />
+ <nvpair id="{bundle_id}-meta_attributes-target-role"
+ name="target-role" value="Stopped" />
+ </meta_attributes>
+ <{container_type} image="{image}" />
+ </bundle>
+ </resources>
+ """
+ .format(
+ container_type=self.container_type,
+ bundle_id=self.bundle_id,
+ image=self.image,
+ )
+ )
+ self.run_bundle_cmd(
+ meta_attributes={
+ "target-role": "Stopped",
+ "is-managed": "false",
+ }
+ )
class ResetWithAllOptions(BaseMixin, AllOptionsMixin, TestCase):
container_type = "docker"
class ResetWithWait(BaseMixin, WaitMixin, TestCase):
container_type = "docker"
+
+class ResetUnknownContainerType(BaseMixin, SetUpMixin, TestCase):
+ container_type = "unknown"
+ def test_error_or_unknown_container(self):
+ self.env_assist.assert_raise_library_error(
+ lambda: bundle_reset(self.env_assist.get_env(), self.bundle_id),
+ [
+ fixture.error(
+ report_codes.RESOURCE_BUNDLE_UNSUPPORTED_CONTAINER_TYPE,
+ bundle_id="B1",
+ supported_container_types=["docker", "podman", "rkt"],
+ ),
+ ]
+ )
+
+class NoMetaIdRegenerationMixin(BaseMixin, SetUpMixin):
+ @property
+ def initial_resources(self):
+ return """
+ <resources>
+ <bundle id="{bundle_id}">
+ <{container_type}
+ image="{image}"
+ promoted-max="0"
+ replicas="1"
+ replicas-per-host="1"
+ />
+ <meta_attributes id="CUSTOM_ID">
+ <nvpair
+ id="ANOTHER_ID-target-role"
+ name="target-role"
+ value="Stopped"
+ />
+ </meta_attributes>
+ </bundle>
+ </resources>
+ """.format(
+ container_type=self.container_type,
+ bundle_id=self.bundle_id,
+ image=self.image,
+ )
+ def test_dont_regenerate_meta_attributes_id(self):
+ self.config.env.push_cib(replace={
+ ".//resources/bundle/meta_attributes":
+ f"""
+ <meta_attributes id="CUSTOM_ID">
+ <nvpair
+ id="CUSTOM_ID-target-role"
+ name="target-role"
+ value="Stopped"
+ />
+ </meta_attributes>
+ """
+ ,
+ })
+ self.bundle_reset(
+ container_options={
+ "image": self.image,
+ "promoted-max": "0",
+ "replicas": "1",
+ "replicas-per-host": "1",
+ },
+ meta_attributes={
+ "target-role": "Stopped",
+ }
+ )
+
+class NoMetaIdRegenerationDocker(NoMetaIdRegenerationMixin, TestCase):
+ container_type = "docker"
+
+class NoMetaIdRegenerationPodman(NoMetaIdRegenerationMixin, TestCase):
+ container_type = "podman"
+
+class NoMetaIdRegenerationRkt(NoMetaIdRegenerationMixin, TestCase):
+ container_type = "rkt"
--
2.21.0