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 objects
37 from ganeti import ssconf
41 """Node-handling class.
43 For each node that we speak with, we create an instance of this
44 class, so that we have a safe place to store the details of this
48 def __init__(self, parent, node):
53 self.http_conn = hc = httplib.HTTPConnection(node, self.parent.port)
56 hc.putrequest('PUT', "/%s" % self.parent.procedure,
57 skip_accept_encoding=True)
58 hc.putheader('Content-Length', str(len(parent.body)))
61 except socket.error, err:
62 logger.Error("Error connecting to %s: %s" % (node, str(err)))
65 def get_response(self):
66 """Try to process the response from the node.
70 # we already failed in connect
72 resp = self.http_conn.getresponse()
73 if resp.status != 200:
76 length = int(resp.getheader('Content-Length', '0'))
80 logger.Error("Zero-length reply from %s" % self.node)
82 payload = resp.read(length)
83 unload = simplejson.loads(payload)
90 This class, given a (remote) method name, a list of parameters and a
91 list of nodes, will contact (in parallel) all nodes, and return a
92 dict of results (key: node name, value: result).
94 One current bug is that generic failure is still signalled by
95 'False' result, which is not good. This overloading of values can
103 def __init__(self, procedure, args):
104 ss = ssconf.SimpleStore()
105 self.port = ss.GetNodeDaemonPort()
106 self.nodepw = ss.GetNodeDaemonPassword()
109 self.procedure = procedure
111 self.body = simplejson.dumps(args)
113 #--- generic connector -------------
115 def connect_list(self, node_list):
116 """Add a list of nodes to the target nodes.
119 for node in node_list:
122 def connect(self, connect_node):
123 """Add a node to the target list.
126 self.nc[connect_node] = nc = NodeController(self, connect_node)
129 """Return the results of the call.
135 """Wrapper over reactor.run().
137 This function simply calls reactor.run() if we have any requests
138 queued, otherwise it does nothing.
141 for node, nc in self.nc.items():
142 self.results[node] = nc.get_response()
145 def call_volume_list(node_list, vg_name):
146 """Gets the logical volumes present in a given volume group.
148 This is a multi-node call.
151 c = Client("volume_list", [vg_name])
152 c.connect_list(node_list)
157 def call_vg_list(node_list):
158 """Gets the volume group list.
160 This is a multi-node call.
163 c = Client("vg_list", [])
164 c.connect_list(node_list)
169 def call_bridges_exist(node, bridges_list):
170 """Checks if a node has all the bridges given.
172 This method checks if all bridges given in the bridges_list are
173 present on the remote node, so that an instance that uses interfaces
174 on those bridges can be started.
176 This is a single-node call.
179 c = Client("bridges_exist", [bridges_list])
182 return c.getresult().get(node, False)
185 def call_instance_start(node, instance, extra_args):
186 """Starts an instance.
188 This is a single-node call.
191 c = Client("instance_start", [instance.ToDict(), extra_args])
194 return c.getresult().get(node, False)
197 def call_instance_shutdown(node, instance):
198 """Stops an instance.
200 This is a single-node call.
203 c = Client("instance_shutdown", [instance.ToDict()])
206 return c.getresult().get(node, False)
209 def call_instance_migrate(node, instance, target, live):
210 """Migrate an instance.
212 This is a single-node call.
215 c = Client("instance_migrate", [instance.name, target, live])
218 return c.getresult().get(node, False)
221 def call_instance_reboot(node, instance, reboot_type, extra_args):
222 """Reboots an instance.
224 This is a single-node call.
227 c = Client("instance_reboot", [instance.ToDict(), reboot_type, extra_args])
230 return c.getresult().get(node, False)
233 def call_instance_os_add(node, inst, osdev, swapdev):
234 """Installs an OS on the given instance.
236 This is a single-node call.
239 params = [inst.ToDict(), osdev, swapdev]
240 c = Client("instance_os_add", params)
243 return c.getresult().get(node, False)
246 def call_instance_run_rename(node, inst, old_name, osdev, swapdev):
247 """Run the OS rename script for an instance.
249 This is a single-node call.
252 params = [inst.ToDict(), old_name, osdev, swapdev]
253 c = Client("instance_run_rename", params)
256 return c.getresult().get(node, False)
259 def call_instance_info(node, instance):
260 """Returns information about a single instance.
262 This is a single-node call.
265 c = Client("instance_info", [instance])
268 return c.getresult().get(node, False)
271 def call_all_instances_info(node_list):
272 """Returns information about all instances on a given node.
274 This is a single-node call.
277 c = Client("all_instances_info", [])
278 c.connect_list(node_list)
283 def call_instance_list(node_list):
284 """Returns the list of running instances on a given node.
286 This is a single-node call.
289 c = Client("instance_list", [])
290 c.connect_list(node_list)
295 def call_node_tcp_ping(node, source, target, port, timeout, live_port_needed):
296 """Do a TcpPing on the remote node
298 This is a single-node call.
300 c = Client("node_tcp_ping", [source, target, port, timeout,
304 return c.getresult().get(node, False)
307 def call_node_info(node_list, vg_name):
308 """Return node information.
310 This will return memory information and volume group size and free
313 This is a multi-node call.
316 c = Client("node_info", [vg_name])
317 c.connect_list(node_list)
319 retux = c.getresult()
321 for node_name in retux:
322 ret = retux.get(node_name, False)
323 if type(ret) != dict:
324 logger.Error("could not connect to node %s" % (node_name))
328 { 'memory_total' : '-',
331 'vg_size' : 'node_unreachable',
338 def call_node_add(node, dsa, dsapub, rsa, rsapub, ssh, sshpub):
339 """Add a node to the cluster.
341 This is a single-node call.
344 params = [dsa, dsapub, rsa, rsapub, ssh, sshpub]
345 c = Client("node_add", params)
348 return c.getresult().get(node, False)
351 def call_node_verify(node_list, checkdict):
352 """Request verification of given parameters.
354 This is a multi-node call.
357 c = Client("node_verify", [checkdict])
358 c.connect_list(node_list)
363 def call_node_start_master(node, start_daemons):
364 """Tells a node to activate itself as a master.
366 This is a single-node call.
369 c = Client("node_start_master", [start_daemons])
372 return c.getresult().get(node, False)
375 def call_node_stop_master(node, stop_daemons):
376 """Tells a node to demote itself from master status.
378 This is a single-node call.
381 c = Client("node_stop_master", [stop_daemons])
384 return c.getresult().get(node, False)
387 def call_version(node_list):
388 """Query node version.
390 This is a multi-node call.
393 c = Client("version", [])
394 c.connect_list(node_list)
399 def call_blockdev_create(node, bdev, size, owner, on_primary, info):
400 """Request creation of a given block device.
402 This is a single-node call.
405 params = [bdev.ToDict(), size, owner, on_primary, info]
406 c = Client("blockdev_create", params)
409 return c.getresult().get(node, False)
412 def call_blockdev_remove(node, bdev):
413 """Request removal of a given block device.
415 This is a single-node call.
418 c = Client("blockdev_remove", [bdev.ToDict()])
421 return c.getresult().get(node, False)
424 def call_blockdev_rename(node, devlist):
425 """Request rename of the given block devices.
427 This is a single-node call.
430 params = [(d.ToDict(), uid) for d, uid in devlist]
431 c = Client("blockdev_rename", params)
434 return c.getresult().get(node, False)
437 def call_blockdev_assemble(node, disk, owner, on_primary):
438 """Request assembling of a given block device.
440 This is a single-node call.
443 params = [disk.ToDict(), owner, on_primary]
444 c = Client("blockdev_assemble", params)
447 return c.getresult().get(node, False)
450 def call_blockdev_shutdown(node, disk):
451 """Request shutdown of a given block device.
453 This is a single-node call.
456 c = Client("blockdev_shutdown", [disk.ToDict()])
459 return c.getresult().get(node, False)
462 def call_blockdev_addchildren(node, bdev, ndevs):
463 """Request adding a list of children to a (mirroring) device.
465 This is a single-node call.
468 params = [bdev.ToDict(), [disk.ToDict() for disk in ndevs]]
469 c = Client("blockdev_addchildren", params)
472 return c.getresult().get(node, False)
475 def call_blockdev_removechildren(node, bdev, ndevs):
476 """Request removing a list of children from a (mirroring) device.
478 This is a single-node call.
481 params = [bdev.ToDict(), [disk.ToDict() for disk in ndevs]]
482 c = Client("blockdev_removechildren", params)
485 return c.getresult().get(node, False)
488 def call_blockdev_getmirrorstatus(node, disks):
489 """Request status of a (mirroring) device.
491 This is a single-node call.
494 params = [dsk.ToDict() for dsk in disks]
495 c = Client("blockdev_getmirrorstatus", params)
498 return c.getresult().get(node, False)
501 def call_blockdev_find(node, disk):
502 """Request identification of a given block device.
504 This is a single-node call.
507 c = Client("blockdev_find", [disk.ToDict()])
510 return c.getresult().get(node, False)
513 def call_blockdev_close(node, disks):
514 """Closes the given block devices.
516 This is a single-node call.
519 params = [cf.ToDict() for cf in disks]
520 c = Client("blockdev_close", params)
523 return c.getresult().get(node, False)
526 def call_upload_file(node_list, file_name):
529 The node will refuse the operation in case the file is not on the
532 This is a multi-node call.
540 st = os.stat(file_name)
541 params = [file_name, data, st.st_mode, st.st_uid, st.st_gid,
542 st.st_atime, st.st_mtime]
543 c = Client("upload_file", params)
544 c.connect_list(node_list)
549 def call_os_diagnose(node_list):
550 """Request a diagnose of OS definitions.
552 This is a multi-node call.
555 c = Client("os_diagnose", [])
556 c.connect_list(node_list)
558 result = c.getresult()
560 for node_name in result:
561 if result[node_name]:
562 nr = [objects.OS.FromDict(oss) for oss in result[node_name]]
565 new_result[node_name] = nr
569 def call_os_get(node, name):
570 """Returns an OS definition.
572 This is a single-node call.
575 c = Client("os_get", [name])
578 result = c.getresult().get(node, False)
579 if isinstance(result, dict):
580 return objects.OS.FromDict(result)
585 def call_hooks_runner(node_list, hpath, phase, env):
586 """Call the hooks runner.
589 - op: the OpCode instance
590 - env: a dictionary with the environment
592 This is a multi-node call.
595 params = [hpath, phase, env]
596 c = Client("hooks_runner", params)
597 c.connect_list(node_list)
599 result = c.getresult()
603 def call_iallocator_runner(node, name, idata):
604 """Call an iallocator on a remote node
607 - name: the iallocator name
608 - input: the json-encoded input string
610 This is a single-node call.
613 params = [name, idata]
614 c = Client("iallocator_runner", params)
617 result = c.getresult().get(node, False)
621 def call_blockdev_grow(node, cf_bdev, amount):
622 """Request a snapshot of the given block device.
624 This is a single-node call.
627 c = Client("blockdev_grow", [cf_bdev.ToDict(), amount])
630 return c.getresult().get(node, False)
633 def call_blockdev_snapshot(node, cf_bdev):
634 """Request a snapshot of the given block device.
636 This is a single-node call.
639 c = Client("blockdev_snapshot", [cf_bdev.ToDict()])
642 return c.getresult().get(node, False)
645 def call_snapshot_export(node, snap_bdev, dest_node, instance):
646 """Request the export of a given snapshot.
648 This is a single-node call.
651 params = [snap_bdev.ToDict(), dest_node, instance.ToDict()]
652 c = Client("snapshot_export", params)
655 return c.getresult().get(node, False)
658 def call_finalize_export(node, instance, snap_disks):
659 """Request the completion of an export operation.
661 This writes the export config file, etc.
663 This is a single-node call.
667 for disk in snap_disks:
668 flat_disks.append(disk.ToDict())
669 params = [instance.ToDict(), flat_disks]
670 c = Client("finalize_export", params)
673 return c.getresult().get(node, False)
676 def call_export_info(node, path):
677 """Queries the export information in a given path.
679 This is a single-node call.
682 c = Client("export_info", [path])
685 result = c.getresult().get(node, False)
688 return objects.SerializableConfigParser.Loads(str(result))
691 def call_instance_os_import(node, inst, osdev, swapdev, src_node, src_image):
692 """Request the import of a backup into an instance.
694 This is a single-node call.
697 params = [inst.ToDict(), osdev, swapdev, src_node, src_image]
698 c = Client("instance_os_import", params)
701 return c.getresult().get(node, False)
704 def call_export_list(node_list):
705 """Gets the stored exports list.
707 This is a multi-node call.
710 c = Client("export_list", [])
711 c.connect_list(node_list)
713 result = c.getresult()
717 def call_export_remove(node, export):
718 """Requests removal of a given export.
720 This is a single-node call.
723 c = Client("export_remove", [export])
726 return c.getresult().get(node, False)
729 def call_node_leave_cluster(node):
730 """Requests a node to clean the cluster information it has.
732 This will remove the configuration information from the ganeti data
735 This is a single-node call.
738 c = Client("node_leave_cluster", [])
741 return c.getresult().get(node, False)
744 def call_node_volumes(node_list):
745 """Gets all volumes on node(s).
747 This is a multi-node call.
750 c = Client("node_volumes", [])
751 c.connect_list(node_list)
756 def call_test_delay(node_list, duration):
757 """Sleep for a fixed time on given node(s).
759 This is a multi-node call.
762 c = Client("test_delay", [duration])
763 c.connect_list(node_list)
768 def call_file_storage_dir_create(node, file_storage_dir):
769 """Create the given file storage directory.
771 This is a single-node call.
774 c = Client("file_storage_dir_create", [file_storage_dir])
777 return c.getresult().get(node, False)
780 def call_file_storage_dir_remove(node, file_storage_dir):
781 """Remove the given file storage directory.
783 This is a single-node call.
786 c = Client("file_storage_dir_remove", [file_storage_dir])
789 return c.getresult().get(node, False)
792 def call_file_storage_dir_rename(node, old_file_storage_dir,
793 new_file_storage_dir):
794 """Rename file storage directory.
796 This is a single-node call.
799 c = Client("file_storage_dir_rename",
800 [old_file_storage_dir, new_file_storage_dir])
803 return c.getresult().get(node, False)