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 """Functions used by the node daemon"""
35 from ganeti import logger
36 from ganeti import errors
37 from ganeti import utils
38 from ganeti import ssh
39 from ganeti import hypervisor
40 from ganeti import constants
41 from ganeti import bdev
42 from ganeti import objects
43 from ganeti import ssconf
47 """Activate local node as master node.
49 There are two needed steps for this:
50 - run the master script
51 - register the cron script
54 result = utils.RunCmd([constants.MASTER_SCRIPT, "-d", "start"])
57 logger.Error("could not activate cluster interface with command %s,"
58 " error: '%s'" % (result.cmd, result.output))
65 """Deactivate this node as master.
68 - run the master stop script
69 - remove link to master cron script.
72 result = utils.RunCmd([constants.MASTER_SCRIPT, "-d", "stop"])
75 logger.Error("could not deactivate cluster interface with command %s,"
76 " error: '%s'" % (result.cmd, result.output))
82 def AddNode(dsa, dsapub, rsa, rsapub, sshkey, sshpub):
83 """ adds the node to the cluster
87 - sets the node status to installed
90 f = open("/etc/ssh/ssh_host_rsa_key", 'w')
94 f = open("/etc/ssh/ssh_host_rsa_key.pub", 'w')
98 f = open("/etc/ssh/ssh_host_dsa_key", 'w')
102 f = open("/etc/ssh/ssh_host_dsa_key.pub", 'w')
106 if not os.path.isdir("/root/.ssh"):
107 os.mkdir("/root/.ssh")
109 f = open("/root/.ssh/id_dsa", 'w')
113 f = open("/root/.ssh/id_dsa.pub", 'w')
117 f = open('/root/.ssh/id_dsa.pub', 'r')
119 utils.AddAuthorizedKey('/root/.ssh/authorized_keys', f.read(8192))
123 utils.RunCmd([constants.SSH_INITD_SCRIPT, "restart"])
129 """Cleans up the current node and prepares it to be removed from the cluster.
132 if os.path.exists(constants.DATA_DIR):
133 for dirpath, dirnames, filenames in os.walk(constants.DATA_DIR):
134 if dirpath == constants.DATA_DIR:
136 os.unlink(os.path.join(dirpath, i))
138 f = open('/root/.ssh/id_dsa.pub', 'r')
140 utils.RemoveAuthorizedKey('/root/.ssh/authorized_keys', f.read(8192))
144 utils.RemoveFile('/root/.ssh/id_dsa')
145 utils.RemoveFile('/root/.ssh/id_dsa.pub')
148 def GetNodeInfo(vgname):
149 """ gives back a hash with different informations
153 { 'vg_size' : xxx, 'vg_free' : xxx, 'memory_domain0': xxx,
154 'memory_free' : xxx, 'memory_total' : xxx }
156 vg_size is the size of the configured volume group in MiB
157 vg_free is the free size of the volume group in MiB
158 memory_dom0 is the memory allocated for domain0 in MiB
159 memory_free is the currently available (free) ram in MiB
160 memory_total is the total number of ram in MiB
164 vginfo = _GetVGInfo(vgname)
165 outputarray['vg_size'] = vginfo['vg_size']
166 outputarray['vg_free'] = vginfo['vg_free']
168 hyper = hypervisor.GetHypervisor()
169 hyp_info = hyper.GetNodeInfo()
170 if hyp_info is not None:
171 outputarray.update(hyp_info)
176 def VerifyNode(what):
177 """Verify the status of the local node.
180 what - a dictionary of things to check:
181 'filelist' : list of files for which to compute checksums
182 'nodelist' : list of nodes we should check communication with
183 'hypervisor': run the hypervisor-specific verify
185 Requested files on local node are checksummed and the result returned.
187 The nodelist is traversed, with the following checks being made
189 - known_hosts key correct
190 - correct resolving of node name (target node returns its own hostname
191 by ssh-execution of 'hostname', result compared against name in list.
196 if 'hypervisor' in what:
197 result['hypervisor'] = hypervisor.GetHypervisor().Verify()
199 if 'filelist' in what:
200 result['filelist'] = utils.FingerprintFiles(what['filelist'])
202 if 'nodelist' in what:
203 result['nodelist'] = {}
204 for node in what['nodelist']:
205 success, message = ssh.VerifyNodeHostname(node)
207 result['nodelist'][node] = message
211 def GetVolumeList(vg_name):
212 """Compute list of logical volumes and their size.
215 dictionary of all partions (key) with their size:
219 result = utils.RunCmd(["lvs", "--noheadings", "--units=m",
220 "-oname,size", vg_name])
222 logger.Error("Failed to list logical volumes, lvs output: %s" %
226 lvlist = [line.split() for line in result.output.splitlines()]
230 def ListVolumeGroups():
231 """List the volume groups and their size
234 Dictionary with keys volume name and values the size of the volume
237 return utils.ListVolumeGroups()
241 """List all volumes on this node.
244 result = utils.RunCmd(["lvs", "--noheadings", "--units=m", "--nosuffix",
246 "--options=lv_name,lv_size,devices,vg_name"])
248 logger.Error("Failed to list logical volumes, lvs output: %s" %
254 return dev.split('(')[0]
260 'name': line[0].strip(),
261 'size': line[1].strip(),
262 'dev': parse_dev(line[2].strip()),
263 'vg': line[3].strip(),
266 return [map_line(line.split('|')) for line in result.output.splitlines()]
269 def BridgesExist(bridges_list):
270 """Check if a list of bridges exist on the current node
273 True if all of them exist, false otherwise
276 for bridge in bridges_list:
277 if not utils.BridgeExists(bridge):
283 def GetInstanceList():
284 """ provides a list of instances
287 A list of all running instances on the current node
288 - instance1.example.com
289 - instance2.example.com
293 names = hypervisor.GetHypervisor().ListInstances()
294 except errors.HypervisorError, err:
295 logger.Error("error enumerating instances: %s" % str(err))
301 def GetInstanceInfo(instance):
302 """ gives back the informations about an instance
306 instance: name of the instance (ex. instance1.example.com)
309 { 'memory' : 511, 'state' : '-b---', 'time' : 3188.8, }
311 memory: memory size of instance (int)
312 state: xen state of instance (string)
313 time: cpu time of instance (float)
318 iinfo = hypervisor.GetHypervisor().GetInstanceInfo(instance)
319 if iinfo is not None:
320 output['memory'] = iinfo[2]
321 output['state'] = iinfo[4]
322 output['time'] = iinfo[5]
327 def GetAllInstancesInfo():
328 """Gather data about all instances.
330 This is the equivalent of `GetInstanceInfo()`, except that it
331 computes data for all instances at once, thus being faster if one
332 needs data about more than one instance.
334 Returns: a dictionary of dictionaries, keys being the instance name,
336 { 'memory' : 511, 'state' : '-b---', 'time' : 3188.8, }
338 memory: memory size of instance (int)
339 state: xen state of instance (string)
340 time: cpu time of instance (float)
341 vcpus: the number of cpus
346 iinfo = hypervisor.GetHypervisor().GetAllInstancesInfo()
348 for name, inst_id, memory, vcpus, state, times in iinfo:
359 def AddOSToInstance(instance, os_disk, swap_disk):
360 """Add an os to an instance.
363 instance: the instance object
364 os_disk: the instance-visible name of the os device
365 swap_disk: the instance-visible name of the swap device
368 inst_os = OSFromDisk(instance.os)
370 create_script = inst_os.create_script
372 os_device = instance.FindDisk(os_disk)
373 if os_device is None:
374 logger.Error("Can't find this device-visible name '%s'" % os_disk)
377 swap_device = instance.FindDisk(swap_disk)
378 if swap_device is None:
379 logger.Error("Can't find this device-visible name '%s'" % swap_disk)
382 real_os_dev = _RecursiveFindBD(os_device)
383 if real_os_dev is None:
384 raise errors.BlockDeviceError("Block device '%s' is not set up" %
388 real_swap_dev = _RecursiveFindBD(swap_device)
389 if real_swap_dev is None:
390 raise errors.BlockDeviceError("Block device '%s' is not set up" %
394 logfile = "%s/add-%s-%s-%d.log" % (constants.LOG_OS_DIR, instance.os,
395 instance.name, int(time.time()))
396 if not os.path.exists(constants.LOG_OS_DIR):
397 os.mkdir(constants.LOG_OS_DIR, 0750)
399 command = utils.BuildShellCmd("cd %s && %s -i %s -b %s -s %s &>%s",
400 inst_os.path, create_script, instance.name,
401 real_os_dev.dev_path, real_swap_dev.dev_path,
404 result = utils.RunCmd(command)
407 logger.Error("os create command '%s' returned error: %s"
409 (command, result.fail_reason, result.output))
415 def RunRenameInstance(instance, old_name, os_disk, swap_disk):
416 """Run the OS rename script for an instance.
419 instance: the instance object
420 old_name: the old name of the instance
421 os_disk: the instance-visible name of the os device
422 swap_disk: the instance-visible name of the swap device
425 inst_os = OSFromDisk(instance.os)
427 script = inst_os.rename_script
429 os_device = instance.FindDisk(os_disk)
430 if os_device is None:
431 logger.Error("Can't find this device-visible name '%s'" % os_disk)
434 swap_device = instance.FindDisk(swap_disk)
435 if swap_device is None:
436 logger.Error("Can't find this device-visible name '%s'" % swap_disk)
439 real_os_dev = _RecursiveFindBD(os_device)
440 if real_os_dev is None:
441 raise errors.BlockDeviceError("Block device '%s' is not set up" %
445 real_swap_dev = _RecursiveFindBD(swap_device)
446 if real_swap_dev is None:
447 raise errors.BlockDeviceError("Block device '%s' is not set up" %
451 logfile = "%s/rename-%s-%s-%s-%d.log" % (constants.LOG_OS_DIR, instance.os,
453 instance.name, int(time.time()))
454 if not os.path.exists(constants.LOG_OS_DIR):
455 os.mkdir(constants.LOG_OS_DIR, 0750)
457 command = utils.BuildShellCmd("cd %s && %s -o %s -n %s -b %s -s %s &>%s",
458 inst_os.path, script, old_name, instance.name,
459 real_os_dev.dev_path, real_swap_dev.dev_path,
462 result = utils.RunCmd(command)
465 logger.Error("os create command '%s' returned error: %s"
467 (command, result.fail_reason, result.output))
473 def _GetVGInfo(vg_name):
474 """Get informations about the volume group.
477 vg_name: the volume group
480 { 'vg_size' : xxx, 'vg_free' : xxx, 'pv_count' : xxx }
482 vg_size is the total size of the volume group in MiB
483 vg_free is the free size of the volume group in MiB
484 pv_count are the number of physical disks in that vg
487 retval = utils.RunCmd(["vgs", "-ovg_size,vg_free,pv_count", "--noheadings",
488 "--nosuffix", "--units=m", "--separator=:", vg_name])
491 errmsg = "volume group %s not present" % vg_name
493 raise errors.LVMError(errmsg)
494 valarr = retval.stdout.strip().split(':')
496 "vg_size": int(round(float(valarr[0]), 0)),
497 "vg_free": int(round(float(valarr[1]), 0)),
498 "pv_count": int(valarr[2]),
503 def _GatherBlockDevs(instance):
504 """Set up an instance's block device(s).
506 This is run on the primary node at instance startup. The block
507 devices must be already assembled.
511 for disk in instance.disks:
512 device = _RecursiveFindBD(disk)
514 raise errors.BlockDeviceError("Block device '%s' is not set up." %
517 block_devices.append((disk, device))
521 def StartInstance(instance, extra_args):
522 """Start an instance.
525 instance - name of instance to start.
528 running_instances = GetInstanceList()
530 if instance.name in running_instances:
533 block_devices = _GatherBlockDevs(instance)
534 hyper = hypervisor.GetHypervisor()
537 hyper.StartInstance(instance, block_devices, extra_args)
538 except errors.HypervisorError, err:
539 logger.Error("Failed to start instance: %s" % err)
545 def ShutdownInstance(instance):
546 """Shut an instance down.
549 instance - name of instance to shutdown.
552 running_instances = GetInstanceList()
554 if instance.name not in running_instances:
557 hyper = hypervisor.GetHypervisor()
559 hyper.StopInstance(instance)
560 except errors.HypervisorError, err:
561 logger.Error("Failed to stop instance: %s" % err)
564 # test every 10secs for 2min
568 for dummy in range(11):
569 if instance.name not in GetInstanceList():
573 # the shutdown did not succeed
574 logger.Error("shutdown of '%s' unsuccessful, using destroy" % instance)
577 hyper.StopInstance(instance, force=True)
578 except errors.HypervisorError, err:
579 logger.Error("Failed to stop instance: %s" % err)
583 if instance.name in GetInstanceList():
584 logger.Error("could not shutdown instance '%s' even by destroy")
590 def CreateBlockDevice(disk, size, on_primary, info):
591 """Creates a block device for an instance.
594 bdev: a ganeti.objects.Disk object
595 size: the size of the physical underlying devices
596 do_open: if the device should be `Assemble()`-d and
597 `Open()`-ed after creation
600 the new unique_id of the device (this can sometime be
601 computed only after creation), or None. On secondary nodes,
602 it's not required to return anything.
607 for child in disk.children:
608 crdev = _RecursiveAssembleBD(child, on_primary)
609 if on_primary or disk.AssembleOnSecondary():
610 # we need the children open in case the device itself has to
617 device = bdev.FindDevice(disk.dev_type, disk.physical_id, clist)
618 if device is not None:
619 logger.Info("removing existing device %s" % disk)
621 except errors.BlockDeviceError, err:
624 device = bdev.Create(disk.dev_type, disk.physical_id,
627 raise ValueError("Can't create child device for %s, %s" %
629 if on_primary or disk.AssembleOnSecondary():
631 device.SetSyncSpeed(constants.SYNC_SPEED)
632 if on_primary or disk.OpenOnSecondary():
633 device.Open(force=True)
637 physical_id = device.unique_id
641 def RemoveBlockDevice(disk):
642 """Remove a block device.
644 This is intended to be called recursively.
648 # since we are removing the device, allow a partial match
649 # this allows removal of broken mirrors
650 rdev = _RecursiveFindBD(disk, allow_partial=True)
651 except errors.BlockDeviceError, err:
652 # probably can't attach
653 logger.Info("Can't attach to device %s in remove" % disk)
656 result = rdev.Remove()
660 for child in disk.children:
661 result = result and RemoveBlockDevice(child)
665 def _RecursiveAssembleBD(disk, as_primary):
666 """Activate a block device for an instance.
668 This is run on the primary and secondary nodes for an instance.
670 This function is called recursively.
673 disk: a objects.Disk object
674 as_primary: if we should make the block device read/write
677 the assembled device or None (in case no device was assembled)
679 If the assembly is not successful, an exception is raised.
684 for chld_disk in disk.children:
685 children.append(_RecursiveAssembleBD(chld_disk, as_primary))
687 if as_primary or disk.AssembleOnSecondary():
688 r_dev = bdev.AttachOrAssemble(disk.dev_type, disk.physical_id, children)
689 r_dev.SetSyncSpeed(constants.SYNC_SPEED)
691 if as_primary or disk.OpenOnSecondary():
700 def AssembleBlockDevice(disk, as_primary):
701 """Activate a block device for an instance.
703 This is a wrapper over _RecursiveAssembleBD.
706 a /dev path for primary nodes
707 True for secondary nodes
710 result = _RecursiveAssembleBD(disk, as_primary)
711 if isinstance(result, bdev.BlockDev):
712 result = result.dev_path
716 def ShutdownBlockDevice(disk):
717 """Shut down a block device.
719 First, if the device is assembled (can `Attach()`), then the device
720 is shutdown. Then the children of the device are shutdown.
722 This function is called recursively. Note that we don't cache the
723 children or such, as oppossed to assemble, shutdown of different
724 devices doesn't require that the upper device was active.
727 r_dev = _RecursiveFindBD(disk)
728 if r_dev is not None:
729 result = r_dev.Shutdown()
733 for child in disk.children:
734 result = result and ShutdownBlockDevice(child)
738 def MirrorAddChild(md_cdev, new_cdev):
739 """Extend an MD raid1 array.
742 md_bdev = _RecursiveFindBD(md_cdev, allow_partial=True)
744 logger.Error("Can't find md device")
746 new_bdev = _RecursiveFindBD(new_cdev)
748 logger.Error("Can't find new device to add")
751 md_bdev.AddChild(new_bdev)
755 def MirrorRemoveChild(md_cdev, new_cdev):
756 """Reduce an MD raid1 array.
759 md_bdev = _RecursiveFindBD(md_cdev)
762 new_bdev = _RecursiveFindBD(new_cdev)
766 md_bdev.RemoveChild(new_bdev.dev_path)
770 def GetMirrorStatus(disks):
771 """Get the mirroring status of a list of devices.
774 disks: list of `objects.Disk`
777 list of (mirror_done, estimated_time) tuples, which
778 are the result of bdev.BlockDevice.CombinedSyncStatus()
783 rbd = _RecursiveFindBD(dsk)
785 raise errors.BlockDeviceError("Can't find device %s" % str(dsk))
786 stats.append(rbd.CombinedSyncStatus())
790 def _RecursiveFindBD(disk, allow_partial=False):
791 """Check if a device is activated.
793 If so, return informations about the real device.
796 disk: the objects.Disk instance
797 allow_partial: don't abort the find if a child of the
798 device can't be found; this is intended to be
799 used when repairing mirrors
802 None if the device can't be found
803 otherwise the device instance
808 for chdisk in disk.children:
809 children.append(_RecursiveFindBD(chdisk))
811 return bdev.FindDevice(disk.dev_type, disk.physical_id, children)
814 def FindBlockDevice(disk):
815 """Check if a device is activated.
817 If so, return informations about the real device.
820 disk: the objects.Disk instance
822 None if the device can't be found
823 (device_path, major, minor, sync_percent, estimated_time, is_degraded)
826 rbd = _RecursiveFindBD(disk)
829 sync_p, est_t, is_degr = rbd.GetSyncStatus()
830 return rbd.dev_path, rbd.major, rbd.minor, sync_p, est_t, is_degr
833 def UploadFile(file_name, data, mode, uid, gid, atime, mtime):
834 """Write a file to the filesystem.
836 This allows the master to overwrite(!) a file. It will only perform
837 the operation if the file belongs to a list of configuration files.
840 if not os.path.isabs(file_name):
841 logger.Error("Filename passed to UploadFile is not absolute: '%s'" %
845 allowed_files = [constants.CLUSTER_CONF_FILE, "/etc/hosts",
846 constants.SSH_KNOWN_HOSTS_FILE]
847 allowed_files.extend(ssconf.SimpleStore().GetFileList())
848 if file_name not in allowed_files:
849 logger.Error("Filename passed to UploadFile not in allowed"
850 " upload targets: '%s'" % file_name)
853 dir_name, small_name = os.path.split(file_name)
854 fd, new_name = tempfile.mkstemp('.new', small_name, dir_name)
855 # here we need to make sure we remove the temp file, if any error
858 os.chown(new_name, uid, gid)
859 os.chmod(new_name, mode)
862 os.utime(new_name, (atime, mtime))
863 os.rename(new_name, file_name)
866 utils.RemoveFile(new_name)
870 def _ErrnoOrStr(err):
871 """Format an EnvironmentError exception.
873 If the `err` argument has an errno attribute, it will be looked up
874 and converted into a textual EXXXX description. Otherwise the string
875 representation of the error will be returned.
878 if hasattr(err, 'errno'):
879 detail = errno.errorcode[err.errno]
885 def _OSOndiskVersion(name, os_dir):
886 """Compute and return the api version of a given OS.
888 This function will try to read the api version of the os given by
889 the 'name' parameter and residing in the 'os_dir' directory.
891 Return value will be either an integer denoting the version or None in the
892 case when this is not a valid OS name.
896 api_file = os.path.sep.join([os_dir, "ganeti_api_version"])
899 st = os.stat(api_file)
900 except EnvironmentError, err:
901 raise errors.InvalidOS(name, "'ganeti_api_version' file not"
902 " found (%s)" % _ErrnoOrStr(err))
904 if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
905 raise errors.InvalidOS(name, "'ganeti_api_version' file is not"
911 api_version = f.read(256)
914 except EnvironmentError, err:
915 raise errors.InvalidOS(name, "error while reading the"
916 " API version (%s)" % _ErrnoOrStr(err))
918 api_version = api_version.strip()
920 api_version = int(api_version)
921 except (TypeError, ValueError), err:
922 raise errors.InvalidOS(name, "API version is not integer (%s)" % str(err))
927 def DiagnoseOS(top_dirs=None):
928 """Compute the validity for all OSes.
930 For each name in all the given top directories (if not given defaults i
931 to constants.OS_SEARCH_PATH it will return an object. If this is a valid
932 os, the object will be an instance of the object.OS class. If not,
933 it will be an instance of errors.InvalidOS and this signifies that
934 this name does not correspond to a valid OS.
941 top_dirs = constants.OS_SEARCH_PATH
945 if os.path.isdir(dir):
947 f_names = os.listdir(dir)
948 except EnvironmentError, err:
949 logger.Error("Can't list the OS directory %s: %s" % (dir,str(err)))
953 os_inst = OSFromDisk(name, os_dir=os.path.sep.join([dir, name]))
954 result.append(os_inst)
955 except errors.InvalidOS, err:
961 def OSFromDisk(name, os_dir=None):
962 """Create an OS instance from disk.
964 This function will return an OS instance if the given name is a
965 valid OS name. Otherwise, it will raise an appropriate
966 `errors.InvalidOS` exception, detailing why this is not a valid
970 os_dir: Directory containing the OS scripts. Defaults to a search
971 in all the OS_SEARCH_PATH directories.
976 for base_dir in constants.OS_SEARCH_PATH:
977 t_os_dir = os.path.sep.join([base_dir, name])
978 if os.path.isdir(t_os_dir):
983 raise errors.InvalidOS(name, "OS dir not found in search path")
985 api_version = _OSOndiskVersion(name, os_dir)
987 if api_version != constants.OS_API_VERSION:
988 raise errors.InvalidOS(name, "API version mismatch (found %s want %s)"
989 % (api_version, constants.OS_API_VERSION))
991 # OS Scripts dictionary, we will populate it with the actual script names
992 os_scripts = {'create': '', 'export': '', 'import': '', 'rename': ''}
994 for script in os_scripts:
995 os_scripts[script] = os.path.sep.join([os_dir, script])
998 st = os.stat(os_scripts[script])
999 except EnvironmentError, err:
1000 raise errors.InvalidOS(name, "'%s' script missing (%s)" %
1001 (script, _ErrnoOrStr(err)))
1003 if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
1004 raise errors.InvalidOS(name, "'%s' script not executable" % script)
1006 if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
1007 raise errors.InvalidOS(name, "'%s' is not a regular file" % script)
1010 return objects.OS(name=name, path=os_dir,
1011 create_script=os_scripts['create'],
1012 export_script=os_scripts['export'],
1013 import_script=os_scripts['import'],
1014 rename_script=os_scripts['rename'],
1015 api_version=api_version)
1018 def SnapshotBlockDevice(disk):
1019 """Create a snapshot copy of a block device.
1021 This function is called recursively, and the snapshot is actually created
1022 just for the leaf lvm backend device.
1025 disk: the disk to be snapshotted
1028 a config entry for the actual lvm device snapshotted.
1032 if len(disk.children) == 1:
1033 # only one child, let's recurse on it
1034 return SnapshotBlockDevice(disk.children[0])
1036 # more than one child, choose one that matches
1037 for child in disk.children:
1038 if child.size == disk.size:
1039 # return implies breaking the loop
1040 return SnapshotBlockDevice(child)
1041 elif disk.dev_type == "lvm":
1042 r_dev = _RecursiveFindBD(disk)
1043 if r_dev is not None:
1044 # let's stay on the safe side and ask for the full size, for now
1045 return r_dev.Snapshot(disk.size)
1049 raise errors.ProgrammerError("Cannot snapshot non-lvm block device"
1050 "'%s' of type '%s'" %
1051 (disk.unique_id, disk.dev_type))
1054 def ExportSnapshot(disk, dest_node, instance):
1055 """Export a block device snapshot to a remote node.
1058 disk: the snapshot block device
1059 dest_node: the node to send the image to
1060 instance: instance being exported
1063 True if successful, False otherwise.
1066 inst_os = OSFromDisk(instance.os)
1067 export_script = inst_os.export_script
1069 logfile = "%s/exp-%s-%s-%s.log" % (constants.LOG_OS_DIR, inst_os.name,
1070 instance.name, int(time.time()))
1071 if not os.path.exists(constants.LOG_OS_DIR):
1072 os.mkdir(constants.LOG_OS_DIR, 0750)
1074 real_os_dev = _RecursiveFindBD(disk)
1075 if real_os_dev is None:
1076 raise errors.BlockDeviceError("Block device '%s' is not set up" %
1080 destdir = os.path.join(constants.EXPORT_DIR, instance.name + ".new")
1081 destfile = disk.physical_id[1]
1083 # the target command is built out of three individual commands,
1084 # which are joined by pipes; we check each individual command for
1087 expcmd = utils.BuildShellCmd("cd %s; %s -i %s -b %s 2>%s", inst_os.path,
1088 export_script, instance.name,
1089 real_os_dev.dev_path, logfile)
1093 destcmd = utils.BuildShellCmd("mkdir -p %s && cat > %s/%s",
1094 destdir, destdir, destfile)
1095 remotecmd = ssh.BuildSSHCmd(dest_node, 'root', destcmd)
1099 # all commands have been checked, so we're safe to combine them
1100 command = '|'.join([expcmd, comprcmd, utils.ShellQuoteArgs(remotecmd)])
1102 result = utils.RunCmd(command)
1105 logger.Error("os snapshot export command '%s' returned error: %s"
1107 (command, result.fail_reason, result.output))
1113 def FinalizeExport(instance, snap_disks):
1114 """Write out the export configuration information.
1117 instance: instance configuration
1118 snap_disks: snapshot block devices
1121 False in case of error, True otherwise.
1124 destdir = os.path.join(constants.EXPORT_DIR, instance.name + ".new")
1125 finaldestdir = os.path.join(constants.EXPORT_DIR, instance.name)
1127 config = objects.SerializableConfigParser()
1129 config.add_section(constants.INISECT_EXP)
1130 config.set(constants.INISECT_EXP, 'version', '0')
1131 config.set(constants.INISECT_EXP, 'timestamp', '%d' % int(time.time()))
1132 config.set(constants.INISECT_EXP, 'source', instance.primary_node)
1133 config.set(constants.INISECT_EXP, 'os', instance.os)
1134 config.set(constants.INISECT_EXP, 'compression', 'gzip')
1136 config.add_section(constants.INISECT_INS)
1137 config.set(constants.INISECT_INS, 'name', instance.name)
1138 config.set(constants.INISECT_INS, 'memory', '%d' % instance.memory)
1139 config.set(constants.INISECT_INS, 'vcpus', '%d' % instance.vcpus)
1140 config.set(constants.INISECT_INS, 'disk_template', instance.disk_template)
1141 for nic_count, nic in enumerate(instance.nics):
1142 config.set(constants.INISECT_INS, 'nic%d_mac' %
1143 nic_count, '%s' % nic.mac)
1144 config.set(constants.INISECT_INS, 'nic%d_ip' % nic_count, '%s' % nic.ip)
1145 # TODO: redundant: on load can read nics until it doesn't exist
1146 config.set(constants.INISECT_INS, 'nic_count' , '%d' % nic_count)
1148 for disk_count, disk in enumerate(snap_disks):
1149 config.set(constants.INISECT_INS, 'disk%d_ivname' % disk_count,
1150 ('%s' % disk.iv_name))
1151 config.set(constants.INISECT_INS, 'disk%d_dump' % disk_count,
1152 ('%s' % disk.physical_id[1]))
1153 config.set(constants.INISECT_INS, 'disk%d_size' % disk_count,
1155 config.set(constants.INISECT_INS, 'disk_count' , '%d' % disk_count)
1157 cff = os.path.join(destdir, constants.EXPORT_CONF_FILE)
1158 cfo = open(cff, 'w')
1164 shutil.rmtree(finaldestdir, True)
1165 shutil.move(destdir, finaldestdir)
1170 def ExportInfo(dest):
1171 """Get export configuration information.
1174 dest: directory containing the export
1177 A serializable config file containing the export info.
1180 cff = os.path.join(dest, constants.EXPORT_CONF_FILE)
1182 config = objects.SerializableConfigParser()
1185 if (not config.has_section(constants.INISECT_EXP) or
1186 not config.has_section(constants.INISECT_INS)):
1192 def ImportOSIntoInstance(instance, os_disk, swap_disk, src_node, src_image):
1193 """Import an os image into an instance.
1196 instance: the instance object
1197 os_disk: the instance-visible name of the os device
1198 swap_disk: the instance-visible name of the swap device
1199 src_node: node holding the source image
1200 src_image: path to the source image on src_node
1203 False in case of error, True otherwise.
1206 inst_os = OSFromDisk(instance.os)
1207 import_script = inst_os.import_script
1209 os_device = instance.FindDisk(os_disk)
1210 if os_device is None:
1211 logger.Error("Can't find this device-visible name '%s'" % os_disk)
1214 swap_device = instance.FindDisk(swap_disk)
1215 if swap_device is None:
1216 logger.Error("Can't find this device-visible name '%s'" % swap_disk)
1219 real_os_dev = _RecursiveFindBD(os_device)
1220 if real_os_dev is None:
1221 raise errors.BlockDeviceError("Block device '%s' is not set up" %
1225 real_swap_dev = _RecursiveFindBD(swap_device)
1226 if real_swap_dev is None:
1227 raise errors.BlockDeviceError("Block device '%s' is not set up" %
1229 real_swap_dev.Open()
1231 logfile = "%s/import-%s-%s-%s.log" % (constants.LOG_OS_DIR, instance.os,
1232 instance.name, int(time.time()))
1233 if not os.path.exists(constants.LOG_OS_DIR):
1234 os.mkdir(constants.LOG_OS_DIR, 0750)
1236 destcmd = utils.BuildShellCmd('cat %s', src_image)
1237 remotecmd = ssh.BuildSSHCmd(src_node, 'root', destcmd)
1240 impcmd = utils.BuildShellCmd("(cd %s; %s -i %s -b %s -s %s &>%s)",
1241 inst_os.path, import_script, instance.name,
1242 real_os_dev.dev_path, real_swap_dev.dev_path,
1245 command = '|'.join([utils.ShellQuoteArgs(remotecmd), comprcmd, impcmd])
1247 result = utils.RunCmd(command)
1250 logger.Error("os import command '%s' returned error: %s"
1252 (command, result.fail_reason, result.output))
1259 """Return a list of exports currently available on this machine.
1262 if os.path.isdir(constants.EXPORT_DIR):
1263 return os.listdir(constants.EXPORT_DIR)
1268 def RemoveExport(export):
1269 """Remove an existing export from the node.
1272 export: the name of the export to remove
1275 False in case of error, True otherwise.
1278 target = os.path.join(constants.EXPORT_DIR, export)
1280 shutil.rmtree(target)
1281 # TODO: catch some of the relevant exceptions and provide a pretty
1282 # error message if rmtree fails.
1287 class HooksRunner(object):
1290 This class is instantiated on the node side (ganeti-noded) and not on
1294 RE_MASK = re.compile("^[a-zA-Z0-9_-]+$")
1296 def __init__(self, hooks_base_dir=None):
1297 """Constructor for hooks runner.
1300 - hooks_base_dir: if not None, this overrides the
1301 constants.HOOKS_BASE_DIR (useful for unittests)
1302 - logs_base_dir: if not None, this overrides the
1303 constants.LOG_HOOKS_DIR (useful for unittests)
1304 - logging: enable or disable logging of script output
1307 if hooks_base_dir is None:
1308 hooks_base_dir = constants.HOOKS_BASE_DIR
1309 self._BASE_DIR = hooks_base_dir
1312 def ExecHook(script, env):
1313 """Exec one hook script.
1317 - script: the full path to the script
1318 - env: the environment with which to exec the script
1321 # exec the process using subprocess and log the output
1324 fdstdin = open("/dev/null", "r")
1325 child = subprocess.Popen([script], stdin=fdstdin, stdout=subprocess.PIPE,
1326 stderr=subprocess.STDOUT, close_fds=True,
1327 shell=False, cwd="/",env=env)
1330 output = child.stdout.read(4096)
1331 child.stdout.close()
1332 except EnvironmentError, err:
1333 output += "Hook script error: %s" % str(err)
1337 result = child.wait()
1339 except EnvironmentError, err:
1340 if err.errno == errno.EINTR:
1344 # try not to leak fds
1345 for fd in (fdstdin, ):
1349 except EnvironmentError, err:
1350 # just log the error
1351 #logger.Error("While closing fd %s: %s" % (fd, err))
1354 return result == 0, output
1356 def RunHooks(self, hpath, phase, env):
1357 """Run the scripts in the hooks directory.
1359 This method will not be usually overriden by child opcodes.
1362 if phase == constants.HOOKS_PHASE_PRE:
1364 elif phase == constants.HOOKS_PHASE_POST:
1367 raise errors.ProgrammerError("Unknown hooks phase: '%s'" % phase)
1370 subdir = "%s-%s.d" % (hpath, suffix)
1371 dir_name = "%s/%s" % (self._BASE_DIR, subdir)
1373 dir_contents = os.listdir(dir_name)
1374 except OSError, err:
1378 # we use the standard python sort order,
1379 # so 00name is the recommended naming scheme
1381 for relname in dir_contents:
1382 fname = os.path.join(dir_name, relname)
1383 if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
1384 self.RE_MASK.match(relname) is not None):
1385 rrval = constants.HKR_SKIP
1388 result, output = self.ExecHook(fname, env)
1390 rrval = constants.HKR_FAIL
1392 rrval = constants.HKR_SUCCESS
1393 rr.append(("%s/%s" % (subdir, relname), rrval, output))