4 # Copyright (C) 2006, 2007 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Script to show add a new node to the cluster
26 # pylint: disable-msg=C0103
34 from ganeti import logger
35 from ganeti import utils
36 from ganeti import errors
37 from ganeti import constants
38 from ganeti import objects
39 from ganeti import ssconf
42 """Node-handling class.
44 For each node that we speak with, we create an instance of this
45 class, so that we have a safe place to store the details of this
49 def __init__(self, parent, node):
54 self.http_conn = hc = httplib.HTTPConnection(node, self.parent.port)
57 hc.putrequest('PUT', "/%s" % self.parent.procedure,
58 skip_accept_encoding=True)
59 hc.putheader('Content-Length', str(len(parent.body)))
62 except socket.error, err:
63 logger.Error("Error connecting to %s: %s" % (node, str(err)))
66 def get_response(self):
67 """Try to process the response from the node.
71 # we already failed in connect
73 resp = self.http_conn.getresponse()
74 if resp.status != 200:
77 length = int(resp.getheader('Content-Length', '0'))
81 logger.Error("Zero-length reply from %s" % self.node)
83 payload = resp.read(length)
84 unload = simplejson.loads(payload)
91 This class, given a (remote) method name, a list of parameters and a
92 list of nodes, will contact (in parallel) all nodes, and return a
93 dict of results (key: node name, value: result).
95 One current bug is that generic failure is still signalled by
96 'False' result, which is not good. This overloading of values can
104 def __init__(self, procedure, args):
105 ss = ssconf.SimpleStore()
106 self.port = ss.GetNodeDaemonPort()
107 self.nodepw = ss.GetNodeDaemonPassword()
110 self.procedure = procedure
112 self.body = simplejson.dumps(args)
114 #--- generic connector -------------
116 def connect_list(self, node_list):
117 """Add a list of nodes to the target nodes.
120 for node in node_list:
123 def connect(self, connect_node):
124 """Add a node to the target list.
127 self.nc[connect_node] = nc = NodeController(self, connect_node)
130 """Return the results of the call.
136 """Wrapper over reactor.run().
138 This function simply calls reactor.run() if we have any requests
139 queued, otherwise it does nothing.
142 for node, nc in self.nc.items():
143 self.results[node] = nc.get_response()
146 def call_volume_list(node_list, vg_name):
147 """Gets the logical volumes present in a given volume group.
149 This is a multi-node call.
152 c = Client("volume_list", [vg_name])
153 c.connect_list(node_list)
158 def call_vg_list(node_list):
159 """Gets the volume group list.
161 This is a multi-node call.
164 c = Client("vg_list", [])
165 c.connect_list(node_list)
170 def call_bridges_exist(node, bridges_list):
171 """Checks if a node has all the bridges given.
173 This method checks if all bridges given in the bridges_list are
174 present on the remote node, so that an instance that uses interfaces
175 on those bridges can be started.
177 This is a single-node call.
180 c = Client("bridges_exist", [bridges_list])
183 return c.getresult().get(node, False)
186 def call_instance_start(node, instance, extra_args):
187 """Starts an instance.
189 This is a single-node call.
192 c = Client("instance_start", [instance.ToDict(), extra_args])
195 return c.getresult().get(node, False)
198 def call_instance_shutdown(node, instance):
199 """Stops an instance.
201 This is a single-node call.
204 c = Client("instance_shutdown", [instance.ToDict()])
207 return c.getresult().get(node, False)
210 def call_instance_reboot(node, instance, reboot_type, extra_args):
211 """Reboots an instance.
213 This is a single-node call.
216 c = Client("instance_reboot", [instance.ToDict(), reboot_type, extra_args])
219 return c.getresult().get(node, False)
222 def call_instance_os_add(node, inst, osdev, swapdev):
223 """Installs an OS on the given instance.
225 This is a single-node call.
228 params = [inst.ToDict(), osdev, swapdev]
229 c = Client("instance_os_add", params)
232 return c.getresult().get(node, False)
235 def call_instance_run_rename(node, inst, old_name, osdev, swapdev):
236 """Run the OS rename script for an instance.
238 This is a single-node call.
241 params = [inst.ToDict(), old_name, osdev, swapdev]
242 c = Client("instance_run_rename", params)
245 return c.getresult().get(node, False)
248 def call_instance_info(node, instance):
249 """Returns information about a single instance.
251 This is a single-node call.
254 c = Client("instance_info", [instance])
257 return c.getresult().get(node, False)
260 def call_all_instances_info(node_list):
261 """Returns information about all instances on a given node.
263 This is a single-node call.
266 c = Client("all_instances_info", [])
267 c.connect_list(node_list)
272 def call_instance_list(node_list):
273 """Returns the list of running instances on a given node.
275 This is a single-node call.
278 c = Client("instance_list", [])
279 c.connect_list(node_list)
284 def call_node_tcp_ping(node, source, target, port, timeout, live_port_needed):
285 """Do a TcpPing on the remote node
287 This is a single-node call.
289 c = Client("node_tcp_ping", [source, target, port, timeout,
293 return c.getresult().get(node, False)
296 def call_node_info(node_list, vg_name):
297 """Return node information.
299 This will return memory information and volume group size and free
302 This is a multi-node call.
305 c = Client("node_info", [vg_name])
306 c.connect_list(node_list)
308 retux = c.getresult()
310 for node_name in retux:
311 ret = retux.get(node_name, False)
312 if type(ret) != dict:
313 logger.Error("could not connect to node %s" % (node_name))
317 { 'memory_total' : '-',
320 'vg_size' : 'node_unreachable',
327 def call_node_add(node, dsa, dsapub, rsa, rsapub, ssh, sshpub):
328 """Add a node to the cluster.
330 This is a single-node call.
333 params = [dsa, dsapub, rsa, rsapub, ssh, sshpub]
334 c = Client("node_add", params)
337 return c.getresult().get(node, False)
340 def call_node_verify(node_list, checkdict):
341 """Request verification of given parameters.
343 This is a multi-node call.
346 c = Client("node_verify", [checkdict])
347 c.connect_list(node_list)
352 def call_node_start_master(node):
353 """Tells a node to activate itself as a master.
355 This is a single-node call.
358 c = Client("node_start_master", [])
361 return c.getresult().get(node, False)
364 def call_node_stop_master(node):
365 """Tells a node to demote itself from master status.
367 This is a single-node call.
370 c = Client("node_stop_master", [])
373 return c.getresult().get(node, False)
376 def call_version(node_list):
377 """Query node version.
379 This is a multi-node call.
382 c = Client("version", [])
383 c.connect_list(node_list)
388 def call_blockdev_create(node, bdev, size, owner, on_primary, info):
389 """Request creation of a given block device.
391 This is a single-node call.
394 params = [bdev.ToDict(), size, owner, on_primary, info]
395 c = Client("blockdev_create", params)
398 return c.getresult().get(node, False)
401 def call_blockdev_remove(node, bdev):
402 """Request removal of a given block device.
404 This is a single-node call.
407 c = Client("blockdev_remove", [bdev.ToDict()])
410 return c.getresult().get(node, False)
413 def call_blockdev_rename(node, devlist):
414 """Request rename of the given block devices.
416 This is a single-node call.
419 params = [(d.ToDict(), uid) for d, uid in devlist]
420 c = Client("blockdev_rename", params)
423 return c.getresult().get(node, False)
426 def call_blockdev_assemble(node, disk, owner, on_primary):
427 """Request assembling of a given block device.
429 This is a single-node call.
432 params = [disk.ToDict(), owner, on_primary]
433 c = Client("blockdev_assemble", params)
436 return c.getresult().get(node, False)
439 def call_blockdev_shutdown(node, disk):
440 """Request shutdown of a given block device.
442 This is a single-node call.
445 c = Client("blockdev_shutdown", [disk.ToDict()])
448 return c.getresult().get(node, False)
451 def call_blockdev_addchildren(node, bdev, ndevs):
452 """Request adding a list of children to a (mirroring) device.
454 This is a single-node call.
457 params = [bdev.ToDict(), [disk.ToDict() for disk in ndevs]]
458 c = Client("blockdev_addchildren", params)
461 return c.getresult().get(node, False)
464 def call_blockdev_removechildren(node, bdev, ndevs):
465 """Request removing a list of children from a (mirroring) device.
467 This is a single-node call.
470 params = [bdev.ToDict(), [disk.ToDict() for disk in ndevs]]
471 c = Client("blockdev_removechildren", params)
474 return c.getresult().get(node, False)
477 def call_blockdev_getmirrorstatus(node, disks):
478 """Request status of a (mirroring) device.
480 This is a single-node call.
483 params = [dsk.ToDict() for dsk in disks]
484 c = Client("blockdev_getmirrorstatus", params)
487 return c.getresult().get(node, False)
490 def call_blockdev_find(node, disk):
491 """Request identification of a given block device.
493 This is a single-node call.
496 c = Client("blockdev_find", [disk.ToDict()])
499 return c.getresult().get(node, False)
502 def call_upload_file(node_list, file_name):
505 The node will refuse the operation in case the file is not on the
508 This is a multi-node call.
516 st = os.stat(file_name)
517 params = [file_name, data, st.st_mode, st.st_uid, st.st_gid,
518 st.st_atime, st.st_mtime]
519 c = Client("upload_file", params)
520 c.connect_list(node_list)
525 def call_os_diagnose(node_list):
526 """Request a diagnose of OS definitions.
528 This is a multi-node call.
531 c = Client("os_diagnose", [])
532 c.connect_list(node_list)
534 result = c.getresult()
536 for node_name in result:
537 if result[node_name]:
538 nr = [objects.OS.FromDict(oss) for oss in result[node_name]]
541 new_result[node_name] = nr
545 def call_os_get(node, name):
546 """Returns an OS definition.
548 This is a single-node call.
551 c = Client("os_get", [name])
554 result = c.getresult().get(node, False)
555 if isinstance(result, dict):
556 return objects.OS.FromDict(result)
561 def call_hooks_runner(node_list, hpath, phase, env):
562 """Call the hooks runner.
565 - op: the OpCode instance
566 - env: a dictionary with the environment
568 This is a multi-node call.
571 params = [hpath, phase, env]
572 c = Client("hooks_runner", params)
573 c.connect_list(node_list)
575 result = c.getresult()
579 def call_blockdev_snapshot(node, cf_bdev):
580 """Request a snapshot of the given block device.
582 This is a single-node call.
585 c = Client("blockdev_snapshot", [cf_bdev.ToDict()])
588 return c.getresult().get(node, False)
591 def call_snapshot_export(node, snap_bdev, dest_node, instance):
592 """Request the export of a given snapshot.
594 This is a single-node call.
597 params = [snap_bdev.ToDict(), dest_node, instance.ToDict()]
598 c = Client("snapshot_export", params)
601 return c.getresult().get(node, False)
604 def call_finalize_export(node, instance, snap_disks):
605 """Request the completion of an export operation.
607 This writes the export config file, etc.
609 This is a single-node call.
613 for disk in snap_disks:
614 flat_disks.append(disk.ToDict())
615 params = [instance.ToDict(), flat_disks]
616 c = Client("finalize_export", params)
619 return c.getresult().get(node, False)
622 def call_export_info(node, path):
623 """Queries the export information in a given path.
625 This is a single-node call.
628 c = Client("export_info", [path])
631 result = c.getresult().get(node, False)
634 return objects.SerializableConfigParser.Loads(result)
637 def call_instance_os_import(node, inst, osdev, swapdev, src_node, src_image):
638 """Request the import of a backup into an instance.
640 This is a single-node call.
643 params = [inst.ToDict(), osdev, swapdev, src_node, src_image]
644 c = Client("instance_os_import", params)
647 return c.getresult().get(node, False)
650 def call_export_list(node_list):
651 """Gets the stored exports list.
653 This is a multi-node call.
656 c = Client("export_list", [])
657 c.connect_list(node_list)
659 result = c.getresult()
663 def call_export_remove(node, export):
664 """Requests removal of a given export.
666 This is a single-node call.
669 c = Client("export_remove", [export])
672 return c.getresult().get(node, False)
675 def call_node_leave_cluster(node):
676 """Requests a node to clean the cluster information it has.
678 This will remove the configuration information from the ganeti data
681 This is a single-node call.
684 c = Client("node_leave_cluster", [])
687 return c.getresult().get(node, False)
690 def call_node_volumes(node_list):
691 """Gets all volumes on node(s).
693 This is a multi-node call.
696 c = Client("node_volumes", [])
697 c.connect_list(node_list)
702 def call_test_delay(node_list, duration):
703 """Sleep for a fixed time on given node(s).
705 This is a multi-node call.
708 c = Client("test_delay", [duration])
709 c.connect_list(node_list)