Skip to content

Commit

Permalink
Add remove-node subcommand
Browse files Browse the repository at this point in the history
closes #160

* Add `remove-node` to remove a specific node from the cluster.
* Add `Cluster.get_node_by_name()` method to return a node based on the node
  name.
* Add optional `stop` option to `Cluster.remove_node()`, to also stop the node
  when `stop=True`.
  • Loading branch information
arcimboldo committed May 25, 2015
1 parent d7f0c4c commit 333225f
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 9 deletions.
41 changes: 32 additions & 9 deletions elasticluster/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,20 +346,32 @@ def add_nodes(self, kind, num, image_id, image_user, flavor,
self.add_node(kind, image_id, image_user, flavor,
security_group, image_userdata=image_userdata, **extra)

def remove_node(self, node):
"""Removes a node from the cluster, but does not stop it. Use this
method with caution.
def remove_node(self, node, stop=False):
"""Removes a node from the cluster.
By default, it doesn't also stop the node, just remove from
the known hosts of this cluster.
:param node: node to remove
:type node: :py:class:`Node`
:param stop: Stop the node
:type stop: bool
"""
if node.kind not in self.nodes:
log.error("Unable to remove node %s: invalid node type `%s`.",
raise NodeNotFound("Unable to remove node %s: invalid node type `%s`.",
node.name, node.kind)
else:
index = self.nodes[node.kind].index(node)
if self.nodes[node.kind][index]:
del self.nodes[node.kind][index]
try:
index = self.nodes[node.kind].index(node)
if self.nodes[node.kind][index]:
del self.nodes[node.kind][index]
if stop:
node.stop()
self.repository.save_or_update(self)
except ValueError:
raise NodeNotFound("Node %s not found in cluster" % node.name)

@staticmethod
def _start_node(node):
Expand Down Expand Up @@ -539,8 +551,7 @@ def timeout_handler(signum, frame):
for node in pending_nodes:
log.error("Stopping node `%s`, since we could not connect to"
" it within the timeout." % node.name)
node.stop()
self.remove_node(node)
self.remove_node(node, stop=True)

signal.alarm(0)

Expand Down Expand Up @@ -637,6 +648,18 @@ def get_all_nodes(self):
else:
return []

def get_node_by_name(self, nodename):
"""Return the node corresponding with name `nodename`
:params nodename: Name of the node
:type nodename: str
"""
nodes = dict((n.name, n) for n in self.get_all_nodes())
try:
return nodes[nodename]
except KeyError:
raise NodeNotFound("Node %s not found" % nodename)

def stop(self, force=False):
"""Destroys all instances of this cluster and calls delete on the
repository.
Expand Down
2 changes: 2 additions & 0 deletions elasticluster/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from elasticluster.subcommands import SshFrontend
from elasticluster.subcommands import SftpFrontend
from elasticluster.subcommands import GC3PieConfig
from elasticluster.subcommands import RemoveNode
from elasticluster.conf import Configurator
from elasticluster.exceptions import ConfigurationError
from elasticluster.migration_tools import MigrationCommand
Expand Down Expand Up @@ -71,6 +72,7 @@ def setup(self):
SftpFrontend(self.params),
GC3PieConfig(self.params),
MigrationCommand(self.params),
RemoveNode(self.params),
]

# global parameters
Expand Down
63 changes: 63 additions & 0 deletions elasticluster/subcommands.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,69 @@ def execute(self):
print(cluster_summary(cluster))


class RemoveNode(AbstractCommand):
"""
Remove a specific node from the cluster
"""
def setup(self, subparsers):
parser = subparsers.add_parser(
"remove-node", help="Remove a specific node from the cluster",
description=self.__doc__)
parser.set_defaults(func=self)
parser.add_argument('cluster',
help='Cluster from which the node must be removed')
parser.add_argument('node', help='Name of node to be removed')
parser.add_argument('-v', '--verbose', action='count', default=0,
help="Increase verbosity.")
parser.add_argument('--no-setup', action="store_true", default=False,
help="Do not re-configure the cluster after "
"removing the node.")
parser.add_argument('--yes', action="store_true", default=False,
help="Assume `yes` to all queries and "
"do not prompt.")
def execute(self):
configurator = Configurator.fromConfig(
self.params.config, storage_path=self.params.storage,
include_config_dirs=True)

# Get current cluster configuration
cluster_name = self.params.cluster

try:
cluster = configurator.load_cluster(cluster_name)
cluster.update()
except (ClusterNotFound, ConfigurationError), ex:
log.error("Error loading cluster %s: %s\n" %
(cluster_name, ex))
return

# Find the node to remove.
try:
node = cluster.get_node_by_name(self.params.node)
except NodeNotFound:
log.error("Node %s not found in cluster %s" % (
self.params.node, self.params.cluster))
sys.exit(1)

# Run
if not self.params.yes:
# Ask for confirmation.
yesno = raw_input(
"Do you really want to remove node %s? [yN] " % node.name)
if yesno.lower() not in ['yes', 'y']:
print("Aborting as per user request.")
sys.exit(0)

cluster.remove_node(node, stop=True)
print("Node %s removed" % node.name)

if self.params.no_setup:
print("NOT reconfiguring the cluster as requested.")
else:
print("Reconfiguring the cluster.")
cluster.setup()


class ListClusters(AbstractCommand):
"""
Print a list of all clusters that have been started.
Expand Down

0 comments on commit 333225f

Please sign in to comment.