diff --git a/confluent_client/bin/nodegrouprename b/confluent_client/bin/nodegrouprename new file mode 100644 index 00000000..55429046 --- /dev/null +++ b/confluent_client/bin/nodegrouprename @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2019 Lenovo +# +# Licensed 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. + +__author__ = 'jjohnson2,alin37,andywray' + +import optparse +import os +import signal +import sys + +try: + signal.signal(signal.SIGPIPE, signal.SIG_DFL) +except AttributeError: + pass +path = os.path.dirname(os.path.realpath(__file__)) +path = os.path.realpath(os.path.join(path, '..', 'lib', 'python')) +if path.startswith('/opt'): + sys.path.append(path) + +import confluent.client as client + +def main(): + argparser = optparse.OptionParser( + usage="Usage: %prog \n") + (options, args) = argparser.parse_args() + noderange="" + nodelist="" + nodelist = '/nodegroups/' + session = client.Command() + exitcode = 0 + requestargs=args[1:] + nodetype='noderange' + if len(args) != 2: + argparser.print_help() + sys.exit(1) + else: + for res in session.update( + '/nodegroups/{0}/attributes/rename'.format(args[0]), + {'rename': args[1]}): + if 'error' in res: + sys.stderr.write(res['error'] + '\n') + exitcode = 1 + else: + print('{0}: {1}'.format(res['oldname'], res['newname'])) + + sys.exit(exitcode) + +if __name__ == '__main__': + main() diff --git a/confluent_client/bin/noderename b/confluent_client/bin/noderename new file mode 100644 index 00000000..3308a293 --- /dev/null +++ b/confluent_client/bin/noderename @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2019 Lenovo +# +# Licensed 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 optparse +import os +import signal +import sys +try: + signal.signal(signal.SIGPIPE, signal.SIG_DFL) +except AttributeError: + pass + +path = os.path.dirname(os.path.realpath(__file__)) +path = os.path.realpath(os.path.join(path, '..', 'lib', 'python')) +if path.startswith('/opt'): + sys.path.append(path) + +import confluent.client as client + +argparser = optparse.OptionParser(usage="Usage: %prog ") +(options, args) = argparser.parse_args() +try: + noderange = args[0] +except IndexError: + argparser.print_help() + sys.exit(1) +client.check_globbing(noderange) +identifystate = None +if len(sys.argv) > 2: + newname = sys.argv[2] +else: + argparser.print_help() + sys.exit(1) +session = client.Command() +exitcode = 0 +sys.exit( + session.simple_noderange_command(noderange, 'attributes/rename', newname)) + diff --git a/confluent_server/confluent/config/configmanager.py b/confluent_server/confluent/config/configmanager.py index 9bf9ed86..9d67a6ab 100644 --- a/confluent_server/confluent/config/configmanager.py +++ b/confluent_server/confluent/config/configmanager.py @@ -181,6 +181,14 @@ def _rpc_master_set_node_attributes(tenant, attribmap, autocreate): ConfigManager(tenant).set_node_attributes(attribmap, autocreate) +def _rpc_master_rename_nodes(tenant, renamemap): + ConfigManager(tenant).rename_nodes(renamemap) + + +def _rpc_master_rename_nodegroups(tenant, renamemap): + ConfigManager(tenant).rename_nodegroups(renamemap) + + def _rpc_master_clear_node_attributes(tenant, nodes, attributes): ConfigManager(tenant).clear_node_attributes(nodes, attributes) @@ -234,6 +242,14 @@ def _rpc_set_node_attributes(tenant, attribmap, autocreate): ConfigManager(tenant)._true_set_node_attributes(attribmap, autocreate) +def _rpc_rename_nodes(tenant, renamemap): + ConfigManager(tenant)._true_rename_nodes(renamemap) + + +def _rpc_rename_nodegroups(tenant, renamemap): + ConfigManager(tenant)._true_rename_nodegroups(renamemap) + + def _rpc_set_group_attributes(tenant, attribmap, autocreate): ConfigManager(tenant)._true_set_group_attributes(attribmap, autocreate) @@ -288,9 +304,9 @@ def logException(): event=confluent.log.Events.stacktrace) -def _do_add_watcher(watcher, added, configmanager): +def _do_add_watcher(watcher, added, configmanager, renamed=()): try: - watcher(added=added, deleting=[], configmanager=configmanager) + watcher(added=added, deleting=(), renamed=renamed, configmanager=configmanager) except Exception: logException() @@ -1705,7 +1721,7 @@ class ConfigManager(object): def _true_del_nodes(self, nodes): if self.tenant in self._nodecollwatchers: for watcher in self._nodecollwatchers[self.tenant].itervalues(): - watcher(added=[], deleting=nodes, configmanager=self) + watcher(added=(), deleting=nodes, renamed=(), configmanager=self) changeset = {} for node in nodes: # set a reserved attribute for the sake of the change notification @@ -1789,6 +1805,86 @@ class ConfigManager(object): attribmap[node]['groups'] = [] self.set_node_attributes(attribmap, autocreate=True) + def rename_nodes(self, renamemap): + if cfgleader: + return exec_on_leader('_rpc_master_rename_nodes', self.tenant, + renamemap) + if cfgstreams: + exec_on_followers('_rpc_rename_nodes', self.tenant, renamemap) + self._true_rename_nodes(renamemap) + + def _true_rename_nodes(self, renamemap): + oldnames = set(renamemap) + exprmgr = None + currnodes = set(self._cfgstore['nodes']) + missingnodes = oldnames - currnodes + if missingnodes: + raise ValueError( + 'The following nodes to rename do not exist: {0}'.format( + ','.join(missingnodes))) + newnames = set([]) + for name in renamemap: + newnames.add(renamemap[name]) + if newnames & currnodes: + raise ValueError( + 'The following requested new names conflict with existing nodes: {0}'.format( + ','.join(newnames & currnodes))) + for name in renamemap: + self._cfgstore['nodes'][renamemap[name]] = self._cfgstore['nodes'][name] + del self._cfgstore['nodes'][name] + _mark_dirtykey('nodes', name, self.tenant) + _mark_dirtykey('nodes', renamemap[name], self.tenant) + for group in self._cfgstore['nodes'][renamemap[name]].get('groups', []): + self._cfgstore['nodegroups'][group]['nodes'].discard(name) + self._cfgstore['nodegroups'][group]['nodes'].add(renamemap[name]) + _mark_dirtykey('nodegroups', group, self.tenant) + cfgobj = self._cfgstore['nodes'][renamemap[name]] + node = renamemap[name] + changeset = {} + if exprmgr is None: + exprmgr = _ExpressionFormat(cfgobj, node) + self._recalculate_expressions(cfgobj, formatter=exprmgr, node=renamemap[name], changeset=changeset) + if self.tenant in self._nodecollwatchers: + nodecollwatchers = self._nodecollwatchers[self.tenant] + for watcher in nodecollwatchers.itervalues(): + eventlet.spawn_n(_do_add_watcher, watcher, (), self, renamemap) + self._bg_sync_to_file() + + def rename_nodegroups(self, renamemap): + if cfgleader: + return exec_on_leader('_rpc_master_rename_nodegroups', self.tenant, renamemap) + if cfgstreams: + exec_on_followers('_rpc_rename_nodegroups', self.tenant, renamemap) + self._true_rename_groups(renamemap) + + def _true_rename_groups(self, renamemap): + oldnames = set(renamemap) + currgroups = set(self._cfgstore['nodegroups']) + missinggroups = oldnames - currgroups + if missinggroups: + raise ValueError( + 'The following groups to rename do not exist: {0}'.format( + ','.join(missinggroups))) + newnames = set([]) + for name in renamemap: + newnames.add(renamemap[name]) + if newnames & currgroups: + raise ValueError( + 'The following requested new names conflict with existing groups: {0}'.format( + ','.join(newnames & currgroups))) + for name in renamemap: + self._cfgstore['nodegroups'][renamemap[name]] = self._cfgstore['nodegroups'][name] + del self._cfgstore['nodegroups'][name] + _mark_dirtykey('nodegroups', name, self.tenant) + _mark_dirtykey('nodegroups', renamemap[name], self.tenant) + for node in self._cfgstore['nodegroups'][renamemap[name]].get('nodes', []): + lidx = self._cfgstore['nodes'][node]['groups'].index(name) + self._cfgstore['nodes'][node]['groups'][lidx] = renamemap[name] + _mark_dirtykey('nodes', node, self.tenant) + self._bg_sync_to_file() + + + def set_node_attributes(self, attribmap, autocreate=False): if cfgleader: # currently config slave to another return exec_on_leader('_rpc_master_set_node_attributes', diff --git a/confluent_server/confluent/consoleserver.py b/confluent_server/confluent/consoleserver.py index 3baba74d..0413d1a6 100644 --- a/confluent_server/confluent/consoleserver.py +++ b/confluent_server/confluent/consoleserver.py @@ -621,11 +621,14 @@ def disconnect_node(node, configmanager): del _handled_consoles[consk] -def _nodechange(added, deleting, configmanager): - for node in added: - connect_node(node, configmanager) +def _nodechange(added, deleting, renamed, configmanager): for node in deleting: disconnect_node(node, configmanager) + for node in renamed: + disconnect_node(node, configmanager) + connect_node(renamed[node], configmanager) + for node in added: + connect_node(node, configmanager) def _start_tenant_sessions(cfm): diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index 47f2cec6..7b897b46 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -144,6 +144,7 @@ def _init_core(): # be enumerated in any collection noderesources = { 'attributes': { + 'rename': PluginRoute({'handler': 'attributes'}), 'all': PluginRoute({'handler': 'attributes'}), 'current': PluginRoute({'handler': 'attributes'}), 'expression': PluginRoute({'handler': 'attributes'}), @@ -379,6 +380,7 @@ def _init_core(): nodegroupresources = { 'attributes': { + 'rename': PluginRoute({'handler': 'attributes'}), 'all': PluginRoute({'handler': 'attributes'}), 'current': PluginRoute({'handler': 'attributes'}), }, diff --git a/confluent_server/confluent/discovery/core.py b/confluent_server/confluent/discovery/core.py index 065a2437..0b445d64 100644 --- a/confluent_server/confluent/discovery/core.py +++ b/confluent_server/confluent/discovery/core.py @@ -1129,11 +1129,12 @@ def _handle_nodelist_change(configmanager): nodeaddhandler = None -def newnodes(added, deleting, configmanager): +def newnodes(added, deleting, renamed, configmanager): global attribwatcher global needaddhandled global nodeaddhandler - for node in deleting: + alldeleting = set(deleting) | set(renamed) + for node in alldeleting: if node not in known_nodes: continue for mac in known_nodes[node]: diff --git a/confluent_server/confluent/messages.py b/confluent_server/confluent/messages.py index 90382558..b317aa44 100644 --- a/confluent_server/confluent/messages.py +++ b/confluent_server/confluent/messages.py @@ -267,6 +267,24 @@ class CreatedResource(ConfluentMessage): pass +class RenamedResource(ConfluentMessage): + notnode = True + readonly = True + + def __init__(self, oldname, newname): + self.kvpairs = {'oldname': oldname, 'newname': newname} + + def strip_node(self, node): + pass + + +class RenamedNode(ConfluentMessage): + def __init__(self, name, rename): + self.desc = 'New Name' + kv = {'rename': {'value': rename}} + self.kvpairs = {name: kv} + + class AssignedResource(ConfluentMessage): notnode = True readonly = True @@ -381,6 +399,8 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False, return InputReseatMessage(path, nodes, inputdata) elif path == ['attributes', 'expression']: return InputExpression(path, inputdata, nodes) + elif path == ['attributes', 'rename']: + return InputConfigChangeSet(path, inputdata, nodes, configmanager) elif path[0] in ('attributes', 'users') and operation != 'retrieve': return InputAttributes(path, inputdata, nodes) elif path == ['boot', 'nextdevice'] and operation != 'retrieve': @@ -539,8 +559,6 @@ class InputConfigClear(ConfluentMessage): raise exc.InvalidArgumentException('Input must be {"clear":true}') class InputConfigChangeSet(InputExpression): - # For now, this is identical to InputExpression, later it may - # internalize formula expansion, but not now.. def __init__(self, path, inputdata, nodes=None, configmanager=None): self.cfm = configmanager super(InputConfigChangeSet, self).__init__(path, inputdata, nodes) diff --git a/confluent_server/confluent/plugins/configuration/attributes.py b/confluent_server/confluent/plugins/configuration/attributes.py index c9dda221..e9b3b1fc 100644 --- a/confluent_server/confluent/plugins/configuration/attributes.py +++ b/confluent_server/confluent/plugins/configuration/attributes.py @@ -161,6 +161,11 @@ def update(nodes, element, configmanager, inputdata): def update_nodegroup(group, element, configmanager, inputdata): + if 'rename' in element: + namemap = {} + namemap[group] = inputdata.attribs['rename'] + configmanager.rename_nodegroups(namemap) + return yield_rename_resources(namemap, isnode=False) try: clearattribs = [] for attrib in inputdata.attribs.iterkeys(): @@ -200,12 +205,26 @@ def create(nodes, element, configmanager, inputdata): if nodes is not None and element[-1] == 'expression': return _expand_expression(nodes, configmanager, inputdata) +def yield_rename_resources(namemap, isnode): + for node in namemap: + if isnode: + yield msg.RenamedNode(node, namemap[node]) + else: + yield msg.RenamedResource(node, namemap[node]) + def update_nodes(nodes, element, configmanager, inputdata): updatedict = {} if not nodes: raise exc.InvalidArgumentException( 'No action to take, noderange is empty (if trying to define ' 'group attributes, use nodegroupattrib)') + if 'rename' in element: + namemap = {} + for node in nodes: + rename = inputdata.get_attributes(node) + namemap[node] = rename['rename'] + configmanager.rename_nodes(namemap) + return yield_rename_resources(namemap, isnode=True) for node in nodes: updatenode = inputdata.get_attributes(node, allattributes.node) clearattribs = []