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
43 """Node-handling class.
45 For each node that we speak with, we create an instance of this
46 class, so that we have a safe place to store the details of this
50 def __init__(self, parent, node):
55 self.http_conn = hc = httplib.HTTPConnection(node, self.parent.port)
58 hc.putrequest('PUT', "/%s" % self.parent.procedure,
59 skip_accept_encoding=True)
60 hc.putheader('Content-Length', str(len(parent.body)))
63 except socket.error, err:
64 logger.Error("Error connecting to %s: %s" % (node, str(err)))
67 def get_response(self):
68 """Try to process the response from the node.
72 # we already failed in connect
74 resp = self.http_conn.getresponse()
75 if resp.status != 200:
78 length = int(resp.getheader('Content-Length', '0'))
82 logger.Error("Zero-length reply from %s" % self.node)
84 payload = resp.read(length)
85 unload = simplejson.loads(payload)
92 This class, given a (remote) method name, a list of parameters and a
93 list of nodes, will contact (in parallel) all nodes, and return a
94 dict of results (key: node name, value: result).
96 One current bug is that generic failure is still signalled by
97 'False' result, which is not good. This overloading of values can
105 def __init__(self, procedure, args):
106 ss = ssconf.SimpleStore()
107 self.port = ss.GetNodeDaemonPort()
108 self.nodepw = ss.GetNodeDaemonPassword()
111 self.procedure = procedure
113 self.body = simplejson.dumps(args)
115 #--- generic connector -------------
117 def connect_list(self, node_list):
118 """Add a list of nodes to the target nodes.
121 for node in node_list:
124 def connect(self, connect_node):
125 """Add a node to the target list.
128 self.nc[connect_node] = nc = NodeController(self, connect_node)
131 """Return the results of the call.
137 """Wrapper over reactor.run().
139 This function simply calls reactor.run() if we have any requests
140 queued, otherwise it does nothing.
143 for node, nc in self.nc.items():
144 self.results[node] = nc.get_response()
147 def call_volume_list(node_list, vg_name):
148 """Gets the logical volumes present in a given volume group.
150 This is a multi-node call.
153 c = Client("volume_list", [vg_name])
154 c.connect_list(node_list)
159 def call_vg_list(node_list):
160 """Gets the volume group list.
162 This is a multi-node call.
165 c = Client("vg_list", [])
166 c.connect_list(node_list)
171 def call_bridges_exist(node, bridges_list):
172 """Checks if a node has all the bridges given.
174 This method checks if all bridges given in the bridges_list are
175 present on the remote node, so that an instance that uses interfaces
176 on those bridges can be started.
178 This is a single-node call.
181 c = Client("bridges_exist", [bridges_list])
184 return c.getresult().get(node, False)
187 def call_instance_start(node, instance, extra_args):
188 """Starts an instance.
190 This is a single-node call.
193 c = Client("instance_start", [instance.ToDict(), extra_args])
196 return c.getresult().get(node, False)
199 def call_instance_shutdown(node, instance):
200 """Stops an instance.
202 This is a single-node call.
205 c = Client("instance_shutdown", [instance.ToDict()])
208 return c.getresult().get(node, False)
211 def call_instance_reboot(node, instance, reboot_type, extra_args):
212 """Reboots an instance.
214 This is a single-node call.
217 c = Client("instance_reboot", [instance.ToDict(), reboot_type, extra_args])
220 return c.getresult().get(node, False)
223 def call_instance_os_add(node, inst, osdev, swapdev):
224 """Installs an OS on the given instance.
226 This is a single-node call.
229 params = [inst.ToDict(), osdev, swapdev]
230 c = Client("instance_os_add", params)
233 return c.getresult().get(node, False)
236 def call_instance_run_rename(node, inst, old_name, osdev, swapdev):
237 """Run the OS rename script for an instance.
239 This is a single-node call.
242 params = [inst.ToDict(), old_name, osdev, swapdev]
243 c = Client("instance_run_rename", params)
246 return c.getresult().get(node, False)
249 def call_instance_info(node, instance):
250 """Returns information about a single instance.
252 This is a single-node call.
255 c = Client("instance_info", [instance])
258 return c.getresult().get(node, False)
261 def call_all_instances_info(node_list):
262 """Returns information about all instances on a given node.
264 This is a single-node call.
267 c = Client("all_instances_info", [])
268 c.connect_list(node_list)
273 def call_instance_list(node_list):
274 """Returns the list of running instances on a given node.
276 This is a single-node call.
279 c = Client("instance_list", [])
280 c.connect_list(node_list)
285 def call_node_tcp_ping(node, source, target, port, timeout, live_port_needed):
286 """Do a TcpPing on the remote node
288 This is a single-node call.
290 c = Client("node_tcp_ping", [source, target, port, timeout,
294 return c.getresult().get(node, False)
297 def call_node_info(node_list, vg_name):
298 """Return node information.
300 This will return memory information and volume group size and free
303 This is a multi-node call.
306 c = Client("node_info", [vg_name])
307 c.connect_list(node_list)
309 retux = c.getresult()
311 for node_name in retux:
312 ret = retux.get(node_name, False)
313 if type(ret) != dict:
314 logger.Error("could not connect to node %s" % (node_name))
318 { 'memory_total' : '-',
321 'vg_size' : 'node_unreachable',
328 def call_node_add(node, dsa, dsapub, rsa, rsapub, ssh, sshpub):
329 """Add a node to the cluster.
331 This is a single-node call.
334 params = [dsa, dsapub, rsa, rsapub, ssh, sshpub]
335 c = Client("node_add", params)
338 return c.getresult().get(node, False)
341 def call_node_verify(node_list, checkdict):
342 """Request verification of given parameters.
344 This is a multi-node call.
347 c = Client("node_verify", [checkdict])
348 c.connect_list(node_list)
353 def call_node_start_master(node):
354 """Tells a node to activate itself as a master.
356 This is a single-node call.
359 c = Client("node_start_master", [])
362 return c.getresult().get(node, False)
365 def call_node_stop_master(node):
366 """Tells a node to demote itself from master status.
368 This is a single-node call.
371 c = Client("node_stop_master", [])
374 return c.getresult().get(node, False)
377 def call_version(node_list):
378 """Query node version.
380 This is a multi-node call.
383 c = Client("version", [])
384 c.connect_list(node_list)
389 def call_blockdev_create(node, bdev, size, owner, on_primary, info):
390 """Request creation of a given block device.
392 This is a single-node call.
395 params = [bdev.ToDict(), size, owner, on_primary, info]
396 c = Client("blockdev_create", params)
399 return c.getresult().get(node, False)
402 def call_blockdev_remove(node, bdev):
403 """Request removal of a given block device.
405 This is a single-node call.
408 c = Client("blockdev_remove", [bdev.ToDict()])
411 return c.getresult().get(node, False)
414 def call_blockdev_rename(node, devlist):
415 """Request rename of the given block devices.
417 This is a single-node call.
420 params = [(d.ToDict(), uid) for d, uid in devlist]
421 c = Client("blockdev_rename", params)
424 return c.getresult().get(node, False)
427 def call_blockdev_assemble(node, disk, owner, on_primary):
428 """Request assembling of a given block device.
430 This is a single-node call.
433 params = [disk.ToDict(), owner, on_primary]
434 c = Client("blockdev_assemble", params)
437 return c.getresult().get(node, False)
440 def call_blockdev_shutdown(node, disk):
441 """Request shutdown of a given block device.
443 This is a single-node call.
446 c = Client("blockdev_shutdown", [disk.ToDict()])
449 return c.getresult().get(node, False)
452 def call_blockdev_addchildren(node, bdev, ndevs):
453 """Request adding a list of children to a (mirroring) device.
455 This is a single-node call.
458 params = [bdev.ToDict(), [disk.ToDict() for disk in ndevs]]
459 c = Client("blockdev_addchildren", params)
462 return c.getresult().get(node, False)
465 def call_blockdev_removechildren(node, bdev, ndevs):
466 """Request removing a list of children from a (mirroring) device.
468 This is a single-node call.
471 params = [bdev.ToDict(), [disk.ToDict() for disk in ndevs]]
472 c = Client("blockdev_removechildren", params)
475 return c.getresult().get(node, False)
478 def call_blockdev_getmirrorstatus(node, disks):
479 """Request status of a (mirroring) device.
481 This is a single-node call.
484 params = [dsk.ToDict() for dsk in disks]
485 c = Client("blockdev_getmirrorstatus", params)
488 return c.getresult().get(node, False)
491 def call_blockdev_find(node, disk):
492 """Request identification of a given block device.
494 This is a single-node call.
497 c = Client("blockdev_find", [disk.ToDict()])
500 return c.getresult().get(node, False)
503 def call_upload_file(node_list, file_name):
506 The node will refuse the operation in case the file is not on the
509 This is a multi-node call.
517 st = os.stat(file_name)
518 params = [file_name, data, st.st_mode, st.st_uid, st.st_gid,
519 st.st_atime, st.st_mtime]
520 c = Client("upload_file", params)
521 c.connect_list(node_list)
526 def call_os_diagnose(node_list):
527 """Request a diagnose of OS definitions.
529 This is a multi-node call.
532 c = Client("os_diagnose", [])
533 c.connect_list(node_list)
535 result = c.getresult()
537 for node_name in result:
538 if result[node_name]:
539 nr = [objects.OS.FromDict(oss) for oss in result[node_name]]
542 new_result[node_name] = nr
546 def call_os_get(node, name):
547 """Returns an OS definition.
549 This is a single-node call.
552 c = Client("os_get", [name])
555 result = c.getresult().get(node, False)
556 if isinstance(result, dict):
557 return objects.OS.FromDict(result)
562 def call_hooks_runner(node_list, hpath, phase, env):
563 """Call the hooks runner.
566 - op: the OpCode instance
567 - env: a dictionary with the environment
569 This is a multi-node call.
572 params = [hpath, phase, env]
573 c = Client("hooks_runner", params)
574 c.connect_list(node_list)
576 result = c.getresult()
580 def call_blockdev_snapshot(node, cf_bdev):
581 """Request a snapshot of the given block device.
583 This is a single-node call.
586 c = Client("blockdev_snapshot", [cf_bdev.ToDict()])
589 return c.getresult().get(node, False)
592 def call_snapshot_export(node, snap_bdev, dest_node, instance):
593 """Request the export of a given snapshot.
595 This is a single-node call.
598 params = [snap_bdev.ToDict(), dest_node, instance.ToDict()]
599 c = Client("snapshot_export", params)
602 return c.getresult().get(node, False)
605 def call_finalize_export(node, instance, snap_disks):
606 """Request the completion of an export operation.
608 This writes the export config file, etc.
610 This is a single-node call.
614 for disk in snap_disks:
615 flat_disks.append(disk.ToDict())
616 params = [instance.ToDict(), flat_disks]
617 c = Client("finalize_export", params)
620 return c.getresult().get(node, False)
623 def call_export_info(node, path):
624 """Queries the export information in a given path.
626 This is a single-node call.
629 c = Client("export_info", [path])
632 result = c.getresult().get(node, False)
635 return objects.SerializableConfigParser.Loads(str(result))
638 def call_instance_os_import(node, inst, osdev, swapdev, src_node, src_image):
639 """Request the import of a backup into an instance.
641 This is a single-node call.
644 params = [inst.ToDict(), osdev, swapdev, src_node, src_image]
645 c = Client("instance_os_import", params)
648 return c.getresult().get(node, False)
651 def call_export_list(node_list):
652 """Gets the stored exports list.
654 This is a multi-node call.
657 c = Client("export_list", [])
658 c.connect_list(node_list)
660 result = c.getresult()
664 def call_export_remove(node, export):
665 """Requests removal of a given export.
667 This is a single-node call.
670 c = Client("export_remove", [export])
673 return c.getresult().get(node, False)
676 def call_node_leave_cluster(node):
677 """Requests a node to clean the cluster information it has.
679 This will remove the configuration information from the ganeti data
682 This is a single-node call.
685 c = Client("node_leave_cluster", [])
688 return c.getresult().get(node, False)
691 def call_node_volumes(node_list):
692 """Gets all volumes on node(s).
694 This is a multi-node call.
697 c = Client("node_volumes", [])
698 c.connect_list(node_list)
703 def call_test_delay(node_list, duration):
704 """Sleep for a fixed time on given node(s).
706 This is a multi-node call.
709 c = Client("test_delay", [duration])
710 c.connect_list(node_list)
715 def call_file_storage_dir_create(node, file_storage_dir):
716 """Create the given file storage directory.
718 This is a single-node call.
721 c = Client("file_storage_dir_create", [file_storage_dir])
724 return c.getresult().get(node, False)
727 def call_file_storage_dir_remove(node, file_storage_dir):
728 """Remove the given file storage directory.
730 This is a single-node call.
733 c = Client("file_storage_dir_remove", [file_storage_dir])
736 return c.getresult().get(node, False)
739 def call_file_storage_dir_rename(node, old_file_storage_dir,
740 new_file_storage_dir):
741 """Rename file storage directory.
743 This is a single-node call.
746 c = Client("file_storage_dir_rename",
747 [old_file_storage_dir, new_file_storage_dir])
750 return c.getresult().get(node, False)