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_migrate(node, instance, target, live):
212 """Migrate an instance.
214 This is a single-node call.
217 c = Client("instance_migrate", [instance.name, target, live])
220 return c.getresult().get(node, False)
223 def call_instance_reboot(node, instance, reboot_type, extra_args):
224 """Reboots an instance.
226 This is a single-node call.
229 c = Client("instance_reboot", [instance.ToDict(), reboot_type, extra_args])
232 return c.getresult().get(node, False)
235 def call_instance_os_add(node, inst, osdev, swapdev):
236 """Installs an OS on the given instance.
238 This is a single-node call.
241 params = [inst.ToDict(), osdev, swapdev]
242 c = Client("instance_os_add", params)
245 return c.getresult().get(node, False)
248 def call_instance_run_rename(node, inst, old_name, osdev, swapdev):
249 """Run the OS rename script for an instance.
251 This is a single-node call.
254 params = [inst.ToDict(), old_name, osdev, swapdev]
255 c = Client("instance_run_rename", params)
258 return c.getresult().get(node, False)
261 def call_instance_info(node, instance):
262 """Returns information about a single instance.
264 This is a single-node call.
267 c = Client("instance_info", [instance])
270 return c.getresult().get(node, False)
273 def call_all_instances_info(node_list):
274 """Returns information about all instances on a given node.
276 This is a single-node call.
279 c = Client("all_instances_info", [])
280 c.connect_list(node_list)
285 def call_instance_list(node_list):
286 """Returns the list of running instances on a given node.
288 This is a single-node call.
291 c = Client("instance_list", [])
292 c.connect_list(node_list)
297 def call_node_tcp_ping(node, source, target, port, timeout, live_port_needed):
298 """Do a TcpPing on the remote node
300 This is a single-node call.
302 c = Client("node_tcp_ping", [source, target, port, timeout,
306 return c.getresult().get(node, False)
309 def call_node_info(node_list, vg_name):
310 """Return node information.
312 This will return memory information and volume group size and free
315 This is a multi-node call.
318 c = Client("node_info", [vg_name])
319 c.connect_list(node_list)
321 retux = c.getresult()
323 for node_name in retux:
324 ret = retux.get(node_name, False)
325 if type(ret) != dict:
326 logger.Error("could not connect to node %s" % (node_name))
330 { 'memory_total' : '-',
333 'vg_size' : 'node_unreachable',
340 def call_node_add(node, dsa, dsapub, rsa, rsapub, ssh, sshpub):
341 """Add a node to the cluster.
343 This is a single-node call.
346 params = [dsa, dsapub, rsa, rsapub, ssh, sshpub]
347 c = Client("node_add", params)
350 return c.getresult().get(node, False)
353 def call_node_verify(node_list, checkdict):
354 """Request verification of given parameters.
356 This is a multi-node call.
359 c = Client("node_verify", [checkdict])
360 c.connect_list(node_list)
365 def call_node_start_master(node, start_daemons):
366 """Tells a node to activate itself as a master.
368 This is a single-node call.
371 c = Client("node_start_master", [start_daemons])
374 return c.getresult().get(node, False)
377 def call_node_stop_master(node, stop_daemons):
378 """Tells a node to demote itself from master status.
380 This is a single-node call.
383 c = Client("node_stop_master", [stop_daemons])
386 return c.getresult().get(node, False)
389 def call_version(node_list):
390 """Query node version.
392 This is a multi-node call.
395 c = Client("version", [])
396 c.connect_list(node_list)
401 def call_blockdev_create(node, bdev, size, owner, on_primary, info):
402 """Request creation of a given block device.
404 This is a single-node call.
407 params = [bdev.ToDict(), size, owner, on_primary, info]
408 c = Client("blockdev_create", params)
411 return c.getresult().get(node, False)
414 def call_blockdev_remove(node, bdev):
415 """Request removal of a given block device.
417 This is a single-node call.
420 c = Client("blockdev_remove", [bdev.ToDict()])
423 return c.getresult().get(node, False)
426 def call_blockdev_rename(node, devlist):
427 """Request rename of the given block devices.
429 This is a single-node call.
432 params = [(d.ToDict(), uid) for d, uid in devlist]
433 c = Client("blockdev_rename", params)
436 return c.getresult().get(node, False)
439 def call_blockdev_assemble(node, disk, owner, on_primary):
440 """Request assembling of a given block device.
442 This is a single-node call.
445 params = [disk.ToDict(), owner, on_primary]
446 c = Client("blockdev_assemble", params)
449 return c.getresult().get(node, False)
452 def call_blockdev_shutdown(node, disk):
453 """Request shutdown of a given block device.
455 This is a single-node call.
458 c = Client("blockdev_shutdown", [disk.ToDict()])
461 return c.getresult().get(node, False)
464 def call_blockdev_addchildren(node, bdev, ndevs):
465 """Request adding a list of children to a (mirroring) device.
467 This is a single-node call.
470 params = [bdev.ToDict(), [disk.ToDict() for disk in ndevs]]
471 c = Client("blockdev_addchildren", params)
474 return c.getresult().get(node, False)
477 def call_blockdev_removechildren(node, bdev, ndevs):
478 """Request removing a list of children from a (mirroring) device.
480 This is a single-node call.
483 params = [bdev.ToDict(), [disk.ToDict() for disk in ndevs]]
484 c = Client("blockdev_removechildren", params)
487 return c.getresult().get(node, False)
490 def call_blockdev_getmirrorstatus(node, disks):
491 """Request status of a (mirroring) device.
493 This is a single-node call.
496 params = [dsk.ToDict() for dsk in disks]
497 c = Client("blockdev_getmirrorstatus", params)
500 return c.getresult().get(node, False)
503 def call_blockdev_find(node, disk):
504 """Request identification of a given block device.
506 This is a single-node call.
509 c = Client("blockdev_find", [disk.ToDict()])
512 return c.getresult().get(node, False)
515 def call_blockdev_close(node, disks):
516 """Closes the given block devices.
518 This is a single-node call.
521 params = [cf.ToDict() for cf in disks]
522 c = Client("blockdev_close", params)
525 return c.getresult().get(node, False)
528 def call_upload_file(node_list, file_name):
531 The node will refuse the operation in case the file is not on the
534 This is a multi-node call.
542 st = os.stat(file_name)
543 params = [file_name, data, st.st_mode, st.st_uid, st.st_gid,
544 st.st_atime, st.st_mtime]
545 c = Client("upload_file", params)
546 c.connect_list(node_list)
551 def call_os_diagnose(node_list):
552 """Request a diagnose of OS definitions.
554 This is a multi-node call.
557 c = Client("os_diagnose", [])
558 c.connect_list(node_list)
560 result = c.getresult()
562 for node_name in result:
563 if result[node_name]:
564 nr = [objects.OS.FromDict(oss) for oss in result[node_name]]
567 new_result[node_name] = nr
571 def call_os_get(node, name):
572 """Returns an OS definition.
574 This is a single-node call.
577 c = Client("os_get", [name])
580 result = c.getresult().get(node, False)
581 if isinstance(result, dict):
582 return objects.OS.FromDict(result)
587 def call_hooks_runner(node_list, hpath, phase, env):
588 """Call the hooks runner.
591 - op: the OpCode instance
592 - env: a dictionary with the environment
594 This is a multi-node call.
597 params = [hpath, phase, env]
598 c = Client("hooks_runner", params)
599 c.connect_list(node_list)
601 result = c.getresult()
605 def call_iallocator_runner(node, name, idata):
606 """Call an iallocator on a remote node
609 - name: the iallocator name
610 - input: the json-encoded input string
612 This is a single-node call.
615 params = [name, idata]
616 c = Client("iallocator_runner", params)
619 result = c.getresult().get(node, False)
623 def call_blockdev_grow(node, cf_bdev, amount):
624 """Request a snapshot of the given block device.
626 This is a single-node call.
629 c = Client("blockdev_grow", [cf_bdev.ToDict(), amount])
632 return c.getresult().get(node, False)
635 def call_blockdev_snapshot(node, cf_bdev):
636 """Request a snapshot of the given block device.
638 This is a single-node call.
641 c = Client("blockdev_snapshot", [cf_bdev.ToDict()])
644 return c.getresult().get(node, False)
647 def call_snapshot_export(node, snap_bdev, dest_node, instance):
648 """Request the export of a given snapshot.
650 This is a single-node call.
653 params = [snap_bdev.ToDict(), dest_node, instance.ToDict()]
654 c = Client("snapshot_export", params)
657 return c.getresult().get(node, False)
660 def call_finalize_export(node, instance, snap_disks):
661 """Request the completion of an export operation.
663 This writes the export config file, etc.
665 This is a single-node call.
669 for disk in snap_disks:
670 flat_disks.append(disk.ToDict())
671 params = [instance.ToDict(), flat_disks]
672 c = Client("finalize_export", params)
675 return c.getresult().get(node, False)
678 def call_export_info(node, path):
679 """Queries the export information in a given path.
681 This is a single-node call.
684 c = Client("export_info", [path])
687 result = c.getresult().get(node, False)
690 return objects.SerializableConfigParser.Loads(str(result))
693 def call_instance_os_import(node, inst, osdev, swapdev, src_node, src_image):
694 """Request the import of a backup into an instance.
696 This is a single-node call.
699 params = [inst.ToDict(), osdev, swapdev, src_node, src_image]
700 c = Client("instance_os_import", params)
703 return c.getresult().get(node, False)
706 def call_export_list(node_list):
707 """Gets the stored exports list.
709 This is a multi-node call.
712 c = Client("export_list", [])
713 c.connect_list(node_list)
715 result = c.getresult()
719 def call_export_remove(node, export):
720 """Requests removal of a given export.
722 This is a single-node call.
725 c = Client("export_remove", [export])
728 return c.getresult().get(node, False)
731 def call_node_leave_cluster(node):
732 """Requests a node to clean the cluster information it has.
734 This will remove the configuration information from the ganeti data
737 This is a single-node call.
740 c = Client("node_leave_cluster", [])
743 return c.getresult().get(node, False)
746 def call_node_volumes(node_list):
747 """Gets all volumes on node(s).
749 This is a multi-node call.
752 c = Client("node_volumes", [])
753 c.connect_list(node_list)
758 def call_test_delay(node_list, duration):
759 """Sleep for a fixed time on given node(s).
761 This is a multi-node call.
764 c = Client("test_delay", [duration])
765 c.connect_list(node_list)
770 def call_file_storage_dir_create(node, file_storage_dir):
771 """Create the given file storage directory.
773 This is a single-node call.
776 c = Client("file_storage_dir_create", [file_storage_dir])
779 return c.getresult().get(node, False)
782 def call_file_storage_dir_remove(node, file_storage_dir):
783 """Remove the given file storage directory.
785 This is a single-node call.
788 c = Client("file_storage_dir_remove", [file_storage_dir])
791 return c.getresult().get(node, False)
794 def call_file_storage_dir_rename(node, old_file_storage_dir,
795 new_file_storage_dir):
796 """Rename file storage directory.
798 This is a single-node call.
801 c = Client("file_storage_dir_rename",
802 [old_file_storage_dir, new_file_storage_dir])
805 return c.getresult().get(node, False)