diff --git a/doc/cluster-merge.rst b/doc/cluster-merge.rst index 8d3f65b6dfd69317cd7df68dd179db2b51b844be..343a757041db347cdc2eab0db3982fd1acccbe9e 100644 --- a/doc/cluster-merge.rst +++ b/doc/cluster-merge.rst @@ -23,8 +23,8 @@ clusters into. The usage of ``cluster-merge`` is as follows:: - cluster-merge [--debug|--verbose] [--watcher-pause-period SECONDS] <cluster> \ - <cluster...> + cluster-merge [--debug|--verbose] [--watcher-pause-period SECONDS] \ + [--groups [merge|rename]] <cluster> [<cluster...>] You can provide multiple clusters. The tool will then go over every cluster in serial and perform the steps to merge it into the invoking @@ -39,6 +39,15 @@ These options can be used to control the behaviour of the tool: ``--watcher-pause-period`` Define the period of time in seconds the watcher shall be disabled, default is 1800 seconds (30 minutes). +``--groups`` + This option controls how ``cluster-merge`` handles duplicate node + group names on the merging clusters. If ``merge`` is specified then + all node groups with the same name will be merged into one. If + ``rename`` is specified, then conflicting node groups on the remove + clusters will have their cluster name appended to the group name. If + this option is not speicifed, then ``cluster-merge`` will refuse to + continue if it finds conflicting group names, otherwise it will + proceed as normal. Rollback diff --git a/tools/cluster-merge b/tools/cluster-merge index d2e8c1f4deab23f05ed17b1cbfeef79402dec5e8..e208b6db3bc50c02811c4396d565cf6274282daa 100755 --- a/tools/cluster-merge +++ b/tools/cluster-merge @@ -42,12 +42,20 @@ from ganeti import ssh from ganeti import utils +_GROUPS_MERGE = "merge" +_GROUPS_RENAME = "rename" +_CLUSTERMERGE_ECID = "clustermerge-ecid" + PAUSE_PERIOD_OPT = cli.cli_option("-p", "--watcher-pause-period", default=1800, action="store", type="int", dest="pause_period", help=("Amount of time in seconds watcher" " should be suspended from running")) -_CLUSTERMERGE_ECID = "clustermerge-ecid" +GROUPS_OPT = cli.cli_option("--groups", default=None, metavar="STRATEGY", + choices=(_GROUPS_MERGE, _GROUPS_RENAME), dest="groups", + help=("How to handle groups that have the" + " same name (One of: %s/%s)" % + (_GROUPS_MERGE, _GROUPS_RENAME))) def Flatten(unflattened_list): @@ -92,11 +100,12 @@ class Merger(object): """Handling the merge. """ - def __init__(self, clusters, pause_period): + def __init__(self, clusters, pause_period, groups): """Initialize object with sane defaults and infos required. @param clusters: The list of clusters to merge in @param pause_period: The time watcher shall be disabled for + @param groups: How to handle group conflicts """ self.merger_data = [] @@ -105,6 +114,7 @@ class Merger(object): self.work_dir = tempfile.mkdtemp(suffix="cluster-merger") (self.cluster_name, ) = cli.GetClient().QueryConfigValues(["cluster_name"]) self.ssh_runner = ssh.SshRunner(self.cluster_name) + self.groups = groups def Setup(self): """Sets up our end so we can do the merger. @@ -304,7 +314,39 @@ class Merger(object): ConfigWriter.AddNodeGroup takes care of making sure there are no conflicts. """ # pylint: disable-msg=R0201 - for grp in other_config.GetAllNodeGroupsInfo().values(): + logging.info("Node group conflict strategy: %s" % self.groups) + + my_grps = my_config.GetAllNodeGroupsInfo().values() + other_grps = other_config.GetAllNodeGroupsInfo().values() + + # Check for node group naming conflicts: + conflicts = [] + for other_grp in other_grps: + for my_grp in my_grps: + if other_grp.name == my_grp.name: + conflicts.append(other_grp) + + if conflicts: + conflict_names = utils.CommaJoin([g.name for g in conflicts]) + logging.info("Node groups in both local and remote cluster: %s" % + conflict_names) + + # User hasn't specified how to handle conflicts + if not self.groups: + raise errors.CommandError("The following node group(s) are in both" + " clusters, and no merge strategy has been" + " supplied (see the --groups option): %s" % + conflict_names) + + # User wants to rename conflicts + if self.groups == _GROUPS_RENAME: + for grp in conflicts: + new_name = "%s-%s" % (grp.name, other_config.GetClusterName()) + logging.info("Renaming remote node group from %s to %s" + " to resolve conflict" % (grp.name, new_name)) + grp.name = new_name + + for grp in other_grps: #TODO: handle node group conflicts my_config.AddNodeGroup(grp, _CLUSTERMERGE_ECID) @@ -483,13 +525,16 @@ def main(): """ program = os.path.basename(sys.argv[0]) - parser = optparse.OptionParser(usage=("%prog [--debug|--verbose]" + parser = optparse.OptionParser(usage=("%%prog [--debug|--verbose]" " [--watcher-pause-period SECONDS]" - " <cluster> <cluster...>"), + " [--groups [%s|%s]]" + " <cluster> [<cluster...>]" % + (_GROUPS_MERGE, _GROUPS_RENAME)), prog=program) parser.add_option(cli.DEBUG_OPT) parser.add_option(cli.VERBOSE_OPT) parser.add_option(PAUSE_PERIOD_OPT) + parser.add_option(GROUPS_OPT) (options, args) = parser.parse_args() @@ -498,7 +543,8 @@ def main(): if not args: parser.error("No clusters specified") - cluster_merger = Merger(utils.UniqueSequence(args), options.pause_period) + cluster_merger = Merger(utils.UniqueSequence(args), options.pause_period, + options.groups) try: try: cluster_merger.Setup()