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, ssh, 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(["/etc/init.d/ssh", "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 for os_device in instance.disks:
373 if os_device.iv_name == os_disk:
376 logger.Error("Can't find this device-visible name '%s'" % os_disk)
379 for swap_device in instance.disks:
380 if swap_device.iv_name == swap_disk:
383 logger.Error("Can't find this device-visible name '%s'" % swap_disk)
386 real_os_dev = _RecursiveFindBD(os_device)
387 if real_os_dev is None:
388 raise errors.BlockDeviceError("Block device '%s' is not set up" %
392 real_swap_dev = _RecursiveFindBD(swap_device)
393 if real_swap_dev is None:
394 raise errors.BlockDeviceError("Block device '%s' is not set up" %
398 logfile = "%s/add-%s-%s-%d.log" % (constants.LOG_OS_DIR, instance.os,
399 instance.name, int(time.time()))
400 if not os.path.exists(constants.LOG_OS_DIR):
401 os.mkdir(constants.LOG_OS_DIR, 0750)
403 command = utils.BuildShellCmd("cd %s && %s -i %s -b %s -s %s &>%s",
404 inst_os.path, create_script, instance.name,
405 real_os_dev.dev_path, real_swap_dev.dev_path,
408 result = utils.RunCmd(command)
411 logger.Error("os create command '%s' returned error: %s"
413 (command, result.fail_reason, result.output))
419 def _GetVGInfo(vg_name):
420 """Get informations about the volume group.
423 vg_name: the volume group
426 { 'vg_size' : xxx, 'vg_free' : xxx, 'pv_count' : xxx }
428 vg_size is the total size of the volume group in MiB
429 vg_free is the free size of the volume group in MiB
430 pv_count are the number of physical disks in that vg
433 retval = utils.RunCmd(["vgs", "-ovg_size,vg_free,pv_count", "--noheadings",
434 "--nosuffix", "--units=m", "--separator=:", vg_name])
437 errmsg = "volume group %s not present" % vg_name
439 raise errors.LVMError(errmsg)
440 valarr = retval.stdout.strip().split(':')
442 "vg_size": int(round(float(valarr[0]), 0)),
443 "vg_free": int(round(float(valarr[1]), 0)),
444 "pv_count": int(valarr[2]),
449 def _GatherBlockDevs(instance):
450 """Set up an instance's block device(s).
452 This is run on the primary node at instance startup. The block
453 devices must be already assembled.
457 for disk in instance.disks:
458 device = _RecursiveFindBD(disk)
460 raise errors.BlockDeviceError("Block device '%s' is not set up." %
463 block_devices.append((disk, device))
467 def StartInstance(instance, extra_args):
468 """Start an instance.
471 instance - name of instance to start.
474 running_instances = GetInstanceList()
476 if instance.name in running_instances:
479 block_devices = _GatherBlockDevs(instance)
480 hyper = hypervisor.GetHypervisor()
483 hyper.StartInstance(instance, block_devices, extra_args)
484 except errors.HypervisorError, err:
485 logger.Error("Failed to start instance: %s" % err)
491 def ShutdownInstance(instance):
492 """Shut an instance down.
495 instance - name of instance to shutdown.
498 running_instances = GetInstanceList()
500 if instance.name not in running_instances:
503 hyper = hypervisor.GetHypervisor()
505 hyper.StopInstance(instance)
506 except errors.HypervisorError, err:
507 logger.Error("Failed to stop instance: %s" % err)
510 # test every 10secs for 2min
514 for dummy in range(11):
515 if instance.name not in GetInstanceList():
519 # the shutdown did not succeed
520 logger.Error("shutdown of '%s' unsuccessful, using destroy" % instance)
523 hyper.StopInstance(instance, force=True)
524 except errors.HypervisorError, err:
525 logger.Error("Failed to stop instance: %s" % err)
529 if instance.name in GetInstanceList():
530 logger.Error("could not shutdown instance '%s' even by destroy")
536 def CreateBlockDevice(disk, size, on_primary, info):
537 """Creates a block device for an instance.
540 bdev: a ganeti.objects.Disk object
541 size: the size of the physical underlying devices
542 do_open: if the device should be `Assemble()`-d and
543 `Open()`-ed after creation
546 the new unique_id of the device (this can sometime be
547 computed only after creation), or None. On secondary nodes,
548 it's not required to return anything.
553 for child in disk.children:
554 crdev = _RecursiveAssembleBD(child, on_primary)
555 if on_primary or disk.AssembleOnSecondary():
556 # we need the children open in case the device itself has to
563 device = bdev.FindDevice(disk.dev_type, disk.physical_id, clist)
564 if device is not None:
565 logger.Info("removing existing device %s" % disk)
567 except errors.BlockDeviceError, err:
570 device = bdev.Create(disk.dev_type, disk.physical_id,
573 raise ValueError("Can't create child device for %s, %s" %
575 if on_primary or disk.AssembleOnSecondary():
577 device.SetSyncSpeed(constants.SYNC_SPEED)
578 if on_primary or disk.OpenOnSecondary():
579 device.Open(force=True)
583 physical_id = device.unique_id
587 def RemoveBlockDevice(disk):
588 """Remove a block device.
590 This is intended to be called recursively.
594 # since we are removing the device, allow a partial match
595 # this allows removal of broken mirrors
596 rdev = _RecursiveFindBD(disk, allow_partial=True)
597 except errors.BlockDeviceError, err:
598 # probably can't attach
599 logger.Info("Can't attach to device %s in remove" % disk)
602 result = rdev.Remove()
606 for child in disk.children:
607 result = result and RemoveBlockDevice(child)
611 def _RecursiveAssembleBD(disk, as_primary):
612 """Activate a block device for an instance.
614 This is run on the primary and secondary nodes for an instance.
616 This function is called recursively.
619 disk: a objects.Disk object
620 as_primary: if we should make the block device read/write
623 the assembled device or None (in case no device was assembled)
625 If the assembly is not successful, an exception is raised.
630 for chld_disk in disk.children:
631 children.append(_RecursiveAssembleBD(chld_disk, as_primary))
633 if as_primary or disk.AssembleOnSecondary():
634 r_dev = bdev.AttachOrAssemble(disk.dev_type, disk.physical_id, children)
635 r_dev.SetSyncSpeed(constants.SYNC_SPEED)
637 if as_primary or disk.OpenOnSecondary():
646 def AssembleBlockDevice(disk, as_primary):
647 """Activate a block device for an instance.
649 This is a wrapper over _RecursiveAssembleBD.
652 a /dev path for primary nodes
653 True for secondary nodes
656 result = _RecursiveAssembleBD(disk, as_primary)
657 if isinstance(result, bdev.BlockDev):
658 result = result.dev_path
662 def ShutdownBlockDevice(disk):
663 """Shut down a block device.
665 First, if the device is assembled (can `Attach()`), then the device
666 is shutdown. Then the children of the device are shutdown.
668 This function is called recursively. Note that we don't cache the
669 children or such, as oppossed to assemble, shutdown of different
670 devices doesn't require that the upper device was active.
673 r_dev = _RecursiveFindBD(disk)
674 if r_dev is not None:
675 result = r_dev.Shutdown()
679 for child in disk.children:
680 result = result and ShutdownBlockDevice(child)
684 def MirrorAddChild(md_cdev, new_cdev):
685 """Extend an MD raid1 array.
688 md_bdev = _RecursiveFindBD(md_cdev, allow_partial=True)
690 logger.Error("Can't find md device")
692 new_bdev = _RecursiveFindBD(new_cdev)
694 logger.Error("Can't find new device to add")
697 md_bdev.AddChild(new_bdev)
701 def MirrorRemoveChild(md_cdev, new_cdev):
702 """Reduce an MD raid1 array.
705 md_bdev = _RecursiveFindBD(md_cdev)
708 new_bdev = _RecursiveFindBD(new_cdev)
712 md_bdev.RemoveChild(new_bdev.dev_path)
716 def GetMirrorStatus(disks):
717 """Get the mirroring status of a list of devices.
720 disks: list of `objects.Disk`
723 list of (mirror_done, estimated_time) tuples, which
724 are the result of bdev.BlockDevice.CombinedSyncStatus()
729 rbd = _RecursiveFindBD(dsk)
731 raise errors.BlockDeviceError("Can't find device %s" % str(dsk))
732 stats.append(rbd.CombinedSyncStatus())
736 def _RecursiveFindBD(disk, allow_partial=False):
737 """Check if a device is activated.
739 If so, return informations about the real device.
742 disk: the objects.Disk instance
743 allow_partial: don't abort the find if a child of the
744 device can't be found; this is intended to be
745 used when repairing mirrors
748 None if the device can't be found
749 otherwise the device instance
754 for chdisk in disk.children:
755 children.append(_RecursiveFindBD(chdisk))
757 return bdev.FindDevice(disk.dev_type, disk.physical_id, children)
760 def FindBlockDevice(disk):
761 """Check if a device is activated.
763 If so, return informations about the real device.
766 disk: the objects.Disk instance
768 None if the device can't be found
769 (device_path, major, minor, sync_percent, estimated_time, is_degraded)
772 rbd = _RecursiveFindBD(disk)
775 sync_p, est_t, is_degr = rbd.GetSyncStatus()
776 return rbd.dev_path, rbd.major, rbd.minor, sync_p, est_t, is_degr
779 def UploadFile(file_name, data, mode, uid, gid, atime, mtime):
780 """Write a file to the filesystem.
782 This allows the master to overwrite(!) a file. It will only perform
783 the operation if the file belongs to a list of configuration files.
786 if not os.path.isabs(file_name):
787 logger.Error("Filename passed to UploadFile is not absolute: '%s'" %
791 allowed_files = [constants.CLUSTER_CONF_FILE, "/etc/hosts",
792 constants.SSH_KNOWN_HOSTS_FILE]
793 allowed_files.extend(ssconf.SimpleStore().GetFileList())
794 if file_name not in allowed_files:
795 logger.Error("Filename passed to UploadFile not in allowed"
796 " upload targets: '%s'" % file_name)
799 dir_name, small_name = os.path.split(file_name)
800 fd, new_name = tempfile.mkstemp('.new', small_name, dir_name)
801 # here we need to make sure we remove the temp file, if any error
804 os.chown(new_name, uid, gid)
805 os.chmod(new_name, mode)
808 os.utime(new_name, (atime, mtime))
809 os.rename(new_name, file_name)
812 utils.RemoveFile(new_name)
815 def _ErrnoOrStr(err):
816 """Format an EnvironmentError exception.
818 If the `err` argument has an errno attribute, it will be looked up
819 and converted into a textual EXXXX description. Otherwise the string
820 representation of the error will be returned.
823 if hasattr(err, 'errno'):
824 detail = errno.errorcode[err.errno]
830 def _OSOndiskVersion(name, os_dir=None):
831 """Compute and return the api version of a given OS.
833 This function will try to read the api version of the os given by
834 the 'name' parameter. By default, it wil use the constants.OS_DIR
835 as top-level directory for OSes, but this can be overriden by the
836 use of the os_dir parameter. Return value will be either an
837 integer denoting the version or None in the case when this is not
842 os_dir = os.path.sep.join([constants.OS_DIR, name])
844 api_file = os.path.sep.join([os_dir, "ganeti_api_version"])
847 st = os.stat(api_file)
848 except EnvironmentError, err:
849 raise errors.InvalidOS(name, "'ganeti_api_version' file not"
850 " found (%s)" % _ErrnoOrStr(err))
852 if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
853 raise errors.InvalidOS(name, "'ganeti_api_version' file is not"
859 api_version = f.read(256)
862 except EnvironmentError, err:
863 raise errors.InvalidOS(name, "error while reading the"
864 " API version (%s)" % _ErrnoOrStr(err))
866 api_version = api_version.strip()
868 api_version = int(api_version)
869 except (TypeError, ValueError), err:
870 raise errors.InvalidOS(name, "API version is not integer (%s)" % str(err))
874 def DiagnoseOS(top_dir=None):
875 """Compute the validity for all OSes.
877 For each name in the give top_dir parameter (if not given, defaults
878 to constants.OS_DIR), it will return an object. If this is a valid
879 os, the object will be an instance of the object.OS class. If not,
880 it will be an instance of errors.InvalidOS and this signifies that
881 this name does not correspond to a valid OS.
888 top_dir = constants.OS_DIR
891 f_names = os.listdir(top_dir)
892 except EnvironmentError, err:
893 logger.Error("Can't list the OS directory: %s" % str(err))
898 os_inst = OSFromDisk(name, os.path.sep.join([top_dir, name]))
899 result.append(os_inst)
900 except errors.InvalidOS, err:
906 def OSFromDisk(name, os_dir=None):
907 """Create an OS instance from disk.
909 This function will return an OS instance if the given name is a
910 valid OS name. Otherwise, it will raise an appropriate
911 `errors.InvalidOS` exception, detailing why this is not a valid
916 os_dir = os.path.sep.join([constants.OS_DIR, name])
918 api_version = _OSOndiskVersion(name, os_dir)
920 if api_version != constants.OS_API_VERSION:
921 raise errors.InvalidOS(name, "API version mismatch (found %s want %s)"
922 % (api_version, constants.OS_API_VERSION))
924 # OS Scripts dictionary, we will populate it with the actual script names
925 os_scripts = {'create': '', 'export': '', 'import': ''}
927 for script in os_scripts:
928 os_scripts[script] = os.path.sep.join([os_dir, script])
931 st = os.stat(os_scripts[script])
932 except EnvironmentError, err:
933 raise errors.InvalidOS(name, "'%s' script missing (%s)" %
934 (script, _ErrnoOrStr(err)))
936 if stat.S_IMODE(st.st_mode) & stat.S_IXUSR != stat.S_IXUSR:
937 raise errors.InvalidOS(name, "'%s' script not executable" % script)
939 if not stat.S_ISREG(stat.S_IFMT(st.st_mode)):
940 raise errors.InvalidOS(name, "'%s' is not a regular file" % script)
943 return objects.OS(name=name, path=os_dir,
944 create_script=os_scripts['create'],
945 export_script=os_scripts['export'],
946 import_script=os_scripts['import'],
947 api_version=api_version)
950 def SnapshotBlockDevice(disk):
951 """Create a snapshot copy of a block device.
953 This function is called recursively, and the snapshot is actually created
954 just for the leaf lvm backend device.
957 disk: the disk to be snapshotted
960 a config entry for the actual lvm device snapshotted.
964 if len(disk.children) == 1:
965 # only one child, let's recurse on it
966 return SnapshotBlockDevice(disk.children[0])
968 # more than one child, choose one that matches
969 for child in disk.children:
970 if child.size == disk.size:
971 # return implies breaking the loop
972 return SnapshotBlockDevice(child)
973 elif disk.dev_type == "lvm":
974 r_dev = _RecursiveFindBD(disk)
975 if r_dev is not None:
976 # let's stay on the safe side and ask for the full size, for now
977 return r_dev.Snapshot(disk.size)
981 raise errors.ProgrammerError("Cannot snapshot non-lvm block device"
982 "'%s' of type '%s'" %
983 (disk.unique_id, disk.dev_type))
986 def ExportSnapshot(disk, dest_node, instance):
987 """Export a block device snapshot to a remote node.
990 disk: the snapshot block device
991 dest_node: the node to send the image to
992 instance: instance being exported
995 True if successful, False otherwise.
998 inst_os = OSFromDisk(instance.os)
999 export_script = inst_os.export_script
1001 logfile = "%s/exp-%s-%s-%s.log" % (constants.LOG_OS_DIR, inst_os.name,
1002 instance.name, int(time.time()))
1003 if not os.path.exists(constants.LOG_OS_DIR):
1004 os.mkdir(constants.LOG_OS_DIR, 0750)
1006 real_os_dev = _RecursiveFindBD(disk)
1007 if real_os_dev is None:
1008 raise errors.BlockDeviceError("Block device '%s' is not set up" %
1012 destdir = os.path.join(constants.EXPORT_DIR, instance.name + ".new")
1013 destfile = disk.physical_id[1]
1015 # the target command is built out of three individual commands,
1016 # which are joined by pipes; we check each individual command for
1019 expcmd = utils.BuildShellCmd("cd %s; %s -i %s -b %s 2>%s", inst_os.path,
1020 export_script, instance.name,
1021 real_os_dev.dev_path, logfile)
1025 destcmd = utils.BuildShellCmd("mkdir -p %s; cat > %s/%s",
1026 destdir, destdir, destfile)
1027 remotecmd = ssh.BuildSSHCmd(dest_node, 'root', destcmd)
1031 # all commands have been checked, so we're safe to combine them
1032 command = '|'.join([expcmd, comprcmd, ' '.join(remotecmd)])
1034 result = utils.RunCmd(command)
1037 logger.Error("os snapshot export command '%s' returned error: %s"
1039 (command, result.fail_reason, result.output))
1045 def FinalizeExport(instance, snap_disks):
1046 """Write out the export configuration information.
1049 instance: instance configuration
1050 snap_disks: snapshot block devices
1053 False in case of error, True otherwise.
1056 destdir = os.path.join(constants.EXPORT_DIR, instance.name + ".new")
1057 finaldestdir = os.path.join(constants.EXPORT_DIR, instance.name)
1059 config = objects.SerializableConfigParser()
1061 config.add_section(constants.INISECT_EXP)
1062 config.set(constants.INISECT_EXP, 'version', '0')
1063 config.set(constants.INISECT_EXP, 'timestamp', '%d' % int(time.time()))
1064 config.set(constants.INISECT_EXP, 'source', instance.primary_node)
1065 config.set(constants.INISECT_EXP, 'os', instance.os)
1066 config.set(constants.INISECT_EXP, 'compression', 'gzip')
1068 config.add_section(constants.INISECT_INS)
1069 config.set(constants.INISECT_INS, 'name', instance.name)
1070 config.set(constants.INISECT_INS, 'memory', '%d' % instance.memory)
1071 config.set(constants.INISECT_INS, 'vcpus', '%d' % instance.vcpus)
1072 config.set(constants.INISECT_INS, 'disk_template', instance.disk_template)
1073 for nic_count, nic in enumerate(instance.nics):
1074 config.set(constants.INISECT_INS, 'nic%d_mac' %
1075 nic_count, '%s' % nic.mac)
1076 config.set(constants.INISECT_INS, 'nic%d_ip' % nic_count, '%s' % nic.ip)
1077 # TODO: redundant: on load can read nics until it doesn't exist
1078 config.set(constants.INISECT_INS, 'nic_count' , '%d' % nic_count)
1080 for disk_count, disk in enumerate(snap_disks):
1081 config.set(constants.INISECT_INS, 'disk%d_ivname' % disk_count,
1082 ('%s' % disk.iv_name))
1083 config.set(constants.INISECT_INS, 'disk%d_dump' % disk_count,
1084 ('%s' % disk.physical_id[1]))
1085 config.set(constants.INISECT_INS, 'disk%d_size' % disk_count,
1087 config.set(constants.INISECT_INS, 'disk_count' , '%d' % disk_count)
1089 cff = os.path.join(destdir, constants.EXPORT_CONF_FILE)
1090 cfo = open(cff, 'w')
1096 shutil.rmtree(finaldestdir, True)
1097 shutil.move(destdir, finaldestdir)
1102 def ExportInfo(dest):
1103 """Get export configuration information.
1106 dest: directory containing the export
1109 A serializable config file containing the export info.
1112 cff = os.path.join(dest, constants.EXPORT_CONF_FILE)
1114 config = objects.SerializableConfigParser()
1117 if (not config.has_section(constants.INISECT_EXP) or
1118 not config.has_section(constants.INISECT_INS)):
1124 def ImportOSIntoInstance(instance, os_disk, swap_disk, src_node, src_image):
1125 """Import an os image into an instance.
1128 instance: the instance object
1129 os_disk: the instance-visible name of the os device
1130 swap_disk: the instance-visible name of the swap device
1131 src_node: node holding the source image
1132 src_image: path to the source image on src_node
1135 False in case of error, True otherwise.
1138 inst_os = OSFromDisk(instance.os)
1139 import_script = inst_os.import_script
1141 for os_device in instance.disks:
1142 if os_device.iv_name == os_disk:
1145 logger.Error("Can't find this device-visible name '%s'" % os_disk)
1148 for swap_device in instance.disks:
1149 if swap_device.iv_name == swap_disk:
1152 logger.Error("Can't find this device-visible name '%s'" % swap_disk)
1155 real_os_dev = _RecursiveFindBD(os_device)
1156 if real_os_dev is None:
1157 raise errors.BlockDeviceError("Block device '%s' is not set up" %
1161 real_swap_dev = _RecursiveFindBD(swap_device)
1162 if real_swap_dev is None:
1163 raise errors.BlockDeviceError("Block device '%s' is not set up" %
1165 real_swap_dev.Open()
1167 logfile = "%s/import-%s-%s-%s.log" % (constants.LOG_OS_DIR, instance.os,
1168 instance.name, int(time.time()))
1169 if not os.path.exists(constants.LOG_OS_DIR):
1170 os.mkdir(constants.LOG_OS_DIR, 0750)
1172 destcmd = utils.BuildShellCmd('cat %s', src_image)
1173 remotecmd = ssh.BuildSSHCmd(src_node, 'root', destcmd)
1176 impcmd = utils.BuildShellCmd("(cd %s; %s -i %s -b %s -s %s &>%s)",
1177 inst_os.path, import_script, instance.name,
1178 real_os_dev.dev_path, real_swap_dev.dev_path,
1181 command = '|'.join([' '.join(remotecmd), comprcmd, impcmd])
1183 result = utils.RunCmd(command)
1186 logger.Error("os import command '%s' returned error: %s"
1188 (command, result.fail_reason, result.output))
1195 """Return a list of exports currently available on this machine.
1198 if os.path.isdir(constants.EXPORT_DIR):
1199 return os.listdir(constants.EXPORT_DIR)
1204 def RemoveExport(export):
1205 """Remove an existing export from the node.
1208 export: the name of the export to remove
1211 False in case of error, True otherwise.
1214 target = os.path.join(constants.EXPORT_DIR, export)
1216 shutil.rmtree(target)
1217 # TODO: catch some of the relevant exceptions and provide a pretty
1218 # error message if rmtree fails.
1223 class HooksRunner(object):
1226 This class is instantiated on the node side (ganeti-noded) and not on
1230 RE_MASK = re.compile("^[a-zA-Z0-9_-]+$")
1232 def __init__(self, hooks_base_dir=None):
1233 """Constructor for hooks runner.
1236 - hooks_base_dir: if not None, this overrides the
1237 constants.HOOKS_BASE_DIR (useful for unittests)
1238 - logs_base_dir: if not None, this overrides the
1239 constants.LOG_HOOKS_DIR (useful for unittests)
1240 - logging: enable or disable logging of script output
1243 if hooks_base_dir is None:
1244 hooks_base_dir = constants.HOOKS_BASE_DIR
1245 self._BASE_DIR = hooks_base_dir
1248 def ExecHook(script, env):
1249 """Exec one hook script.
1253 - script: the full path to the script
1254 - env: the environment with which to exec the script
1257 # exec the process using subprocess and log the output
1260 fdstdin = open("/dev/null", "r")
1261 child = subprocess.Popen([script], stdin=fdstdin, stdout=subprocess.PIPE,
1262 stderr=subprocess.STDOUT, close_fds=True,
1263 shell=False, cwd="/",env=env)
1266 output = child.stdout.read(4096)
1267 child.stdout.close()
1268 except EnvironmentError, err:
1269 output += "Hook script error: %s" % str(err)
1273 result = child.wait()
1275 except EnvironmentError, err:
1276 if err.errno == errno.EINTR:
1280 # try not to leak fds
1281 for fd in (fdstdin, ):
1285 except EnvironmentError, err:
1286 # just log the error
1287 #logger.Error("While closing fd %s: %s" % (fd, err))
1290 return result == 0, output
1292 def RunHooks(self, hpath, phase, env):
1293 """Run the scripts in the hooks directory.
1295 This method will not be usually overriden by child opcodes.
1298 if phase == constants.HOOKS_PHASE_PRE:
1300 elif phase == constants.HOOKS_PHASE_POST:
1303 raise errors.ProgrammerError("Unknown hooks phase: '%s'" % phase)
1306 subdir = "%s-%s.d" % (hpath, suffix)
1307 dir_name = "%s/%s" % (self._BASE_DIR, subdir)
1309 dir_contents = os.listdir(dir_name)
1310 except OSError, err:
1314 # we use the standard python sort order,
1315 # so 00name is the recommended naming scheme
1317 for relname in dir_contents:
1318 fname = os.path.join(dir_name, relname)
1319 if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
1320 self.RE_MASK.match(relname) is not None):
1321 rrval = constants.HKR_SKIP
1324 result, output = self.ExecHook(fname, env)
1326 rrval = constants.HKR_FAIL
1328 rrval = constants.HKR_SUCCESS
1329 rr.append(("%s/%s" % (subdir, relname), rrval, output))