Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 709 lines (628 sloc) 29.348 kb
797f4ae Initial commit
Sylvain Lebresne authored
1 # ccm node
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
2 from __future__ import with_statement
797f4ae Initial commit
Sylvain Lebresne authored
3
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
4 import common, yaml, os, errno, signal, time, subprocess, shutil, sys, glob, re
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
5 import repository
2d535e2 @pcmanus Return self from method when that make sense
authored
6 from cli_session import CliSession
797f4ae Initial commit
Sylvain Lebresne authored
7
8 class Status():
9 UNINITIALIZED = "UNINITIALIZED"
10 UP = "UP"
11 DOWN = "DOWN"
12 DECOMMISIONNED = "DECOMMISIONNED"
13
7c09bd4 @pcmanus Add missing file and fix a number of bug due to previous refactorings
authored
14 class NodeError(Exception):
555db73 @pcmanus Move all logic into Cluster and Node classes
authored
15 def __init__(self, msg, process=None):
7c09bd4 @pcmanus Add missing file and fix a number of bug due to previous refactorings
authored
16 Exception.__init__(self, msg)
797f4ae Initial commit
Sylvain Lebresne authored
17 self.process = process
18
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
19 class TimeoutError(Exception):
20 def __init__(self, data):
21 Exception.__init__(self, str(data))
22
dca3d49 @pcmanus Allow specifying tokens to populate and other bug fixes
authored
23 # Groups: 1 = cf, 2 = tmp or none, 3 = suffix (Compacted or Data.db)
2bd208b @driftx 1.1-compatible data_size
driftx authored
24 _sstable_regexp = re.compile('(?P<cf>[\S]+)+-(?P<tmp>tmp-)?[\S]+-(?P<suffix>[a-zA-Z.]+)')
dca3d49 @pcmanus Allow specifying tokens to populate and other bug fixes
authored
25
797f4ae Initial commit
Sylvain Lebresne authored
26 class Node():
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
27 """
28 Provides interactions to a Cassandra node.
29 """
30
ca40b22 @pcmanus Add support for the binary protocol
authored
31 def __init__(self, name, cluster, auto_bootstrap, thrift_interface, storage_interface, jmx_port, remote_debug_port, initial_token, save=True, binary_interface=None):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
32 """
33 Create a new Node.
34 - name: the name for that node
35 - cluster: the cluster this node is part of
36 - auto_boostrap: whether or not this node should be set for auto-boostrap
37 - thrift_interface: the (host, port) tuple for thrift
38 - storage_interface: the (host, port) tuple for internal cluster communication
39 - jmx_port: the port for JMX to bind to
8d2a68e @mebigfatguy allow for generating a remote debugging port per node
mebigfatguy authored
40 - remote_debug_port: the port for remote debugging
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
41 - initial_token: the token for this node. If None, use Cassandra token auto-assigment
42 - save: copy all data useful for this node to the right position. Leaving this true
43 is almost always the right choice.
44 """
797f4ae Initial commit
Sylvain Lebresne authored
45 self.name = name
46 self.cluster = cluster
47 self.status = Status.UNINITIALIZED
48 self.auto_bootstrap = auto_bootstrap
ca40b22 @pcmanus Add support for the binary protocol
authored
49 self.network_interfaces = { 'thrift' : thrift_interface, 'storage' : storage_interface, 'binary' : binary_interface }
797f4ae Initial commit
Sylvain Lebresne authored
50 self.jmx_port = jmx_port
8d2a68e @mebigfatguy allow for generating a remote debugging port per node
mebigfatguy authored
51 self.remote_debug_port = remote_debug_port
ae58ff1 add initial_token option to add command
Jonathan Ellis authored
52 self.initial_token = initial_token
797f4ae Initial commit
Sylvain Lebresne authored
53 self.pid = None
68da7fa @pcmanus Add multi-DC support
authored
54 self.data_center = None
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
55 self.__config_options = {}
56 self.__cassandra_dir = None
57 self.__log_level = "INFO"
7c09bd4 @pcmanus Add missing file and fix a number of bug due to previous refactorings
authored
58 if save:
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
59 self.import_config_files()
797f4ae Initial commit
Sylvain Lebresne authored
60
61 @staticmethod
62 def load(path, name, cluster):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
63 """
64 Load a node from from the path on disk to the config files, the node name and the
65 cluster the node is part of.
66 """
797f4ae Initial commit
Sylvain Lebresne authored
67 node_path = os.path.join(path, name)
68 filename = os.path.join(node_path, 'node.conf')
69 with open(filename, 'r') as f:
70 data = yaml.load(f)
71 try:
72 itf = data['interfaces'];
4e85efb Add option to start node without joining the ring
Sylvain Lebresne authored
73 initial_token = None
74 if 'initial_token' in data:
75 initial_token = data['initial_token']
35ed89f @pcmanus Ensure compatibility when remote_debug_port is not set
authored
76 remote_debug_port = 2000
77 if 'remote_debug_port' in data:
78 remote_debug_port = data['remote_debug_port']
ca40b22 @pcmanus Add support for the binary protocol
authored
79 binary_interface = None
80 if 'binary' in itf and itf['binary'] is not None:
81 binary_interface = tuple(itf['binary'])
82 node = Node(data['name'], cluster, data['auto_bootstrap'], tuple(itf['thrift']), tuple(itf['storage']), data['jmx_port'], remote_debug_port, initial_token, save=False, binary_interface=binary_interface)
797f4ae Initial commit
Sylvain Lebresne authored
83 node.status = data['status']
84 if 'pid' in data:
85 node.pid = int(data['pid'])
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
86 if 'cassandra_dir' in data:
87 node.__cassandra_dir = data['cassandra_dir']
88 if 'config_options' in data:
89 node.__config_options = data['config_options']
68da7fa @pcmanus Add multi-DC support
authored
90 if 'data_center' in data:
91 node.data_center = data['data_center']
797f4ae Initial commit
Sylvain Lebresne authored
92 return node
93 except KeyError as k:
94 raise common.LoadError("Error Loading " + filename + ", missing property: " + str(k))
95
96 def get_path(self):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
97 """
98 Returns the path to this node top level directory (where config/data is stored)
99 """
797f4ae Initial commit
Sylvain Lebresne authored
100 return os.path.join(self.cluster.get_path(), self.name)
101
102 def get_conf_dir(self):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
103 """
104 Returns the path to the directory where Cassandra config are located
105 """
797f4ae Initial commit
Sylvain Lebresne authored
106 return os.path.join(self.get_path(), 'conf')
107
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
108 def address(self):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
109 """
110 Returns the IP use by this node for internal communication
111 """
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
112 return self.network_interfaces['storage'][0]
113
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
114 def get_cassandra_dir(self):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
115 """
116 Returns the path to the cassandra source directory used by this node.
117 """
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
118 if self.__cassandra_dir is None:
119 return self.cluster.get_cassandra_dir()
120 else:
121 common.validate_cassandra_dir(self.__cassandra_dir)
122 return self.__cassandra_dir
123
7d802df @pcmanus fix typo
authored
124 def set_cassandra_dir(self, cassandra_dir=None, cassandra_version=None, verbose=False):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
125 """
126 Sets the path to the cassandra source directory for use by this node.
127 """
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
128 if cassandra_version is None:
129 self.__cassandra_dir = cassandra_dir
130 if cassandra_dir is not None:
131 common.validate_cassandra_dir(cassandra_dir)
132 else:
bd3985a @pcmanus Fix bug in setting C* dir for individual nodes
authored
133 dir, v = repository.setup(cassandra_version, verbose=verbose)
134 self.__cassandra_dir = dir
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
135 self.import_config_files()
136 return self
137
138 def set_configuration_options(self, values=None, batch_commitlog=None):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
139 """
140 Set Cassandra configuration options.
141 ex:
142 node.set_configuration_options(values={
143 'hinted_handoff_enabled' : True,
144 'concurrent_writes' : 64,
145 })
146 The batch_commitlog option gives an easier way to switch to batch
147 commitlog (since it requires setting 2 options and unsetting one).
148 """
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
149 if values is not None:
150 for k, v in values.iteritems():
151 self.__config_options[k] = v
152 if batch_commitlog is not None:
153 if batch_commitlog:
154 self.__config_options["commitlog_sync"] = "batch"
155 self.__config_options["commitlog_sync_batch_window_in_ms"] = 5
156 self.__config_options["commitlog_sync_period_in_ms"] = None
157 else:
158 self.__config_options["commitlog_sync"] = "periodic"
159 self.__config_options["commitlog_sync_period_in_ms"] = 10000
160 self.__config_options["commitlog_sync_batch_window_in_ms"] = None
161
162 self.import_config_files()
797f4ae Initial commit
Sylvain Lebresne authored
163
164 def show(self, only_status=False, show_cluster=True):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
165 """
166 Print infos on this node configuration.
167 """
9f34b2c @pcmanus make some methods private
authored
168 self.__update_status()
797f4ae Initial commit
Sylvain Lebresne authored
169 indent = ''.join([ " " for i in xrange(0, len(self.name) + 2) ])
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
170 print "%s: %s" % (self.name, self.__get_status_string())
797f4ae Initial commit
Sylvain Lebresne authored
171 if not only_status:
ca40b22 @pcmanus Add support for the binary protocol
authored
172 if show_cluster:
173 print "%s%s=%s" % (indent, 'cluster', self.cluster.name)
174 print "%s%s=%s" % (indent, 'auto_bootstrap', self.auto_bootstrap)
175 print "%s%s=%s" % (indent, 'thrift', self.network_interfaces['thrift'])
176 if self.network_interfaces['binary'] is not None:
177 print "%s%s=%s" % (indent, 'binary', self.network_interfaces['binary'])
178 print "%s%s=%s" % (indent, 'storage', self.network_interfaces['storage'])
179 print "%s%s=%s" % (indent, 'jmx_port', self.jmx_port)
180 print "%s%s=%s" % (indent, 'remote_debug_port', self.remote_debug_port)
181 print "%s%s=%s" % (indent, 'initial_token', self.initial_token)
182 if self.pid:
183 print "%s%s=%s" % (indent, 'pid', self.pid)
797f4ae Initial commit
Sylvain Lebresne authored
184
185 def is_running(self):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
186 """
187 Return true if the node is running
188 """
9f34b2c @pcmanus make some methods private
authored
189 self.__update_status()
797f4ae Initial commit
Sylvain Lebresne authored
190 return self.status == Status.UP or self.status == Status.DECOMMISIONNED
191
192 def is_live(self):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
193 """
194 Return true if the node is live (it's run and is not decommissionned).
195 """
9f34b2c @pcmanus make some methods private
authored
196 self.__update_status()
797f4ae Initial commit
Sylvain Lebresne authored
197 return self.status == Status.UP
198
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
199 def logfilename(self):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
200 """
201 Return the path to the current Cassandra log of this node.
202 """
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
203 return os.path.join(self.get_path(), 'logs', 'system.log')
204
205 def grep_log(self, expr):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
206 """
207 Returns a list of lines matching the regular expression in parameter
208 in the Cassandra log of this node
209 """
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
210 matchings = []
211 pattern = re.compile(expr)
212 with open(self.logfilename()) as f:
213 for line in f:
214 m = pattern.search(line)
215 if m:
216 matchings.append((line, m))
217 return matchings
218
219 def mark_log(self):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
220 """
221 Returns "a mark" to the current position of this node Cassandra log.
222 This is for use with the from_mark parameter of watch_log_for_* methods,
223 allowing to watch the log from the position when this method was called.
224 """
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
225 with open(self.logfilename()) as f:
226 f.seek(0, os.SEEK_END)
45ec371 @driftx Add 1k look-behind for mark_log
driftx authored
227 mark = f.tell() - 1024
ca40b22 @pcmanus Add support for the binary protocol
authored
228 if mark < 0:
229 mark = 0
45ec371 @driftx Add 1k look-behind for mark_log
driftx authored
230 return mark
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
231
232 # This will return when exprs are found or it timeouts
233 def watch_log_for(self, exprs, from_mark=None, timeout=60):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
234 """
235 Watch the log until one or more (regular) expression are found.
236 This methods when all the expressions have been found or the method
237 timeouts (a TimeoutError is then raised). On successful completion,
238 a list of pair (line matched, match object) is returned.
239 """
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
240 elapsed = 0
241 tofind = [exprs] if isinstance(exprs, basestring) else exprs
242 tofind = [ re.compile(e) for e in tofind ]
243 matchings = []
244 reads = ""
245 if len(tofind) == 0:
246 return None
247 with open(self.logfilename()) as f:
248 if from_mark:
249 f.seek(from_mark)
250
251 while True:
252 line = f.readline()
253 if line:
254 reads = reads + line
255 for e in tofind:
256 m = e.search(line)
257 if m:
258 matchings.append((line, m))
259 tofind.remove(e)
260 if len(tofind) == 0:
261 return matchings[0] if isinstance(exprs, basestring) else matchings
262 else:
263 # yep, it's ugly
264 time.sleep(.3)
265 elapsed = elapsed + .3
266 if elapsed > timeout:
267 raise TimeoutError(time.strftime("%d %b %Y %H:%M:%S", time.gmtime()) + " [" + self.name + "] Missing: " + str([e.pattern for e in tofind]) + ":\n" + reads)
268
269 def watch_log_for_death(self, nodes, from_mark=None, timeout=600):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
270 """
271 Watch the log of this node until it detects that the provided other
272 nodes are marked dead. This method returns nothing but throw a
273 TimeoutError if all the requested node have not been found to be
274 marked dead before timeout sec.
275 A mark as returned by mark_log() can be used as the from_mark
276 parameter to start watching the log from a given position. Otherwise
277 the log is watched from the beginning.
278 """
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
279 tofind = nodes if isinstance(nodes, list) else [nodes]
280 tofind = [ "%s is now dead" % node.address() for node in tofind ]
281 self.watch_log_for(tofind, from_mark=from_mark, timeout=timeout)
282
283 def watch_log_for_alive(self, nodes, from_mark=None, timeout=60):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
284 """
285 Watch the log of this node until it detects that the provided other
286 nodes are marked UP. This method works similarily to watch_log_for_death.
287 """
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
288 tofind = nodes if isinstance(nodes, list) else [nodes]
dca3d49 @pcmanus Allow specifying tokens to populate and other bug fixes
authored
289 tofind = [ "%s is now UP" % node.address() for node in tofind ]
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
290 self.watch_log_for(tofind, from_mark=from_mark, timeout=timeout)
291
bd3985a @pcmanus Fix bug in setting C* dir for individual nodes
authored
292 def start(self, join_ring=True, no_wait=False, verbose=False, update_pid=True, wait_other_notice=False, replace_token=None, jvm_args=[]):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
293 """
294 Start the node. Options includes:
295 - join_ring: if false, start the node with -Dcassandra.join_ring=False
296 - no_wait: by default, this method returns when the node is started and listening to clients.
297 If no_wait=True, the method returns sooner.
298 - wait_other_notice: if True, this method returns only when all other live node of the cluster
299 have marked this node UP.
300 - replace_token: start the node with the -Dcassandra.replace_token option.
301 """
555db73 @pcmanus Move all logic into Cluster and Node classes
authored
302 if self.is_running():
7c09bd4 @pcmanus Add missing file and fix a number of bug due to previous refactorings
authored
303 raise NodeError("%s is already running" % self.name)
555db73 @pcmanus Move all logic into Cluster and Node classes
authored
304
eb8fcd2 @pcmanus Check socket availabe before starting cassandra and change how the ca…
authored
305 for itf in self.network_interfaces.values():
ca40b22 @pcmanus Add support for the binary protocol
authored
306 if itf is not None:
307 common.check_socket_available(itf)
eb8fcd2 @pcmanus Check socket availabe before starting cassandra and change how the ca…
authored
308
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
309 if wait_other_notice:
310 marks = [ (node, node.mark_log()) for node in self.cluster.nodes.values() if node.is_running() ]
311
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
312 cdir = self.get_cassandra_dir()
2632d5a @pcmanus Make the path to the cassandra_dir be a 'persistent' property
authored
313 cass_bin = os.path.join(cdir, 'bin', 'cassandra')
314 env = common.make_cassandra_env(cdir, self.get_path())
797f4ae Initial commit
Sylvain Lebresne authored
315 pidfile = os.path.join(self.get_path(), 'cassandra.pid')
4e85efb Add option to start node without joining the ring
Sylvain Lebresne authored
316 args = [ cass_bin, '-p', pidfile, '-Dcassandra.join_ring=%s' % str(join_ring) ]
018110a @pcmanus Update README file and add support for replace_token
authored
317 if replace_token is not None:
318 args = args + [ '-Dcassandra.replace_token=%s' % str(replace_token) ]
bd3985a @pcmanus Fix bug in setting C* dir for individual nodes
authored
319 args = args + jvm_args
555db73 @pcmanus Move all logic into Cluster and Node classes
authored
320 process = subprocess.Popen(args, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
321
7c09bd4 @pcmanus Add missing file and fix a number of bug due to previous refactorings
authored
322 if update_pid:
323 if no_wait:
324 time.sleep(2) # waiting 2 seconds nevertheless to check for early errors and for the pid to be set
325 else:
326 for line in process.stdout:
327 if verbose:
328 print line.rstrip('\n')
555db73 @pcmanus Move all logic into Cluster and Node classes
authored
329
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
330 self._update_pid(process)
555db73 @pcmanus Move all logic into Cluster and Node classes
authored
331
7c09bd4 @pcmanus Add missing file and fix a number of bug due to previous refactorings
authored
332 if not self.is_running():
333 raise NodeError("Error starting node %s" % self.name, process)
555db73 @pcmanus Move all logic into Cluster and Node classes
authored
334
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
335 if wait_other_notice:
336 for node, mark in marks:
337 node.watch_log_for_alive(self, from_mark=mark)
338
555db73 @pcmanus Move all logic into Cluster and Node classes
authored
339 return process
797f4ae Initial commit
Sylvain Lebresne authored
340
48d1ec1 @pcmanus Add simple bulkloading support and option to populate with nodes for …
authored
341 def stop(self, wait=True, wait_other_notice=False, gently=False):
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
342 """
343 Stop the node.
344 - wait: if True (the default), wait for the Cassandra process to be
345 really dead. Otherwise return after having sent the kill signal.
346 - wait_other_notice: return only when the other live nodes of the
347 cluster have marked this node has dead.
348 """
797f4ae Initial commit
Sylvain Lebresne authored
349 if self.is_running():
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
350 if wait_other_notice:
351 #tstamp = time.time()
352 marks = [ (node, node.mark_log()) for node in self.cluster.nodes.values() if node.is_running() and node is not self ]
353
48d1ec1 @pcmanus Add simple bulkloading support and option to populate with nodes for …
authored
354 if gently:
355 os.kill(self.pid, signal.SIGTERM)
356 else:
357 os.kill(self.pid, signal.SIGKILL)
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
358
359 if wait_other_notice:
360 for node, mark in marks:
361 node.watch_log_for_death(self, from_mark=mark)
362 #print node.name, "has marked", self.name, "down in " + str(time.time() - tstamp) + "s"
363 else:
364 time.sleep(.1)
365
7c09bd4 @pcmanus Add missing file and fix a number of bug due to previous refactorings
authored
366 still_running = self.is_running()
367 if still_running and wait:
368 wait_time_sec = 1
369 for i in xrange(0, 7):
370 # we'll double the wait time each try and cassandra should
371 # not take more than 1 minute to shutdown
372 time.sleep(wait_time_sec)
373 if not self.is_running():
374 return True
375 wait_time_sec = wait_time_sec * 2
376 raise NodeError("Problem stopping node %s" % self.name)
377 else:
378 return True
379 else:
380 return False
797f4ae Initial commit
Sylvain Lebresne authored
381
2632d5a @pcmanus Make the path to the cassandra_dir be a 'persistent' property
authored
382 def nodetool(self, cmd):
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
383 cdir = self.get_cassandra_dir()
2632d5a @pcmanus Make the path to the cassandra_dir be a 'persistent' property
authored
384 nodetool = os.path.join(cdir, 'bin', 'nodetool')
385 env = common.make_cassandra_env(cdir, self.get_path())
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
386 host = self.address()
797f4ae Initial commit
Sylvain Lebresne authored
387 args = [ nodetool, '-h', host, '-p', str(self.jmx_port), cmd ]
388 p = subprocess.Popen(args, env=env)
389 p.wait()
390
18a1132 @pcmanus Add offline scrub
authored
391 def scrub(self, options):
392 cdir = self.get_cassandra_dir()
393 scrub_bin = os.path.join(cdir, 'bin', 'sstablescrub')
394 env = common.make_cassandra_env(cdir, self.get_path())
395 os.execve(scrub_bin, [ 'sstablescrub' ] + options, env)
396
2632d5a @pcmanus Make the path to the cassandra_dir be a 'persistent' property
authored
397 def run_cli(self, cmds=None, show_output=False, cli_options=[]):
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
398 cdir = self.get_cassandra_dir()
2632d5a @pcmanus Make the path to the cassandra_dir be a 'persistent' property
authored
399 cli = os.path.join(cdir, 'bin', 'cassandra-cli')
400 env = common.make_cassandra_env(cdir, self.get_path())
797f4ae Initial commit
Sylvain Lebresne authored
401 host = self.network_interfaces['thrift'][0]
402 port = self.network_interfaces['thrift'][1]
a5ffd83 @mebigfatguy remove mistaken cli option
mebigfatguy authored
403 args = [ '-h', host, '-p', str(port) , '--jmxport', str(self.jmx_port) ] + cli_options
797f4ae Initial commit
Sylvain Lebresne authored
404 sys.stdout.flush()
537b881 @pcmanus Add option to submit commands to cli silently
authored
405 if cmds is None:
406 os.execve(cli, [ 'cassandra-cli' ] + args, env)
407 else:
408 p = subprocess.Popen([ cli ] + args, env=env, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
409 for cmd in cmds.split(';'):
410 p.stdin.write(cmd + ';\n')
411 p.stdin.write("quit;\n")
412 p.wait()
413 for err in p.stderr:
414 print "(EE) " + err,
415 if show_output:
416 i = 0
417 for log in p.stdout:
2d535e2 @pcmanus Return self from method when that make sense
authored
418 # first four lines are not interesting
e0dad11 @pcmanus Add option to switch to batch commit log
authored
419 if i >= 4:
420 print log,
537b881 @pcmanus Add option to submit commands to cli silently
authored
421 i = i + 1
422
2d535e2 @pcmanus Return self from method when that make sense
authored
423 def cli(self):
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
424 cdir = self.get_cassandra_dir()
2d535e2 @pcmanus Return self from method when that make sense
authored
425 cli = os.path.join(cdir, 'bin', 'cassandra-cli')
426 env = common.make_cassandra_env(cdir, self.get_path())
427 host = self.network_interfaces['thrift'][0]
428 port = self.network_interfaces['thrift'][1]
a5ffd83 @mebigfatguy remove mistaken cli option
mebigfatguy authored
429 args = [ '-h', host, '-p', str(port) , '--jmxport', str(self.jmx_port) ]
2d535e2 @pcmanus Return self from method when that make sense
authored
430 return CliSession(subprocess.Popen([ cli ] + args, env=env, stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=subprocess.PIPE))
431
797f4ae Initial commit
Sylvain Lebresne authored
432 def set_log_level(self, new_level):
555db73 @pcmanus Move all logic into Cluster and Node classes
authored
433 known_level = [ 'TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR' ]
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
434 if new_level not in known_level:
0128c65 @pcmanus Minor bug corrections
authored
435 raise common.ArgumentError("Unknown log level %s (use one of %s)" % (new_level, " ".join(known_level)))
555db73 @pcmanus Move all logic into Cluster and Node classes
authored
436
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
437 self.__log_level = new_level
438 self.__update_log4j()
2d535e2 @pcmanus Return self from method when that make sense
authored
439 return self
797f4ae Initial commit
Sylvain Lebresne authored
440
dca3d49 @pcmanus Allow specifying tokens to populate and other bug fixes
authored
441 def clear(self, clear_all = False, only_data = False):
442 data_dirs = [ 'data' ]
443 if not only_data:
444 data_dirs = data_dirs + [ 'commitlogs']
445 if clear_all:
446 data_dirs = data_dirs + [ 'saved_caches', 'logs']
797f4ae Initial commit
Sylvain Lebresne authored
447 for d in data_dirs:
448 full_dir = os.path.join(self.get_path(), d)
dca3d49 @pcmanus Allow specifying tokens to populate and other bug fixes
authored
449 if only_data:
450 for dir in os.listdir(full_dir):
0128c65 @pcmanus Minor bug corrections
authored
451 keyspace_dir = os.path.join(full_dir, dir)
452 if os.path.isdir(keyspace_dir) and dir != "system":
453 for f in os.listdir(keyspace_dir):
454 full_path = os.path.join(keyspace_dir, f)
dca3d49 @pcmanus Allow specifying tokens to populate and other bug fixes
authored
455 if os.path.isfile(full_path):
456 os.remove(full_path)
457 else:
458 shutil.rmtree(full_dir)
459 os.mkdir(full_dir)
797f4ae Initial commit
Sylvain Lebresne authored
460
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
461 def run_sstable2json(self, keyspace=None, datafile=None, column_families=None, enumerate_keys=False):
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
462 cdir = self.get_cassandra_dir()
2632d5a @pcmanus Make the path to the cassandra_dir be a 'persistent' property
authored
463 sstable2json = os.path.join(cdir, 'bin', 'sstable2json')
464 env = common.make_cassandra_env(cdir, self.get_path())
1628005 Add sstable2json support
Sylvain Lebresne authored
465 datafiles = []
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
466 if keyspace is None:
1628005 Add sstable2json support
Sylvain Lebresne authored
467 for k in self.list_keyspaces():
468 datafiles = datafiles + self.get_sstables(k, "")
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
469 elif datafile is None:
470 if column_families is None:
1628005 Add sstable2json support
Sylvain Lebresne authored
471 datafiles = datafiles + self.get_sstables(keyspace, "")
472 else:
473 for cf in column_families:
474 datafiles = datafiles + self.get_sstables(keyspace, cf)
0348717 @pcmanus Add option to specify the file for json command and for calling sstab…
authored
475 else:
476 keyspace_dir = os.path.join(self.get_path(), 'data', keyspace)
477 datafiles = [ os.path.join(keyspace_dir, datafile) ]
1628005 Add sstable2json support
Sylvain Lebresne authored
478 for file in datafiles:
479 print "-- {0} -----".format(os.path.basename(file))
480 args = [ sstable2json , file ]
0348717 @pcmanus Add option to specify the file for json command and for calling sstab…
authored
481 if enumerate_keys:
ca40b22 @pcmanus Add support for the binary protocol
authored
482 args = args + ["-e"]
1628005 Add sstable2json support
Sylvain Lebresne authored
483 subprocess.call(args, env=env)
484 print ""
485
486 def list_keyspaces(self):
487 keyspaces = os.listdir(os.path.join(self.get_path(), 'data'))
488 keyspaces.remove('system')
489 return keyspaces
490
491 def get_sstables(self, keyspace, column_family):
492 keyspace_dir = os.path.join(self.get_path(), 'data', keyspace)
493 if not os.path.exists(keyspace_dir):
555db73 @pcmanus Move all logic into Cluster and Node classes
authored
494 raise common.ArgumentError("Unknown keyspace {0}".format(keyspace))
1628005 Add sstable2json support
Sylvain Lebresne authored
495
406395f @yukim use new directory layout when c* version >= 1.1
yukim authored
496 version = self.cluster.version()
497 # data directory layout is changed from 1.1
498 if float(version[:version.index('.')+2]) < 1.1:
499 files = glob.glob(os.path.join(keyspace_dir, "{0}*-Data.db".format(column_family)))
500 else:
501 files = glob.glob(os.path.join(keyspace_dir, column_family or "*", "%s-%s*-Data.db" % (keyspace, column_family)))
1628005 Add sstable2json support
Sylvain Lebresne authored
502 for f in files:
503 if os.path.exists(f.replace('Data.db', 'Compacted')):
504 files.remove(f)
505 return files
506
2632d5a @pcmanus Make the path to the cassandra_dir be a 'persistent' property
authored
507 def stress(self, stress_options):
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
508 stress = common.get_stress_bin(self.get_cassandra_dir())
cfb5186 Add stress support
Sylvain Lebresne authored
509 args = [ stress ] + stress_options
510 try:
511 subprocess.call(args)
512 except KeyboardInterrupt:
513 pass
514
dca3d49 @pcmanus Allow specifying tokens to populate and other bug fixes
authored
515 def data_size(self, live_data=True):
516 size = 0
406395f @yukim use new directory layout when c* version >= 1.1
yukim authored
517 if live_data:
518 for ks in self.list_keyspaces():
519 size += sum((os.path.getsize(path) for path in self.get_sstables(ks, "")))
520 else:
521 for ks in self.list_keyspaces():
522 for root, dirs, files in os.walk(os.path.join(self.get_path(), 'data', ks)):
523 size += sum((os.path.getsize(os.path.join(root, f)) for f in files if os.path.isfile(os.path.join(root, f))))
dca3d49 @pcmanus Allow specifying tokens to populate and other bug fixes
authored
524 return size
4fbdfa2 @pcmanus Add drain command
authored
525
dca3d49 @pcmanus Allow specifying tokens to populate and other bug fixes
authored
526 def flush(self):
527 self.nodetool("flush")
528
529 def compact(self):
530 self.nodetool("compact")
531
4fbdfa2 @pcmanus Add drain command
authored
532 def drain(self):
533 self.nodetool("drain")
534
dca3d49 @pcmanus Allow specifying tokens to populate and other bug fixes
authored
535 def repair(self):
536 self.nodetool("repair")
537
538 def move(self, new_token):
539 self.nodetool("move " + str(new_token))
540
541 def cleanup(self):
542 self.nodetool("cleanup")
543
0128c65 @pcmanus Minor bug corrections
authored
544 def decommission(self):
545 self.nodetool("decommission")
546 self.status = Status.DECOMMISIONNED
547 self.__update_config()
548
549 def removeToken(self, token):
550 self.nodetool("removeToken " + str(token))
551
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
552 def import_config_files(self):
553 self.__update_config()
554
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
555 conf_dir = os.path.join(self.get_cassandra_dir(), 'conf')
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
556 for name in os.listdir(conf_dir):
557 filename = os.path.join(conf_dir, name)
558 if os.path.isfile(filename):
559 shutil.copy(filename, self.get_conf_dir())
560
561 self.__update_yaml()
562 self.__update_log4j()
563 self.__update_envfile()
564
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
565 def _save(self):
566 self.__update_yaml()
567 self.__update_log4j()
568 self.__update_envfile()
569
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
570 def __update_config(self):
9f34b2c @pcmanus make some methods private
authored
571 dir_name = self.get_path()
572 if not os.path.exists(dir_name):
573 os.mkdir(dir_name)
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
574 for dir in self.__get_diretories():
9f34b2c @pcmanus make some methods private
authored
575 os.mkdir(os.path.join(dir_name, dir))
576
577 filename = os.path.join(dir_name, 'node.conf')
578 values = {
579 'name' : self.name,
580 'status' : self.status,
581 'auto_bootstrap' : self.auto_bootstrap,
582 'interfaces' : self.network_interfaces,
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
583 'jmx_port' : self.jmx_port,
68da7fa @pcmanus Add multi-DC support
authored
584 'config_options' : self.__config_options,
9f34b2c @pcmanus make some methods private
authored
585 }
586 if self.pid:
587 values['pid'] = self.pid
588 if self.initial_token:
589 values['initial_token'] = self.initial_token
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
590 if self.__cassandra_dir is not None:
591 values['cassandra_dir'] = self.__cassandra_dir
68da7fa @pcmanus Add multi-DC support
authored
592 if self.data_center:
593 values['data_center'] = self.data_center
35ed89f @pcmanus Ensure compatibility when remote_debug_port is not set
authored
594 if self.remote_debug_port:
595 values['remote_debug_port'] = self.remote_debug_port
9f34b2c @pcmanus make some methods private
authored
596 with open(filename, 'w') as f:
0128c65 @pcmanus Minor bug corrections
authored
597 yaml.safe_dump(values, f)
9f34b2c @pcmanus make some methods private
authored
598
599 def __update_yaml(self):
600 conf_file = os.path.join(self.get_conf_dir(), common.CASSANDRA_CONF)
601 with open(conf_file, 'r') as f:
602 data = yaml.load(f)
603
7d2ff56 Use cluster name as cluster name
Sylvain Lebresne authored
604 data['cluster_name'] = self.cluster.name
9f34b2c @pcmanus make some methods private
authored
605 data['auto_bootstrap'] = self.auto_bootstrap
606 data['initial_token'] = self.initial_token
607 if 'seeds' in data:
608 # cassandra 0.7
609 data['seeds'] = self.cluster.get_seeds()
610 else:
611 # cassandra 0.8
612 data['seed_provider'][0]['parameters'][0]['seeds'] = ','.join(self.cluster.get_seeds())
613 data['listen_address'], data['storage_port'] = self.network_interfaces['storage']
614 data['rpc_address'], data['rpc_port'] = self.network_interfaces['thrift']
ca40b22 @pcmanus Add support for the binary protocol
authored
615 if self.network_interfaces['binary'] is not None:
616 data['native_transport_address'], data['native_transport_port'] = self.network_interfaces['binary']
9f34b2c @pcmanus make some methods private
authored
617
618 data['data_file_directories'] = [ os.path.join(self.get_path(), 'data') ]
619 data['commitlog_directory'] = os.path.join(self.get_path(), 'commitlogs')
620 data['saved_caches_directory'] = os.path.join(self.get_path(), 'saved_caches')
621
622 if self.cluster.partitioner:
623 data['partitioner'] = self.cluster.partitioner
624
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
625 full_options = dict(self.cluster._config_options.items() + self.__config_options.items()) # last win and we want node options to win
626 for name in full_options:
627 value = full_options[name]
9f34b2c @pcmanus make some methods private
authored
628 if value is None:
0ed3ca7 @pcmanus Allow setting (almost) any cassandra config options
authored
629 try:
630 del data[name]
631 except KeyError:
632 # it is fine to remove a key not there:w
633 pass
9f34b2c @pcmanus make some methods private
authored
634 else:
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
635 data[name] = full_options[name]
9f34b2c @pcmanus make some methods private
authored
636
637 with open(conf_file, 'w') as f:
0128c65 @pcmanus Minor bug corrections
authored
638 yaml.safe_dump(data, f, default_flow_style=False)
9f34b2c @pcmanus make some methods private
authored
639
640 def __update_log4j(self):
ca40b22 @pcmanus Add support for the binary protocol
authored
641 append_pattern='log4j.appender.R.File='
9f34b2c @pcmanus make some methods private
authored
642 conf_file = os.path.join(self.get_conf_dir(), common.LOG4J_CONF)
643 log_file = os.path.join(self.get_path(), 'logs', 'system.log')
644 common.replace_in_file(conf_file, append_pattern, append_pattern + log_file)
645
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
646 # Setting the right log level
ca40b22 @pcmanus Add support for the binary protocol
authored
647 append_pattern='log4j.rootLogger='
2424148 @pcmanus Better handling of config options (allow cluster level ones in partic…
authored
648 l = self.__log_level + ",stdout,R"
649 common.replace_in_file(conf_file, append_pattern, append_pattern + l)
650
9f34b2c @pcmanus make some methods private
authored
651 def __update_envfile(self):
ca40b22 @pcmanus Add support for the binary protocol
authored
652 jmx_port_pattern='JMX_PORT='
653 remote_debug_port_pattern='address='
9f34b2c @pcmanus make some methods private
authored
654 conf_file = os.path.join(self.get_conf_dir(), common.CASSANDRA_ENV)
655 common.replace_in_file(conf_file, jmx_port_pattern, jmx_port_pattern + self.jmx_port)
35ed89f @pcmanus Ensure compatibility when remote_debug_port is not set
authored
656 if self.remote_debug_port != '0':
2b49e70 @eevans cast int to str to avoid TypeError
eevans authored
657 common.replace_in_file(conf_file, remote_debug_port_pattern, 'JVM_OPTS="$JVM_OPTS -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=' + str(self.remote_debug_port) + '"')
9f34b2c @pcmanus make some methods private
authored
658
659 def __update_status(self):
660 if self.pid is None:
661 if self.status == Status.UP or self.status == Status.DECOMMISIONNED:
662 self.status = Status.DOWN
663 return
664
665 old_status = self.status
666 try:
667 os.kill(self.pid, 0)
668 except OSError, err:
669 if err.errno == errno.ESRCH:
670 # not running
671 if self.status == Status.UP or self.status == Status.DECOMMISIONNED:
672 self.status = Status.DOWN
673 elif err.errno == errno.EPERM:
674 # no permission to signal this process
675 if self.status == Status.UP or self.status == Status.DECOMMISIONNED:
676 self.status = Status.DOWN
677 else:
678 # some other error
679 raise err
680 else:
681 if self.status == Status.DOWN or self.status == Status.UNINITIALIZED:
682 self.status = Status.UP
7c09bd4 @pcmanus Add missing file and fix a number of bug due to previous refactorings
authored
683
9f34b2c @pcmanus make some methods private
authored
684 if not old_status == self.status:
7c09bd4 @pcmanus Add missing file and fix a number of bug due to previous refactorings
authored
685 if old_status == Status.UP and self.status == Status.DOWN:
686 self.pid = None
aef3fe8 @pcmanus Repo support, methods to help inspecting logs, few fixes
authored
687 self.__update_config()
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
688
689 def __get_diretories(self):
690 dirs = {}
691 for i in ['data', 'commitlogs', 'saved_caches', 'logs', 'conf', 'bin']:
48d1ec1 @pcmanus Add simple bulkloading support and option to populate with nodes for …
authored
692 dirs[i] = os.path.join(self.get_path(), i)
2e76e05 @pcmanus Fix a bunch of bugs and add some comments
authored
693 return dirs
694
695 def __get_status_string(self):
696 if self.status == Status.UNINITIALIZED:
697 return "%s (%s)" % (Status.DOWN, "Not initialized")
698 else:
699 return self.status
700
701 def _update_pid(self, process):
702 pidfile = os.path.join(self.get_path(), 'cassandra.pid')
703 try:
704 with open(pidfile, 'r') as f:
705 self.pid = int(f.readline().strip())
706 except IOError:
707 raise NodeError('Problem starting node %s' % self.name, process)
708 self.__update_status()
Something went wrong with that request. Please try again.