Blob Blame History Raw
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