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):
366 """Tells a node to activate itself as a master.
368 This is a single-node call.
371 c = Client("node_start_master", [])
374 return c.getresult().get(node, False)
377 def call_node_stop_master(node):
378 """Tells a node to demote itself from master status.
380 This is a single-node call.
383 c = Client("node_stop_master", [])
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_upload_file(node_list, file_name):
518 The node will refuse the operation in case the file is not on the
521 This is a multi-node call.
529 st = os.stat(file_name)
530 params = [file_name, data, st.st_mode, st.st_uid, st.st_gid,
531 st.st_atime, st.st_mtime]
532 c = Client("upload_file", params)
533 c.connect_list(node_list)
538 def call_os_diagnose(node_list):
539 """Request a diagnose of OS definitions.
541 This is a multi-node call.
544 c = Client("os_diagnose", [])
545 c.connect_list(node_list)
547 result = c.getresult()
549 for node_name in result:
550 if result[node_name]:
551 nr = [objects.OS.FromDict(oss) for oss in result[node_name]]
554 new_result[node_name] = nr
558 def call_os_get(node, name):
559 """Returns an OS definition.
561 This is a single-node call.
564 c = Client("os_get", [name])
567 result = c.getresult().get(node, False)
568 if isinstance(result, dict):
569 return objects.OS.FromDict(result)
574 def call_hooks_runner(node_list, hpath, phase, env):
575 """Call the hooks runner.
578 - op: the OpCode instance
579 - env: a dictionary with the environment
581 This is a multi-node call.
584 params = [hpath, phase, env]
585 c = Client("hooks_runner", params)
586 c.connect_list(node_list)
588 result = c.getresult()
592 def call_iallocator_runner(node, name, idata):
593 """Call an iallocator on a remote node
596 - name: the iallocator name
597 - input: the json-encoded input string
599 This is a single-node call.
602 params = [name, idata]
603 c = Client("iallocator_runner", params)
606 result = c.getresult().get(node, False)
610 def call_blockdev_grow(node, cf_bdev, amount):
611 """Request a snapshot of the given block device.
613 This is a single-node call.
616 c = Client("blockdev_grow", [cf_bdev.ToDict(), amount])
619 return c.getresult().get(node, False)
622 def call_blockdev_snapshot(node, cf_bdev):
623 """Request a snapshot of the given block device.
625 This is a single-node call.
628 c = Client("blockdev_snapshot", [cf_bdev.ToDict()])
631 return c.getresult().get(node, False)
634 def call_snapshot_export(node, snap_bdev, dest_node, instance):
635 """Request the export of a given snapshot.
637 This is a single-node call.
640 params = [snap_bdev.ToDict(), dest_node, instance.ToDict()]
641 c = Client("snapshot_export", params)
644 return c.getresult().get(node, False)
647 def call_finalize_export(node, instance, snap_disks):
648 """Request the completion of an export operation.
650 This writes the export config file, etc.
652 This is a single-node call.
656 for disk in snap_disks:
657 flat_disks.append(disk.ToDict())
658 params = [instance.ToDict(), flat_disks]
659 c = Client("finalize_export", params)
662 return c.getresult().get(node, False)
665 def call_export_info(node, path):
666 """Queries the export information in a given path.
668 This is a single-node call.
671 c = Client("export_info", [path])
674 result = c.getresult().get(node, False)
677 return objects.SerializableConfigParser.Loads(str(result))
680 def call_instance_os_import(node, inst, osdev, swapdev, src_node, src_image):
681 """Request the import of a backup into an instance.
683 This is a single-node call.
686 params = [inst.ToDict(), osdev, swapdev, src_node, src_image]
687 c = Client("instance_os_import", params)
690 return c.getresult().get(node, False)
693 def call_export_list(node_list):
694 """Gets the stored exports list.
696 This is a multi-node call.
699 c = Client("export_list", [])
700 c.connect_list(node_list)
702 result = c.getresult()
706 def call_export_remove(node, export):
707 """Requests removal of a given export.
709 This is a single-node call.
712 c = Client("export_remove", [export])
715 return c.getresult().get(node, False)
718 def call_node_leave_cluster(node):
719 """Requests a node to clean the cluster information it has.
721 This will remove the configuration information from the ganeti data
724 This is a single-node call.
727 c = Client("node_leave_cluster", [])
730 return c.getresult().get(node, False)
733 def call_node_volumes(node_list):
734 """Gets all volumes on node(s).
736 This is a multi-node call.
739 c = Client("node_volumes", [])
740 c.connect_list(node_list)
745 def call_test_delay(node_list, duration):
746 """Sleep for a fixed time on given node(s).
748 This is a multi-node call.
751 c = Client("test_delay", [duration])
752 c.connect_list(node_list)
757 def call_file_storage_dir_create(node, file_storage_dir):
758 """Create the given file storage directory.
760 This is a single-node call.
763 c = Client("file_storage_dir_create", [file_storage_dir])
766 return c.getresult().get(node, False)
769 def call_file_storage_dir_remove(node, file_storage_dir):
770 """Remove the given file storage directory.
772 This is a single-node call.
775 c = Client("file_storage_dir_remove", [file_storage_dir])
778 return c.getresult().get(node, False)
781 def call_file_storage_dir_rename(node, old_file_storage_dir,
782 new_file_storage_dir):
783 """Rename file storage directory.
785 This is a single-node call.
788 c = Client("file_storage_dir_rename",
789 [old_file_storage_dir, new_file_storage_dir])
792 return c.getresult().get(node, False)