Joey Boggs
2010-Oct-22 14:38 UTC
[Ovirt-devel] [PATCH node] First draft of replacing some of the ovirt-config-* scripts with python equivalents.
Putting these out for feedback and comments. These will eventually support the new newt/python based ui for installation/configuration storage.py functions will be moved under a class for better data portability before final version --- scripts/ovirtfunctions.py | 672 +++++++++++++++++++++++++++++++++++++++++++++ scripts/storage.py | 451 ++++++++++++++++++++++++++++++ 2 files changed, 1123 insertions(+), 0 deletions(-) create mode 100644 scripts/ovirtfunctions.py create mode 100644 scripts/storage.py diff --git a/scripts/ovirtfunctions.py b/scripts/ovirtfunctions.py new file mode 100644 index 0000000..f2b9e39 --- /dev/null +++ b/scripts/ovirtfunctions.py @@ -0,0 +1,672 @@ +#!/usr/bin/python +import subprocess +import os +from subprocess import Popen, PIPE, STDOUT +import tempfile +import string +import sys + + +OVIRT_LOGFILE="/var/log/ovirt.log" +OVIRT_TMP_LOGFILE="/tmp/ovirt.log" +# label of the oVirt partition +OVIRT_LABEL="OVIRT" +# configuration values are loaded in the following order: +# 1. /etc/sysconfig/node-config sets the default values +# 2. /etc/default/ovirt is loaded to override defaults with karg values +NODE_SYSCONFIG="/etc/sysconfig/node-config" +OVIRT_DEFAULTS="/etc/default/ovirt" + +OVIRT_VARS = {} +# Parse all OVIRT_* variables +if os.path.exists(NODE_SYSCONFIG): + try: + f = open(NODE_SYSCONFIG, 'r') + for line in f: + try: + line = line.strip() + key, value = line.split("\"", 1) + key = key.strip("=") + value = value.strip("\"") + OVIRT_VARS[key] = value + except: + pass + f.close() + except: + pass + +f = open(OVIRT_DEFAULTS, 'r') +for line in f: + try: + line = line.strip() + key, value = line.split("\"", 1) + key = key.strip("=") + value = value.strip("\"") + OVIRT_VARS[key] = value + except: + pass +f.close() + +OVIRT_BACKUP_DIR="/var/lib/ovirt-backup" + +MANAGEMENT_SCRIPTS_DIR="/etc/node.d" + +OVIRT_CONFIG_FILES = ["/etc/sysconfig/network-scripts/ifcfg-*", \ + "/etc/rsyslog.conf", \ + "/etc/libvirt/libvirtd.conf", \ + "/etc/sasl2/libvirt.conf", \ + "/etc/libvirt/passwd.db", \ + "/etc/passwd", \ + "/etc/shadow", \ + "/etc/ssh/ssh_host*_key*", \ + "/etc/default/ovirt", \ + "/etc/sysconfig/network", \ + "/etc/collectd.conf", \ + "/etc/logrotate.d/ovirt-logrotate.conf" ] + +def log(log_entry): + # placeholder for now + print log_entry + +def set_console_colors(): + GIO_CMAP = 0x4B70 + PIO_CMAP = 0x4B71 + + tty_file = open("/dev/console", "rw") + existing_color_array = bytearray(fcntl.ioctl(tty_file.fileno(), GIO_CMAP, b"\x00" * 48)) + color_array = existing_color_array + color_array[3] = 0xde + color_array[4] = 0xde + color_array[5] = 0xde + color_array[6] = 0x30 + color_array[7] = 0x30 + color_array[8] = 0x30 + color_array[12] = 0x38 + color_array[13] = 0x8f + color_array[14] = 0xcd + color_array[15] = 0xea + color_array[16] = 0xea + color_array[17] = 0xea + color_array[18] = 0x71 + color_array[19] = 0x71 + color_array[20] = 0x71 + color_array[22] = 0xff + color_array[23] = 0xff + color_array[9] = 0x52 + color_array[10] = 0x52 + color_array[11] = 0x52 + fcntl.ioctl(tty_file.fileno(), PIO_CMAP, bytes(color_array)) + +def restore_console_colors(): + GIO_CMAP = 0x4B70 + PIO_CMAP = 0x4B71 + tty_file = open("/dev/console", "rw") + fcntl.ioctl(tty_file.fileno(), PIO_CMAP, bytes(.existing_color_array)) + +def ovirt_store_firstboot_config(): + # persist config for standalone + ovirt_store_config(OVIRT_CONFIG_FILES) + +# return 1 if oVirt Node is running in standalone mode +# return 0 if oVirt Node is managed by the oVirt Server +def is_managed(): + return OVIRT_VARS["OVIRT_STANDALONE"] + +# oVirt Node in standalone mode does not try to contact the oVirt Server +def is_standalone(): + if is_managed: + return False + else: + return True + +# return 0 if local storage is configured +# return 1 if local storage is not configured +def is_local_storage_configured(): + ret = os.system("lvs HostVG/Config >/dev/null >&1") + if ret > 0: + return False + return True + +# perform automatic local disk installation +# when at least following boot parameters are present: +# for networking - OVIRT_BOOTIF, management NIC +# if other ip bootparams are not specified, IPv4 DHCP is assumed +# for storage - OVIRT_INIT, local disk to use +# if ovirt_vol is not specified, default volume sizes are set +def is_auto_install(): + if OVIRT_VARS.has_key("OVIRT_BOOTIF") and OVIRT_VARS.has_key("OVIRT_INIT"): + return True + else: + return False + +# return 0 if this is an upgrade +# return 1 otherwise +def is_upgrade(): + if OVIRT_VARS.has_key("OVIRT_UPGRADE") and OVIRT_VARS["OVIRT_UPGRADE"] == 1: + return True + else: + return False + +# return 0 if booted from local disk +# return 1 if booted from other media +def is_booted_from_local_disk(): + ret = os.system("grep -q /dev/HostVG/ /proc/cmdline") + if ret == 0: + return True + else: + return False + +# was firstboot menu already shown? +# state is stored in persistent config partition +def is_firstboot(): + if not OVIRT_VARS.has_key("OVIRT_FIRSTBOOT") or OVIRT_VARS["OVIRT_FIRSTBOOT"] == 1: + return True + else: + return False + +def disable_firstboot(): + if mount_config: + firstboot = augeas.Augeas(root="/") + firstboot.set("/files/etc/defaults/ovirt/OVIRT_FIRSTBOOT", "0") + firstboot.set("/files/etc/defaults/ovirt/OVIRT_INIT", '""') + firstboot.set("/files/etc/defaults/OVIRT_UPGRADE", "0") + firstboot.save() + +# Destroys a particular volume group and its logical volumes. + +def wipe_volume_group(vg): + files_cmd = "grep %s /proc/mounts|awk '{print $2}'|sort -r" % vg + files = subprocess.Popen(files_cmd, shell=True, stdout=PIPE, stderr=STDOUT) + files_output = files.stdout.read() + for file in files_output.split(): + umount_cmd = "umount %s" % file + os.system(umount_cmd) + + + swap_cmd = "grep %s /proc/swaps|awk '{print $1}'" % vg + swap = subprocess.Popen(swap_cmd, shell=True, stdout=PIPE, stderr=STDOUT) + swap_output = swap.stdout.read() + for d in swap_output: + log ("Turning off %s") % d + os.system("swapoff %s") % d + vgremove_cmd = "vgremove -f %s" % vg + os.system(vgremove_cmd) + +# find_srv SERVICE PROTO +# +# reads DNS SRV record +# sets SRV_HOST and SRV_PORT if DNS SRV record found, clears them if not +# Example usage: +# find_srv ovirt tcp +def find_srv(srv, proto): + domain = subprocess.Popen("dnsdomainname 2>/dev/null", shell=True, stdout=PIPE, stderr=STDOUT) + domain_output = domain.stdout.read() + if domain_output == "localdomain": + domain="" + # FIXME dig +search does not seem to work with -t srv + # dnsreply=$(dig +short +search -t srv _$1._$2) + # This is workaround: + search = subprocess.Popen("grep search /etc/resolv.conf", shell=True, stdout=PIPE, stderr=STDOUT) + search_output = search.stdout.read() + search = search.replace("search ","") + domain_search = domain_output + search_output + for d in domain_search.split(): + dig_cmd = "dig +short -t srv _%s._%s.%s" % (srv, proto,search) + dig = subprocess.Popen(dig_cmd, shell=True, stdout=PIPE, stderr=STDOUT) + dig_output = dig.stdout.read() + dig.poll() + dig_rc = dnsreply.returncode() + if dig_rc == 0: + a,b,port,host = dig_output.split("=", 4) + return (port, host) + return False + +def ovirt_setup_libvirtd(): + # just to get a boot warning to shut up + os.system("touch /etc/resolv.conf") + + # make libvirtd listen on the external interfaces + os.system("sed -i -e 's/^#\(LIBVIRTD_ARGS=\"--listen\"\).*/\1/' /etc/sysconfig/libvirtd") + + # set up qemu daemon to allow outside VNC connections + os.system("sed -i -e 's/^[[:space:]]*#[[:space:]]*\(vnc_listen = \"0.0.0.0\"\).*/\1/' /etc/libvirt/qemu.conf") + # set up libvirtd to listen on TCP (for kerberos) + os.system('sed -i -e "s/^[[:space:]]*#[[:space:]]*\(listen_tcp\)\>.*/\1 = 1/" \ + -e "s/^[[:space:]]*#[[:space:]]*\(listen_tls\)\>.*/\1 = 0/" \ + /etc/libvirt/libvirtd.conf') + + # with libvirt (0.4.0), make sure we we setup gssapi in the mech_list + sasl_conf="/etc/sasl2/libvirt.conf" + ret = os.system('grep -qE "^mech_list: gssapi %s') % sasl_conf + if ret > 0: + os.system("sed -i -e \"s/^\([[:space:]]*mech_list.*\)/#\1/\" %s") % sasl_conf + os.system("echo \"mech_list: gssapi\" >> %s") % sasl_conf + +def ovirt_setup_anyterm(): + # configure anyterm + anyterm_conf = open("/etc/sysconfig/anyterm", "w") + anyterm_conf.write("ANYTERM_CMD=\"sudo /usr/bin/virsh console %p\"") + anyterm_conf.write("ANYTERM_LOCAL_ONLY=false") + anyterm_conf.close() + # permit it to run the virsh console + os.system("echo \"anyterm ALL=NOPASSWD: /usr/bin/virsh console *\" >> /etc/sudoers") + +# mount livecd media +# e.g. CD /dev/sr0, USB /dev/sda1, +# PXE /dev/loop0 (loopback ISO) +# not available when booted from local disk installation +def mount_live(): + if os.path.ismount("/live"): + return + + live_dev="/dev/live" + if not os.path.exists(live_dev): + ret = os.system("losetup /dev/loop0|grep -q '\.iso'") + if ret == 0: + # PXE boot + live_dev="/dev/loop0" + else: + return False + os.system("mkdir -p /live") + os.system("mount -r %s /live || mount %s /live") % (live_dev, live_dev) + + +# mount root partition +# boot loader + kernel + initrd + LiveOS +def mount_liveos(): + if os.path.ismount("/liveos"): + return + else: + os.system("mkdir -p /liveos") + os.system("mount LABEL=Root /liveos") + +# mount config partition +# /config for persistance +def mount_config(): + # Only try to mount /config if the persistent storage exists + if os.path.exists("/dev/HostVG/Config"): + os.system("mkdir -p /config") + if not os.path.ismount("/config"): + ret = os.system("mount /dev/HostVG/Config /config") + if ret > 0: + return False + + # optional config embedded in the livecd image + if os.path.exists("/live/config"): + os.system("cp -rv --update /live/config/* /config") + + # bind mount all persisted configs to rootfs + for f in os.listdir("/config"): + if os.path.isfile("/config/%s") % f: + target = string.replace(f, "/config", "") + mounted = os.system("grep -q %s ext3 /proc/mounts") % target + if mounted == 0: + # skip if already bind-mounted + pass + else: + dirname = os.path.dirname(target) + os.system("mkdir -p %s") % dirname + if not os.path.exists(target): + os.open(target, 'w').close() + os.system("mount -n --bind %s %s" % (f,target)) + return True + else: + # /config is not available + return False + +def mount_boot(): + if os.path.ismount("/boot"): + return + else: + os.system("mkdir -p /boot") + os.system("mount LABEL=Boot /boot") + +# stop any service which keeps /var/log busy +# keep the list of services +def unmount_logging_services(): + # mapping command->service is lame, but works for most initscripts + logging_services="" + prgs_cmd = "cd /etc/init.d|sudo lsof -Fc +D /var/log|grep ^c|sort -u" + prgs = subprocess.Popen(prgs_cmd, shell=True, stdout=PIPE, stderr=STDOUT) + prgs_output = prgs.stdout.read() + for prg in prgs_output: + srvs = subprocess.Popen("grep -l ${prg#c}$ *", shell=True, stdout=PIPE, stderr=STDOUT) + srvs_output = srvs.stdout.read() + for svc in srv_output: + os.system("service %s stop") % svc + logging_services+= svc + # debugging help + os.system("lsof +D /var/log") + +# mount logging partition +# this only gets executed when disk is re-partitioned, HostVG/Logging is empty +def mount_logging(): + if os.path.ismount("/var/log"): + return True + if os.path.exists("/dev/HostVG/Logging"): + log("Mounting log partition") + # temporary mount-point + log2 = tempfile.mkdtemp() + os.system("mount /dev/HostVG/Logging %s") % log2 + unmount_logging_services() + # save logs from tmpfs + os.system("cp -av /var/log/* %s") % log2 + # save temporary log + if os.path.exists("/tmp/ovirt.log"): + os.system("cp /tmp/ovirt.log %s/ovirt.log-tmp") % log2 + os.system("mount --move %s /var/log") % log2 + shutil.rmtree(log2) + os.system("restorecon -rv /var/log") + for srv in logging_services: + os.system("service %s start") % srv + return + else: + # /var/log is not available + log("\nThe logging partion has not been created. Please create it at the main menu.\n") + return False + +def unmount_logging(): + if not os.path.ismount("/var/log"): + return True + log("Unmounting log partition") + # plymouthd keeps /var/log/boot.log + ret = os.system("plymouth --ping") + if ret == 0: + os.system("plymouth --quit") + unmount_logging_services() + + ret = os.system("umount /var/log") + if ret > 0: + return ret + for srv in logging_services: + os.system("service %s start") % srv + return + +# mount data partition +def mount_data(): + if os.path.ismount("/data"): + return + + if os.path.exists("/dev/HostVG/Data"): + os.system("mkdir -p /data") + os.system("mount /data") + os.system("mkdir -p /data/images") + os.system("mkdir -p /var/lib/libvirt/images") + os.system("mount /var/lib/libvirt/images") + os.system("restorecon -rv /var/lib/libvirt/images") + os.system("mkdir -p /data/core") + os.system("mkdir -p /var/log/core") + os.system("mount /var/log/core") + os.system("restorecon -rv /var/log/core") + return + else: + # /data is not available + log("\nThe data partion has not been created. Please create it at the main menu.\n") + return False + +def md5sum(filename): + m = md5() + with open(filename) as f: + data = f.read(buf_size) + while data: + m.update(data) + data = f.read(buf_size) + return m.hexdigest() + + +# persist configuration to /config +# ovirt_store_config /etc/config /etc/config2 ... +# copy to /config and bind-mount back + +def ovirt_store_config(files): + if os.path.ismount("/config"): + for p in files: + filename = os.path.abspath(filename) + persist_it=true + + # ensure that, if this is a directory + # that it's not already persisted + if os.path.isdir(filename): + if os.path.isdir("/config/" + filename): + log("Directory already persisted: ${filename}\n") + log("You need to unpersist its child directories and/or files and try again.\n") + persist_it=false + + # if it's a file then make sure it's not already + # persisted + if os.path.isfile(filename): + if os.path.isfile("/config/" + filename): + md5root=md5sum(filename) + md5stored=md5sum("/config" + filename) + if md5root == md5stored: + log("File already persisted: %s\n") % filename + persist_it=false + else: + # persistent copy needs refresh + ret = os.system("umount -n %s 2> /dev/null") % filename + if ret != 0: + os.system("rm -f /config%s") % filename + + if persist_it: + # skip if file does not exist + if not os.path.exists(filename): + log("Skipping, file '%s' does not exist\n") % filename + # skip if already bind-mounted + if not check_bind_mount(filename): + dirname = os.path.dirname(filename) + os.system("mkdir -p /config/%s") % dirname + ret = os.system("cp -a %s /config%s") % (filename, filename) + if ret == 0: + ret = os.system("mount -n --bind /config%s %s") % (filename, filename) + if ret != 0: + log("Failed to persist\n") + rc=1 + else: + log("File persisted\n") + # register in /config/files used by rc.sysinit + ret = os.system("grep -q \"^$%s$\" /config/files 2> /dev/null") % filename + if ret > 0: + os.system("echo \"%s\n\" >> /config/files") % filename + log("\nSuccessfully persisted ${filename}\n") + else: + log("WARNING: persistent config storage not available\n") + rc=2 + return rc + +def is_persisted(filename): + abspath = os.path.abspath(filename) + if os.path.exists("/config" + abspath): + return True + else: + return False + +# unmount bindmounted config files +# unmount_config /etc/config /etc/config2 ... +# +# Use before running commands which fail on bindmounted files. +# After the file is replaced, call ovirt_store_config /etc/config ... +# to bindmount the config file again. +# + +def check_bind_mount(config_file): + bind_mount_cmd = "grep -q \"%s ext4\" /proc/mounts" % config_file + bind_mount_ret = os.system(bind_mount_cmd) + return bind_mount_ret + +def unmount_config(config_file): + if os.path.ismount("/config"): + for p in config_file.split(): + f = os.path.abspath(p) + if check_bind_mount(f): + ret = os.system("umount -n %s" % f) + if ret == 0: + if os.path.exists("/config%s" % f): + # refresh the file in rootfs if it was mounted over + shutil.copy("/config%s %s" % (f,f)) + +# remove persistent config files +# remove_config /etc/config /etc/config2 ... +# +def remove_config(config_file): + # if there are no persisted files then just exit + if os.path.exists("/config/files"): + if os.path.getsize('/config/files') == 0: + print "There are currently no persisted files." + return + if os.path.ismount("/config"): + for p in config_file: + filename = os.path.abspath(filename) + ret = os.system("grep \"^%s\$\" /config/files > /dev/null 2>&1") % filename + if ret == 0: + if check_bind_mount(filename): + ret = os.system("umount -n %s") % filename + if ret == 0: + if os.path.isdir(filename): + ret = os.system("cp -ar /config/%s/* %s") % (filename,filename) + if ret > 0: + log(" Failed to unpersist %s\n") % filename + return False + else: + log("%s successully unpersisted\n") % filename + return True + else: + if os.path.isfile(filename): + # refresh the file in rootfs if it was mounted over + ret = os.system("cp -a /config%s") % (filename, filename) + if ret > 0: + log("Failed to unpersist %s\n") % filename + return False + else: + log("%s successully unpersisted\n") % filename + # clean up the persistent store + os.system("rm -Rf /config%s") % filename + # unregister in /config/files used by rc.sysinit + os.system("sed --copy -i \"\|^%s$|d\" /config/files") % filename + else: + log("%s is not a persisted file.\n") % filename + else: + log("File not explicitly persisted: %s\n") % filename + +# ovirt_safe_delete_config +# ovirt_safe_delete_config /etc/config /etc/config2 ... +# +# Use to *permanently* remove persisted configuration file. +# WARNING: file is shredded and removed +# +def ovirt_safe_delete_config(target): + for t in target: + if check_bind_mount(target): + os.system("umount -n %s") % target + + os.system("sed --copy -i \"\|%s$|d\" /config/files") % target + + if os.path.isdir(target): + child = subprocess.Popen("ls -d %s')", shell=True, stdout=PIPE, stderr=STDOUT) % target + child_output = swap.stdout.read() + for child in child_output: + ovirt_safe_delete_config(child) + os.system("rm -rf /config%s") % target + os.system("rm -rf %s") % target + else: + os.system("shred -u /config%s") % target + os.system("shred -u %s") % target + + +# compat function to handle different udev versions +def udev_info(name, query): + # old udev command with shortopts + udev_cmd = "udevinfo -n %s -q %s" % (name, query) + udev = subprocess.Popen(udev_cmd, shell=True, stdout=PIPE, stderr=STDOUT) + udev_output = udev.stdout.read() + udev.poll() + udev_rc = udev.returncode() + if udev_rc > 0: + udev_cmd = "udevadm info --name=%s --query=%s" % (name, query) + udev = subprocess.Popen(udev_cmd, shell=True, stdout=PIPE, stderr=STDOUT) + udev_output = udev.stdout.read() + udev.poll() + udev_rc = udev.returncode() + if udev_rc == 0: + os.system("echo %s") % udev_output + return udev_output + +def backup_file(file): + dir = os.path.dirname(file) + if dir in os.listdir("/"): + print "unexpected non-absolute dir: %s" % dir + sys.exit(1) + os.system("mkdir -p %s%s") % (OVIRT_BACKUP_DIR, dir) + if os.path.exists(file): + shutil.copy(file, OVIRT_BACKUP_DIR + file) + +# reboot wrapper +# cleanup before reboot +def reboot(): + if not OVIRT_VARS.has_key("$OVIRT_ISCSI_ENABLED"): + # setup new Root if update is prepared + ret = os.system("findfs LABEL=RootUpdate >/dev/null 2>&1") + if ret == 0: + root_update_dev_lookup = subprocess.Popen("findfs LABEL=RootUpdate >/dev/null 2>&1", shell=True, stdout=PIPE, stderr=STDOUT) + root_update_dev = root_update_dev_lookup.stdout.read() + root_dev_lookup = subprocess.Popen("findfs LABEL=Root >/dev/null 2>&1", shell=True, stdout=PIPE, stderr=STDOUT) + root_dev = root_dev_lookup.stdout.read() + os.system("e2label %s RootBackup") % root_dev + os.system("e2label %s Root") % root_update_dev + # run post-install hooks + # e.g. to avoid reboot loops using Cobbler PXE only once + # Cobbler XMLRPC post-install trigger (XXX is there cobbler SRV record?): + # wget "http://192.168.50.2/cblr/svc/op/trig/mode/post/system/$(hostname)" + # -O /dev/null + for hook in os.listdir("/etc/ovirt-config-boot.d"): + os.system(hook) + os.system("/sbin/reboot") + +# Check if networking is already up +def network_up(): + ret = os.system("ip addr show | grep -q 'inet.*scope global'") + if ret == 0: + return True + return False +# Cleans partition tables +def wipe_partitions(drive): + log("Wiping old boot sector") + os.system("dd if=/dev/zero of=\"%s\" bs=1024K count=1") % drive + # zero out the GPT secondary header + log("Wiping secondary gpt header") + disk_kb = subprocess.Popen("sfdisk -s \"%s\" 2>/dev/null", shell=True, stdout=PIPE, stderr=STDOUT) % drive + disk_kb_count = disk_kb.stdout.read() + os.system("dd if=/dev/zero of=\"%s\" bs=1024 seek=$((%s - 1)) count=1") % (drive,disk_kb_count) + +def test_ntp_configuration(): + # stop ntpd service for testing + os.system("service ntpd stop > /dev/null 2>&1") + for server in ntp_servers: + ret = os.system("ntpdate %s > /dev/null 2>&1") % server + if ret > 0: + print "\n Unable to verify NTP server: %s \n" % server + else: + log("\n Verified NTP server: %s \n") % server + os.system("service ntpd start") + +def get_dm_device(device): + dev_path = os.path.abspath(device) + major_cmd = "ls -al %s|awk {'print $5'}" % dev_path + major = subprocess.Popen("major_dev_cmd", shell=True, stdout=PIPE, stderr=STDOUT) + major = major.stdout.read() + minor_cmd = "ls -al %s|awk {'print $6'}" % dev_path + minor = subprocess.Popen("minor_dev_cmd", shell=True, stdout=PIPE, stderr=STDOUT) + minor = minor.stdout.read() + dm_device="" + rc = 1 + for dm in os.listdir("/dev/mapper/"): + dm_major_cmd = "ls -al %s |awk {'print $5'}" % dm + dm_major = subprocess.Popen("dm_major_dev_cmd", shell=True, stdout=PIPE, stderr=STDOUT) + dm_major = dm_major.stdout.read() + dm_major = dm_major.strip(",") + dm_minor_cmd = "ls -al %s|awk {'print $6'}" % dm + dm_minor = subprocess.Popen("dm_minor_dev_cmd", shell=True, stdout=PIPE, stderr=STDOUT) + dm_minor = dm_minor.stdout.read() + if dm_major == major and dm_minor == minor: + dm_device=dm + return dm_device + diff --git a/scripts/storage.py b/scripts/storage.py new file mode 100644 index 0000000..a2a83a5 --- /dev/null +++ b/scripts/storage.py @@ -0,0 +1,451 @@ +#!/usr/bin/python +from ovirtfunctions import * +import os +import time +import re +import subprocess +from subprocess import PIPE, STDOUT + + +default_overcommit=0.5 +default_boot_size=50 +default_root_size=256 +default_config_size=5 +default_logging_size=2048 +BOOTDRIVE = "" +RootBackup_end = "" +# -1 indicates data partition should use remaining disk +default_data_size=-1 + +mem_size_cmd = "awk '/MemTotal:/ { print $2 }' /proc/meminfo" +mem_size_mb = subprocess.Popen(mem_size_cmd, shell=True, stdout=PIPE, stderr=STDOUT) +MEM_SIZE_MB = mem_size_mb.stdout.read() + +MEM_SIZE_MB= int(MEM_SIZE_MB) / 1024 + +overcommit=default_overcommit +# we multiply the overcommit coefficient by 10 then divide the +# product by 10 to avoid decimals in the result +OVERCOMMIT_SWAP_SIZE = int(MEM_SIZE_MB) * overcommit * 10 / 10 + +# add to the swap the amounts from http://kbase.redhat.com/faq/docs/DOC-15252 +MEM_SIZE_GB= MEM_SIZE_MB/1024 +if MEM_SIZE_GB < 4: + BASE_SWAP_SIZE=2048 +elif MEM_SIZE_GB < 16: + BASE_SWAP_SIZE=4096 +elif MEM_SIZE_GB < 64: + BASE_SWAP_SIZE=8192 +else: + BASE_SWAP_SIZE=16384 + +CALC_SWAP_SIZE = int(BASE_SWAP_SIZE) + int(OVERCOMMIT_SWAP_SIZE) + +BOOT_SIZE=default_boot_size +SWAP_SIZE=CALC_SWAP_SIZE +ROOT_SIZE=default_root_size +CONFIG_SIZE=default_config_size +LOGGING_SIZE=default_logging_size +DATA_SIZE=default_data_size + +def translate_multipath_device(dev): + #trim so that only sdX is stored, but support passing /dev/sdX + if dev is None: + return False + if "/dev/mapper" in dev: + return dev + mpath_cmd = "multipath -ll %s" % dev + ret = os.system(mpath_cmd) + if ret > 0: + return dev + dm_dev_cmd = "multipath -ll \"%s\" | egrep dm-[0-9]+ | sed -r 's/^.& (dm-[0-9]+) .*$/\1/'" % dev + dm_dev = subprocess.Popen(dm_dev_cmd, shell=True, stdout=PIPE, stderr=STDOUT) + dm_dev_output = "/dev/" + dm_dev.stdout.read() + mpath_device = get_dm_device(dm_dev_output) + + if mpath_device is None: + return dev + else: + return mpath_device + +def get_drive_size(drive): + size_cmd = "sfdisk -s %s 2>null" % drive + size = subprocess.Popen(size_cmd, shell=True, stdout=PIPE, stderr=STDOUT) + size = size.stdout.read() +# size = int(size.stdout.read().strip()) # / 1024) + return size + +if OVIRT_VARS.has_key("OVIRT_INIT"): + # if present, use the drive selected with 'ovirt_init' boot parameter + # setting these the same until kernel cmdline argument implemented + DRIVE=translate_multipath_device(OVIRT_VARS["OVIRT_INIT"]) + ROOTDRIVE = DRIVE + HOSTVGDRIVE = DRIVE + ROOTDRIVESPACE = get_drive_size(ROOTDRIVE) + +# if the node is Fedora then use GPT, otherwise use MBR +if os.path.isfile("/etc/fedora-release"): + LABEL_TYPE="gpt" +else: + LABEL_TYPE="msdos" + + + +################################################################## + +def wipe_lvm_on_disk(dev): + unmount_logging + part_delim="p" + if "/dev/sd" in dev: + part_delim="" + vg_cmd = "pvs -o vg_uuid --noheadings %s \"%s%s[0-9]\"* 2>/dev/null|sort -u" % (dev, dev, part_delim) + vg = subprocess.Popen(vg_cmd, shell=True, stdout=PIPE, stderr=STDOUT) + vg_output = vg.stdout.read() + for vg in vg_output: + pvs = os.system("pvscan -o pv_name,vg_uuid --noheadings | grep \"%s\" | egrep -v -q \"%s%s[0-9]+|%s \" 2>/dev/null") % (vg, dev, part_delim, dev) + if pvs > 0: + log("The volume group \"%s\" spans multiple disks.") % vg + log("This operation cannot complete. Please manually") + log("cleanup the storage using standard disk tools.") + sys.exit(1) + wipe_volume_group(vg) + return + + +def reread_partitions(drive): + if "dev/mapper" in drive: + # kpartx -a -p p "$drive" + # XXX fails with spaces in device names (TBI) + # ioctl(3, DM_TABLE_LOAD, 0x966980) = -1 EINVAL (Invalid argument) + # create/reload failed on 0QEMU QEMU HARDDISK drive-scsi0-0-0p1 + os.system("partprobe ||:") + # partprobe fails on cdrom: + # Error: Invalid partition table - recursive partition on /dev/sr0. + else: + os.system("blockdev --rereadpt %s") % drive + + +def get_sd_name(id): + device_sys_cmd = "grep -H \"^%s$\" /sys/block/*/dev | cut -d: -f1" % id + device_sys = subprocess.Popen(device_sys_cmd, shell=True, stdout=PIPE, stderr=STDOUT) + device_sys_output = device_sys.stdout.read().strip() + if not device_sys_output is "": + device = os.path.basename(device_sys_output) + return device + return False + + +# gets the dependent block devices for multipath devices +def get_multipath_deps(mpath_device, deplist_var): + return + deplist="" + #get dependencies for multipath device + deps_cmd ="dmsetup deps -u \"mpath-%s\" \ + | sed -r 's/\(([0-9]+), ([0-9]+)\)/\1:\2/g' \ + | sed 's/ /\n/g' | grep [0-9]:[0-9] 2>/dev/null" % mpath_device + deps = subprocess.Popen(deps_cmd, shell=True, stdout=PIPE, stderr=STDOUT) + deps_output = deps.stdout.read() + + for dep in deps_output.split(): + device=get_sd_name(dep) + dep_list=[] + if device is None: + if deplist is None: + deplist = device + else: + deplist.append(device) + return deplist + +# Find a usable/selected storage device. +# If there are none, give a diagnostic and return nonzero. +# If there is just one, e.g., /dev/sda, treat it as selected (see below). +# and return 0. If there are two or more, make the user select one +# or decline. Upon decline, return nonzero. Otherwise, print the +# selected name, then return 0. +# Sample output: /dev/sda +def get_dev_name(): + devices="" + # list separator + for d in os.listdir("/sys/block/"): + if re.match("^[hsv]+d", d): + devices="/dev/%s %s" % (d,devices) + byid_list_cmd = "find /dev/disk/by-id -mindepth 1 -not -name '*-part*' 2>/dev/null" + byid_list = subprocess.Popen(byid_list_cmd, shell=True, stdout=PIPE, stderr=STDOUT) + byid_list_output = byid_list.stdout.read() + for d in byid_list_output.split(): + d = os.readlink(d) + d_basename = os.path.basename(d) + udev_cmd = "udevadm info --name=/dev/%s --query=property | grep -q ^ID_BUS:" % d_basename + if os.system(udev_cmd): + devices="/dev/%s %s" % (d_basename, devices) + # FIXME: workaround for detecting cciss devices + if os.path.exists("/dev/cciss"): + for d in os.listdir("/dev/cciss"): + if not re.match("p[0-9]+\$", d): + devices="%s %s" % (d, devices) + + # include multipath devices + devs_to_remove="" + multipath_list_cmd = "dmsetup ls --target=multipath | cut -f1" + multipath_list = subprocess.Popen(multipath_list_cmd, shell=True, stdout=PIPE, stderr=STDOUT) + multipath_list_output = multipath_list.stdout.read() + + for d in multipath_list_output: + devices="/dev/mapper/%s %s" % (d, devices) + sd_devs="" + get_multipath_deps(d, sd_devs) + + dm_dev_cmd = "multipath -ll \"%s\" | grep \"%s\" | sed -r 's/^.*(dm-[0-9]+ ).*$/\1/' )" % (d, d) + dm_dev = subprocess.Popen(dm_dev_cmd, shell=True, stdout=PIPE, stderr=STDOUT) + dm_dev_output = dm_dev.stdout.read() + devs_to_remove="%s %s %s" % (devs_to_remove, sd_devs, dm_dev) + # Remove /dev/sd* devices that are part of a multipath device + dev_list=[] + for d in devices.split(): + if os.path.basename(d) not in devs_to_remove: + dev_list.append(d) + + for dev in dev_list: + if dev_list.count(dev) > 1: + count = dev_list.count(dev) + while (count > 1): + dev_list.remove(dev) + count = count - 1 + + return dev_list + +def check_partition_sizes(): + # disk_size need_size + drive_list = [] + drive_space_dict = {} + min_data_size = OVIRT_VARS["DATA_SIZE"] + if DATA_SIZE == -1 : + min_data_size=5 + + if OVIRT_VARS["OVIRT_ISCSI_ENABLED"] == "y": + BOOTDRIVESPACE = get_drive_size(BOOTDRIVE) + drive_list.append("BOOT") + drive_space_dict["BOOTDRIVESPACE"] = BOOTDRIVESPACE + drive_space_dict["BOOT_NEED_SIZE"] = BOOT_SIZE + else: + ROOTDRIVESPACE = get_drive_size(ROOTDRIVE) + HOSTVGDRIVESPACE = get_drive_size(HOSTVGDRIVE) + ROOT_NEED_SIZE=ROOT_SIZE * 2 + HOSTVG_NEED_SIZE=SWAP_SIZE + CONFIG_SIZE + LOGGING_SIZE + min_data_size + drive_space_dict["ROOTDRIVESPACE"] = ROOTDRIVESPACE + drive_space_dict["ROOT_NEED_SIZE"] = ROOT_NEED_SIZE + drive_space_dict["HOSTVGDRIVESPACE"] = HOSTVGDRIVESPACE + drive_space_dict["HOSTVG_NEED_SIZE"] = HOSTVG_NEED_SIZE + + if ROOTDRIVE == HOSTVGDRIVE: + drive_list.append("ROOT") + ROOT_NEED_SIZE=ROOT_SIZE * 2 + HOSTVG_NEED_SIZE + drive_space_dict["ROOT_NEED_SIZE"] = ROOT_NEED_SIZE + else: + drive_list.append("ROOT") + drive_list.append("HOSTVG") + + for drive in drive_list: + drive_need_size = drive_space_dict["drive" + "NEED_SIZE"] + drive_disk_size= drive_space_dict["drive" + "DRIVESPACE"] + + if drive_need_size > drive_disk_size: + gap_size = drive_need_size - drive_disk_size + log("\n") + log("=============================================================\n") + log("The target storage device is too small for the desired sizes:\n") + log(" Disk Target: $drive \n") + log(" Size of target storage device: $drive_disk_size MB\n") + log(" Total storage size to be used: $drive_need_size MB\n") + log("\n") + log("You need an additional $gap_size MB of storage.\n") + log("\n") + sys.exit(1) + else: + log("Required Space : $drive_need_size MB\n\n") + + +#def get_drive_size(drive): +# size_cmd = "sfdisk -s \"%s\" 2>null)" % drive +# size = subprocess.Popen(size_cmd, shell=True, stdout=PIPE, stderr=STDOUT) +# size_output = size.stdout.read() +# size_output = size_output / 1024 +# log("%s (%s MB)") % (drive, size_output) +# if not space_var is None: +# return space_var + +def check_existing_hostvg(install_dev): + if install_dev is None: + devices="$(pvs --separator=\" \" -o pv_name,vg_name --noheadings | grep \"HostVG\" | cut -f1)" + else: + devices="$(pvs --separator=\" \" -o pv_name,vg_name --noheadings | grep -v \"%s\" | grep \"HostVG\" | cut -f1)" % install_dev + if devices is not None: + log("\n") + log("There appears to already be an installation on another device:\n") + for device in devices.split(): + log("\t%s\n") % device + log("The installation cannot proceed until the device is removed\n") + log("from the system of the HostVG volume group is removed.\n") + return devices + +#ROOTDRIVE = "/dev/sda" +def create_hostvg(): + BOOTDRIVE = "" + log("Creating LVM partition") + global ROOTDRIVE + global HOSTVGDRIVE + global BOOTDRIVE + global RootBackup_end + if ROOTDRIVE == HOSTVGDRIVE: + parted_cmd = "parted %s -s \"mkpart primary ext2 %sM -1\"" % (HOSTVGDRIVE, RootBackup_end) + os.system(parted_cmd) + hostvgpart="3" + elif BOOTDRIVE == HOSTVGDRIVE: + parted_cmd = "parted %s -s \"mkpart primary ext2 %s -1\"" % (HOSTVGDRIVE, boot_size_si) + os.system(parted_cmd) + hostvgpart="2" + ROOTDRIVE = BOOTDRIVE + else: + os.system("parted \"%s\" -s \"mkpart primary ext2 0M -1") % HOSTVGDRIVE + hostvgpart = "1" + log("Toggling LVM on") + parted_cmd = "parted \"%s\" -s \"set %s lvm on\"" % (HOSTVGDRIVE, hostvgpart) + os.system(parted_cmd) + os.system("parted \"%s\" -s \"print\"") % ROOTDRIVE + os.system("udevadm settle 2> /dev/null || udevsettle") + reread_partitions(HOSTVGDRIVE) + + # sync GPT to the legacy MBR partitions + if OVIRT_VARS.has_key["OVIRT_INSTALL_ROOT"] and OVIRT_VARS["OVIRT_INSTALL_ROOT"] == "y" : + if self.LABEL_TYPE == "gpt": + log("Running gptsync to create legacy mbr") + os.system("gptsync \"%s\"") % ROOTDRIVE + + partpv = HOSTVGDRIVE + hostvgpart + if not os.path.exists(partpv): + # e.g. /dev/cciss/c0d0p2 + partpv = HOSTVGDRIVE + "p" + hostvgpart + log("Creating physical volume") + if not os.path.exists(partpv): + log("%s is not available!") % partpv + sys.exit(1) + os.system("dd if=/dev/zero of=\"%s\" bs=1024k count=1") % partpv + os.system("pvcreate -ff -y \"%s\"") % partpv + log("Creating volume group") + os.system("vgcreate /dev/HostVG \"%s\"") % partpv + + if SWAP_SIZE > 0: + log("Creating swap partition") + os.system("lvcreate --name Swap --size %sM /dev/HostVG") % SWAP_SIZE + os.system("mkswap -L \"SWAP\" /dev/HostVG/Swap") + os.system("echo \"/dev/HostVG/Swap swap swap defaults 0 0\" >> /etc/fstab") + if CONFIG_SIZE > 0: + log("Creating config partition") + os.system("lvcreate --name Config --size %sM /dev/HostVG") % CONFIG_SIZE + os.system("mke2fs -j /dev/HostVG/Config -L \"CONFIG\"") + os.system("tune2fs -c 0 -i 0 /dev/HostVG/Config") + if LOGGING_SIZE > 0: + log("Creating log partition") + os.system("lvcreate --name Logging --size %sM /dev/HostVG") % LOGGING_SIZE + os.system("mke2fs -j /dev/HostVG/Logging -L \"LOGGING\"") + os.system("tune2fs -c 0 -i 0 /dev/HostVG/Logging") + os.system("echo \"/dev/HostVG/Logging /var/log ext3 defaults,noatime 0 0\" >> /etc/fstab") + use_data=1 + if DATA_SIZE == -1: + log("Creating data partition with remaining free space") + os.system("lvcreate --name Data -l 100%FREE /dev/HostVG") + use_data=0 + elif DATA_SIZE > 0: + log("Creating data partition") + os.system("lvcreate --name Data --size %sM /dev/HostVG") % DATA_SIZE + use_data=0 + + if use_data == 0: + os.system("mke2fs -j /dev/HostVG/Data -L \"DATA\"") + os.system("tune2fs -c 0 -i 0 /dev/HostVG/Data") + os.system("echo \"/dev/HostVG/Data /data ext3 defaults,noatime 0 0\" >> /etc/fstab") + os.system("echo \"/data/images /var/lib/libvirt/images bind bind 0 0\" >> /etc/fstab") + os.system("echo \"/data/core /var/log/core bind bind 0 0\" >> /etc/fstab") + + log("Mounting config partition") + if mount_config: + ovirt_store_config /etc/fstab + + mount_logging + if use_data == 0: + log("Mounting data partition") + mount_data + log("Completed!") + + +def perform_partitioning(): + if HOSTVGDRIVE is None and OVIRT_VARS["OVIRT_ISCSI_ENABLED"] != "y": + log("\nNo storage device selected.\n") + return False + + if BOOTDRIVE is None and OVIRT_VARS["OVIRT_ISCSI_ENABLED"] == "y": + log("\nNo storage device selected.\n") + return False + + log("Saving parameters") + unmount_config("/etc/default/ovirt") + + log("Removing old LVM partitions") + wipe_volume_group("HostVG") + wipe_lvm_on_disk(HOSTVGDRIVE) + wipe_lvm_on_disk(ROOTDRIVE) + + mem_size_cmd = "awk '/MemTotal:/ { print $2 }' /proc/meminfo" + mem_size_mb = subprocess.Popen(mem_size_cmd, shell=True, stdout=PIPE, stderr=STDOUT) + MEM_SIZE_MB = int(mem_size_mb.stdout.read()) + + MEM_SIZE_MB = MEM_SIZE_MB / 1024 + boot_size_si = BOOT_SIZE * (1024 * 1024) / (1000 * 1000) + + if OVIRT_VARS.has_key("OVIRT_ISCSI_ENABLED") and OVIRT_VARS["OVIRT_ISCSI_ENABLED"] == "y": + log("iSCSI enabled, partitioning boot drive: $BOOTDRIVE") + wipe_partitions(BOOTDRIVE) + reread_partitions(BOOTDRIVE) + log("Creating boot partition") + os.system("parted \"%s\" -s \"mklabel %s\"") % (BOOTDRIVE, LABEL_TYPE) + os.system("parted \"%s\" -s \"mkpartfs primary ext2 0M %sM") % (BOOTDRIVE, boot_size_si) + reread_partitions(BOOTDRIVE) + partboot= BOOTDRIVE + "1" + if not os.path.exists(partboot): + partboot = BOOTDRIVE + "p1" + # sleep to ensure filesystems are created before continuing + time.sleep("10") + os.system("mke2fs \"%s\" -L Boot") % partboot + os.system("tune2fs -c 0 -i 0 %s") % partboot + if OVIRT_VARS["OVIRT_ISCSI_HOSTVG"] == "y": + create_hostvg() + log("Completed!") + return + + if OVIRT_VARS.has_key("OVIRT_ROOT_INSTALL") and OVIRT_VARS["OVIRT_ROOT_INSTALL"] == "y": + log("Partitioning root drive: %s") % ROOTDRIVE + wipe_partitions(ROOTDRIVE) + reread_partitions(ROOTDRIVE) + log("Labeling Drive: %s") % ROOTDRIVE + os.system("parted \"%s\" -s \"mklabel %s\"") % (ROOTDRIVE, LABEL_TYPE) + log("Creating Root and RootBackup Partitions") + RootBackup_end= ROOT_SIZE * 2 + os.system("parted \"%s\" -s \"mkpart primary ext2 0M %sM\"") % (ROOTDRIVE, ROOT_SIZE) + os.system("parted \"%s\" -s \"mkpart primary ext2 %sM %sM\"") % (ROOTDRIVE, ROOT_SIZE, RootBackup_end) + # sleep to ensure filesystems are created before continuing + time.sleep("10") + reread_partitions(ROOTDRIVE) + partroot = ROOTDRIVE + "1" + partrootbackup = ROOTDRIVE + "2" + if not os.path.exists(partroot): + partroot = ROOTDRIVE + "p1" + partrootbackup= ROOTDRIVE + "p2" + os.system("mke2fs \"%s\" -L Root") % partroot + os.system("mke2fs \"%s\" -L RootBackup") % partrootbackup + os.system("tune2fs -c 0 -i 0 \"%s\"") % partroot + os.system("tune2fs -c 0 -i 0 \"%s\"") % partrootbackup + + if ROOTDRIVE != HOSTVGDRIVE: + log("Labeling Drive: %s") % abc #HOSTVGDRIVE + os.system("parted \"%s\" -s \"mklabel %s\"") % (HOSTVGDRIVE, LABEL_TYPE) + create_hostvg() -- 1.7.2.3
Maybe Matching Threads
- [PATCH node] Enables stateless iscsi remote boot
- [PATCH] RFC: Advanced Storage Configuration
- [PATCH node] Handle space in storage wwid
- [PATCH node] iscsi remote root basework This lays most of the groundwork for iscsi installation and configuration. At this time configuring iscsi is disabled due to multiple issues with dependent pieces.
- [PATCH][node REPOST] Improve performance of multipath translations