From jboggs at redhat.com Thu Oct 1 01:27:57 2009 From: jboggs at redhat.com (Joey Boggs) Date: Wed, 30 Sep 2009 21:27:57 -0400 Subject: [Ovirt-devel] [PATCH node 6/6] Added new menu for Multipath configuration In-Reply-To: <1253932438-24628-8-git-send-email-mburns@redhat.com> References: <20090926023338.GA24661@mburns-laptop.bos.redhat.com> <1253932438-24628-8-git-send-email-mburns@redhat.com> Message-ID: <4AC4059D.1080603@redhat.com> Michael Burns wrote: > Also added new kernel command line arg to setup multipath. > New menu takes user through mpath whitelisting and choosing an > mpath device for installation. > > Signed-off-by: Michael Burns > --- > scripts/ovirt-config-boot | 2 +- > scripts/ovirt-config-storage | 188 +++++++++++++++++++++++++++++++++++++++- > scripts/ovirt-config-uninstall | 8 ++ > scripts/ovirt-early | 16 +++- > 4 files changed, 207 insertions(+), 7 deletions(-) > > diff --git a/scripts/ovirt-config-boot b/scripts/ovirt-config-boot > index 87cf832..ed2bb9f 100755 > --- a/scripts/ovirt-config-boot > +++ b/scripts/ovirt-config-boot > @@ -218,7 +218,7 @@ set -e\ > cp /sbin/dmsetup.static sbin/dmsetup > cp /sbin/kpartx.static sbin/kpartx > for M in /sbin/mpath_prio_*.static ; do > - cp ${M} $/${M%%.static}; > + cp ${M} .${M%%.static}; > done > fi > > diff --git a/scripts/ovirt-config-storage b/scripts/ovirt-config-storage > index 91662c5..c448aee 100755 > --- a/scripts/ovirt-config-storage > +++ b/scripts/ovirt-config-storage > @@ -130,6 +130,172 @@ check_partition_sizes() > return $rc > } > > +is_multipath_disabled() > +{ > + grep -q "^ *devnode \"\*\"" /etc/multipath.conf > +} > + > +multipath_warn() > +{ > + sp=' ' > + w='!!WARNING' > + wb="$w"'!!' > + w8="$w$w$w$w$w$w$w$w" > + printf '%s!!\n' \ > + "$w8" \ > + "$w8" \ > + "$wb$sp$w" \ > + "$wb$sp$w" \ > + "$wb Multipath Configurations should only be made $w" \ > + "$wb on true multipath devices. Configuring any $w" \ > + "$wb other devices may cause problems with your $w" \ > + "$wb system. $w" \ > + "$wb$sp$w" \ > + "$wb Proceeding from this menu will erase all $w" \ > + "$wb previous multipath configurations. $w" \ > + "$wb$sp$w" \ > + "$wb$sp$w" \ > + "$w8" \ > + "$w8" > + > +} > + > +abort_multipath() > +{ > + printf "Cleaning up multipath configuration\n">&2 > + sed -i 's/^\s*wwid \*$/ devnode "*"/' /etc/multipath.conf > + sed -i 's/^\s*wwid \w.*$//g' /etc/multipath.conf > + service multipathd reload >&2 > +} > + > +config_multipath_conf() > +{ > + ! is_multipath_disabled && abort_multipath > + if [[ $OVIRT_MPATH = "ALL" ]]; then > + sed -i 's/^\s*devnode ".*$/ #devnode "*"/' /etc/multipath.conf > + else > + sed -i 's/^\s*devnode ".*$/ wwid */' /etc/multipath.conf > + test $(grep ^blacklist_exceptions /etc/multipath.conf | wc -l) = 0 \ > + && printf "blacklist_exceptions {\n}" >> /etc/multipath.conf > + local wwid="" > + for d in $whitelisted > + do > + d=$(basename $d) > + local scsi_id=$(scsi_id --whitelisted --device=/dev/$d) > + if [[ -z $scsi_id ]]; then > + echo "scsi_id failed for device /dev/$d. Not white listing." >&2 > + else > + wwid="${wwid} $scsi_id" > + fi > + done > + wwid=$(echo $wwid | sed 's/ /\n/g' | sort -u) > + for scsi_id in $wwid > + do > + sed -i "s/^blacklist_exceptions {$/blacklist_exceptions {\n wwid $scsi_id"/ /etc/multipath.conf > + done > + for d in $OVIRT_MPATH > + do > + sed -i "s/^blacklist_exceptions {$/blacklist_exceptions {\n wwid $d"/ /etc/multipath.conf > + > + done > + fi > + service multipathd reload >&2 > + start_log > + echo "Generated multipath.conf" > + cat /etc/multipath.conf > + echo "Output of multiatph -v6 command" > + multipath -v6 > + stop_log > + return 0 > +} > + > + > +generate_whitelist() > +{ > + whitelisted="" > + local devs=$devices > + local PS3="Please select devices to add or remove from multipath whitelist: " > + while true; do > + local choices="" > + for d in $devs > + do > + get_drive_size $d >&2 > + if [[ $whitelisted =~ $d ]]; then > + choices="$choices $d-whitelisted" > + else > + choices="$choices $d" > + fi > + done > + > + choices="$choices Done Abort" > + select whitelist in $choices > + do > + test "$whitelist" = Abort && return 1 > + test "$whitelist" = Done && return 0 > + #first check if it is already whitelisted > + #and remove from whitelist > + if [[ $whitelist =~ "whitelisted" ]]; then > + local tmp_whitelist="" > + whitelist=${whitelist%-whitelisted} > + for d in $whitelisted > + do > + test $d != $whitelist && tmp_whitelist="$tmp_whitelist $d" > + done > + whitelisted=$tmp_whitelist > + #else add it to whitelist > + else > + whitelisted="$whitelisted $whitelist" > + fi > + break > + done > + done > +} > + > +# Choose a multipathed device for installation > +get_multipath_device() > +{ > + multipath_warn >&2 > + local DRIVE_VAR=$1 > + generate_whitelist > + test $? = 1 && return 1 > + config_multipath_conf > + local count=0 > + echo "Detecting Multipath Devices..." >&2 > + while [ $count -lt 60 ] > + do > + test ! `ls /dev/mapper/mpath* 2> /dev/null | wc -l` = 0 && break > + let count=$count+1 > + sleep 1 > + done > + if [ `ls /dev/mapper/mpath* 2> /dev/null | wc -l` = 0 ]; then > + echo "No Multipath devices found. Aborting...">&2 > + abort_multipath > + return 1 > + fi > + #need to provide a menu to choose a multipath device > + while true > + do > + local choices="" > + for dev in $(ls /dev/mapper/mpath* | egrep -v "p[0-9]+$") > + do > + choices="$choices $dev" > + get_drive_size $dev >&2 > + done > + choices="$choices Abort" > + local PS3="Select Multipath Device: " > + select mpath_device in $choices > + do > + if [[ $mpath_device = Abort ]]; then > + abort_multipath > + return 1 > + elif [[ -e $mpath_device ]]; then > + eval $DRIVE_VAR=$mpath_device > + return 0 > + fi > + done > + done > +} > + > # 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). > @@ -140,6 +306,7 @@ check_partition_sizes() > get_dev_name() > { > local udi_list=$(hal-find-by-capability --capability storage) > + local DRIVE_VAR=$1 > if test -z "$udi_list"; then > warn "ERROR: no usable storage devices detected" > return 1 > @@ -152,6 +319,9 @@ get_dev_name() > local block_dev=$(hal-get-property --udi "$d" --key block.device) > # Must start with a '/'. > case $block_dev in > + '') > + #if block.device not defined, suppress warning > + continue;; > *' '*) > # we use space as separator > warn "block device name '$block_dev' contains space; skipping"; > @@ -189,12 +359,13 @@ get_dev_name() > for d in $devices; do > get_drive_size $d >&2 > done > - local choices="$devices Abort" > + local choices="$devices Multipath Abort" > select device in $choices > do > test "$device" = Abort && return 1 > + test "$device" = "Multipath" && get_multipath_device device; > test -z "$device" && continue > - echo "$device" > + eval $DRIVE_VAR=$device > return 0 > done > } > > @@ -202,7 +373,9 @@ get_dev_name() > do_configure() > { > local name_and_size > - DRIVE=$(get_dev_name) || return 0 > + get_dev_name DRIVE > + test ! -e $DRIVE && echo "Drive '$DRIVE' is not a valid drive" >&2 \ > + && return 0 > get_drive_size $DRIVE SPACE > > In this section altering DRIVE= kills me trying to use local disks. Took me awhile to track this one down. Here's how to fix it: local num_devices=$(echo "$devices" | wc -w) # If there's only one device, use it. case $num_devices in 0) warn "ERROR: found no usable block device"; return 1;; 1) echo "$devices"; return 0;; *) ;; # found more than one esac set 1) echo "$devices"; return 0;; to 1) eval $DRIVE_VAR=$devices; return0;; > printf "\n\nPlease configure storage partitions.\n\n" > @@ -537,6 +710,15 @@ CONFIG_SIZE=${OVIRT_VOL_CONFIG_SIZE:-$default_config_size} > LOGGING_SIZE=${OVIRT_VOL_LOGGING_SIZE:-$default_logging_size} > DATA_SIZE=${OVIRT_VOL_DATA_SIZE:-$default_data_size} > > +if [ -n "$OVIRT_MPATH" ]; then > + #if present then setup multipath.conf with value > + config_multipath_conf > + while true > + do > + test ! `ls /dev/mapper/mpath* 2> /dev/null | wc -l` = 0 && break > + done > +fi > + > if [ -n "$OVIRT_INIT" ]; then > # if present, use the drive selected with 'ovirt_init' boot parameter > DRIVE=$OVIRT_INIT > diff --git a/scripts/ovirt-config-uninstall b/scripts/ovirt-config-uninstall > index fd65cbb..20f8c2c 100755 > --- a/scripts/ovirt-config-uninstall > +++ b/scripts/ovirt-config-uninstall > @@ -47,6 +47,14 @@ if ask_yes_or_no "Do you wish to continue and uninstall this node ([Y]es/[N]o)?" > log "Unmounting boot partition" > umount $partition > log "Removing partitions" > + eval $(echo $partition | awk ' { > + print "drive=" substr($0,1,length($1)-1); > + print "drive2=" substr($0,1,length($1)-2); > + }') > + if [ ! -e "$drive" ]; then > + # e.g. c0d0p1 > + drive="$drive2" > + fi > drive=$(echo $partition | awk '{ print substr($0, 1, length($0) - 1) }') > parted -s $drive "rm 1" > parted -s $drive "rm 2" > diff --git a/scripts/ovirt-early b/scripts/ovirt-early > index 8990727..5632fb0 100755 > --- a/scripts/ovirt-early > +++ b/scripts/ovirt-early > @@ -148,6 +148,7 @@ start() { > # collectd=server[:port] > # hostname=fqdn > # TBD logrotate maxsize > + # mpath format: mpath=wwid:[wwid] > > # BOOTIF=link|eth*| (appended by pxelinux) > # network boot interface is assumed to be on management network where > @@ -236,6 +237,12 @@ start() { > collectd_server= > collectd_port= > > + # mpath=wwid:[wwid...] > + # set a list of comma or colon separated wwids to allow > + # for multipath install > + # Specify ALL to use all devices > + mpath= > + > # save boot parameters like console= for local disk boot menu > bootparams= > cat /etc/system-release >> $OVIRT_LOGFILE > @@ -286,8 +293,7 @@ start() { > =/dev/*) > bus= > serial= > - i=${i#=} > - init=$(ls -1 "$i" 2>/dev/null | head -n1) > + init=${i#=} > ;; > *) > bus= > @@ -374,6 +380,10 @@ start() { > console=*) > bootparams="$bootparams $i" > ;; > + mpath=*) > + i=${i#mpath=} > + mpath=$(echo $i|tr ",:;" " ") > + ;; > esac > done > > @@ -384,7 +394,7 @@ start() { > ip_gateway=$gateway > fi > # save boot parameters as defaults for ovirt-config-* > - params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan ssh_pwauth syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot" > + params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan ssh_pwauth syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot mpath" > # mount /config unless firstboot is forced > if [ "$firstboot" != "1" ]; then > mount_config > From acathrow at redhat.com Thu Oct 1 01:48:13 2009 From: acathrow at redhat.com (Andrew Cathrow) Date: Wed, 30 Sep 2009 21:48:13 -0400 (EDT) Subject: [Ovirt-devel] [PATCH node 6/6] Added new menu for Multipath configuration In-Reply-To: <4AC4059D.1080603@redhat.com> Message-ID: <32053749.141254361698637.JavaMail.javamailuser@localhost> so if we're using blacklist exceptions (aka whitelist) then anytime a new lun is provisioned we'd have to edit the multipath configuration. Is that something that we're going to automate in ovirt? Aic ----- "Joey Boggs" wrote: > Michael Burns wrote: > > Also added new kernel command line arg to setup multipath. > > New menu takes user through mpath whitelisting and choosing an > > mpath device for installation. > > > > Signed-off-by: Michael Burns > > --- > > scripts/ovirt-config-boot | 2 +- > > scripts/ovirt-config-storage | 188 +++++++++++++++++++++++++++++++++++++++- > > scripts/ovirt-config-uninstall | 8 ++ > > scripts/ovirt-early | 16 +++- > > 4 files changed, 207 insertions(+), 7 deletions(-) > > > > diff --git a/scripts/ovirt-config-boot b/scripts/ovirt-config-boot > > index 87cf832..ed2bb9f 100755 > > --- a/scripts/ovirt-config-boot > > +++ b/scripts/ovirt-config-boot > > @@ -218,7 +218,7 @@ set -e\ > > cp /sbin/dmsetup.static sbin/dmsetup > > cp /sbin/kpartx.static sbin/kpartx > > for M in /sbin/mpath_prio_*.static ; do > > - cp ${M} $/${M%%.static}; > > + cp ${M} .${M%%.static}; > > done > > fi > > > > diff --git a/scripts/ovirt-config-storage b/scripts/ovirt-config-storage > > index 91662c5..c448aee 100755 > > --- a/scripts/ovirt-config-storage > > +++ b/scripts/ovirt-config-storage > > @@ -130,6 +130,172 @@ check_partition_sizes() > > return $rc > > } > > > > +is_multipath_disabled() > > +{ > > + grep -q "^ *devnode \"\*\"" /etc/multipath.conf > > +} > > + > > +multipath_warn() > > +{ > > + sp=' ' > > + w='!!WARNING' > > + wb="$w"'!!' > > + w8="$w$w$w$w$w$w$w$w" > > + printf '%s!!\n' \ > > + "$w8" \ > > + "$w8" \ > > + "$wb$sp$w" \ > > + "$wb$sp$w" \ > > + "$wb Multipath Configurations should only be made $w" \ > > + "$wb on true multipath devices. Configuring any $w" \ > > + "$wb other devices may cause problems with your $w" \ > > + "$wb system. $w" \ > > + "$wb$sp$w" \ > > + "$wb Proceeding from this menu will erase all $w" \ > > + "$wb previous multipath configurations. $w" \ > > + "$wb$sp$w" \ > > + "$wb$sp$w" \ > > + "$w8" \ > > + "$w8" > > + > > +} > > + > > +abort_multipath() > > +{ > > + printf "Cleaning up multipath configuration\n">&2 > > + sed -i 's/^\s*wwid \*$/ devnode "*"/' /etc/multipath.conf > > + sed -i 's/^\s*wwid \w.*$//g' /etc/multipath.conf > > + service multipathd reload >&2 > > +} > > + > > +config_multipath_conf() > > +{ > > + ! is_multipath_disabled && abort_multipath > > + if [[ $OVIRT_MPATH = "ALL" ]]; then > > + sed -i 's/^\s*devnode ".*$/ #devnode "*"/' /etc/multipath.conf > > + else > > + sed -i 's/^\s*devnode ".*$/ wwid */' /etc/multipath.conf > > + test $(grep ^blacklist_exceptions /etc/multipath.conf | wc -l) = 0 \ > > + && printf "blacklist_exceptions {\n}" >> /etc/multipath.conf > > + local wwid="" > > + for d in $whitelisted > > + do > > + d=$(basename $d) > > + local scsi_id=$(scsi_id --whitelisted --device=/dev/$d) > > + if [[ -z $scsi_id ]]; then > > + echo "scsi_id failed for device /dev/$d. Not white listing." >&2 > > + else > > + wwid="${wwid} $scsi_id" > > + fi > > + done > > + wwid=$(echo $wwid | sed 's/ /\n/g' | sort -u) > > + for scsi_id in $wwid > > + do > > + sed -i "s/^blacklist_exceptions {$/blacklist_exceptions {\n wwid $scsi_id"/ /etc/multipath.conf > > + done > > + for d in $OVIRT_MPATH > > + do > > + sed -i "s/^blacklist_exceptions {$/blacklist_exceptions {\n wwid $d"/ /etc/multipath.conf > > + > > + done > > + fi > > + service multipathd reload >&2 > > + start_log > > + echo "Generated multipath.conf" > > + cat /etc/multipath.conf > > + echo "Output of multiatph -v6 command" > > + multipath -v6 > > + stop_log > > + return 0 > > +} > > + > > + > > +generate_whitelist() > > +{ > > + whitelisted="" > > + local devs=$devices > > + local PS3="Please select devices to add or remove from multipath whitelist: " > > + while true; do > > + local choices="" > > + for d in $devs > > + do > > + get_drive_size $d >&2 > > + if [[ $whitelisted =~ $d ]]; then > > + choices="$choices $d-whitelisted" > > + else > > + choices="$choices $d" > > + fi > > + done > > + > > + choices="$choices Done Abort" > > + select whitelist in $choices > > + do > > + test "$whitelist" = Abort && return 1 > > + test "$whitelist" = Done && return 0 > > + #first check if it is already whitelisted > > + #and remove from whitelist > > + if [[ $whitelist =~ "whitelisted" ]]; then > > + local tmp_whitelist="" > > + whitelist=${whitelist%-whitelisted} > > + for d in $whitelisted > > + do > > + test $d != $whitelist && tmp_whitelist="$tmp_whitelist $d" > > + done > > + whitelisted=$tmp_whitelist > > + #else add it to whitelist > > + else > > + whitelisted="$whitelisted $whitelist" > > + fi > > + break > > + done > > + done > > +} > > + > > +# Choose a multipathed device for installation > > +get_multipath_device() > > +{ > > + multipath_warn >&2 > > + local DRIVE_VAR=$1 > > + generate_whitelist > > + test $? = 1 && return 1 > > + config_multipath_conf > > + local count=0 > > + echo "Detecting Multipath Devices..." >&2 > > + while [ $count -lt 60 ] > > + do > > + test ! `ls /dev/mapper/mpath* 2> /dev/null | wc -l` = 0 && break > > + let count=$count+1 > > + sleep 1 > > + done > > + if [ `ls /dev/mapper/mpath* 2> /dev/null | wc -l` = 0 ]; then > > + echo "No Multipath devices found. Aborting...">&2 > > + abort_multipath > > + return 1 > > + fi > > + #need to provide a menu to choose a multipath device > > + while true > > + do > > + local choices="" > > + for dev in $(ls /dev/mapper/mpath* | egrep -v "p[0-9]+$") > > + do > > + choices="$choices $dev" > > + get_drive_size $dev >&2 > > + done > > + choices="$choices Abort" > > + local PS3="Select Multipath Device: " > > + select mpath_device in $choices > > + do > > + if [[ $mpath_device = Abort ]]; then > > + abort_multipath > > + return 1 > > + elif [[ -e $mpath_device ]]; then > > + eval $DRIVE_VAR=$mpath_device > > + return 0 > > + fi > > + done > > + done > > +} > > + > > # 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). > > @@ -140,6 +306,7 @@ check_partition_sizes() > > get_dev_name() > > { > > local udi_list=$(hal-find-by-capability --capability storage) > > + local DRIVE_VAR=$1 > > if test -z "$udi_list"; then > > warn "ERROR: no usable storage devices detected" > > return 1 > > @@ -152,6 +319,9 @@ get_dev_name() > > local block_dev=$(hal-get-property --udi "$d" --key block.device) > > # Must start with a '/'. > > case $block_dev in > > + '') > > + #if block.device not defined, suppress warning > > + continue;; > > *' '*) > > # we use space as separator > > warn "block device name '$block_dev' contains space; skipping"; > > @@ -189,12 +359,13 @@ get_dev_name() > > for d in $devices; do > > get_drive_size $d >&2 > > done > > - local choices="$devices Abort" > > + local choices="$devices Multipath Abort" > > select device in $choices > > do > > test "$device" = Abort && return 1 > > + test "$device" = "Multipath" && get_multipath_device device; > > test -z "$device" && continue > > - echo "$device" > > + eval $DRIVE_VAR=$device > > return 0 > > done > > } > > > > > @@ -202,7 +373,9 @@ get_dev_name() > > do_configure() > > { > > local name_and_size > > - DRIVE=$(get_dev_name) || return 0 > > + get_dev_name DRIVE > > + test ! -e $DRIVE && echo "Drive '$DRIVE' is not a valid drive" >&2 \ > > + && return 0 > > get_drive_size $DRIVE SPACE > > > > > In this section altering DRIVE= kills me trying to use local disks. > Took me awhile to track this one down. Here's how to fix it: > > > local num_devices=$(echo "$devices" | wc -w) > # If there's only one device, use it. > case $num_devices in > 0) warn "ERROR: found no usable block device"; return 1;; > 1) echo "$devices"; return 0;; > *) ;; # found more than one > esac > > > set > 1) echo "$devices"; return 0;; > to > 1) eval $DRIVE_VAR=$devices; return0;; > > > > > > > printf "\n\nPlease configure storage partitions.\n\n" > > @@ -537,6 +710,15 @@ CONFIG_SIZE=${OVIRT_VOL_CONFIG_SIZE:-$default_config_size} > > LOGGING_SIZE=${OVIRT_VOL_LOGGING_SIZE:-$default_logging_size} > > DATA_SIZE=${OVIRT_VOL_DATA_SIZE:-$default_data_size} > > > > +if [ -n "$OVIRT_MPATH" ]; then > > + #if present then setup multipath.conf with value > > + config_multipath_conf > > + while true > > + do > > + test ! `ls /dev/mapper/mpath* 2> /dev/null | wc -l` = 0 && break > > + done > > +fi > > + > > if [ -n "$OVIRT_INIT" ]; then > > # if present, use the drive selected with 'ovirt_init' boot parameter > > DRIVE=$OVIRT_INIT > > diff --git a/scripts/ovirt-config-uninstall b/scripts/ovirt-config-uninstall > > index fd65cbb..20f8c2c 100755 > > --- a/scripts/ovirt-config-uninstall > > +++ b/scripts/ovirt-config-uninstall > > @@ -47,6 +47,14 @@ if ask_yes_or_no "Do you wish to continue and uninstall this node ([Y]es/[N]o)?" > > log "Unmounting boot partition" > > umount $partition > > log "Removing partitions" > > + eval $(echo $partition | awk ' { > > + print "drive=" substr($0,1,length($1)-1); > > + print "drive2=" substr($0,1,length($1)-2); > > + }') > > + if [ ! -e "$drive" ]; then > > + # e.g. c0d0p1 > > + drive="$drive2" > > + fi > > drive=$(echo $partition | awk '{ print substr($0, 1, length($0) - 1) }') > > parted -s $drive "rm 1" > > parted -s $drive "rm 2" > > diff --git a/scripts/ovirt-early b/scripts/ovirt-early > > index 8990727..5632fb0 100755 > > --- a/scripts/ovirt-early > > +++ b/scripts/ovirt-early > > @@ -148,6 +148,7 @@ start() { > > # collectd=server[:port] > > # hostname=fqdn > > # TBD logrotate maxsize > > + # mpath format: mpath=wwid:[wwid] > > > > # BOOTIF=link|eth*| (appended by pxelinux) > > # network boot interface is assumed to be on management network where > > @@ -236,6 +237,12 @@ start() { > > collectd_server= > > collectd_port= > > > > + # mpath=wwid:[wwid...] > > + # set a list of comma or colon separated wwids to allow > > + # for multipath install > > + # Specify ALL to use all devices > > + mpath= > > + > > # save boot parameters like console= for local disk boot menu > > bootparams= > > cat /etc/system-release >> $OVIRT_LOGFILE > > @@ -286,8 +293,7 @@ start() { > > =/dev/*) > > bus= > > serial= > > - i=${i#=} > > - init=$(ls -1 "$i" 2>/dev/null | head -n1) > > + init=${i#=} > > ;; > > *) > > bus= > > @@ -374,6 +380,10 @@ start() { > > console=*) > > bootparams="$bootparams $i" > > ;; > > + mpath=*) > > + i=${i#mpath=} > > + mpath=$(echo $i|tr ",:;" " ") > > + ;; > > esac > > done > > > > @@ -384,7 +394,7 @@ start() { > > ip_gateway=$gateway > > fi > > # save boot parameters as defaults for ovirt-config-* > > - params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan ssh_pwauth syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot" > > + params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan ssh_pwauth syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot mpath" > > # mount /config unless firstboot is forced > > if [ "$firstboot" != "1" ]; then > > mount_config > > > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > -------------- next part -------------- An HTML attachment was scrubbed... URL: From ignazio.cassano at provincia.torino.it Thu Oct 1 06:15:04 2009 From: ignazio.cassano at provincia.torino.it (ignazio.cassano at provincia.torino.it) Date: Thu, 01 Oct 2009 08:15:04 +0200 Subject: [Ovirt-devel] Problems with creating VM In-Reply-To: <707774A2-9B40-48ED-A086-F34EF4EA5F6F@gmail.com> References: <8A455D70-2E33-43B3-99C9-0D95218A70E1@gmail.com> <2be7262f0909300855p6e4c93f4rc173485f087d6f19@mail.gmail.com> <707774A2-9B40-48ED-A086-F34EF4EA5F6F@gmail.com> Message-ID: <20091001081504.3lrzu0kzta9cocwo@webmail.provincia.torino.it> Login root without password. Yum must login only to reboot the node. Configuration steps must me executed using web interface ----- Messaggio da pavel.sab at gmail.com --------- Data: Wed, 30 Sep 2009 19:00:32 +0300 Da: Pavel Sabirjanov Rispondi-A:Pavel Sabirjanov Oggetto: Re: [Ovirt-devel] Problems with creating VM A: Alan Pevec Cc: ovirt-devel at redhat.com > How can i login in ovirt node? > I don't know what login and password should i use... > I'm new to this :) > > node52 login: > > > 30 ????. 2009, ? 18:55, Alan Pevec ???????(?): > >> On Wed, Sep 30, 2009 at 4:47 PM, Pavel Sabirjanov >> wrote: >>> In oVirt interface Tasks log: >>> Error creating virtual machine: Error: Error creating new domain >>> (virDomainCreate). Subsystem qemu: internal error unable to start guest: >>> bind(unix:/var/lib/libvirt/qemu/test.monitor): Permission denied >>> qemu: could >>> not open monitor device >>> 'unix:/var/lib/libvirt/qemu/test.monitor,server,nowait' in >>> DomainWrap.cpp:ManagementMethod:115 >>> What rights should be on /var/lib/libvirt/qemu/ ? >>> Default is: >>> drwx------. 2 root root 4096 2009-08-19 20:16 quemu >> >> It's selinux issue but it should fixed in selinux-policy-3.6.12-83 >> Which oVirt Node version do you have - could you send >> srpm-manifest.txt or check rpm -q selinux-policy on the running node? >> >> Alan > > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel ----- Fine del messaggio da pavel.sab at gmail.com ----- From ignazio.cassano at provincia.torino.it Thu Oct 1 06:31:32 2009 From: ignazio.cassano at provincia.torino.it (ignazio.cassano at provincia.torino.it) Date: Thu, 01 Oct 2009 08:31:32 +0200 Subject: [Ovirt-devel] Storage pool error Message-ID: <20091001083132.g1gqwz133q8gwckk@webmail.provincia.torino.it> Hello, I cannot install vm because dtorage disk are read only. On the node in the /var/log/message file the following error appears: node100 libvirtd: 06:26:57.511: error : storagePoolRefresh:813 : internal error storage pool is not active From ignazio.cassano at provincia.torino.it Thu Oct 1 07:01:50 2009 From: ignazio.cassano at provincia.torino.it (ignazio.cassano at provincia.torino.it) Date: Thu, 01 Oct 2009 09:01:50 +0200 Subject: [Ovirt-devel] Storage pool error In-Reply-To: <20091001083132.g1gqwz133q8gwckk@webmail.provincia.torino.it> References: <20091001083132.g1gqwz133q8gwckk@webmail.provincia.torino.it> Message-ID: <20091001090150.547kbv6tj63o8sww@webmail.provincia.torino.it> hello, further information about the problem: on the note in the /etc/libvirt/storage directory there is no autostart directory ----- Messaggio da ignazio.cassano at provincia.torino.it --------- Data: Thu, 01 Oct 2009 08:31:32 +0200 Da: ignazio.cassano at provincia.torino.it Rispondi-A:ignazio.cassano at provincia.torino.it Oggetto: [Ovirt-devel] Storage pool error A: ovirt-devel at redhat.com > Hello, > I cannot install vm because dtorage disk are read only. > On the node in the /var/log/message file the following error appears: > node100 libvirtd: 06:26:57.511: error : storagePoolRefresh:813 : > internal error storage pool is not active > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel ----- Fine del messaggio da ignazio.cassano at provincia.torino.it ----- From ignazio.cassano at provincia.torino.it Thu Oct 1 10:58:14 2009 From: ignazio.cassano at provincia.torino.it (ignazio.cassano at provincia.torino.it) Date: Thu, 01 Oct 2009 12:58:14 +0200 Subject: [Ovirt-devel] nfs storage issue ? In-Reply-To: <20090930185848.pez82nfkhl44ckcc@webmail.provincia.torino.it> References: <20090930185848.pez82nfkhl44ckcc@webmail.provincia.torino.it> Message-ID: <20091001125814.0ep913lg0jk4gkc8@webmail.provincia.torino.it> I tried to setup an nfs storage only for iso cd files and an iscsi storage for disk images and it works. ----- Messaggio da ignazio.cassano at provincia.torino.it --------- Data: Wed, 30 Sep 2009 18:58:48 +0200 Da: ignazio.cassano at provincia.torino.it Rispondi-A:ignazio.cassano at provincia.torino.it Oggetto: [Ovirt-devel] nfs storage issue ? A: ovirt-devel at redhat.com > Hello , I havve successfully attacched a nfs storage (its address is > 192.168.1.150). > Sometimes when I refresh it from ovirt web interface a timeout error is > displayed. > On node the directory /mnt/192.168.1.150-_ovirt is empty but on the nfs > server there are a lot of files. > In this situation when I try to install a vm, it sees the disk but is > not able to write on it. > My nfs exports options are: > > /ovirt *(rw,no_root_squash,async) > > Where is the problem ? > > Thanks > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel ----- Fine del messaggio da ignazio.cassano at provincia.torino.it ----- From ignazio.cassano at provincia.torino.it Thu Oct 1 11:00:16 2009 From: ignazio.cassano at provincia.torino.it (ignazio.cassano at provincia.torino.it) Date: Thu, 01 Oct 2009 13:00:16 +0200 Subject: [Ovirt-devel] ovirt-viewer black screen Message-ID: <20091001130016.kjfxgpvypqw0ccks@webmail.provincia.torino.it> I am trying to see virtual machine display with ovirt viewer but it displays a black screen. I am sure vm is running infact I can display it with vncviewer. Any suggestion ? Thanks Ignazio From pavel.sab at gmail.com Thu Oct 1 11:16:24 2009 From: pavel.sab at gmail.com (Pavel Sabirjanov) Date: Thu, 1 Oct 2009 14:16:24 +0300 Subject: [Ovirt-devel] oVirt in production Message-ID: <242C2600-6567-43C0-983A-AED6D9B10DC2@gmail.com> Hi. Currently i have about 20 servers with Intel-VT/AMD virtualization technology. I want to offer VPS services. Can i use oVirt for that? Or oVirt is still in a deep development? WBR, Pavel From mburns at redhat.com Thu Oct 1 15:25:08 2009 From: mburns at redhat.com (Mike Burns) Date: Thu, 1 Oct 2009 11:25:08 -0400 Subject: [Ovirt-devel] [PATCH node 6/6] Added new menu for Multipath configuration In-Reply-To: <4AC4059D.1080603@redhat.com> References: <20090926023338.GA24661@mburns-laptop.bos.redhat.com> <1253932438-24628-8-git-send-email-mburns@redhat.com> <4AC4059D.1080603@redhat.com> Message-ID: <20091001152508.GB12588@mburns-laptop.bos.redhat.com> On Wed, Sep 30, 2009 at 09:27:57PM -0400, Joey Boggs wrote: > Michael Burns wrote: >> Also added new kernel command line arg to setup multipath. >> New menu takes user through mpath whitelisting and choosing an >> mpath device for installation. >> >> Signed-off-by: Michael Burns >> --- >> scripts/ovirt-config-boot | 2 +- >> scripts/ovirt-config-storage | 188 +++++++++++++++++++++++++++++++++++++++- >> scripts/ovirt-config-uninstall | 8 ++ >> scripts/ovirt-early | 16 +++- >> 4 files changed, 207 insertions(+), 7 deletions(-) >> >> diff --git a/scripts/ovirt-config-boot b/scripts/ovirt-config-boot >> index 87cf832..ed2bb9f 100755 >> --- a/scripts/ovirt-config-boot >> +++ b/scripts/ovirt-config-boot >> @@ -218,7 +218,7 @@ set -e\ >> cp /sbin/dmsetup.static sbin/dmsetup >> cp /sbin/kpartx.static sbin/kpartx >> for M in /sbin/mpath_prio_*.static ; do >> - cp ${M} $/${M%%.static}; >> + cp ${M} .${M%%.static}; >> done >> fi >> >> diff --git a/scripts/ovirt-config-storage b/scripts/ovirt-config-storage >> index 91662c5..c448aee 100755 >> --- a/scripts/ovirt-config-storage >> +++ b/scripts/ovirt-config-storage >> @@ -130,6 +130,172 @@ check_partition_sizes() >> return $rc >> } >> >> +is_multipath_disabled() >> +{ >> + grep -q "^ *devnode \"\*\"" /etc/multipath.conf >> +} >> + >> +multipath_warn() >> +{ >> + sp=' ' >> + w='!!WARNING' >> + wb="$w"'!!' >> + w8="$w$w$w$w$w$w$w$w" >> + printf '%s!!\n' \ >> + "$w8" \ >> + "$w8" \ >> + "$wb$sp$w" \ >> + "$wb$sp$w" \ >> + "$wb Multipath Configurations should only be made $w" \ >> + "$wb on true multipath devices. Configuring any $w" \ >> + "$wb other devices may cause problems with your $w" \ >> + "$wb system. $w" \ >> + "$wb$sp$w" \ >> + "$wb Proceeding from this menu will erase all $w" \ >> + "$wb previous multipath configurations. $w" \ >> + "$wb$sp$w" \ >> + "$wb$sp$w" \ >> + "$w8" \ >> + "$w8" >> + >> +} >> + >> +abort_multipath() >> +{ >> + printf "Cleaning up multipath configuration\n">&2 >> + sed -i 's/^\s*wwid \*$/ devnode "*"/' /etc/multipath.conf >> + sed -i 's/^\s*wwid \w.*$//g' /etc/multipath.conf >> + service multipathd reload >&2 >> +} >> + >> +config_multipath_conf() >> +{ >> + ! is_multipath_disabled && abort_multipath >> + if [[ $OVIRT_MPATH = "ALL" ]]; then >> + sed -i 's/^\s*devnode ".*$/ #devnode "*"/' /etc/multipath.conf >> + else >> + sed -i 's/^\s*devnode ".*$/ wwid */' /etc/multipath.conf >> + test $(grep ^blacklist_exceptions /etc/multipath.conf | wc -l) = 0 \ >> + && printf "blacklist_exceptions {\n}" >> /etc/multipath.conf >> + local wwid="" >> + for d in $whitelisted >> + do >> + d=$(basename $d) >> + local scsi_id=$(scsi_id --whitelisted --device=/dev/$d) >> + if [[ -z $scsi_id ]]; then >> + echo "scsi_id failed for device /dev/$d. Not white listing." >&2 >> + else >> + wwid="${wwid} $scsi_id" >> + fi >> + done >> + wwid=$(echo $wwid | sed 's/ /\n/g' | sort -u) >> + for scsi_id in $wwid >> + do >> + sed -i "s/^blacklist_exceptions {$/blacklist_exceptions {\n wwid $scsi_id"/ /etc/multipath.conf >> + done >> + for d in $OVIRT_MPATH >> + do >> + sed -i "s/^blacklist_exceptions {$/blacklist_exceptions {\n wwid $d"/ /etc/multipath.conf >> + >> + done >> + fi >> + service multipathd reload >&2 >> + start_log >> + echo "Generated multipath.conf" >> + cat /etc/multipath.conf >> + echo "Output of multiatph -v6 command" >> + multipath -v6 >> + stop_log >> + return 0 >> +} >> + >> + >> +generate_whitelist() >> +{ >> + whitelisted="" >> + local devs=$devices >> + local PS3="Please select devices to add or remove from multipath whitelist: " >> + while true; do >> + local choices="" >> + for d in $devs >> + do >> + get_drive_size $d >&2 >> + if [[ $whitelisted =~ $d ]]; then >> + choices="$choices $d-whitelisted" >> + else >> + choices="$choices $d" >> + fi >> + done >> + >> + choices="$choices Done Abort" >> + select whitelist in $choices >> + do >> + test "$whitelist" = Abort && return 1 >> + test "$whitelist" = Done && return 0 >> + #first check if it is already whitelisted >> + #and remove from whitelist >> + if [[ $whitelist =~ "whitelisted" ]]; then >> + local tmp_whitelist="" >> + whitelist=${whitelist%-whitelisted} >> + for d in $whitelisted >> + do >> + test $d != $whitelist && tmp_whitelist="$tmp_whitelist $d" >> + done >> + whitelisted=$tmp_whitelist >> + #else add it to whitelist >> + else >> + whitelisted="$whitelisted $whitelist" >> + fi >> + break >> + done >> + done >> +} >> + >> +# Choose a multipathed device for installation >> +get_multipath_device() >> +{ >> + multipath_warn >&2 >> + local DRIVE_VAR=$1 >> + generate_whitelist >> + test $? = 1 && return 1 >> + config_multipath_conf >> + local count=0 >> + echo "Detecting Multipath Devices..." >&2 >> + while [ $count -lt 60 ] >> + do >> + test ! `ls /dev/mapper/mpath* 2> /dev/null | wc -l` = 0 && break >> + let count=$count+1 >> + sleep 1 >> + done >> + if [ `ls /dev/mapper/mpath* 2> /dev/null | wc -l` = 0 ]; then >> + echo "No Multipath devices found. Aborting...">&2 >> + abort_multipath >> + return 1 >> + fi >> + #need to provide a menu to choose a multipath device >> + while true >> + do >> + local choices="" >> + for dev in $(ls /dev/mapper/mpath* | egrep -v "p[0-9]+$") >> + do >> + choices="$choices $dev" >> + get_drive_size $dev >&2 >> + done >> + choices="$choices Abort" >> + local PS3="Select Multipath Device: " >> + select mpath_device in $choices >> + do >> + if [[ $mpath_device = Abort ]]; then >> + abort_multipath >> + return 1 >> + elif [[ -e $mpath_device ]]; then >> + eval $DRIVE_VAR=$mpath_device >> + return 0 >> + fi >> + done >> + done >> +} >> + >> # 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). >> @@ -140,6 +306,7 @@ check_partition_sizes() >> get_dev_name() >> { >> local udi_list=$(hal-find-by-capability --capability storage) >> + local DRIVE_VAR=$1 >> if test -z "$udi_list"; then >> warn "ERROR: no usable storage devices detected" >> return 1 >> @@ -152,6 +319,9 @@ get_dev_name() >> local block_dev=$(hal-get-property --udi "$d" --key block.device) >> # Must start with a '/'. >> case $block_dev in >> + '') >> + #if block.device not defined, suppress warning >> + continue;; >> *' '*) >> # we use space as separator >> warn "block device name '$block_dev' contains space; skipping"; >> @@ -189,12 +359,13 @@ get_dev_name() >> for d in $devices; do >> get_drive_size $d >&2 >> done >> - local choices="$devices Abort" >> + local choices="$devices Multipath Abort" >> select device in $choices >> do >> test "$device" = Abort && return 1 >> + test "$device" = "Multipath" && get_multipath_device device; >> test -z "$device" && continue >> - echo "$device" >> + eval $DRIVE_VAR=$device >> return 0 >> done >> } >> > >> @@ -202,7 +373,9 @@ get_dev_name() >> do_configure() >> { >> local name_and_size >> - DRIVE=$(get_dev_name) || return 0 >> + get_dev_name DRIVE >> + test ! -e $DRIVE && echo "Drive '$DRIVE' is not a valid drive" >&2 \ >> + && return 0 >> get_drive_size $DRIVE SPACE >> >> > In this section altering DRIVE= kills me trying to use local disks. > Took me awhile to track this one down. Here's how to fix it: > > > local num_devices=$(echo "$devices" | wc -w) > # If there's only one device, use it. > case $num_devices in > 0) warn "ERROR: found no usable block device"; return 1;; > 1) echo "$devices"; return 0;; > *) ;; # found more than one > esac > > > set > 1) echo "$devices"; return 0;; > to > 1) eval $DRIVE_VAR=$devices; return0;; > Ok, I'll look at this, fix the patch, and re-submit From mburns at redhat.com Thu Oct 1 15:29:25 2009 From: mburns at redhat.com (Mike Burns) Date: Thu, 1 Oct 2009 11:29:25 -0400 Subject: [Ovirt-devel] [PATCH node 6/6] Added new menu for Multipath configuration In-Reply-To: <32053749.141254361698637.JavaMail.javamailuser@localhost> References: <4AC4059D.1080603@redhat.com> <32053749.141254361698637.JavaMail.javamailuser@localhost> Message-ID: <20091001152925.GC12588@mburns-laptop.bos.redhat.com> On Wed, Sep 30, 2009 at 09:48:13PM -0400, Andrew Cathrow wrote: > so if we're using blacklist exceptions (aka whitelist) then anytime a new lun is provisioned we'd have to edit the multipath configuration. Is that something that we're going to automate in ovirt? > > Aic > > There are a couple of things that we will be looking at to improve this functionality long term. Functionality in this patch is geared toward being able to install to a multipath device. There is currently no automated or scripted method for configuring multipath if you are not installing to a multipath device. I'll be looking at a couple different ways to do this, but haven't settled on a final design yet. Once I get there, I'll submit a high level design for comments. A couple ideas that I'm playing around with: - Breaking off multipath configuration into a separate o-c-multipath script - Having a separate script that a user can run that will whitelist a newly provisioned lun. Mike > ----- "Joey Boggs" wrote: > > Michael Burns wrote: > > > Also added new kernel command line arg to setup multipath. > > > New menu takes user through mpath whitelisting and choosing an > > > mpath device for installation. > > > > > > Signed-off-by: Michael Burns > > > --- > > > scripts/ovirt-config-boot | 2 +- > > > scripts/ovirt-config-storage | 188 +++++++++++++++++++++++++++++++++++++++- > > > scripts/ovirt-config-uninstall | 8 ++ > > > scripts/ovirt-early | 16 +++- > > > 4 files changed, 207 insertions(+), 7 deletions(-) > > > > > > diff --git a/scripts/ovirt-config-boot b/scripts/ovirt-config-boot > > > index 87cf832..ed2bb9f 100755 > > > --- a/scripts/ovirt-config-boot > > > +++ b/scripts/ovirt-config-boot > > > @@ -218,7 +218,7 @@ set -e\ > > > cp /sbin/dmsetup.static sbin/dmsetup > > > cp /sbin/kpartx.static sbin/kpartx > > > for M in /sbin/mpath_prio_*.static ; do > > > - cp ${M} $/${M%%.static}; > > > + cp ${M} .${M%%.static}; > > > done > > > fi > > > > > > diff --git a/scripts/ovirt-config-storage b/scripts/ovirt-config-storage > > > index 91662c5..c448aee 100755 > > > --- a/scripts/ovirt-config-storage > > > +++ b/scripts/ovirt-config-storage > > > @@ -130,6 +130,172 @@ check_partition_sizes() > > > return $rc > > > } > > > > > > +is_multipath_disabled() > > > +{ > > > + grep -q "^ *devnode \"\*\"" /etc/multipath.conf > > > +} > > > + > > > +multipath_warn() > > > +{ > > > + sp=' ' > > > + w='!!WARNING' > > > + wb="$w"'!!' > > > + w8="$w$w$w$w$w$w$w$w" > > > + printf '%s!!\n' \ > > > + "$w8" \ > > > + "$w8" \ > > > + "$wb$sp$w" \ > > > + "$wb$sp$w" \ > > > + "$wb Multipath Configurations should only be made $w" \ > > > + "$wb on true multipath devices. Configuring any $w" \ > > > + "$wb other devices may cause problems with your $w" \ > > > + "$wb system. $w" \ > > > + "$wb$sp$w" \ > > > + "$wb Proceeding from this menu will erase all $w" \ > > > + "$wb previous multipath configurations. $w" \ > > > + "$wb$sp$w" \ > > > + "$wb$sp$w" \ > > > + "$w8" \ > > > + "$w8" > > > + > > > +} > > > + > > > +abort_multipath() > > > +{ > > > + printf "Cleaning up multipath configuration\n">&2 > > > + sed -i 's/^\s*wwid \*$/ devnode "*"/' /etc/multipath.conf > > > + sed -i 's/^\s*wwid \w.*$//g' /etc/multipath.conf > > > + service multipathd reload >&2 > > > +} > > > + > > > +config_multipath_conf() > > > +{ > > > + ! is_multipath_disabled && abort_multipath > > > + if [[ $OVIRT_MPATH = "ALL" ]]; then > > > + sed -i 's/^\s*devnode ".*$/ #devnode "*"/' /etc/multipath.conf > > > + else > > > + sed -i 's/^\s*devnode ".*$/ wwid */' /etc/multipath.conf > > > + test $(grep ^blacklist_exceptions /etc/multipath.conf | wc -l) = 0 \ > > > + && printf "blacklist_exceptions {\n}" >> /etc/multipath.conf > > > + local wwid="" > > > + for d in $whitelisted > > > + do > > > + d=$(basename $d) > > > + local scsi_id=$(scsi_id --whitelisted --device=/dev/$d) > > > + if [[ -z $scsi_id ]]; then > > > + echo "scsi_id failed for device /dev/$d. Not white listing." >&2 > > > + else > > > + wwid="${wwid} $scsi_id" > > > + fi > > > + done > > > + wwid=$(echo $wwid | sed 's/ /\n/g' | sort -u) > > > + for scsi_id in $wwid > > > + do > > > + sed -i "s/^blacklist_exceptions {$/blacklist_exceptions {\n wwid $scsi_id"/ /etc/multipath.conf > > > + done > > > + for d in $OVIRT_MPATH > > > + do > > > + sed -i "s/^blacklist_exceptions {$/blacklist_exceptions {\n wwid $d"/ /etc/multipath.conf > > > + > > > + done > > > + fi > > > + service multipathd reload >&2 > > > + start_log > > > + echo "Generated multipath.conf" > > > + cat /etc/multipath.conf > > > + echo "Output of multiatph -v6 command" > > > + multipath -v6 > > > + stop_log > > > + return 0 > > > +} > > > + > > > + > > > +generate_whitelist() > > > +{ > > > + whitelisted="" > > > + local devs=$devices > > > + local PS3="Please select devices to add or remove from multipath whitelist: " > > > + while true; do > > > + local choices="" > > > + for d in $devs > > > + do > > > + get_drive_size $d >&2 > > > + if [[ $whitelisted =~ $d ]]; then > > > + choices="$choices $d-whitelisted" > > > + else > > > + choices="$choices $d" > > > + fi > > > + done > > > + > > > + choices="$choices Done Abort" > > > + select whitelist in $choices > > > + do > > > + test "$whitelist" = Abort && return 1 > > > + test "$whitelist" = Done && return 0 > > > + #first check if it is already whitelisted > > > + #and remove from whitelist > > > + if [[ $whitelist =~ "whitelisted" ]]; then > > > + local tmp_whitelist="" > > > + whitelist=${whitelist%-whitelisted} > > > + for d in $whitelisted > > > + do > > > + test $d != $whitelist && tmp_whitelist="$tmp_whitelist $d" > > > + done > > > + whitelisted=$tmp_whitelist > > > + #else add it to whitelist > > > + else > > > + whitelisted="$whitelisted $whitelist" > > > + fi > > > + break > > > + done > > > + done > > > +} > > > + > > > +# Choose a multipathed device for installation > > > +get_multipath_device() > > > +{ > > > + multipath_warn >&2 > > > + local DRIVE_VAR=$1 > > > + generate_whitelist > > > + test $? = 1 && return 1 > > > + config_multipath_conf > > > + local count=0 > > > + echo "Detecting Multipath Devices..." >&2 > > > + while [ $count -lt 60 ] > > > + do > > > + test ! `ls /dev/mapper/mpath* 2> /dev/null | wc -l` = 0 && break > > > + let count=$count+1 > > > + sleep 1 > > > + done > > > + if [ `ls /dev/mapper/mpath* 2> /dev/null | wc -l` = 0 ]; then > > > + echo "No Multipath devices found. Aborting...">&2 > > > + abort_multipath > > > + return 1 > > > + fi > > > + #need to provide a menu to choose a multipath device > > > + while true > > > + do > > > + local choices="" > > > + for dev in $(ls /dev/mapper/mpath* | egrep -v "p[0-9]+$") > > > + do > > > + choices="$choices $dev" > > > + get_drive_size $dev >&2 > > > + done > > > + choices="$choices Abort" > > > + local PS3="Select Multipath Device: " > > > + select mpath_device in $choices > > > + do > > > + if [[ $mpath_device = Abort ]]; then > > > + abort_multipath > > > + return 1 > > > + elif [[ -e $mpath_device ]]; then > > > + eval $DRIVE_VAR=$mpath_device > > > + return 0 > > > + fi > > > + done > > > + done > > > +} > > > + > > > # 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). > > > @@ -140,6 +306,7 @@ check_partition_sizes() > > > get_dev_name() > > > { > > > local udi_list=$(hal-find-by-capability --capability storage) > > > + local DRIVE_VAR=$1 > > > if test -z "$udi_list"; then > > > warn "ERROR: no usable storage devices detected" > > > return 1 > > > @@ -152,6 +319,9 @@ get_dev_name() > > > local block_dev=$(hal-get-property --udi "$d" --key block.device) > > > # Must start with a '/'. > > > case $block_dev in > > > + '') > > > + #if block.device not defined, suppress warning > > > + continue;; > > > *' '*) > > > # we use space as separator > > > warn "block device name '$block_dev' contains space; skipping"; > > > @@ -189,12 +359,13 @@ get_dev_name() > > > for d in $devices; do > > > get_drive_size $d >&2 > > > done > > > - local choices="$devices Abort" > > > + local choices="$devices Multipath Abort" > > > select device in $choices > > > do > > > test "$device" = Abort && return 1 > > > + test "$device" = "Multipath" && get_multipath_device device; > > > test -z "$device" && continue > > > - echo "$device" > > > + eval $DRIVE_VAR=$device > > > return 0 > > > done > > > } > > > > > > > > @@ -202,7 +373,9 @@ get_dev_name() > > > do_configure() > > > { > > > local name_and_size > > > - DRIVE=$(get_dev_name) || return 0 > > > + get_dev_name DRIVE > > > + test ! -e $DRIVE && echo "Drive '$DRIVE' is not a valid drive" >&2 \ > > > + && return 0 > > > get_drive_size $DRIVE SPACE > > > > > > > > In this section altering DRIVE= kills me trying to use local disks. > > Took me awhile to track this one down. Here's how to fix it: > > > > > > local num_devices=$(echo "$devices" | wc -w) > > # If there's only one device, use it. > > case $num_devices in > > 0) warn "ERROR: found no usable block device"; return 1;; > > 1) echo "$devices"; return 0;; > > *) ;; # found more than one > > esac > > > > > > set > > 1) echo "$devices"; return 0;; > > to > > 1) eval $DRIVE_VAR=$devices; return0;; > > > > > > > > > > > > > printf "\n\nPlease configure storage partitions.\n\n" > > > @@ -537,6 +710,15 @@ CONFIG_SIZE=${OVIRT_VOL_CONFIG_SIZE:-$default_config_size} > > > LOGGING_SIZE=${OVIRT_VOL_LOGGING_SIZE:-$default_logging_size} > > > DATA_SIZE=${OVIRT_VOL_DATA_SIZE:-$default_data_size} > > > > > > +if [ -n "$OVIRT_MPATH" ]; then > > > + #if present then setup multipath.conf with value > > > + config_multipath_conf > > > + while true > > > + do > > > + test ! `ls /dev/mapper/mpath* 2> /dev/null | wc -l` = 0 && break > > > + done > > > +fi > > > + > > > if [ -n "$OVIRT_INIT" ]; then > > > # if present, use the drive selected with 'ovirt_init' boot parameter > > > DRIVE=$OVIRT_INIT > > > diff --git a/scripts/ovirt-config-uninstall b/scripts/ovirt-config-uninstall > > > index fd65cbb..20f8c2c 100755 > > > --- a/scripts/ovirt-config-uninstall > > > +++ b/scripts/ovirt-config-uninstall > > > @@ -47,6 +47,14 @@ if ask_yes_or_no "Do you wish to continue and uninstall this node ([Y]es/[N]o)?" > > > log "Unmounting boot partition" > > > umount $partition > > > log "Removing partitions" > > > + eval $(echo $partition | awk ' { > > > + print "drive=" substr($0,1,length($1)-1); > > > + print "drive2=" substr($0,1,length($1)-2); > > > + }') > > > + if [ ! -e "$drive" ]; then > > > + # e.g. c0d0p1 > > > + drive="$drive2" > > > + fi > > > drive=$(echo $partition | awk '{ print substr($0, 1, length($0) - 1) }') > > > parted -s $drive "rm 1" > > > parted -s $drive "rm 2" > > > diff --git a/scripts/ovirt-early b/scripts/ovirt-early > > > index 8990727..5632fb0 100755 > > > --- a/scripts/ovirt-early > > > +++ b/scripts/ovirt-early > > > @@ -148,6 +148,7 @@ start() { > > > # collectd=server[:port] > > > # hostname=fqdn > > > # TBD logrotate maxsize > > > + # mpath format: mpath=wwid:[wwid] > > > > > > # BOOTIF=link|eth*| (appended by pxelinux) > > > # network boot interface is assumed to be on management network where > > > @@ -236,6 +237,12 @@ start() { > > > collectd_server= > > > collectd_port= > > > > > > + # mpath=wwid:[wwid...] > > > + # set a list of comma or colon separated wwids to allow > > > + # for multipath install > > > + # Specify ALL to use all devices > > > + mpath= > > > + > > > # save boot parameters like console= for local disk boot menu > > > bootparams= > > > cat /etc/system-release >> $OVIRT_LOGFILE > > > @@ -286,8 +293,7 @@ start() { > > > =/dev/*) > > > bus= > > > serial= > > > - i=${i#=} > > > - init=$(ls -1 "$i" 2>/dev/null | head -n1) > > > + init=${i#=} > > > ;; > > > *) > > > bus= > > > @@ -374,6 +380,10 @@ start() { > > > console=*) > > > bootparams="$bootparams $i" > > > ;; > > > + mpath=*) > > > + i=${i#mpath=} > > > + mpath=$(echo $i|tr ",:;" " ") > > > + ;; > > > esac > > > done > > > > > > @@ -384,7 +394,7 @@ start() { > > > ip_gateway=$gateway > > > fi > > > # save boot parameters as defaults for ovirt-config-* > > > - params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan ssh_pwauth syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot" > > > + params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan ssh_pwauth syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot mpath" > > > # mount /config unless firstboot is forced > > > if [ "$firstboot" != "1" ]; then > > > mount_config > > > > > > > _______________________________________________ > > Ovirt-devel mailing list > > Ovirt-devel at redhat.com > > https://www.redhat.com/mailman/listinfo/ovirt-devel > > From pmyers at redhat.com Thu Oct 1 16:04:33 2009 From: pmyers at redhat.com (Perry Myers) Date: Thu, 01 Oct 2009 12:04:33 -0400 Subject: [Ovirt-devel] [PATCH node 6/6] Added new menu for Multipath configuration In-Reply-To: <20091001152925.GC12588@mburns-laptop.bos.redhat.com> References: <4AC4059D.1080603@redhat.com> <32053749.141254361698637.JavaMail.javamailuser@localhost> <20091001152925.GC12588@mburns-laptop.bos.redhat.com> Message-ID: <4AC4D311.9000506@redhat.com> On 10/01/2009 11:29 AM, Mike Burns wrote: > On Wed, Sep 30, 2009 at 09:48:13PM -0400, Andrew Cathrow wrote: >> so if we're using blacklist exceptions (aka whitelist) then anytime a new lun is provisioned we'd have to edit the multipath configuration. Is that something that we're going to automate in ovirt? >> >> Aic >> >> > > There are a couple of things that we will be looking at to improve this functionality long term. Functionality in this patch is geared toward being able to install to a multipath device. There is currently no automated or scripted method for configuring multipath if you are not installing to a multipath device. I'll be looking at a couple different ways to do this, but haven't settled on a final design yet. Once I get there, I'll submit a high level design for comments. > > A couple ideas that I'm playing around with: > > - Breaking off multipath configuration into a separate o-c-multipath script > - Having a separate script that a user can run that will whitelist a newly provisioned lun. Using blacklist "*" with specific whitelisted devices Pros - Prevents random devices not intended to be monitored by multipathd from generating error messages Cons - Requires specific addition to whitelist to add a new LUN Removing blacklist "*" with specific blacklisted devices Pros - New LUNs show up automatically Cons - Insert a USB stick and it'll generate errors We could either: Use the blacklist "*" method and implement a udev hook so that when a new LUN is attached it causes multipath configuration file change and daemon restart Use the whitelist "*" method and find a way to block usb devices somehow in the blacklist. This might involve core changes to multipathd. Perry From mburns at redhat.com Thu Oct 1 17:42:32 2009 From: mburns at redhat.com (Mike Burns) Date: Thu, 1 Oct 2009 13:42:32 -0400 Subject: [Ovirt-devel] Repost of Patch 6/6 for ovirt-node Message-ID: <20091001174232.GD12588@mburns-laptop.bos.redhat.com> All other patches from the sequence remain unchanged. Repost of patch 6 based on comments from Joey to follow. Mike From imain at redhat.com Thu Oct 1 17:47:48 2009 From: imain at redhat.com (Ian Main) Date: Thu, 1 Oct 2009 10:47:48 -0700 Subject: [Ovirt-devel] Few question about fresh install Ovirt In-Reply-To: References: Message-ID: <20091001104748.04d27bc6@tp.mains.priv> On Wed, 30 Sep 2009 16:56:26 +0300 Pavel Sabirjanov wrote: > Hi! > > I have problems with running ovirt on fresh Fedora 11 server. > > After running this command > /etc/init.d/libvirt-qpid start libvirt-qpid runs on the nodes, and is not required on the server. Server isn't configured to run it .. Ian From mburns at redhat.com Thu Oct 1 17:54:48 2009 From: mburns at redhat.com (Michael Burns) Date: Thu, 1 Oct 2009 13:54:48 -0400 Subject: [Ovirt-devel] [PATCH node] Added new menu for Multipath configuration In-Reply-To: <20091001174232.GD12588@mburns-laptop.bos.redhat.com> References: <20091001174232.GD12588@mburns-laptop.bos.redhat.com> Message-ID: <1254419688-30788-1-git-send-email-mburns@redhat.com> Also added new kernel command line arg to setup multipath. New menu takes user through mpath whitelisting and choosing an mpath device for installation. Signed-off-by: Michael Burns --- scripts/ovirt-config-boot | 2 +- scripts/ovirt-config-storage | 190 +++++++++++++++++++++++++++++++++++++++- scripts/ovirt-config-uninstall | 8 ++ scripts/ovirt-early | 16 +++- 4 files changed, 208 insertions(+), 8 deletions(-) diff --git a/scripts/ovirt-config-boot b/scripts/ovirt-config-boot index fb9d74f..01c1821 100755 --- a/scripts/ovirt-config-boot +++ b/scripts/ovirt-config-boot @@ -219,7 +219,7 @@ set -e\ cp /sbin/dmsetup.static sbin/dmsetup cp /sbin/kpartx.static sbin/kpartx for M in /sbin/mpath_prio_*.static ; do - cp ${M} $/${M%%.static}; + cp ${M} .${M%%.static}; done fi diff --git a/scripts/ovirt-config-storage b/scripts/ovirt-config-storage index 7ced820..3829abb 100755 --- a/scripts/ovirt-config-storage +++ b/scripts/ovirt-config-storage @@ -130,6 +130,172 @@ check_partition_sizes() return $rc } +is_multipath_disabled() +{ + grep -q "^ *devnode \"\*\"" /etc/multipath.conf +} + +multipath_warn() +{ + sp=' ' + w='!!WARNING' + wb="$w"'!!' + w8="$w$w$w$w$w$w$w$w" + printf '%s!!\n' \ + "$w8" \ + "$w8" \ + "$wb$sp$w" \ + "$wb$sp$w" \ + "$wb Multipath Configurations should only be made $w" \ + "$wb on true multipath devices. Configuring any $w" \ + "$wb other devices may cause problems with your $w" \ + "$wb system. $w" \ + "$wb$sp$w" \ + "$wb Proceeding from this menu will erase all $w" \ + "$wb previous multipath configurations. $w" \ + "$wb$sp$w" \ + "$wb$sp$w" \ + "$w8" \ + "$w8" + +} + +abort_multipath() +{ + printf "Cleaning up multipath configuration\n">&2 + sed -i 's/^\s*wwid \*$/ devnode "*"/' /etc/multipath.conf + sed -i 's/^\s*wwid \w.*$//g' /etc/multipath.conf + service multipathd reload >&2 +} + +config_multipath_conf() +{ + ! is_multipath_disabled && abort_multipath + if [[ $OVIRT_MPATH = "ALL" ]]; then + sed -i 's/^\s*devnode ".*$/ #devnode "*"/' /etc/multipath.conf + else + sed -i 's/^\s*devnode ".*$/ wwid */' /etc/multipath.conf + test $(grep ^blacklist_exceptions /etc/multipath.conf | wc -l) = 0 \ + && printf "blacklist_exceptions {\n}" >> /etc/multipath.conf + local wwid="" + for d in $whitelisted + do + d=$(basename $d) + local scsi_id=$(scsi_id --whitelisted --device=/dev/$d) + if [[ -z $scsi_id ]]; then + echo "scsi_id failed for device /dev/$d. Not white listing." >&2 + else + wwid="${wwid} $scsi_id" + fi + done + wwid=$(echo $wwid | sed 's/ /\n/g' | sort -u) + for scsi_id in $wwid + do + sed -i "s/^blacklist_exceptions {$/blacklist_exceptions {\n wwid $scsi_id"/ /etc/multipath.conf + done + for d in $OVIRT_MPATH + do + sed -i "s/^blacklist_exceptions {$/blacklist_exceptions {\n wwid $d"/ /etc/multipath.conf + + done + fi + service multipathd reload >&2 + start_log + echo "Generated multipath.conf" + cat /etc/multipath.conf + echo "Output of multiatph -v6 command" + multipath -v6 + stop_log + return 0 +} + + +generate_whitelist() +{ + whitelisted="" + local devs=$devices + local PS3="Please select devices to add or remove from multipath whitelist: " + while true; do + local choices="" + for d in $devs + do + get_drive_size $d >&2 + if [[ $whitelisted =~ $d ]]; then + choices="$choices $d-whitelisted" + else + choices="$choices $d" + fi + done + + choices="$choices Done Abort" + select whitelist in $choices + do + test "$whitelist" = Abort && return 1 + test "$whitelist" = Done && return 0 + #first check if it is already whitelisted + #and remove from whitelist + if [[ $whitelist =~ "whitelisted" ]]; then + local tmp_whitelist="" + whitelist=${whitelist%-whitelisted} + for d in $whitelisted + do + test $d != $whitelist && tmp_whitelist="$tmp_whitelist $d" + done + whitelisted=$tmp_whitelist + #else add it to whitelist + else + whitelisted="$whitelisted $whitelist" + fi + break + done + done +} + +# Choose a multipathed device for installation +get_multipath_device() +{ + multipath_warn >&2 + local DRIVE_VAR=$1 + generate_whitelist + test $? = 1 && return 1 + config_multipath_conf + local count=0 + echo "Detecting Multipath Devices..." >&2 + while [ $count -lt 60 ] + do + test ! `ls /dev/mapper/mpath* 2> /dev/null | wc -l` = 0 && break + let count=$count+1 + sleep 1 + done + if [ `ls /dev/mapper/mpath* 2> /dev/null | wc -l` = 0 ]; then + echo "No Multipath devices found. Aborting...">&2 + abort_multipath + return 1 + fi + #need to provide a menu to choose a multipath device + while true + do + local choices="" + for dev in $(ls /dev/mapper/mpath* | egrep -v "p[0-9]+$") + do + choices="$choices $dev" + get_drive_size $dev >&2 + done + choices="$choices Abort" + local PS3="Select Multipath Device: " + select mpath_device in $choices + do + if [[ $mpath_device = Abort ]]; then + abort_multipath + return 1 + elif [[ -e $mpath_device ]]; then + eval $DRIVE_VAR=$mpath_device + return 0 + fi + done + done +} + # 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). @@ -140,6 +306,7 @@ check_partition_sizes() get_dev_name() { local udi_list=$(hal-find-by-capability --capability storage) + local DRIVE_VAR=$1 if test -z "$udi_list"; then warn "ERROR: no usable storage devices detected" return 1 @@ -152,6 +319,9 @@ get_dev_name() local block_dev=$(hal-get-property --udi "$d" --key block.device) # Must start with a '/'. case $block_dev in + '') + #if block.device not defined, suppress warning + continue;; *' '*) # we use space as separator warn "block device name '$block_dev' contains space; skipping"; @@ -180,7 +350,7 @@ get_dev_name() # If there's only one device, use it. case $num_devices in 0) warn "ERROR: found no usable block device"; return 1;; - 1) echo "$devices"; return 0;; + 1) eval $DRIVE_VAR=$devices; return 0;; *) ;; # found more than one esac @@ -189,12 +359,13 @@ get_dev_name() for d in $devices; do get_drive_size $d >&2 done - local choices="$devices Abort" + local choices="$devices Multipath Abort" select device in $choices do test "$device" = Abort && return 1 + test "$device" = "Multipath" && get_multipath_device device; test -z "$device" && continue - echo "$device" + eval $DRIVE_VAR=$device return 0 done } @@ -202,7 +373,9 @@ get_dev_name() do_configure() { local name_and_size - DRIVE=$(get_dev_name) || return 0 + get_dev_name DRIVE + test ! -e $DRIVE && echo "Drive '$DRIVE' is not a valid drive" >&2 \ + && return 0 get_drive_size $DRIVE SPACE printf "\n\nPlease configure storage partitions.\n\n" @@ -539,6 +712,15 @@ CONFIG_SIZE=${OVIRT_VOL_CONFIG_SIZE:-$default_config_size} LOGGING_SIZE=${OVIRT_VOL_LOGGING_SIZE:-$default_logging_size} DATA_SIZE=${OVIRT_VOL_DATA_SIZE:-$default_data_size} +if [ -n "$OVIRT_MPATH" ]; then + #if present then setup multipath.conf with value + config_multipath_conf + while true + do + test ! `ls /dev/mapper/mpath* 2> /dev/null | wc -l` = 0 && break + done +fi + if [ -n "$OVIRT_INIT" ]; then # if present, use the drive selected with 'ovirt_init' boot parameter DRIVE=$OVIRT_INIT diff --git a/scripts/ovirt-config-uninstall b/scripts/ovirt-config-uninstall index 92ce2d0..95a71a9 100755 --- a/scripts/ovirt-config-uninstall +++ b/scripts/ovirt-config-uninstall @@ -47,6 +47,14 @@ if ask_yes_or_no "Do you wish to continue and uninstall this node ([Y]es/[N]o)?" log "Unmounting boot partition" umount $partition log "Removing partitions" + eval $(echo $partition | awk ' { + print "drive=" substr($0,1,length($1)-1); + print "drive2=" substr($0,1,length($1)-2); + }') + if [ ! -e "$drive" ]; then + # e.g. c0d0p1 + drive="$drive2" + fi drive=$(echo $partition | awk '{ print substr($0, 1, length($0) - 1) }') parted -s $drive "rm 1" parted -s $drive "rm 2" diff --git a/scripts/ovirt-early b/scripts/ovirt-early index 8990727..5632fb0 100755 --- a/scripts/ovirt-early +++ b/scripts/ovirt-early @@ -148,6 +148,7 @@ start() { # collectd=server[:port] # hostname=fqdn # TBD logrotate maxsize + # mpath format: mpath=wwid:[wwid] # BOOTIF=link|eth*| (appended by pxelinux) # network boot interface is assumed to be on management network where @@ -236,6 +237,12 @@ start() { collectd_server= collectd_port= + # mpath=wwid:[wwid...] + # set a list of comma or colon separated wwids to allow + # for multipath install + # Specify ALL to use all devices + mpath= + # save boot parameters like console= for local disk boot menu bootparams= cat /etc/system-release >> $OVIRT_LOGFILE @@ -286,8 +293,7 @@ start() { =/dev/*) bus= serial= - i=${i#=} - init=$(ls -1 "$i" 2>/dev/null | head -n1) + init=${i#=} ;; *) bus= @@ -374,6 +380,10 @@ start() { console=*) bootparams="$bootparams $i" ;; + mpath=*) + i=${i#mpath=} + mpath=$(echo $i|tr ",:;" " ") + ;; esac done @@ -384,7 +394,7 @@ start() { ip_gateway=$gateway fi # save boot parameters as defaults for ovirt-config-* - params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan ssh_pwauth syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot" + params="bootif init vol_boot_size vol_swap_size vol_root_size vol_config_size vol_logging_size vol_data_size local_boot standalone overcommit ip_address ip_netmask ip_gateway ipv6 dns ntp vlan ssh_pwauth syslog_server syslog_port collectd_server collectd_port bootparams hostname firstboot mpath" # mount /config unless firstboot is forced if [ "$firstboot" != "1" ]; then mount_config -- 1.6.2.5 From jboggs at redhat.com Fri Oct 2 01:58:39 2009 From: jboggs at redhat.com (Joey Boggs) Date: Thu, 01 Oct 2009 21:58:39 -0400 Subject: [Ovirt-devel] [PATCH node 2/6] add LVM filter support in initrd In-Reply-To: <1253932438-24628-3-git-send-email-mburns@redhat.com> References: <20090926023338.GA24661@mburns-laptop.bos.redhat.com> <1253932438-24628-3-git-send-email-mburns@redhat.com> Message-ID: <4AC55E4F.9090305@redhat.com> > diff --git a/scripts/ovirt-config-boot b/scripts/ovirt-config-boot > index 2961f76..7c5943b 100755 > --- a/scripts/ovirt-config-boot > +++ b/scripts/ovirt-config-boot > @@ -108,6 +108,19 @@ ovirt_boot_setup() { > init_script=sbin/real-init > fi > sed -i '/^\/sbin\/udev.*trigger/ a \ > +filter=\ > +for o in `cat /proc/cmdline` ; do\ > + case $o in\ > + filter=*)\ > + filter=${o#filter=}\ > + ;;\ > + esac\ > +done\ > +if [ -n "$filter" ]; then\ > + mkdir -p /etc/lvm\ > + echo "devices { filter = [ \\\"a|^$filter|\\\", \\\"r|.*|\\\" ] }" > /etc/lvm/lvm.conf\ > +fi\ > + > This extra empty line ^^ causes the install to hang for me on local disk. Take it out and it installs fine. I'm hitting another issue where the rebuilt initrd causes the boot to hang like we've seen before right after starting udev. Been a long night, so I'll keep working on it in the morning. Have you ran into similar issues during testing? From mburns at redhat.com Fri Oct 2 14:45:35 2009 From: mburns at redhat.com (Mike Burns) Date: Fri, 2 Oct 2009 10:45:35 -0400 Subject: [Ovirt-devel] [PATCH node 2/6] add LVM filter support in initrd In-Reply-To: <4AC55E4F.9090305@redhat.com> References: <20090926023338.GA24661@mburns-laptop.bos.redhat.com> <1253932438-24628-3-git-send-email-mburns@redhat.com> <4AC55E4F.9090305@redhat.com> Message-ID: <20091002144535.GA31010@mburns-laptop.bos.redhat.com> On Thu, Oct 01, 2009 at 09:58:39PM -0400, Joey Boggs wrote: > >> diff --git a/scripts/ovirt-config-boot b/scripts/ovirt-config-boot >> index 2961f76..7c5943b 100755 >> --- a/scripts/ovirt-config-boot >> +++ b/scripts/ovirt-config-boot >> @@ -108,6 +108,19 @@ ovirt_boot_setup() { >> init_script=sbin/real-init >> fi >> sed -i '/^\/sbin\/udev.*trigger/ a \ >> +filter=\ >> +for o in `cat /proc/cmdline` ; do\ >> + case $o in\ >> + filter=*)\ >> + filter=${o#filter=}\ >> + ;;\ >> + esac\ >> +done\ >> +if [ -n "$filter" ]; then\ >> + mkdir -p /etc/lvm\ >> + echo "devices { filter = [ \\\"a|^$filter|\\\", \\\"r|.*|\\\" ] }" > /etc/lvm/lvm.conf\ >> +fi\ >> + >> > This extra empty line ^^ causes the install to hang for me on local > disk. Take it out and it installs fine. I'm hitting another issue where > the rebuilt initrd causes the boot to hang like we've seen before right > after starting udev. Been a long night, so I'll keep working on it in > the morning. Have you ran into similar issues during testing? > > I'll fix this, but I'm going to pull back and do some more testing on the complete set. I didn't see these issues, but I haven't used local disks since the first couple versions of the patch set. Once I saw it work on local disk, I switched to multipath disks. I never went back to test local disk again after the final revisions. Mike From imain at redhat.com Fri Oct 2 15:53:18 2009 From: imain at redhat.com (Ian Main) Date: Fri, 2 Oct 2009 08:53:18 -0700 Subject: [Ovirt-devel] Storage pool error In-Reply-To: <20091001090150.547kbv6tj63o8sww@webmail.provincia.torino.it> References: <20091001083132.g1gqwz133q8gwckk@webmail.provincia.torino.it> <20091001090150.547kbv6tj63o8sww@webmail.provincia.torino.it> Message-ID: <20091002085318.1b2b86e5@tp.mains.priv> On Thu, 01 Oct 2009 09:01:50 +0200 ignazio.cassano at provincia.torino.it wrote: > > hello, > further information about the problem: on the note in the > /etc/libvirt/storage directory there is no autostart directory Do you have logs from /var/log/ovirt-server/taskomatic.log while trying to do the thing that failed? Thanks, Ian From imain at redhat.com Fri Oct 2 15:55:24 2009 From: imain at redhat.com (Ian Main) Date: Fri, 2 Oct 2009 08:55:24 -0700 Subject: [Ovirt-devel] oVirt in production In-Reply-To: <242C2600-6567-43C0-983A-AED6D9B10DC2@gmail.com> References: <242C2600-6567-43C0-983A-AED6D9B10DC2@gmail.com> Message-ID: <20091002085524.281c4302@tp.mains.priv> On Thu, 1 Oct 2009 14:16:24 +0300 Pavel Sabirjanov wrote: > Hi. > > Currently i have about 20 servers with Intel-VT/AMD virtualization > technology. > I want to offer VPS services. Can i use oVirt for that? Or oVirt is > still in a deep development? It's not really in deep development right now, however it hasn't had a good amount of time to get rid of bugs yet. There are still a number of issues/bugs that would make me say that it is not yet ready for production use. This may change in the next few months though as we are basically doing bugfixing right now. Ian From justin at redfish-group.com Sun Oct 4 02:23:21 2009 From: justin at redfish-group.com (Justin Clacherty) Date: Sun, 4 Oct 2009 12:23:21 +1000 (EST) Subject: [Ovirt-devel] iscsi problems Message-ID: <1813844556.211254623001403.JavaMail.root@mail.redfish.com.au> Hi, I had to restart everything again yesterday and was confronted with the whole iscsi issue again (ovirt can't find the volumes when you try to start a VM). I've now figured out what the problem is, but have no idea how to fix it. I took a look on the node and it is able to mount the volumes without problems. The issue seems to be that ovirt is trying to find the volume by id once it's mounted, but the id changes between reboots as it is automagically generated by udev. The error that appears is: Unable to find volume /dev/disk/by-id/scsi-149455400000000000000000005000000061080000000d00f attached to pool 192.168.50.190-iqn.2009-3.san0:services-3260. And yes indeed that id doesn't exist it's actually in the form /dev/disk/by-id/scsi-14945540000000000000000000?0000000??080000000d00f or something like that. So the node mounts the volume fine but the VM can't see it because it doesn't know which VM to use. Has this been fixed in a later release (I'm running next from about a month ago) or is this an ongoing problem awaiting a solution? Also, has the provisioning from a cobbler ISO image on NFS been sorted out? Trying to work out if I should move to the latest next. Cheers, Justin. From ignazio.cassano at provincia.torino.it Mon Oct 5 06:35:31 2009 From: ignazio.cassano at provincia.torino.it (ignazio.cassano at provincia.torino.it) Date: Mon, 05 Oct 2009 08:35:31 +0200 Subject: [Ovirt-devel] Storage pool error In-Reply-To: <20091002085318.1b2b86e5@tp.mains.priv> References: <20091001083132.g1gqwz133q8gwckk@webmail.provincia.torino.it> <20091001090150.547kbv6tj63o8sww@webmail.provincia.torino.it> <20091002085318.1b2b86e5@tp.mains.priv> Message-ID: <20091005083531.l2u2dw7y4z0o0sg8@webmail.provincia.torino.it> Hi, I don't remember when I tried to use nfs storage but I am sure it was on 1th october. Attacched here the is rhe log you requested Thanks ----- Messaggio da imain at redhat.com --------- Data: Fri, 02 Oct 2009 08:53:18 -0700 Da: Ian Main Rispondi-A:Ian Main Oggetto: Re: [Ovirt-devel] Storage pool error A: ovirt-devel at redhat.com > On Thu, 01 Oct 2009 09:01:50 +0200 > ignazio.cassano at provincia.torino.it wrote: > >> >> hello, >> further information about the problem: on the note in the >> /etc/libvirt/storage directory there is no autostart directory > > Do you have logs from /var/log/ovirt-server/taskomatic.log while trying > to do the thing that failed? > > Thanks, > > Ian > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > ----- Fine del messaggio da imain at redhat.com ----- -------------- next part -------------- A non-text attachment was scrubbed... Name: db-omatic.log-20091004.gz Type: application/x-gzip Size: 5048 bytes Desc: not available URL: From jboggs at redhat.com Mon Oct 5 19:50:25 2009 From: jboggs at redhat.com (Joey Boggs) Date: Mon, 5 Oct 2009 15:50:25 -0400 Subject: [Ovirt-devel] [PATCH node-image] enable dca kernel module for igb kernel module support Message-ID: <1254772225-870-1-git-send-email-jboggs@redhat.com> Some Intel nics require the igb kernel module. In order for igb to work the dca module is needed. --- common-blacklist.ks | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/common-blacklist.ks b/common-blacklist.ks index 81f46d6..3499ccf 100644 --- a/common-blacklist.ks +++ b/common-blacklist.ks @@ -66,7 +66,7 @@ driver_mods="drivers/auxdisplay drivers/net/appletalk \ drivers/net/hamradio drivers/net/pcmcia drivers/net/tokenring \ drivers/net/wireless drivers/net/irda drivers/atm drivers/usb/atm \ drivers/acpi drivers/char/drm drivers/char/mwave \ - drivers/char/ipmp drivers/char/pcmcia drivers/crypto drivers/dca \ + drivers/char/ipmp drivers/char/pcmcia drivers/crypto \ drivers/firmware drivers/memstick drivers/mmc drivers/mfs \ drivers/parport drivers/video drivers/watchdog drivers/net/ppp* \ drivers/usb/serial drivers/usb/misc drivers/usb/class \ -- 1.6.2.5 From mloiseleur at linagora.com Tue Oct 6 12:43:35 2009 From: mloiseleur at linagora.com (Loiseleur Michel) Date: Tue, 6 Oct 2009 12:43:35 +0000 Subject: [Ovirt-devel] [PATCH] Fix refreshing vms list This patch fix vms index view when a smart pool is not destroyed correctly. Message-ID: <1254833015-19807-1-git-send-email-mloiseleur@linagora.com> Signed-off-by: Loiseleur Michel --- app/models/smart_pool.rb | 10 ++++------ 1 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/models/smart_pool.rb b/app/models/smart_pool.rb index 1f718a8..9afc354 100644 --- a/app/models/smart_pool.rb +++ b/app/models/smart_pool.rb @@ -62,18 +62,16 @@ class SmartPool < Pool if nested_pools nested_pools.each do |pool_element| pool = pool_element[:obj] - if pool.hasChildren + if pool.hasChildren and pool_element.has_key?(:children) if pool.name == user pool_element[:children].each do |child_element| child_pool = child_element[:obj] user_pools <<[child_pool.name, child_pool.id] end else - if pool_element.has_key?(:children) - pool_element[:children].each do |child_element| - child_pool = child_element[:obj] - other_pools << [pool.name + " > " + child_pool.name, child_pool.id] - end + pool_element[:children].each do |child_element| + child_pool = child_element[:obj] + other_pools << [pool.name + " > " + child_pool.name, child_pool.id] end end end -- 1.6.2.5 From mmorsi at redhat.com Tue Oct 6 22:31:30 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Tue, 6 Oct 2009 18:31:30 -0400 Subject: [Ovirt-devel] [PATCH server] new host networking wui Message-ID: <1254868290-15067-1-git-send-email-mmorsi@redhat.com> Frontend and backend changes needed to implement http://ovirt.org/page/Networking_UX#Option_3 --- src/app/controllers/host_controller.rb | 38 ++- src/app/services/host_service.rb | 35 ++ src/app/views/host/edit_network.rhtml | 593 ++++++++++++++++++++++++++++---- src/public/stylesheets/components.css | 76 ++++ 4 files changed, 676 insertions(+), 66 deletions(-) diff --git a/src/app/controllers/host_controller.rb b/src/app/controllers/host_controller.rb index 20e9fca..5cb89a9 100644 --- a/src/app/controllers/host_controller.rb +++ b/src/app/controllers/host_controller.rb @@ -104,10 +104,46 @@ class HostController < ApplicationController render :layout => 'popup' end + def update_network + # adjust parameters as necessary + params[:nics] = {} if params[:nics].nil? + params[:bondings] = {} if params[:bondings].nil? + params[:nics].each { |id,n| + unless n[:ip_address].nil? + # FIXME make this able to be v4 or v6 address + n[:ip_addresses] = [IpV4Address.new({ :address => n[:ip_address] })] + n.delete(:ip_address) + end + } + params[:bondings].each { |id,b| + unless b[:ip_address].nil? + # FIXME make this able to be v4 or v6 address + b[:ip_addresses] = [IpV4Address.new({ :address => b[:ip_address] })] + b.delete(:ip_address) + end + + if b[:nic_ids].nil? || b[:nic_ids].size == 0 + b[:nics] = [] + else + b[:nics] = b[:nic_ids].collect { |n| Nic.find(n) } + b.delete(:nic_ids) + end + + b[:interface_name] = b[:name] if b[:interface_name].nil? && !b[:name].nil? + } + params[:bondings].delete('NBID') # delete new bonding row + + svc_update_network(params[:id], params[:nics], params[:bondings]) + render :json => { + :object => "host", + :success => true, + :alert => "Host network entities successfully updated." + } + end + def bondings_json svc_show(params[:id]) bondings = @host.bondings render :json => bondings.collect{ |x| {:id => x.id, :name => x.name} } end - end diff --git a/src/app/services/host_service.rb b/src/app/services/host_service.rb index 4ace9fb..3b59f0e 100644 --- a/src/app/services/host_service.rb +++ b/src/app/services/host_service.rb @@ -72,6 +72,41 @@ module HostService # [Privilege::MODIFY] on host's HardwarePool def svc_modify(id) lookup(id, Privilege::MODIFY) + @networks = Network.find(:all) + @bonding_types = BondingType.find(:all) + end + + # Update the network entities associated with the Host with +id+ + # === Instance variables + # [@host] stores the Host with +id+ + # === Required permissions + # [Privilege::MODIFY] on host's HardwarePool + def svc_update_network(id, nics, bondings) + lookup(id, Privilege::MODIFY) + + nics.each { |nic_id, nic| + dnic = @host.nics.all.find { |n| n.id == nic_id.to_i } + unless dnic.nil? + dnic.update_attributes!(nic) + end + } + + # delete any bondings associated w/ host but not in bondings array + @host.bondings.each { |b| + b.destroy unless bondings.include?(b.id.to_s) + } + + bondings.each { |bond_id, bond| + dbond = @host.bondings.all.find { |b| b.id == bond_id.to_i } + unless dbond.nil? + # update original bonding + dbond.update_attributes!(bond) + else + bond[:host_id] = id + # create new bonding + Bonding.create!(bond) + end + } end # Set the disabled state of the Host with +id+ to :enabled diff --git a/src/app/views/host/edit_network.rhtml b/src/app/views/host/edit_network.rhtml index 760f508..1154595 100644 --- a/src/app/views/host/edit_network.rhtml +++ b/src/app/views/host/edit_network.rhtml @@ -6,80 +6,543 @@ Select and edit nics and bonded interfaces on <%= @host.hostname %> <%- end -%> - - +
+ +<%= hidden_field_tag 'id', @host.id %> +
+
+   +
+ Device +
+
+ Network +
+ +
+
+ + <% @host.nics.each { |nic| %> +
+   +
+ <%= nic.interface_name + " " + nic.mac %> +
+ +
+ + +
+   Ip Address: + + + +
+
+ +
+
+ <%= image_tag("icon_edit_11px.png") %> +
+
+ +
+
+ <% } %> + + <% @host.bondings.each { |bonding| %> +
+   +
+
+ <%= bonding.interface_name + " - " + bonding.bonding_type.label %> +
+ + +
+ <% bonding.nics.each { |nic| %> +
+   <%= nic.interface_name + " " + nic.mac %> +
+ <% } %> + + <% if bonding.nics.size == 0 %> +   No NICs associated + <% end %> +
+ + + +
+ +
+ + +
+   Ip Address: + +
+ + +
+ +
+
+ <%= image_tag("icon_edit_11px.png") %> +
+
+ <%= image_tag("icon_delete_11px.png") %> +
+
+ +
+
-
-
+ <% } %> + + <%# create hidden row for new bondings, NBID will be replaced w/ New Bonding ID %> + + +
+   +
+ <%= image_tag("icon_add_11px.png") %> New Bonded Interface +
+
+
+ +
+
diff --git a/src/public/stylesheets/components.css b/src/public/stylesheets/components.css index 2cda65d..b973b3b 100644 --- a/src/public/stylesheets/components.css +++ b/src/public/stylesheets/components.css @@ -389,3 +389,79 @@ #vm_network_config_add:hover { cursor: pointer; } + + +/* Host > Edit Network Configuration */ + +#host_network_table { + padding-top: 20px; + padding-left: 20px; + padding-bottom: 20px; + width: 93%; +} + +.host_network_table_row { + padding-top: 5px; + padding-bottom: 5px; + border-bottom: 1px solid gray; +} + +.host_network_table_device_column { + float: left; + width: 37%; +} + +.host_network_table_network_column { + float: left; + width: 49%; +} + +.host_network_table_subcolumn { + margin-top: 4px; +} + +.host_network_table_subcolumn input { + margin-top: 4px; + text-align: right; + width: 140px; +} + +.host_network_table_buttons_column { + float: left; + width: 8%; +} + +.host_network_table_buttons_column img { + cursor: pointer; +} + +.host_network_table_select_net { + min-width: 230px; +} + +.host_network_table_select_nics { + min-width: 160px; +} + +.host_network_table_edit_column, +.host_network_table_delete_column { + padding-top: 7px; + padding-left: 5px; + float: left; +} + +.host_network_table_show_label { + padding-top: 7px; +} + +.host_network_table_edit_label input { + width: 110px; +} + +#host_network_table_add_bonding { + border: 1px solid black; + padding: 5px; + width: 150px; + cursor: pointer; +} +/* ***** */ -- 1.6.0.6 From jboggs at redhat.com Thu Oct 8 01:00:41 2009 From: jboggs at redhat.com (Joey Boggs) Date: Wed, 07 Oct 2009 21:00:41 -0400 Subject: [Ovirt-devel] [PATCH server] oVirt server single network installer In-Reply-To: <4AC0DAE0.4050808@redhat.com> References: <1253225298-24068-1-git-send-email-mmorsi@redhat.com> <4ABA9051.6040504@redhat.com> <4ABA9350.1060204@redhat.com> <4ABBA115.6070607@redhat.com> <4AC0DAE0.4050808@redhat.com> Message-ID: <4ACD39B9.9090803@redhat.com> On 09/28/2009 11:48 AM, Mohammed Morsi wrote: > Joey Boggs wrote: > >> M. Morsi wrote: >> >>> What does your httpd config look like? Where are you seeing the >>> redirects? >>> >>> -Mo >>> >>> >> Here's the ovirt-server.conf. Nothing looks abnormal that I can see >> so far, maybe it's just caching somewhere? >> >> http://ovirt.pastebin.com/m1ccbed27 >> > Your ovirt-server.conf is still setup for the dual network scenario. My > patch updates the installer to handle the case where admin network > interface == private network interace to write a different > ovirt-server.conf to the local machine. I tried it out myself and it > seemed to work, albiet it was on my appliance setup, the but installer > changes proceeded as expected and the correct ovirt-server.conf was written. > > Did you make sure to select the same interface for admin/guest networks > and run the installer on a machine w/ an ovirt-server-installer rpm w/ > my patch included? > > -Mo > ACK Looks like this worked for me now, ended up testing with chrome rather than firefox, guess caching caused a problem. From mloiseleur at linagora.com Thu Oct 8 19:40:46 2009 From: mloiseleur at linagora.com (Loiseleur Michel) Date: Thu, 8 Oct 2009 19:40:46 +0000 Subject: [Ovirt-devel] [PATCH] fix storages crazyness Message-ID: <1255030846-8053-1-git-send-email-mloiseleur@linagora.com> Signed-off-by: Loiseleur Michel --- app/models/vm.rb | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/app/models/vm.rb b/app/models/vm.rb index 88e0aef..0be3f89 100644 --- a/app/models/vm.rb +++ b/app/models/vm.rb @@ -27,7 +27,7 @@ class Vm < ActiveRecord::Base find(:all, :conditions=>{:state=>Task::STATE_QUEUED}) end end - has_and_belongs_to_many :storage_volumes + has_and_belongs_to_many :storage_volumes, :uniq => true has_many :nics, :dependent => :destroy -- 1.6.2.5 From nicolas.ochem at free.fr Fri Oct 9 16:29:04 2009 From: nicolas.ochem at free.fr (nicolas.ochem at free.fr) Date: Fri, 9 Oct 2009 18:29:04 +0200 (CEST) Subject: [Ovirt-devel] ovirt install : several issues In-Reply-To: <1076330585.13246831255105740835.JavaMail.root@zimbra2-e1.priv.proxad.net> Message-ID: <455807546.13246881255105744191.JavaMail.root@zimbra2-e1.priv.proxad.net> I installed ovirt-agent on a fresh fedora 11 install (not in a virtual machine). The only thing I modified prior to installation was /etc/resolv.conf in order to be able to yum update and install the ovirt packages via yum (from the ovirt repo), and /etc/ntp.conf to get the correct time from the NTP server of my network. I also disabled NetworkManager via chkconfig. I used all the options for ovirt-install exactly as recommended in the install guide in the homepage (yes for DNS, DHCP with PXE, cobbler) I am using one admin network and one guest network. Now the web interface is working, PXE booting is fine, however my nodes never appear on the web interface. I have this error message every 5 sec in /var/log/ovirt-server/ovirt-agent.log : ERROR Fri Oct 09 18:02:46 +0200 2009 (2434) ********** get_query DEBUG Fri Oct 09 18:02:46 +0200 2009 (2434) Query: context=338 class=agent object_id= DEBUG Fri Oct 09 18:02:46 +0200 2009 (2434) User ID: ERROR Fri Oct 09 18:02:46 +0200 2009 (2434) Error in ovirt-agent: Unknown class agent ERROR Fri Oct 09 18:02:46 +0200 2009 (2434) /usr/share/ovirt-server/ovirt-agent/ovirt-agent.rb:201:in `get_query' /usr/lib/ruby/site_ruby/1.8/qmf.rb:282:in `do_agent_events' /usr/lib/ruby/site_ruby/1.8/qmf.rb:320:in `do_events' /usr/lib/ruby/site_ruby/1.8/qmf.rb:342:in `sess_event_recv' /usr/lib/ruby/site_ruby/1.8/qmf.rb:121:in `run' /usr/lib/ruby/site_ruby/1.8/qmf.rb:83:in `initialize' /usr/lib/ruby/site_ruby/1.8/qmf.rb:82:in `new' /usr/lib/ruby/site_ruby/1.8/qmf.rb:82:in `initialize' /usr/share/ovirt-server/ovirt-agent/ovirt-agent.rb:146:in `new' /usr/share/ovirt-server/ovirt-agent/ovirt-agent.rb:146:in `initialize' /usr/share/ovirt-server/ovirt-agent/ovirt-agent.rb:283:in `new' /usr/share/ovirt-server/ovirt-agent/ovirt-agent.rb:283 And in tail /var/log/krb5kdc.log Oct 09 17:50:34 management.ovirt.priv krb5kdc[1902](info): AS_REQ (7 etypes {18 17 16 23 1 3 2}) 192.168.50.1: ISSUE: authtime 1255103434, etypes {rep=18 tkt=18 ses=18}, qpidd/management.ovirt.priv at OVIRT.PRIV for krbtgt/OVIRT.PRIV at OVIRT.PRIV Oct 09 17:50:48 management.ovirt.priv krb5kdc[1902](info): AS_REQ (7 etypes {18 17 16 23 1 3 2}) 192.168.50.1: NEEDED_PREAUTH: libvirt/management.ovirt.priv at OVIRT.PRIV for krbtgt/OVIRT.PRIV at OVIRT.PRIV, Additional pre-authentication required Oct 09 17:50:48 management.ovirt.priv krb5kdc[1902](info): AS_REQ (7 etypes {18 17 16 23 1 3 2}) 192.168.50.1: ISSUE: authtime 1255103448, etypes {rep=18 tkt=18 ses=18}, libvirt/management.ovirt.priv at OVIRT.PRIV for krbtgt/OVIRT.PRIV at OVIRT.PRIV Oct 09 17:50:53 management.ovirt.priv krb5kdc[1902](info): TGS_REQ (7 etypes {18 17 16 23 1 3 2}) 192.168.50.1: ISSUE: authtime 1255103448, etypes {rep=18 tkt=18 ses=18}, libvirt/management.ovirt.priv at OVIRT.PRIV for qpidd/management.ovirt.priv at OVIRT.PRIV I can monitor the booting of my node via serial console and here are the relevant error messages : First : Bringing up interface eth0: RTNETLINK answers: No such file or directory [ OK ] Bringing up interface breth0: Determining IP information for breth0... failed. [FAILED] This one is I assume because there is no DHCP server on the guest network. It shouldn't be a problem since the hardware node should not be reachable by the guest network ? I plan to configure the IP of my guests statically. Second : ntpdate: Synchronizing with time server: [FAILED] Starting ntpd: [ OK ] So the time does not synchronize with the management node. But I did put PEERNTP=yes in ifcfg-eth1 in my management node. Third : WARNING: Deprecated config file /etc/modprobe.conf, all config files belong into /etc/modprobe.d/. WARNING: Could not open '/lib/modules/2.6.30.5-43.fc11.x86_64/kernel/fs/exportfs/exportfs.ko': No such file or directory WARNING: Deprecated config file /etc/modprobe.conf, all config files belong into /etc/modprobe.d/. WARNING: Could not open '/lib/modules/2.6.30.5-43.fc11.x86_64/kernel/fs/exportfs/exportfs.ko': No such file or directory FATAL: Error inserting nfsd (/lib/modules/2.6.30.5-43.fc11.x86_64/kernel/fs/nfsd/nfsd.ko): Unknown symbol in module, or unknown parameter (see dmesg) FATAL: Error running install command for nfsd Starting NFS services: exportfs: can't open /var/lib/nfs/rmtab for reading exportfs: can't open /var/lib/nfs/xtab for reading I assume this is because I do not have a NFS server in the management node running yet. Last : oVirt Node release 1.0.2 Beta (1.fc11) Kernel 2.6.30.5-43.fc11.x86_64 on an x86_64 (/dev/ttyS0) localhost.localdomain login: So the node did not get DNS information since it should be named node#.ovirt.priv, I assume. Then I ran a wireshark on the management node eth1 because I was wondering which IP address it got. I noticed that the DHCP exchange prior to TFTP transfer was giving address 192.168.50.21 to the node. However, I cannot ping this address once the node is booted. Since there is no eth1 configuration message in the boot sequence of the node, I assume that the linux kernel does not know about this IP. I think the DHCP exchange should happen a second time : once for the BIOS, once for the linux kernel. This is all I can think about. I hope you can sort me out. Thanks in advance. From jboggs at redhat.com Fri Oct 9 17:51:20 2009 From: jboggs at redhat.com (Joey Boggs) Date: Fri, 9 Oct 2009 13:51:20 -0400 Subject: [Ovirt-devel] [PATCH node] validify ipv4/ipv6 static/dhcp choice else loop Message-ID: <1255110680-19004-1-git-send-email-jboggs@redhat.com> If you select an option that's not listed during ipv4/ipv6 setup it will accept it without verification. This corrects that behavior and forces a valid option to be picked. --- scripts/ovirt-config-networking | 106 +++++++++++++++++++++------------------ 1 files changed, 58 insertions(+), 48 deletions(-) diff --git a/scripts/ovirt-config-networking b/scripts/ovirt-config-networking index 7d4e363..45f7129 100755 --- a/scripts/ovirt-config-networking +++ b/scripts/ovirt-config-networking @@ -160,56 +160,66 @@ function configure_interface return;; esac - read -ep "Enable IPv4 support ([S]tatic IP, [D]HCP, [N]o or [A]bort)? " - case $REPLY in - D|d) - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" - ;; - S|s) - printf "\n" - read -ep "IP Address: "; IPADDR=$REPLY - read -ep " Netmask: "; NETMASK=$REPLY - read -ep " Gateway: "; GATEWAY=$REPLY - - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO none" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPADDR $IPADDR" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/NETMASK $NETMASK" - if [ -n "${GATEWAY}" ]; then - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/GATEWAY $GATEWAY" - fi - ;; - A|a) - CONFIGURED_NIC="" - VLAN_ID="" - return - ;; - esac + while true; do + read -ep "Enable IPv4 support ([S]tatic IP, [D]HCP, [N]o or [A]bort)? " + case $REPLY in + D|d) + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO dhcp" + break + ;; + S|s) + printf "\n" + read -ep "IP Address: "; IPADDR=$REPLY + read -ep " Netmask: "; NETMASK=$REPLY + read -ep " Gateway: "; GATEWAY=$REPLY + + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/BOOTPROTO none" + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPADDR $IPADDR" + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/NETMASK $NETMASK" + if [ -n "${GATEWAY}" ]; then + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/GATEWAY $GATEWAY" + fi + break + ;; + A|a) + CONFIGURED_NIC="" + VLAN_ID="" + return + ;; + esac + done printf "\n" - read -ep "Enable IPv6 support ([S]tatic, [D]HCPv6, A[u]to, [N]o or [A]bort)? " - case $REPLY in - S|s) - read -ep "IPv6 Address: "; IPADDR=$REPLY - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IP6ADDR $IPADDR" - ;; - D|d) - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6AUTCONF no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/DHCPV6C yes" - ;; - U|u) - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" - BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6AUTOCONF yes" - ;; - A|a) - CONFIGURED_NIC="" - VLAN_ID="" - return - ;; - esac + + while true; do + read -ep "Enable IPv6 support ([S]tatic, [D]HCPv6, A[u]to, [N]o or [A]bort)? " + case $REPLY in + S|s) + read -ep "IPv6 Address: "; IPADDR=$REPLY + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IP6ADDR $IPADDR" + break + ;; + D|d) + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6AUTCONF no" + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/DHCPV6C yes" + break + ;; + U|u) + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6INIT yes" + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6FORWARDING no" + BR_CONFIG="$BR_CONFIG\nset $BR_ROOT/IPV6AUTOCONF yes" + break + ;; + A|a) + CONFIGURED_NIC="" + VLAN_ID="" + return + ;; + esac + done printf "\n" ask_yes_or_no "Is this correct ([Y]es/[N]o/[A]bort)?" true true -- 1.6.2.5 From dpierce at redhat.com Mon Oct 12 13:11:51 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 12 Oct 2009 09:11:51 -0400 Subject: [Ovirt-devel] First draft: node storage admin Message-ID: <1255353112-5615-1-git-send-email-dpierce@redhat.com> This patch provides the ability to create a "dir" type storage pool, and to add and remove volumes for existing pools. From dpierce at redhat.com Mon Oct 12 13:11:52 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 12 Oct 2009 09:11:52 -0400 Subject: [Ovirt-devel] [PATCH node] Provides a new storage administration system to the managed node. In-Reply-To: <1255353112-5615-1-git-send-email-dpierce@redhat.com> References: <1255353112-5615-1-git-send-email-dpierce@redhat.com> Message-ID: <1255353112-5615-2-git-send-email-dpierce@redhat.com> Users can now: * Add a new storage pool. * Add a new storage volume. * Delete a storage volume. * List existing storage pools, with details. Signed-off-by: Darryl L. Pierce --- Makefile.am | 29 +++++--- nodeadmin/addpool.py | 129 +++++++++++++++++++++++++++++++++++ nodeadmin/addvolume.py | 160 ++++++++++++++++++++++++++++++++++++++++++++ nodeadmin/configscreen.py | 52 ++++++++++++++ nodeadmin/createmeter.py | 30 ++++++++ nodeadmin/definedomain.py | 15 +---- nodeadmin/libvirtworker.py | 41 ++++++++++-- nodeadmin/listpools.py | 63 +++++++++++++++++ nodeadmin/mainmenu.py | 24 ++++--- nodeadmin/poolconfig.py | 43 ++++++++++++ nodeadmin/removevolume.py | 76 +++++++++++++++++++++ nodeadmin/setup.py.in | 7 ++- nodeadmin/startpool.py | 62 +++++++++++++++++ nodeadmin/storagemenu.py | 59 ++++++++++++++++ nodeadmin/utils.py | 10 +++ nodeadmin/volumeconfig.py | 76 +++++++++++++++++++++ ovirt-node.spec.in | 45 ++++++++---- 17 files changed, 866 insertions(+), 55 deletions(-) create mode 100644 nodeadmin/addpool.py create mode 100644 nodeadmin/addvolume.py create mode 100644 nodeadmin/createmeter.py create mode 100644 nodeadmin/listpools.py create mode 100644 nodeadmin/poolconfig.py create mode 100644 nodeadmin/removevolume.py create mode 100644 nodeadmin/startpool.py create mode 100644 nodeadmin/storagemenu.py create mode 100644 nodeadmin/volumeconfig.py diff --git a/Makefile.am b/Makefile.am index abb7c33..85383d7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -27,30 +27,39 @@ EXTRA_DIST = \ images/grub-splash.xpm.gz \ images/syslinux-vesa-splash.jpg \ nodeadmin/__init__.py \ + nodeadmin/addpool.py \ + nodeadmin/addvolume.py \ nodeadmin/configscreen.py \ + nodeadmin/createdomain.py \ + nodeadmin/createmeter.py \ nodeadmin/createnetwork.py \ nodeadmin/createuser.py \ + nodeadmin/definedomain.py \ + nodeadmin/definenet.py \ nodeadmin/destroydomain.py \ nodeadmin/destroynetwork.py \ + nodeadmin/domainconfig.py \ nodeadmin/halworker.py \ nodeadmin/libvirtworker.py \ - nodeadmin/userworker.py \ + nodeadmin/listdomains.py \ + nodeadmin/listnetworks.py \ + nodeadmin/listpools.py \ nodeadmin/mainmenu.py \ nodeadmin/menuscreen.py \ nodeadmin/netmenu.py \ - nodeadmin/nodemenu.py \ - nodeadmin/undefinedomain.py \ - nodeadmin/undefinenetwork.py \ - nodeadmin/createdomain.py \ - nodeadmin/definedomain.py \ - nodeadmin/definenet.py \ - nodeadmin/domainconfig.py \ nodeadmin/networkconfig.py \ - nodeadmin/listdomains.py \ - nodeadmin/listnetworks.py \ nodeadmin/nodeadmin.py \ + nodeadmin/nodemenu.py \ + nodeadmin/poolconfig.py \ + nodeadmin/removevolume.py \ nodeadmin/setup.py \ + nodeadmin/startpool.py \ + nodeadmin/storagemenu.py \ + nodeadmin/undefinedomain.py \ + nodeadmin/undefinenetwork.py \ + nodeadmin/userworker.py \ nodeadmin/utils.py \ + nodeadmin/volumeconfig.py \ scripts/collectd.conf.in \ scripts/ovirt \ scripts/ovirt-awake \ diff --git a/nodeadmin/addpool.py b/nodeadmin/addpool.py new file mode 100644 index 0000000..9497e1c --- /dev/null +++ b/nodeadmin/addpool.py @@ -0,0 +1,129 @@ +# addstorage.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +import traceback +import utils + +from configscreen import * +from poolconfig import PoolConfig +from virtinst import Storage + +POOL_NAME_PAGE = 1 +DIR_TYPE_DETAILS = 2 +CONFIRM_PAGE = 99 + +class AddStoragePoolConfigScreen(ConfigScreen): + def __init__(self): + ConfigScreen.__init__(self, "Add A Storage Pool") + self.__config = PoolConfig() + + def get_elements_for_page(self, screen, page): + if page is POOL_NAME_PAGE: return self.get_pool_name_page(screen) + elif page is DIR_TYPE_DETAILS: return self.get_dir_type_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + return page < CONFIRM_PAGE + + def page_has_back(self, page): + return page > POOL_NAME_PAGE + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def get_next_page(self, page): + if page is POOL_NAME_PAGE: + if self.__config.get_type() is "dir": return DIR_TYPE_DETAILS + raise Exception("self.__config.get_type() = %s" % self.__config.get_type()) + elif page is DIR_TYPE_DETAILS: return CONFIRM_PAGE + + def get_back_page(self, page): + if page is DIR_TYPE_DETAILS: return POOL_NAME_PAGE + if page is CONFIRM_PAGE: + if self.__config.get_type() is "dir": return DIR_TYPE_DETAILS + + def validate_input(self, page, errors): + if page is POOL_NAME_PAGE: + if utils.string_is_not_blank(self.__name.value()): + if not self.get_libvirt().storage_pool_exists(self.__name.value()): + return True + else: + errors.append("Name '%s' already in use by another pool." % self.__name.value()) + else: + errors.append("Storage object name must be a string between 0 and 50 characters.") + elif page is DIR_TYPE_DETAILS: + if utils.string_is_not_blank(self.__target_path.value()): + if self.__target_path.value()[0:1] is '/': + return True + else: + errors.append("'%s' is not an absolute path." % self.__target_path.value()) + else: + errors.append("You must enter a target path.") + elif page is CONFIRM_PAGE: return True + return False + + def process_input(self, page): + if page is POOL_NAME_PAGE: + self.__config.set_name(self.__name.value()) + self.__config.set_type(self.__type.current()) + elif page is DIR_TYPE_DETAILS: + self.__config.set_target_path(self.__target_path.value()) + elif page is CONFIRM_PAGE: + self.get_libvirt().define_storage_pool(self.__config.get_name(), config = self.__config) + self.get_libvirt().create_storage_pool(self.__config.get_name()) + self.set_finished() + + def get_pool_name_page(self, screen): + self.__name = Entry(50, self.__config.get_name()) + self.__type = Listbox(0) + for type in Storage.StoragePool.get_pool_types(): + self.__type.append(Storage.StoragePool.get_pool_type_desc(type), type) + if self.__config.get_type() is not None: + self.__type.setCurrent(self.__config.get_type()) + grid = Grid(2, 2) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(self.__name, 1, 0, anchorLeft = 1) + grid.setField(Label("Type:"), 0, 1, anchorRight = 1, anchorTop = 1) + grid.setField(self.__type, 1, 1, anchorLeft = 1) + return [Label("Add Storage Pool"), + grid] + + def get_dir_type_page(self, screen): + self.__target_path = Entry(50, self.__config.get_target_path()) + grid = Grid(2, 1) + grid.setField(Label("Target Path:"), 0, 0, anchorRight = 1) + grid.setField(self.__target_path, 1, 0, anchorLeft = 1) + return self.get_location_page(grid) + + def get_location_page(self, grid): + return [Label("Specify a storage location to be later split into virtual machine storage"), + grid] + + def get_confirm_page(self, screen): + grid = Grid(2, 2) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(Label(self.__config.get_name()), 1, 0, anchorLeft = 1) + grid.setField(Label("Target Path:"), 0, 1, anchorRight = 1) + grid.setField(Label(self.__config.get_target_path()), 1, 1, anchorLeft = 1) + return [Label("Confirm Pool Details"), + grid] + +def AddStoragePool(): + screen = AddStoragePoolConfigScreen() + screen.start() diff --git a/nodeadmin/addvolume.py b/nodeadmin/addvolume.py new file mode 100644 index 0000000..82c014c --- /dev/null +++ b/nodeadmin/addvolume.py @@ -0,0 +1,160 @@ +# addvolume.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +import traceback + +from createmeter import CreateMeter +from configscreen import * +from volumeconfig import StorageVolumeConfig +from utils import * + +SELECT_POOL_PAGE = 1 +VOLUME_NAME_PAGE = 2 +VOLUME_FORMAT_PAGE = 3 +MAX_CAPACITY_PAGE = 4 +CONFIRM_PAGE = 5 + +class AddVolumeConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Add A New Storage Volume") + self.__config = StorageVolumeConfig() + + def get_elements_for_page(self, screen, page): + if page is SELECT_POOL_PAGE: return self.get_storage_pool_list_page(screen) + elif page is VOLUME_NAME_PAGE: return self.get_volume_name_page(screen) + elif page is VOLUME_FORMAT_PAGE: return self.get_volume_format_page(screen) + elif page is MAX_CAPACITY_PAGE: return self.get_max_capacity_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + if page is SELECT_POOL_PAGE: + return self.has_selectable_pools() + else: + if page < CONFIRM_PAGE: return True + return False + + def page_has_back(self, page): + if page > SELECT_POOL_PAGE: return True + return False + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def validate_input(self, page, errors): + if page is SELECT_POOL_PAGE: + if self.get_selected_pool() is not None: + return True + else: + errors.append("You must select a storage pool.") + elif page is VOLUME_NAME_PAGE: + if string_is_not_blank(self.__name.value()): + return True + else: + errors.append("Storage object name can only contain alphanumeric, '_', '.', or '-' characters.") + elif page is VOLUME_FORMAT_PAGE: + if self.__formats.current() is not None: + return True + else: + errors.append("You must select a volume format.") + elif page is MAX_CAPACITY_PAGE: + if string_is_not_blank(self.__capacity.value()): + if string_is_not_blank(self.__allocation.value()): + capacity = int(self.__capacity.value()) + allocation = int(self.__allocation.value()) + if capacity > 0: + if capacity <= self.__config.get_pool().info()[3] / 1024**2: + if allocation >= 0: + if allocation <= capacity: + return True + else: + errors.append("Allocation cannot exceed the maximum capacity.") + else: + errors.append("The allocation must be greater than or equal to 0.") + else: + errors.append("The maximum capacity cannot exceed the storage pool size.") + else: + errors.append("The capacity must be greater than zero.") + else: + errors.append("An allocation value must be entered.") + else: + errors.append("A maximum volume capacity must be entered.") + elif page is CONFIRM_PAGE: return True + return False + + def process_input(self, page): + if page is SELECT_POOL_PAGE: + self.__config.set_pool(self.get_libvirt().get_storage_pool(self.get_selected_pool())) + elif page is VOLUME_NAME_PAGE: + self.__config.set_name(self.__name.value()) + elif page is VOLUME_FORMAT_PAGE: + self.__config.set_format(self.__formats.current()) + elif page is MAX_CAPACITY_PAGE: + self.__config.set_max_capacity(int(self.__capacity.value())) + self.__config.set_allocation(int(self.__allocation.value())) + elif page is CONFIRM_PAGE: + self.get_libvirt().define_storage_volume(self.__config, CreateMeter()) + self.set_finished() + + def get_volume_name_page(self, screen): + self.__name = Entry(50, self.__config.get_name()) + grid = Grid(2, 1) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(self.__name, 1, 0, anchorLeft = 1) + return [Label("New Storage Volume"), + grid, + Label("Name of the volume to create. File extension may be appended.")] + + def get_volume_format_page(self, screen): + self.__formats = Listbox(0) + for format in self.__config.get_formats_for_pool(): + self.__formats.append(format, format) + grid = Grid(1, 1) + grid.setField(self.__formats, 0, 0) + return [Label("Select The Volume Format"), + grid] + + def get_max_capacity_page(self, screen): + self.__capacity = Entry(6, str(self.__config.get_max_capacity())) + self.__allocation = Entry(6, str(self.__config.get_allocation())) + grid = Grid(2, 2) + grid.setField(Label("Max. Capacity (MB):"), 0, 0, anchorRight = 1) + grid.setField(self.__capacity, 1, 0, anchorLeft = 1) + grid.setField(Label("Allocation (MB):"), 0, 1, anchorRight = 1) + grid.setField(self.__allocation, 1, 1, anchorLeft = 1) + return [Label("Storage Volume Quota"), + Label("%s's available space: %0.2f GB" % (self.__config.get_pool().name(), + self.__config.get_pool().info()[3] / 1024.0**3)), + grid] + + def get_confirm_page(self, screen): + grid = Grid(2, 5) + grid.setField(Label("Volume Name:"), 0, 0, anchorRight = 1) + grid.setField(Label("%s (%s)" % (self.__config.get_name(), self.__config.get_pool().name())), 1, 0, anchorLeft = 1) + grid.setField(Label("Format:"), 0, 1, anchorRight = 1) + grid.setField(Label(self.__config.get_format()), 1, 1, anchorLeft = 1) + grid.setField(Label("Max. Capacity:"), 0, 2, anchorRight = 1) + grid.setField(Label("%0.2f GB" % (self.__config.get_max_capacity() / 1024.0)), 1, 2, anchorLeft = 1) + grid.setField(Label("Allocation:"), 0, 3, anchorRight = 1) + grid.setField(Label("%0.2f GB" % (self.__config.get_allocation() / 1024.0)), 1, 3, anchorLeft = 1) + return [Label("Ready To Allocation New Storage Volume"), + grid] + +def AddStorageVolume(): + screen = AddVolumeConfigScreen() + screen.start() diff --git a/nodeadmin/configscreen.py b/nodeadmin/configscreen.py index f214aea..7654697 100644 --- a/nodeadmin/configscreen.py +++ b/nodeadmin/configscreen.py @@ -179,3 +179,55 @@ class NetworkListConfigScreen(ConfigScreen): def has_selectable_networks(self): return self.__has_networks + +class StorageListConfigScreen(ConfigScreen): + '''Provides a base class for any configuration screen that deals with storage pool lists.''' + + def __init__(self, title): + ConfigScreen.__init__(self, title) + + def get_storage_pool_list_page(self, screen, defined=True, created=True): + pools = self.get_libvirt().list_storage_pools(defined=defined, created=created) + if len(pools) > 0: + self.__has_pools = True + self.__pools_list = Listbox(0) + for pool in pools: + self.__pools_list.append(pool, pool) + result = self.__pools_list + else: + self.__has_pools = False + result = Label("There are no storage pools available.") + grid = Grid(1, 1) + grid.setField(result, 0, 0) + return [Label("Storage Pool List"), + grid] + + def get_selected_pool(self): + return self.__pools_list.current() + + def has_selectable_pools(self): + return self.__has_pools + + def get_storage_volume_list_page(self, screen): + '''Requires that self.__pools_list have a selected element.''' + pool = self.get_libvirt().get_storage_pool(self.get_selected_pool()) + if len(pool.listVolumes()) > 0: + self.__has_volumes = True + self.__volumes_list = Listbox(0) + for volname in pool.listVolumes(): + volume = pool.storageVolLookupByName(volname) + self.__volumes_list.append("%s (%0.2f GB)" % (volume.name(), volume.info()[2] / 1024**3), volume.name()) + result = self.__volumes_list + else: + self.__has_volumes = False + result = Label("There are no storage volumes available.") + grid = Grid(1, 1) + grid.setField(result, 0, 0) + return [Label("Storage Volume List"), + grid] + + def get_selected_volume(self): + return self.__volumes_list.current() + + def has_selectable_volumes(self): + return self.__has_volumes diff --git a/nodeadmin/createmeter.py b/nodeadmin/createmeter.py new file mode 100644 index 0000000..521e7d8 --- /dev/null +++ b/nodeadmin/createmeter.py @@ -0,0 +1,30 @@ +# createmeter.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +import urlgrabber.progress as progress +import logging + +class CreateMeter(progress.BaseMeter): + def _do_start(self, now = None): + logging.info("Starting...") + + def _do_end(self, amount_read, now = None): + logging.info("Ending: read=%d" % amount_read) + + def _do_update(self, amount_read, now = None): + logging.info("Update: read=%d" % amount_read) diff --git a/nodeadmin/definedomain.py b/nodeadmin/definedomain.py index 6a6612c..067f9e8 100755 --- a/nodeadmin/definedomain.py +++ b/nodeadmin/definedomain.py @@ -20,11 +20,10 @@ from snack import * import os +from createmeter import CreateMeter from domainconfig import DomainConfig from configscreen import ConfigScreen -import urlgrabber.progress as progress import utils -import logging from virtinst import * @@ -51,16 +50,6 @@ OS_VARIANT="os.variant" MEMORY="memory" CPUS="cpus" -class DummyMeter(progress.BaseMeter): - def _do_start(self, now = None): - logging.info("Starting...") - - def _do_end(self, amount_read, now = None): - logging.info("Ending: read=%d" % amount_read) - - def _do_update(self, amount_read, now = None): - logging.info("Update: read=%d" % amount_read) - class DomainConfigScreen(ConfigScreen): def __init__(self): ConfigScreen.__init__(self, "Create A New Virtual Machine") @@ -212,7 +201,7 @@ class DomainConfigScreen(ConfigScreen): self.__config.set_virt_type(self.__virt_types.getSelection()) self.__config.set_architecture(self.__architectures.getSelection()) elif page == CONFIRM_PAGE: - self.get_libvirt().define_domain(self.__config, DummyMeter()) + self.get_libvirt().define_domain(self.__config, CreateMeter()) self.set_finished() def get_back_page(self, page): diff --git a/nodeadmin/libvirtworker.py b/nodeadmin/libvirtworker.py index ba07605..01ad689 100644 --- a/nodeadmin/libvirtworker.py +++ b/nodeadmin/libvirtworker.py @@ -134,9 +134,12 @@ class LibvirtWorker: network = self.get_network(name) network.undefine() - def list_storage_pools(self): + def list_storage_pools(self, defined=True, created=True): '''Returns the list of all defined storage pools.''' - return self.__conn.listStoragePools() + pools = [] + if defined: pools.extend(self.__conn.listDefinedStoragePools()) + if created: pools.extend(self.__conn.listStoragePools()) + return pools def storage_pool_exists(self, name): '''Returns whether a storage pool exists.''' @@ -144,16 +147,42 @@ class LibvirtWorker: if name in pools: return True return False - def define_storage_pool(self, name): + def create_storage_pool(self, name): + '''Starts the named storage pool if it is not currently started.''' + if name not in self.list_storage_pools(defined = False): + pool = self.get_storage_pool(name) + pool.create(0) + + def define_storage_pool(self, name, config = None, meter = None): '''Defines a storage pool with the given name.''' - try: + if config is None: pool = virtinst.Storage.DirectoryPool(conn=self.__conn, name=name, target_path=DEFAULT_POOL_TARGET_PATH) newpool = pool.install(build=True, create=True) newpool.setAutostart(True) - except Exception, error: - raise RuntimeError("Could not create pool: %s - %s", str(error)) + else: + pool_class = virtinst.Storage.StoragePool.get_pool_class(config.get_type()) + pool = pool_class(name = name, conn = self.__conn) + pool.target_path = config.get_target_path() + poolobj = pool.install(meter = meter, build = True) + poolobj.setAutostart(True) + + def get_storage_pool(self, name): + '''Returns the storage pool with the specified name.''' + return self.__conn.storagePoolLookupByName(name) + + def define_storage_volume(self, config, meter): + '''Defines a new storage volume.''' + self.create_storage_pool(config.get_pool().name()) + volume = config.create_volume() + volume.install(meter = meter) + + def remove_storage_volume(self, poolname, volumename): + '''Removes the specified storage volume.''' + pool = self.get_storage_pool(poolname) + volume = pool.storageVolLookupByName(volumename) + volume.delete(0) def list_bridges(self): '''Lists all defined and active bridges.''' diff --git a/nodeadmin/listpools.py b/nodeadmin/listpools.py new file mode 100644 index 0000000..686c42d --- /dev/null +++ b/nodeadmin/listpools.py @@ -0,0 +1,63 @@ +# listpools.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * + +from configscreen import * + +LIST_PAGE = 1 +DETAILS_PAGE = 2 + +class ListStoragePoolsConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "List Storage Pools") + + def get_elements_for_page(self, screen, page): + if page is LIST_PAGE: return self.get_storage_pool_list_page(screen) + elif page is DETAILS_PAGE: return self.get_pool_details_page(screen) + + def page_has_next(self, page): + if page is LIST_PAGE and self.has_selectable_pools(): + return True + return False + + def page_has_back(self, page): + if page is DETAILS_PAGE: return True + return False + + def get_pool_details_page(self, screen): + pool = self.get_libvirt().get_storage_pool(self.get_selected_pool()) + volumes = Listbox(0); + for name in pool.listVolumes(): + volume = pool.storageVolLookupByName(name) + volumes.append("%s (%0.1f G)" % (name, volume.info()[1] / 1024**3), name) + grid = Grid(2, 3) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(Label(pool.name()), 1, 0, anchorLeft = 1) + grid.setField(Label("Volumes:"), 0, 1, anchorRight = 1) + grid.setField(volumes, 1, 1, anchorLeft = 1) + grid.setField(Label("Autostart:"), 0, 2, anchorRight = 1) + label = "No" + if pool.autostart(): label = "Yes" + grid.setField(Label(label), 1, 2, anchorLeft = 1) + return [Label("Details For Storage Pool: %s" % self.get_selected_pool()), + grid] + +def ListStoragePools(): + screen = ListStoragePoolsConfigScreen() + screen.start() diff --git a/nodeadmin/mainmenu.py b/nodeadmin/mainmenu.py index 73501fa..52d9298 100755 --- a/nodeadmin/mainmenu.py +++ b/nodeadmin/mainmenu.py @@ -19,28 +19,32 @@ from snack import * import traceback -from menuscreen import MenuScreen -from nodemenu import NodeMenu -from netmenu import NetworkMenu +from menuscreen import MenuScreen +from nodemenu import NodeMenu +from netmenu import NetworkMenu +from storagemenu import StoragePoolMenu import utils import logging NODE_MENU = 1 NETWORK_MENU = 2 -EXIT_CONSOLE = 99 +STORAGE_MENU = 3 +EXIT_CONSOLE = 4 class MainMenuScreen(MenuScreen): def __init__(self): MenuScreen.__init__(self, "Main Menu") def get_menu_items(self): - return (("Node Administration", NODE_MENU), - ("Network Administration", NETWORK_MENU)) - - def handle_selection(self, page): - if page is NODE_MENU: NodeMenu() - elif page is NETWORK_MENU: NetworkMenu() + return (("Node Administration", NODE_MENU), + ("Network Administration", NETWORK_MENU), + ("Storage Pool Administration", STORAGE_MENU)) + + def handle_selection(self, item): + if item is NODE_MENU: NodeMenu() + elif item is NETWORK_MENU: NetworkMenu() + elif item is STORAGE_MENU: StoragePoolMenu() def MainMenu(): screen = MainMenuScreen() diff --git a/nodeadmin/poolconfig.py b/nodeadmin/poolconfig.py new file mode 100644 index 0000000..4f8b4c4 --- /dev/null +++ b/nodeadmin/poolconfig.py @@ -0,0 +1,43 @@ +# poolconfig.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +ROOT_TARGET_PATH="/var/lib/libvirt/images/%s" + +class PoolConfig: + def __init__(self): + self.__name = "" + self.__type = None + + def set_name(self, name): + self.__name = name + + def get_name(self): + return self.__name + + def set_type(self, type): + self.__type = type + if type is "dir": self.__target_path = ROOT_TARGET_PATH % self.__name + + def get_type(self): + return self.__type + + def set_target_path(self, path): + self.__target_path = path + + def get_target_path(self): + return self.__target_path diff --git a/nodeadmin/removevolume.py b/nodeadmin/removevolume.py new file mode 100644 index 0000000..5ad3058 --- /dev/null +++ b/nodeadmin/removevolume.py @@ -0,0 +1,76 @@ +# removevolume.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +import traceback + +from createmeter import CreateMeter +from configscreen import * +from volumeconfig import StorageVolumeConfig +from utils import * + +SELECT_POOL_PAGE = 1 +SELECT_VOLUME_PAGE = 2 +CONFIRM_PAGE = 3 + +class RemoveVolumeConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Add A New Storage Volume") + self.__config = StorageVolumeConfig() + + def get_elements_for_page(self, screen, page): + if page is SELECT_POOL_PAGE: return self.get_storage_pool_list_page(screen) + elif page is SELECT_VOLUME_PAGE: return self.get_storage_volume_list_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + if page is SELECT_POOL_PAGE: return self.has_selectable_pools() + elif page is SELECT_VOLUME_PAGE: return self.has_selectable_volumes() + return False + + def validate_input(self, page, errors): + if page is SELECT_POOL_PAGE: return self.get_selected_pool() is not None + elif page is SELECT_VOLUME_PAGE: return self.get_selected_volume() is not None + elif page is CONFIRM_PAGE: + if self.__confirm.value(): + return True + else: + errors.append("You must confirm deleting a storage volume.") + return False + + def process_input(self, page): + if page is CONFIRM_PAGE: + self.get_libvirt().remove_storage_volume(self.get_selected_pool(), self.get_selected_volume()) + self.set_finished() + + def page_has_back(self, page): + return page > SELECT_POOL_PAGE + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def get_confirm_page(self, screen): + self.__confirm = Checkbox("Check here to confirm deleting volume: %s" % self.get_selected_volume()) + grid = Grid(1, 1) + grid.setField(self.__confirm, 0, 0) + return [Label("Remove Selected Storage Volume"), + grid] + +def RemoveStorageVolume(): + screen = RemoveVolumeConfigScreen() + screen.start() diff --git a/nodeadmin/setup.py.in b/nodeadmin/setup.py.in index 3635810..c7af07e 100644 --- a/nodeadmin/setup.py.in +++ b/nodeadmin/setup.py.in @@ -35,5 +35,10 @@ setup(name = "nodeadmin", 'createnet = nodeadmin.createnetwork:CreateNetwork', 'destroynet = nodeadmin.destroynetwork:DestroyNetwork', 'undefinenet = nodeadmin.undefinenetwork:UndefineNetwork', - 'listnets = nodeadmin.listnetworks:ListNetworks'] + 'listnets = nodeadmin.listnetworks:ListNetworks', + 'addpool = nodeadmin.addpool:AddStoragePool', + 'startpool = nodeadmin.startpool:StartStoragePool', + 'addvolume = nodeadmin.addvolume:AddStorageVolume', + 'listpools = nodeadmin.listpools:ListPools', + 'rmvolume = nodeadmin.removevolume:RemoveStorageVolume'] }) diff --git a/nodeadmin/startpool.py b/nodeadmin/startpool.py new file mode 100644 index 0000000..8a84512 --- /dev/null +++ b/nodeadmin/startpool.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# +# startpool.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +from configscreen import * + +LIST_POOLS_PAGE = 1 +FINAL_PAGE = 2 + +class StartStoragePoolConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Start A Storage Pool") + + def get_elements_for_page(self, screen, page): + if page is LIST_POOLS_PAGE: return self.get_storage_pool_list_page(screen, created = False) + elif page is FINAL_PAGE: return self.get_final_page(screen) + + def page_has_next(self, page): + return page is LIST_POOLS_PAGE and self.has_selectable_pools() + + def page_has_back(self, page): + return False + + def page_has_finish(self, page): + return page is FINAL_PAGE + + def validate_input(self, page, errors): + if page is LIST_POOLS_PAGE: + if self.get_selected_pool() is not None: + return True + else: + errors.append("Please select a storage pool to be started.") + return False + + def process_input(self, page): + if page is LIST_POOLS_PAGE: + self.get_libvirt().create_storage_pool(self.get_selected_pool()) + self.set_finished() + + def get_final_page(self, screen): + return [Label("Storage pool started: %s" % self.get_selected_pool())] + +def StartStoragePool(): + screen = StartStoragePoolConfigScreen() + screen.start() diff --git a/nodeadmin/storagemenu.py b/nodeadmin/storagemenu.py new file mode 100644 index 0000000..a50ed78 --- /dev/null +++ b/nodeadmin/storagemenu.py @@ -0,0 +1,59 @@ +# storagemenu.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +import traceback + +from menuscreen import MenuScreen +from addpool import AddStoragePool +from startpool import StartStoragePool +from addvolume import AddStorageVolume +from removevolume import RemoveStorageVolume +from listpools import ListStoragePools + +ADD_POOL = 1 +ADD_VOLUME = 2 +REMOVE_VOLUME = 3 +START_POOL = 4 +STOP_POOL = 5 +UNADD_POOL = 6 +LIST_POOLS = 7 + +class StoragePoolMenuScreen(MenuScreen): + def __init__(self): + MenuScreen.__init__(self, "Storage Pool Administration") + + def get_menu_items(self): + return (("Add A Storage Pool", ADD_POOL), + ("Start A Storage Pool", START_POOL), + ("Stop A Storage Pool", STOP_POOL), + ("Add A Storage Volume", ADD_VOLUME), + ("Remove A Storage Volume", REMOVE_VOLUME), + ("Unadd A Storage Pool", UNADD_POOL), + ("List Storage Pools", LIST_POOLS)) + + def handle_selection(self, item): + if item is ADD_POOL: AddStoragePool() + elif item is START_POOL: StartStoragePool() + elif item is ADD_VOLUME: AddStorageVolume() + elif item is REMOVE_VOLUME: RemoveStorageVolume() + elif item is LIST_POOLS: ListStoragePools() + +def StoragePoolMenu(): + screen = StoragePoolMenuScreen() + screen.start() diff --git a/nodeadmin/utils.py b/nodeadmin/utils.py index 55a838c..28ccb8b 100644 --- a/nodeadmin/utils.py +++ b/nodeadmin/utils.py @@ -17,9 +17,19 @@ # also available at http://www.gnu.org/copyleft/gpl.html. import logging +import re logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', filename='/var/log/ovirt-nodeadmin.log', filemode='w') + +def string_is_not_blank(value): + if len(value) > 0: return True + return False + +def string_has_no_spaces(value): + if re.match("^[a-zA-Z0-9_]*$", value): + return True + return False diff --git a/nodeadmin/volumeconfig.py b/nodeadmin/volumeconfig.py new file mode 100644 index 0000000..7741391 --- /dev/null +++ b/nodeadmin/volumeconfig.py @@ -0,0 +1,76 @@ +# volumeconfig.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +import virtinst +from virtinst import Storage + +class StorageVolumeConfig: + def __init__(self): + self.__pool = None + self.__name = "" + self.__formats = None + self.__format = None + self.__max_capacity = 10000 + self.__allocation = 0 + + def set_pool(self, pool): + self.__pool = pool + self.__formats = None + self.__pool_type = virtinst.util.get_xml_path(self.__pool.XMLDesc(0), '/pool/@type') + self.__volume_class = Storage.StoragePool.get_volume_for_pool(self.__pool_type) + + def get_pool(self): + return self.__pool + + def create_volume(self): + volume = self.__volume_class(name = self.__name + ".img", + allocation = self.__allocation * 1024**2, + capacity = self.__max_capacity * 1024**2, + pool = self.__pool) + volume.pool = self.__pool + volume.format = self.__format + return volume + + def set_name(self, name): + self.__name = name + + def get_name(self): + return self.__name + + def get_formats_for_pool(self): + if self.__formats is None: + self.__formats = self.__volume_class.formats + return self.__formats + + def set_format(self, format): + self.__format = format + + def get_format(self): + return self.__format + + def set_max_capacity(self, capacity): + self.__max_capacity = capacity + + def get_max_capacity(self): + return self.__max_capacity + + def set_allocation(self, allocation): + self.__allocation = allocation + + def get_allocation(self): + return self.__allocation diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index 2a6b7b6..c52eb02 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -182,23 +182,33 @@ cd - %{__install} -p -m0755 nodeadmin/nodeadmin.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/mainmenu.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0644 nodeadmin/nodemenu.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/definedomain.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/createdomain.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/definedomain.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/destroydomain.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/undefinedomain.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/listdomains.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/domainconfig.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/listdomains.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/nodemenu.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/undefinedomain.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0644 nodeadmin/netmenu.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0644 nodeadmin/networkconfig.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/definenet.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/createnetwork.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/definenet.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/destroynetwork.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/netmenu.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/networkconfig.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/undefinenetwork.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/addpool.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/addvolume.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/listpools.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/poolconfig.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/removevolume.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/startpool.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/storagemenu.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/volumeconfig.py %{buildroot}%{python_sitelib}/nodeadmin + %{__install} -p -m0755 nodeadmin/createuser.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/createmeter.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/halworker.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/libvirtworker.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/userworker.py %{buildroot}%{python_sitelib}/nodeadmin @@ -368,18 +378,23 @@ fi %{_sbindir}/ovirt-awake %{_initrddir}/ovirt-functions %defattr(-,root,root,0644) -%{_bindir}/nodeadmin -%{_bindir}/definedom +%{_bindir}/addpool +%{_bindir}/addvolume %{_bindir}/createdom -%{_bindir}/destroydom -%{_bindir}/undefinedom -%{_bindir}/listdoms -%{_bindir}/definenet %{_bindir}/createnet +%{_bindir}/createuser +%{_bindir}/definedom +%{_bindir}/definenet +%{_bindir}/destroydom %{_bindir}/destroynet -%{_bindir}/undefinenet +%{_bindir}/listdoms %{_bindir}/listnets -%{_bindir}/createuser +%{_bindir}/listpools +%{_bindir}/nodeadmin +%{_bindir}/rmvolume +%{_bindir}/startpool +%{_bindir}/undefinedom +%{_bindir}/undefinenet %{_sysconfdir}/collectd.conf.in %{python_sitelib}/nodeadmin %{python_sitelib}/nodeadmin- at VERSION@-py2.6.egg-info -- 1.6.2.5 From jboggs at redhat.com Mon Oct 12 16:39:33 2009 From: jboggs at redhat.com (Joey Boggs) Date: Mon, 12 Oct 2009 12:39:33 -0400 Subject: [Ovirt-devel] [PATCH node] Provides a new storage administration system to the managed node. In-Reply-To: <1255353112-5615-2-git-send-email-dpierce@redhat.com> References: <1255353112-5615-1-git-send-email-dpierce@redhat.com> <1255353112-5615-2-git-send-email-dpierce@redhat.com> Message-ID: <4AD35BC5.9080100@redhat.com> Ran through the list of options, here's the comments per action. Guessing that some of the things I ran into were intended as it's not fully implmented just yet. add storage pool - need to validate input, once the pool is attempted to be created libvirt raises an exception on accepting names with just numbers start a storage pool - no pools listed, even though "list storage pools" shows one stop a storage pool - returns right to storage menu, no selection prompted unadd a storage pool - returns right to storage menu, no selection prompted - rename to "remove"? Other than those it seems to work for me and covers pretty much anything an admin would need to do. From dpierce at redhat.com Mon Oct 12 17:16:47 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 12 Oct 2009 13:16:47 -0400 Subject: [Ovirt-devel] [PATCH node] Provides a new storage administration system to the managed node. In-Reply-To: <4AD35BC5.9080100@redhat.com> References: <1255353112-5615-1-git-send-email-dpierce@redhat.com> <1255353112-5615-2-git-send-email-dpierce@redhat.com> <4AD35BC5.9080100@redhat.com> Message-ID: <20091012171647.GH3711@mcpierce-laptop.rdu.redhat.com> On Mon, Oct 12, 2009 at 12:39:33PM -0400, Joey Boggs wrote: > Ran through the list of options, here's the comments per action. > Guessing that some of the things I ran into were intended as it's not > fully implmented just yet. > > add storage pool > - need to validate input, once the pool is attempted to be created > libvirt raises an exception on accepting names with just numbers Okay, that's cool. I'll dig into virt-manager's logic and see what else I can scrape out to the model. > start a storage pool > - no pools listed, even though "list storage pools" shows one It only shows pools that are defined but not created. Was the one pool on the list page already started? > stop a storage pool > - returns right to storage menu, no selection prompted Not implemented yet. > unadd a storage pool > - returns right to storage menu, no selection prompted > - rename to "remove"? Yeah, I had done a search-and-replace of "Define" with "Add" and missed that "Undefine" there. ;) > Other than those it seems to work for me and covers pretty much anything > an admin would need to do. I'm implementing the additional pool types now and will have a more complete patch shortly. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From jboggs at redhat.com Tue Oct 13 19:14:21 2009 From: jboggs at redhat.com (Joey Boggs) Date: Tue, 13 Oct 2009 15:14:21 -0400 Subject: [Ovirt-devel] [PATCH server] remove init scripts from being logrotated Message-ID: <1255461261-17005-1-git-send-email-jboggs@redhat.com> ovirt-host-register and ovirt-host-collect init scripts are being set as log files and are being rotated, this removes them from the list. --- conf/ovirt-server.logrotate | 3 --- 1 files changed, 0 insertions(+), 3 deletions(-) diff --git a/conf/ovirt-server.logrotate b/conf/ovirt-server.logrotate index 3239783..d7c3b0c 100644 --- a/conf/ovirt-server.logrotate +++ b/conf/ovirt-server.logrotate @@ -4,9 +4,6 @@ compress missingok notifempty -/etc/init.d/ovirt-host-register -/etc/init.d/ovirt-host-collect - /var/log/ovirt-server/taskomatic.log { postrotate /etc/init.d/ovirt-taskomatic restart -- 1.6.2.5 From mmorsi at redhat.com Wed Oct 14 00:20:10 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Tue, 13 Oct 2009 20:20:10 -0400 Subject: [Ovirt-devel] [PATCH server] hack betternested set to enable pool deletion Message-ID: <1255479610-24158-1-git-send-email-mmorsi@redhat.com> This is an ugly hack to fix the betternestedset plugin as currently a bug does not allow pools to be deleted. The single line commented out in this patch deletes the pool itself instead of only its children for whatever reason. This wouldn't be a problem were it not for the recent change to active record which enforces optimistic locking upon destroy operations. Since the database record is mistakingly deleted in betternestedset, via the 'before_destroy' hook, the record does not exist when the activerecord destroy operation is actually performed and thus an exception is thrown at /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/locking/optimistic.rb line 100 Note this patch only works since we explicitly disable a hw pool from being deleted unless all its children are in the oVirt hardware_pool_server. It is ugly but works until something more elegant can be found. --- .../betternestedset/lib/better_nested_set.rb | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/src/vendor/plugins/betternestedset/lib/better_nested_set.rb b/src/vendor/plugins/betternestedset/lib/better_nested_set.rb index 9efbf25..ea8aea3 100644 --- a/src/vendor/plugins/betternestedset/lib/better_nested_set.rb +++ b/src/vendor/plugins/betternestedset/lib/better_nested_set.rb @@ -430,7 +430,7 @@ module SymetrieCom return unless reloaded dif = self[right_col_name] - self[left_col_name] + 1 if acts_as_nested_set_options[:dependent] == :delete_all - base_set_class.delete_all( "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})" ) + #base_set_class.delete_all( "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})" ) else set = base_set_class.find(:all, :conditions => "#{scope_condition} AND (#{prefixed_left_col_name} BETWEEN #{self[left_col_name]} AND #{self[right_col_name]})", :order => "#{prefixed_right_col_name} DESC") set.each { |child| child.skip_before_destroy = true; remove_descendant(child) } -- 1.6.0.6 From ignazio.cassano at provincia.torino.it Wed Oct 14 10:17:58 2009 From: ignazio.cassano at provincia.torino.it (ignazio.cassano at provincia.torino.it) Date: Wed, 14 Oct 2009 12:17:58 +0200 Subject: [Ovirt-devel] ovirt web interface 500 Internal Server error Message-ID: <20091014121758.0qdpeft178ooc8wo@webmail.provincia.torino.it> Hello, I built ovirt this morning and ovirt web interface does not work anymore. I am able to login but only dashboard and networks are visible... no default hardware pool is visible. On /var/log/httpd/error.log I read: /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/connection_adapters/abstract_adapter.rb:212:in `log': PGError: ERROR: relation "pools" does not exist (ActiveRecord::StatementInvalid) Thanks From mmorsi at redhat.com Wed Oct 14 19:03:59 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 14 Oct 2009 15:03:59 -0400 Subject: [Ovirt-devel] [PATCH] Fix refreshing vms list This patch fix vms index view when a smart pool is not destroyed correctly. In-Reply-To: <1254833015-19807-1-git-send-email-mloiseleur@linagora.com> References: <1254833015-19807-1-git-send-email-mloiseleur@linagora.com> Message-ID: <4AD6209F.7070608@redhat.com> Loiseleur Michel wrote: > Signed-off-by: Loiseleur Michel > --- > app/models/smart_pool.rb | 10 ++++------ > 1 files changed, 4 insertions(+), 6 deletions(-) > > diff --git a/app/models/smart_pool.rb b/app/models/smart_pool.rb > index 1f718a8..9afc354 100644 > --- a/app/models/smart_pool.rb > +++ b/app/models/smart_pool.rb > @@ -62,18 +62,16 @@ class SmartPool < Pool > if nested_pools > nested_pools.each do |pool_element| > pool = pool_element[:obj] > - if pool.hasChildren > + if pool.hasChildren and pool_element.has_key?(:children) > if pool.name == user > pool_element[:children].each do |child_element| > child_pool = child_element[:obj] > user_pools <<[child_pool.name, child_pool.id] > end > else > - if pool_element.has_key?(:children) > - pool_element[:children].each do |child_element| > - child_pool = child_element[:obj] > - other_pools << [pool.name + " > " + child_pool.name, child_pool.id] > - end > + pool_element[:children].each do |child_element| > + child_pool = child_element[:obj] > + other_pools << [pool.name + " > " + child_pool.name, child_pool.id] > end > end > end > I tried this out and it works, and the code looks good, but couldn't figure out the use case which this is supposed to fix inorder to test it out. Regardless every seems to work with it included so ACK and pushed (though the path to the 'smart_pool.rb' file in the patch seems to be messed up as its missing the leading 'src/', which I tweaked before commiting) One other thing, you can split your git commit message into multiple lines if you want, the first being a very short summary of the patch to appear in the shortlog http://git.et.redhat.com/?p=ovirt-server.git;a=shortlog;h=refs/heads/next and the rest will appear before the patch itself. Up to you; regardless thanks for the contribution and sorry for the delay in ack'ing it. -Mo From mmorsi at redhat.com Wed Oct 14 19:04:14 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 14 Oct 2009 15:04:14 -0400 Subject: [Ovirt-devel] [PATCH] fixes rails launch when a rack gem is present on the system In-Reply-To: <1253978752-8502-1-git-send-email-mloiseleur@linagora.com> References: <1253978752-8502-1-git-send-email-mloiseleur@linagora.com> Message-ID: <4AD620AE.8090305@redhat.com> Michel Loiseleur wrote: > Signed-off-by: Michel Loiseleur > --- > src/config/initializers/mime_types.rb | 6 ++++++ > 1 files changed, 6 insertions(+), 0 deletions(-) > > diff --git a/src/config/initializers/mime_types.rb b/src/config/initializers/mime_types.rb > index 72aca7e..826051b 100644 > --- a/src/config/initializers/mime_types.rb > +++ b/src/config/initializers/mime_types.rb > @@ -3,3 +3,9 @@ > # Add new mime types for use in respond_to blocks: > # Mime::Type.register "text/richtext", :rtf > # Mime::Type.register_alias "text/html", :iphone > + > +# Workaround for rack gem + rails gem 2.3.2 on a same machine > +# See https://rails.lighthouseapp.com/projects/8994/tickets/2784-private-method-split-called-for-mimetype0x226f618 > +class Mime::Type > + delegate :split, :to => :to_s > +end > ACK and pushed. Thanks for this. -Mo From mmorsi at redhat.com Wed Oct 14 19:13:11 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 14 Oct 2009 15:13:11 -0400 Subject: [Ovirt-devel] Multi-databases support In-Reply-To: <4AC1173D.7070408@linagora.com> References: <4AC1173D.7070408@linagora.com> Message-ID: <4AD622C7.6010701@redhat.com> Loiseleur Michel wrote: > Hi, > > While I was hacking ovirt-server, I have found that it's currently > restricted to Postgres DB. Even if I like postgres for serious work on > a server, I really prefer to hack/dev locally on a Sqlite or MySQL DB. > > I have googled on rails in order to find a good answer for the > "foreign key problem" which forces OVirt to stay on pg. I have found a > plugin on this particular problem, named foreigner : > http://github.com/matthuhiggins/foreigner > > It provides a rails' syntax to common operation on them. For instance : > > add_foreign_key(from_table, to_table, options) > remove_foreign_key(from_table, options) > foreign_keys(table_name) > > I really like to keep my development computer to run as fast as > possible (ie without a database service), so I have even tried to > integrate this plugin into OVirt, in a transparent way for existing db. > > After a couple of patch on the plugin itself and some hack on > migrations, it seems that I manage to do it. The 3 patchs following > this email allows OVirt to support multiple open source database > without losing functionality, without losing existing databases and > with a small readability enhancement on db migrations. > > What do you think of it ? Feel free to contact me by email, on this > list or by irc (nick: Coren`) > > Regards, Thank you greatly for this. I agree, and am sure many others do that multiple database support would be great. I applied all three patches but ran into some problems when running them. Comments are in line in the patches. -Mo From mmorsi at redhat.com Wed Oct 14 19:13:29 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 14 Oct 2009 15:13:29 -0400 Subject: [Ovirt-devel] [PATCH 1/3] Add foreigner plugin from Matthu Higgins, allowing to manage foreign key in a multi-db way. In-Reply-To: <1254168512-4166-1-git-send-email-mloiseleur@linagora.com> References: <4AC1173D.7070408@linagora.com> <1254168512-4166-1-git-send-email-mloiseleur@linagora.com> Message-ID: <4AD622D9.3090306@redhat.com> Michel Loiseleur wrote: > .... > --- /dev/null > +++ b/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/postgresql_adapter.rb > @@ -0,0 +1,46 @@ > +module Foreigner > + module ConnectionAdapters > + module PostgreSQLAdapter > + include Foreigner::ConnectionAdapters::Sql2003 > + > + def foreign_keys(table_name) > + fk_info = select_all %{ > + SELECT tc.constraint_name as name > + ,ccu.table_name as to_table > + ,ccu.column_name as primary_key > + ,kcu.column_name as column > + ,rc.delete_rule as dependency > + FROM information_schema.table_constraints tc > + JOIN information_schema.key_column_usage kcu > + USING (constraint_catalog, constraint_schema, constraint_name) > + JOIN information_schema.referential_constraints rc > + USING (constraint_catalog, constraint_schema, constraint_name) > + JOIN information_schema.constraint_column_usage ccu > + USING (constraint_catalog, constraint_schema, constraint_name) > + WHERE tc.constraint_type = 'FOREIGN KEY' > + AND tc.constraint_catalog = '#{@config[:database]}' > + AND tc.table_name = '#{table_name}' > + } > + > + fk_info.map do |row| > + options = {:column => row['column'], :name => row['name'], :primary_key = row['primary_key']} > Syntax error here prevents mongrel from starting up, the last "=" should be changed to a "=>" like so :primary_key => row['primary_key'] After I fix this mongrel/rails starts up and the wui works again. -Mo From mmorsi at redhat.com Wed Oct 14 19:14:40 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 14 Oct 2009 15:14:40 -0400 Subject: [Ovirt-devel] [PATCH 2/3] enhance & fix foreigner plugin to manage correctly named foreign key In-Reply-To: <1254168512-4166-2-git-send-email-mloiseleur@linagora.com> References: <4AC1173D.7070408@linagora.com> <1254168512-4166-1-git-send-email-mloiseleur@linagora.com> <1254168512-4166-2-git-send-email-mloiseleur@linagora.com> Message-ID: <4AD62320.4010301@redhat.com> A couple things. First I notice alot of whitespace changes, ideally these wouldn't be here, or even be in their own formatting patch, as they kinda clutter up the actual content changes. Second I was just wondering if you could provide a little more detail as to what this patch does as it's not immediately apparent looking at it. If this is a known foreigner plugin fix could you link the thread of discussion detailing it, or if we need it for our project perhaps a blurb below the patch summary as to the changes needed to foreigner to adapt it to ovirt. Thanks alot, -Mo Michel Loiseleur wrote: > Signed-off-by: Michel Loiseleur > --- > .../abstract/schema_definitions.rb | 26 ++++++++++---------- > .../lib/foreigner/connection_adapters/sql_2003.rb | 21 +++++++-------- > 2 files changed, 23 insertions(+), 24 deletions(-) > > diff --git a/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/abstract/schema_definitions.rb b/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/abstract/schema_definitions.rb > index e417c5b..f31fc2b 100644 > --- a/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/abstract/schema_definitions.rb > +++ b/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/abstract/schema_definitions.rb > @@ -8,17 +8,17 @@ module Foreigner > base::TableDefinition.class_eval do > include Foreigner::ConnectionAdapters::TableDefinition > end > - > + > base::Table.class_eval do > include Foreigner::ConnectionAdapters::Table > end > end > end > - > + > module TableDefinition > class ForeignKey < Struct.new(:base, :to_table, :options) > def to_sql > - base.foreign_key_definition(to_table, options) > + base.foreign_key_definition(@table_name, to_table, options) > end > alias to_s :to_sql > end > @@ -30,18 +30,18 @@ module Foreigner > alias_method_chain :to_sql, :foreign_keys > end > end > - > + > module InstanceMethods > # Adds a :foreign_key option to TableDefinition.references. > # If :foreign_key is true, a foreign key constraint is added to the table. > # You can also specify a hash, which is passed as foreign key options. > - # > + # > # ===== Examples > # ====== Add goat_id column and a foreign key to the goats table. > # t.references(:goat, :foreign_key => true) > # ====== Add goat_id column and a cascading foreign key to the goats table. > # t.references(:goat, :foreign_key => {:dependent => :delete}) > - # > + # > # Note: No foreign key is created if :polymorphic => true is used. > # Note: If no name is specified, the database driver creates one for you! > def references_with_foreign_keys(*args) > @@ -55,7 +55,7 @@ module Foreigner > > references_without_foreign_keys(*(args << options)) > end > - > + > # Defines a foreign key for the table. +to_table+ can be a single Symbol, or > # an Array of Symbols. See SchemaStatements#add_foreign_key > # > @@ -74,13 +74,13 @@ module Foreigner > foreign_keys << ForeignKey.new(@base, to_table, options) > end > end > - > + > def to_sql_with_foreign_keys > sql = to_sql_without_foreign_keys > sql << ', ' << (foreign_keys * ', ') if foreign_keys.present? > sql > end > - > + > private > def foreign_keys > @foreign_keys ||= [] > @@ -112,7 +112,7 @@ module Foreigner > def foreign_key(to_table, options = {}) > @base.add_foreign_key(@table_name, to_table, options) > end > - > + > # Remove the given foreign key from the table. > # > # ===== Examples > @@ -125,17 +125,17 @@ module Foreigner > def remove_foreign_key(options = {}) > @base.remove_foreign_key(@table_name, options) > end > - > + > # Adds a :foreign_key option to TableDefinition.references. > # If :foreign_key is true, a foreign key constraint is added to the table. > # You can also specify a hash, which is passed as foreign key options. > - # > + # > # ===== Examples > # ====== Add goat_id column and a foreign key to the goats table. > # t.references(:goat, :foreign_key => true) > # ====== Add goat_id column and a cascading foreign key to the goats table. > # t.references(:goat, :foreign_key => {:dependent => :delete}) > - # > + # > # Note: No foreign key is created if :polymorphic => true is used. > def references_with_foreign_keys(*args) > options = args.extract_options! > diff --git a/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/sql_2003.rb b/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/sql_2003.rb > index 1a1019b..957111b 100644 > --- a/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/sql_2003.rb > +++ b/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/sql_2003.rb > @@ -4,25 +4,24 @@ module Foreigner > def supports_foreign_keys? > true > end > - > + > def add_foreign_key(from_table, to_table, options = {}) > column = options[:column] || "#{to_table.to_s.singularize}_id" > - foreign_key_name = foreign_key_name(from_table, column, options) > > - sql = > - "ALTER TABLE #{quote_table_name(from_table)} " + > - "ADD CONSTRAINT #{quote_column_name(foreign_key_name)} " + > - foreign_key_definition(to_table, options) > - > + sql = "ALTER TABLE #{quote_table_name(from_table)} ADD " << > + foreign_key_definition(from_table, to_table, options) > + > execute(sql) > end > - > - def foreign_key_definition(to_table, options = {}) > + > + def foreign_key_definition(from_table, to_table, options = {}) > column = options[:column] || "#{to_table.to_s.singularize}_id" > + foreign_key_name = foreign_key_name(from_table, column, options) > primary_key = options[:primary_key] || "id" > dependency = dependency_sql(options[:dependent]) > > - sql = "FOREIGN KEY (#{quote_column_name(column)}) REFERENCES #{quote_table_name(to_table)}(#{primary_key})" > + sql = "CONSTRAINT #{quote_column_name(foreign_key_name)} " > + sql << "FOREIGN KEY (#{quote_column_name(column)}) REFERENCES #{quote_table_name(to_table)}(#{primary_key})" > sql << " #{dependency}" unless dependency.blank? > sql > end > @@ -36,7 +35,7 @@ module Foreigner > > execute "ALTER TABLE #{quote_table_name(table)} DROP FOREIGN KEY #{quote_column_name(foreign_key_name)}" > end > - > + > private > def foreign_key_name(table, column, options = {}) > if options[:name] > From mmorsi at redhat.com Wed Oct 14 19:19:04 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Wed, 14 Oct 2009 15:19:04 -0400 Subject: [Ovirt-devel] [PATCH 3/3] enhance migrations in order to use the new system. It should be backward compatible with existing database, since it uses the same fk name & index. In-Reply-To: <1254168512-4166-3-git-send-email-mloiseleur@linagora.com> References: <4AC1173D.7070408@linagora.com> <1254168512-4166-1-git-send-email-mloiseleur@linagora.com> <1254168512-4166-2-git-send-email-mloiseleur@linagora.com> <1254168512-4166-3-git-send-email-mloiseleur@linagora.com> Message-ID: <4AD62428.6060006@redhat.com> Michel Loiseleur wrote: > diff --git a/src/db/migrate/027_fix_uniqueness_constraints_in_bondings_nics.rb b/src/db/migrate/027_fix_uniqueness_constraints_in_bondings_nics.rb > index 5f58c5c..e2c638c 100644 > --- a/src/db/migrate/027_fix_uniqueness_constraints_in_bondings_nics.rb > +++ b/src/db/migrate/027_fix_uniqueness_constraints_in_bondings_nics.rb > @@ -1,8 +1,18 @@ > > class FixUniquenessConstraintsInBondingsNics < ActiveRecord::Migration > def self.up > + # Mysql 5.* forces foreign key to have an index, for performance reason. > + # One cannot remove an index before drop foreign key on it. > + remove_foreign_key :bondings_nics, :name => 'fk_bondings_nics_bonding' > + remove_foreign_key :bondings_nics, :name => 'fk_bondings_nics_nic' > + > remove_index :bondings_nics, [:bonding_id, :nic_id] > add_index :bondings_nics, :nic_id, :unique => true > + > + # it can be re-added afterwards, without any problem > + add_foreign_key :bondings_nics, :bondings, > + :name => 'fk_bondings_nics_bonding' > + add_foreign_key :bondings_nics, :nics, :name => 'fk_bondings_nics_nic' > end > > def self.down > @@ -10,4 +20,3 @@ class FixUniquenessConstraintsInBondingsNics < ActiveRecord::Migration > add_index :bondings_nics, [:bonding_id, :nic_id], :unique => true > end > end > - > I ran into an issue with this migration, #27, when running "rake db:migrate" against a new "ovirt_development" postgres database, created via the instructions in config/database.yml The error I got was: 1. == FixUniquenessConstraintsInBondingsNics: migrating ========================= 2. -- remove_foreign_key(:bondings_nics, {:name=>"fk_bondings_nics_bonding"}) 3. rake aborted! 4. An error has occurred, this and all later migrations canceled: 5. 6. PGError: ERROR: syntax error at or near "FOREIGN" 7. LINE 1: ALTER TABLE "bondings_nics" DROP FOREIGN KEY "fk_bondings_ni... 8. ^ 9. : ALTER TABLE "bondings_nics" DROP FOREIGN KEY "fk_bondings_nics_bonding" Rake stops at this point, so I'm not sure if any other migrations are broken. Does this work when trying it against postgres locally? -Mo -------------- next part -------------- An HTML attachment was scrubbed... URL: From dpierce at redhat.com Wed Oct 14 19:53:28 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 14 Oct 2009 15:53:28 -0400 Subject: [Ovirt-devel] [PATCH node] Provides a new storage administration system to the managed node. Message-ID: <1255550008-5110-1-git-send-email-dpierce@redhat.com> Users can now: * Add a new storage pool. * Delete a storage pool. * Start and stop storage pools. * Add a new storage volume. * Delete a storage volume. * List existing storage pools, with details. Signed-off-by: Darryl L. Pierce --- Makefile.am | 31 +++++--- nodeadmin/addpool.py | 182 ++++++++++++++++++++++++++++++++++++++++++++ nodeadmin/addvolume.py | 160 ++++++++++++++++++++++++++++++++++++++ nodeadmin/configscreen.py | 52 +++++++++++++ nodeadmin/createmeter.py | 30 +++++++ nodeadmin/definedomain.py | 15 +--- nodeadmin/libvirtworker.py | 67 +++++++++++++++-- nodeadmin/listpools.py | 63 +++++++++++++++ nodeadmin/mainmenu.py | 24 ++++--- nodeadmin/poolconfig.py | 137 +++++++++++++++++++++++++++++++++ nodeadmin/removepool.py | 72 +++++++++++++++++ nodeadmin/removevolume.py | 76 ++++++++++++++++++ nodeadmin/setup.py.in | 9 ++- nodeadmin/startpool.py | 62 +++++++++++++++ nodeadmin/stoppool.py | 62 +++++++++++++++ nodeadmin/storagemenu.py | 63 +++++++++++++++ nodeadmin/utils.py | 10 +++ nodeadmin/volumeconfig.py | 76 ++++++++++++++++++ ovirt-node.spec.in | 49 ++++++++---- 19 files changed, 1184 insertions(+), 56 deletions(-) create mode 100644 nodeadmin/addpool.py create mode 100644 nodeadmin/addvolume.py create mode 100644 nodeadmin/createmeter.py create mode 100644 nodeadmin/listpools.py create mode 100644 nodeadmin/poolconfig.py create mode 100644 nodeadmin/removepool.py create mode 100644 nodeadmin/removevolume.py create mode 100644 nodeadmin/startpool.py create mode 100644 nodeadmin/stoppool.py create mode 100644 nodeadmin/storagemenu.py create mode 100644 nodeadmin/volumeconfig.py diff --git a/Makefile.am b/Makefile.am index abb7c33..14b8ccb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -27,30 +27,41 @@ EXTRA_DIST = \ images/grub-splash.xpm.gz \ images/syslinux-vesa-splash.jpg \ nodeadmin/__init__.py \ + nodeadmin/addpool.py \ + nodeadmin/addvolume.py \ nodeadmin/configscreen.py \ + nodeadmin/createdomain.py \ + nodeadmin/createmeter.py \ nodeadmin/createnetwork.py \ nodeadmin/createuser.py \ + nodeadmin/definedomain.py \ + nodeadmin/definenet.py \ nodeadmin/destroydomain.py \ nodeadmin/destroynetwork.py \ + nodeadmin/domainconfig.py \ nodeadmin/halworker.py \ nodeadmin/libvirtworker.py \ - nodeadmin/userworker.py \ + nodeadmin/listdomains.py \ + nodeadmin/listnetworks.py \ + nodeadmin/listpools.py \ nodeadmin/mainmenu.py \ nodeadmin/menuscreen.py \ nodeadmin/netmenu.py \ - nodeadmin/nodemenu.py \ - nodeadmin/undefinedomain.py \ - nodeadmin/undefinenetwork.py \ - nodeadmin/createdomain.py \ - nodeadmin/definedomain.py \ - nodeadmin/definenet.py \ - nodeadmin/domainconfig.py \ nodeadmin/networkconfig.py \ - nodeadmin/listdomains.py \ - nodeadmin/listnetworks.py \ nodeadmin/nodeadmin.py \ + nodeadmin/nodemenu.py \ + nodeadmin/poolconfig.py \ + nodeadmin/removepool.py \ + nodeadmin/removevolume.py \ nodeadmin/setup.py \ + nodeadmin/startpool.py \ + nodeadmin/stoppool.py \ + nodeadmin/storagemenu.py \ + nodeadmin/undefinedomain.py \ + nodeadmin/undefinenetwork.py \ + nodeadmin/userworker.py \ nodeadmin/utils.py \ + nodeadmin/volumeconfig.py \ scripts/collectd.conf.in \ scripts/ovirt \ scripts/ovirt-awake \ diff --git a/nodeadmin/addpool.py b/nodeadmin/addpool.py new file mode 100644 index 0000000..389be52 --- /dev/null +++ b/nodeadmin/addpool.py @@ -0,0 +1,182 @@ +# addstorage.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +import traceback +import utils + +from configscreen import * +from poolconfig import PoolConfig +from virtinst import Storage + +POOL_NAME_PAGE = 1 +POOL_DETAILS_PAGE = 2 +CONFIRM_PAGE = 3 + +class AddStoragePoolConfigScreen(ConfigScreen): + def __init__(self): + ConfigScreen.__init__(self, "Add A Storage Pool") + self.__config = PoolConfig(self.get_libvirt()) + + def get_elements_for_page(self, screen, page): + if page is POOL_NAME_PAGE: return self.get_pool_name_page(screen) + elif page is POOL_DETAILS_PAGE: return self.get_pool_details_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + return page < CONFIRM_PAGE + + def page_has_back(self, page): + return page > POOL_NAME_PAGE + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def validate_input(self, page, errors): + if page is POOL_NAME_PAGE: + if utils.string_is_not_blank(self.__name.value()): + if self.get_libvirt().storage_pool_exists(self.__name.value()): + errors.append("Name '%s' already in use by another pool." % self.__name.value()) + else: + return True + else: + errors.append("Storage object name must be a string between 0 and 50 characters.") + elif page is POOL_DETAILS_PAGE: + result = True + if self.__config.needs_target_path(): + if utils.string_is_not_blank(self.__target_path.value()): + if self.__target_path.value()[0:1] is not '/': + errors.append("'%s' is not an absolute path." % self.__target_path.value()) + result = False + else: + errors.append("You must enter a target path.") + result = False + if self.__config.needs_format(): + if self.__formats.getSelection() is None: + errors.append("You must select a pool format.") + result = False + if self.__config.needs_hostname(): + if utils.string_is_not_blank(self.__hostname.value()): + errors.append("You must enter a hostname.") + result = False + if self.__config.needs_source_path(): + if utils.string_is_not_blank(self.__source_path.value()): + if self.__source_path.value()[0:1] is not '/': + errors.append("'%s' is not an absolute path." % self.__source_path.value()) + result = False + else: + errors.append("you must enter a source path.") + result = False + return result + elif page is CONFIRM_PAGE: return True + return False + + def process_input(self, page): + if page is POOL_NAME_PAGE: + self.__config.set_name(self.__name.value()) + self.__config.set_type(self.__type.getSelection()) + #self._reset_flags(self.__type.current()) + elif page is POOL_DETAILS_PAGE: + if self.__config.needs_target_path(): + self.__config.set_target_path(self.__target_path.value()) + if self.__config.needs_format(): + self.__config.set_format(self.__formats.getSelection()) + if self.__config.needs_hostname(): + self.__config.set_hostname(self.__hostname.value()) + if self.__config.needs_source_path(): + self.__config.set_source_path(self.__source_path.value()) + if self.__config.needs_build_pool(): + self.__config.set_build_pool(self.__build_pool.value()) + elif page is CONFIRM_PAGE: + self.get_libvirt().define_storage_pool(self.__config.get_name(), config = self.__config) + self.get_libvirt().create_storage_pool(self.__config.get_name()) + self.set_finished() + + def get_pool_name_page(self, screen): + self.__name = Entry(50, self.__config.get_name()) + pooltypes = [] + for pooltype in Storage.StoragePool.get_pool_types(): + pooltypes.append(["%s: %s" % (pooltype, Storage.StoragePool.get_pool_type_desc(pooltype)), + pooltype, + self.__config.get_type() is pooltype]) + self.__type = RadioBar(screen, pooltypes) + grid = Grid(2, 2) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(self.__name, 1, 0, anchorLeft = 1) + grid.setField(Label("Type:"), 0, 1, anchorRight = 1, anchorTop = 1) + grid.setField(self.__type, 1, 1, anchorLeft = 1) + return [Label("Add Storage Pool"), + grid] + + def get_pool_details_page(self, screen): + rows = 0 + if self.__config.needs_target_path(): + self.__target_path = Entry(50, self.__config.get_target_path()) + rows += 1 + if self.__config.needs_format(): + formats = [] + for format in self.__config.get_formats(): + formats.append([format, format, format is self.__config.get_format()]) + self.__formats = RadioBar(screen, formats) + rows += 1 + if self.__config.needs_hostname(): + self.__hostname = Entry(50, self.__config.get_hostname()) + rows += 1 + if self.__config.needs_source_path(): + self.__source_path = Entry(50, self.__config.get_source_path()) + rows += 1 + if self.__config.needs_build_pool(): + self.__build_pool = Checkbox("Build Pool", self.__config.get_build_pool()) + rows += 1 + grid = Grid(2, rows) + currentrow = 0 + if self.__config.needs_target_path(): + grid.setField(Label("Target Path:"), 0, currentrow, anchorRight = 1) + grid.setField(self.__target_path, 1, currentrow, anchorLeft = 1) + currentrow += 1 + if self.__config.needs_format(): + grid.setField(Label("Format:"), 0, currentrow, anchorRight = 1, anchorTop = 1) + grid.setField(self.__formats, 1, currentrow, anchorLeft = 1) + currentrow += 1 + if self.__config.needs_hostname(): + grid.setField(Label("Host Name:"), 0, currentrow, anchorRight = 1) + grid.setField(self.__hostname, 1, currentrow, anchorRight = 1) + currentrow += 1 + if self.__config.needs_source_path(): + grid.setField(Label("Source Path:"), 0, currentrow, anchorRight = 1) + grid.setField(self.__source_path, 1, currentrow, anchorLeft = 1) + currentrow += 1 + if self.__config.needs_build_pool(): + grid.setField(Label(" "), 0, currentrow, anchorRight = 1) + grid.setField(self.__build_pool, 1, currentrow, anchorLeft = 1) + currentrow += 1 + return [Label("Specify a storage location to be later split into virtual machine storage"), + grid] + + def get_confirm_page(self, screen): + grid = Grid(2, 2) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(Label(self.__config.get_name()), 1, 0, anchorLeft = 1) + grid.setField(Label("Target Path:"), 0, 1, anchorRight = 1) + grid.setField(Label(self.__config.get_target_path()), 1, 1, anchorLeft = 1) + return [Label("Confirm Pool Details"), + grid] + +def AddStoragePool(): + screen = AddStoragePoolConfigScreen() + screen.start() diff --git a/nodeadmin/addvolume.py b/nodeadmin/addvolume.py new file mode 100644 index 0000000..82c014c --- /dev/null +++ b/nodeadmin/addvolume.py @@ -0,0 +1,160 @@ +# addvolume.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +import traceback + +from createmeter import CreateMeter +from configscreen import * +from volumeconfig import StorageVolumeConfig +from utils import * + +SELECT_POOL_PAGE = 1 +VOLUME_NAME_PAGE = 2 +VOLUME_FORMAT_PAGE = 3 +MAX_CAPACITY_PAGE = 4 +CONFIRM_PAGE = 5 + +class AddVolumeConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Add A New Storage Volume") + self.__config = StorageVolumeConfig() + + def get_elements_for_page(self, screen, page): + if page is SELECT_POOL_PAGE: return self.get_storage_pool_list_page(screen) + elif page is VOLUME_NAME_PAGE: return self.get_volume_name_page(screen) + elif page is VOLUME_FORMAT_PAGE: return self.get_volume_format_page(screen) + elif page is MAX_CAPACITY_PAGE: return self.get_max_capacity_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + if page is SELECT_POOL_PAGE: + return self.has_selectable_pools() + else: + if page < CONFIRM_PAGE: return True + return False + + def page_has_back(self, page): + if page > SELECT_POOL_PAGE: return True + return False + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def validate_input(self, page, errors): + if page is SELECT_POOL_PAGE: + if self.get_selected_pool() is not None: + return True + else: + errors.append("You must select a storage pool.") + elif page is VOLUME_NAME_PAGE: + if string_is_not_blank(self.__name.value()): + return True + else: + errors.append("Storage object name can only contain alphanumeric, '_', '.', or '-' characters.") + elif page is VOLUME_FORMAT_PAGE: + if self.__formats.current() is not None: + return True + else: + errors.append("You must select a volume format.") + elif page is MAX_CAPACITY_PAGE: + if string_is_not_blank(self.__capacity.value()): + if string_is_not_blank(self.__allocation.value()): + capacity = int(self.__capacity.value()) + allocation = int(self.__allocation.value()) + if capacity > 0: + if capacity <= self.__config.get_pool().info()[3] / 1024**2: + if allocation >= 0: + if allocation <= capacity: + return True + else: + errors.append("Allocation cannot exceed the maximum capacity.") + else: + errors.append("The allocation must be greater than or equal to 0.") + else: + errors.append("The maximum capacity cannot exceed the storage pool size.") + else: + errors.append("The capacity must be greater than zero.") + else: + errors.append("An allocation value must be entered.") + else: + errors.append("A maximum volume capacity must be entered.") + elif page is CONFIRM_PAGE: return True + return False + + def process_input(self, page): + if page is SELECT_POOL_PAGE: + self.__config.set_pool(self.get_libvirt().get_storage_pool(self.get_selected_pool())) + elif page is VOLUME_NAME_PAGE: + self.__config.set_name(self.__name.value()) + elif page is VOLUME_FORMAT_PAGE: + self.__config.set_format(self.__formats.current()) + elif page is MAX_CAPACITY_PAGE: + self.__config.set_max_capacity(int(self.__capacity.value())) + self.__config.set_allocation(int(self.__allocation.value())) + elif page is CONFIRM_PAGE: + self.get_libvirt().define_storage_volume(self.__config, CreateMeter()) + self.set_finished() + + def get_volume_name_page(self, screen): + self.__name = Entry(50, self.__config.get_name()) + grid = Grid(2, 1) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(self.__name, 1, 0, anchorLeft = 1) + return [Label("New Storage Volume"), + grid, + Label("Name of the volume to create. File extension may be appended.")] + + def get_volume_format_page(self, screen): + self.__formats = Listbox(0) + for format in self.__config.get_formats_for_pool(): + self.__formats.append(format, format) + grid = Grid(1, 1) + grid.setField(self.__formats, 0, 0) + return [Label("Select The Volume Format"), + grid] + + def get_max_capacity_page(self, screen): + self.__capacity = Entry(6, str(self.__config.get_max_capacity())) + self.__allocation = Entry(6, str(self.__config.get_allocation())) + grid = Grid(2, 2) + grid.setField(Label("Max. Capacity (MB):"), 0, 0, anchorRight = 1) + grid.setField(self.__capacity, 1, 0, anchorLeft = 1) + grid.setField(Label("Allocation (MB):"), 0, 1, anchorRight = 1) + grid.setField(self.__allocation, 1, 1, anchorLeft = 1) + return [Label("Storage Volume Quota"), + Label("%s's available space: %0.2f GB" % (self.__config.get_pool().name(), + self.__config.get_pool().info()[3] / 1024.0**3)), + grid] + + def get_confirm_page(self, screen): + grid = Grid(2, 5) + grid.setField(Label("Volume Name:"), 0, 0, anchorRight = 1) + grid.setField(Label("%s (%s)" % (self.__config.get_name(), self.__config.get_pool().name())), 1, 0, anchorLeft = 1) + grid.setField(Label("Format:"), 0, 1, anchorRight = 1) + grid.setField(Label(self.__config.get_format()), 1, 1, anchorLeft = 1) + grid.setField(Label("Max. Capacity:"), 0, 2, anchorRight = 1) + grid.setField(Label("%0.2f GB" % (self.__config.get_max_capacity() / 1024.0)), 1, 2, anchorLeft = 1) + grid.setField(Label("Allocation:"), 0, 3, anchorRight = 1) + grid.setField(Label("%0.2f GB" % (self.__config.get_allocation() / 1024.0)), 1, 3, anchorLeft = 1) + return [Label("Ready To Allocation New Storage Volume"), + grid] + +def AddStorageVolume(): + screen = AddVolumeConfigScreen() + screen.start() diff --git a/nodeadmin/configscreen.py b/nodeadmin/configscreen.py index f214aea..7654697 100644 --- a/nodeadmin/configscreen.py +++ b/nodeadmin/configscreen.py @@ -179,3 +179,55 @@ class NetworkListConfigScreen(ConfigScreen): def has_selectable_networks(self): return self.__has_networks + +class StorageListConfigScreen(ConfigScreen): + '''Provides a base class for any configuration screen that deals with storage pool lists.''' + + def __init__(self, title): + ConfigScreen.__init__(self, title) + + def get_storage_pool_list_page(self, screen, defined=True, created=True): + pools = self.get_libvirt().list_storage_pools(defined=defined, created=created) + if len(pools) > 0: + self.__has_pools = True + self.__pools_list = Listbox(0) + for pool in pools: + self.__pools_list.append(pool, pool) + result = self.__pools_list + else: + self.__has_pools = False + result = Label("There are no storage pools available.") + grid = Grid(1, 1) + grid.setField(result, 0, 0) + return [Label("Storage Pool List"), + grid] + + def get_selected_pool(self): + return self.__pools_list.current() + + def has_selectable_pools(self): + return self.__has_pools + + def get_storage_volume_list_page(self, screen): + '''Requires that self.__pools_list have a selected element.''' + pool = self.get_libvirt().get_storage_pool(self.get_selected_pool()) + if len(pool.listVolumes()) > 0: + self.__has_volumes = True + self.__volumes_list = Listbox(0) + for volname in pool.listVolumes(): + volume = pool.storageVolLookupByName(volname) + self.__volumes_list.append("%s (%0.2f GB)" % (volume.name(), volume.info()[2] / 1024**3), volume.name()) + result = self.__volumes_list + else: + self.__has_volumes = False + result = Label("There are no storage volumes available.") + grid = Grid(1, 1) + grid.setField(result, 0, 0) + return [Label("Storage Volume List"), + grid] + + def get_selected_volume(self): + return self.__volumes_list.current() + + def has_selectable_volumes(self): + return self.__has_volumes diff --git a/nodeadmin/createmeter.py b/nodeadmin/createmeter.py new file mode 100644 index 0000000..521e7d8 --- /dev/null +++ b/nodeadmin/createmeter.py @@ -0,0 +1,30 @@ +# createmeter.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +import urlgrabber.progress as progress +import logging + +class CreateMeter(progress.BaseMeter): + def _do_start(self, now = None): + logging.info("Starting...") + + def _do_end(self, amount_read, now = None): + logging.info("Ending: read=%d" % amount_read) + + def _do_update(self, amount_read, now = None): + logging.info("Update: read=%d" % amount_read) diff --git a/nodeadmin/definedomain.py b/nodeadmin/definedomain.py index 6a6612c..067f9e8 100755 --- a/nodeadmin/definedomain.py +++ b/nodeadmin/definedomain.py @@ -20,11 +20,10 @@ from snack import * import os +from createmeter import CreateMeter from domainconfig import DomainConfig from configscreen import ConfigScreen -import urlgrabber.progress as progress import utils -import logging from virtinst import * @@ -51,16 +50,6 @@ OS_VARIANT="os.variant" MEMORY="memory" CPUS="cpus" -class DummyMeter(progress.BaseMeter): - def _do_start(self, now = None): - logging.info("Starting...") - - def _do_end(self, amount_read, now = None): - logging.info("Ending: read=%d" % amount_read) - - def _do_update(self, amount_read, now = None): - logging.info("Update: read=%d" % amount_read) - class DomainConfigScreen(ConfigScreen): def __init__(self): ConfigScreen.__init__(self, "Create A New Virtual Machine") @@ -212,7 +201,7 @@ class DomainConfigScreen(ConfigScreen): self.__config.set_virt_type(self.__virt_types.getSelection()) self.__config.set_architecture(self.__architectures.getSelection()) elif page == CONFIRM_PAGE: - self.get_libvirt().define_domain(self.__config, DummyMeter()) + self.get_libvirt().define_domain(self.__config, CreateMeter()) self.set_finished() def get_back_page(self, page): diff --git a/nodeadmin/libvirtworker.py b/nodeadmin/libvirtworker.py index ba07605..b2acabe 100644 --- a/nodeadmin/libvirtworker.py +++ b/nodeadmin/libvirtworker.py @@ -35,6 +35,10 @@ class LibvirtWorker: self.__net.setup(self.__conn) (self.__new_guest, self.__new_domain) = virtinst.CapabilitiesParser.guest_lookup(conn = self.__conn) + def get_connection(self): + '''Returns the underlying connection.''' + return self.__conn + def list_domains(self, defined = True, started = True): '''Lists all domains.''' result = [] @@ -134,9 +138,12 @@ class LibvirtWorker: network = self.get_network(name) network.undefine() - def list_storage_pools(self): + def list_storage_pools(self, defined=True, created=True): '''Returns the list of all defined storage pools.''' - return self.__conn.listStoragePools() + pools = [] + if defined: pools.extend(self.__conn.listDefinedStoragePools()) + if created: pools.extend(self.__conn.listStoragePools()) + return pools def storage_pool_exists(self, name): '''Returns whether a storage pool exists.''' @@ -144,16 +151,62 @@ class LibvirtWorker: if name in pools: return True return False - def define_storage_pool(self, name): + def create_storage_pool(self, name): + '''Starts the named storage pool if it is not currently started.''' + if name not in self.list_storage_pools(defined = False): + pool = self.get_storage_pool(name) + pool.create(0) + + def destroy_storage_pool(self, name): + '''Stops the specified storage pool.''' + if name in self.list_storage_pools(defined = False): + pool = self.get_storage_pool(name) + pool.destroy() + + def define_storage_pool(self, name, config = None, meter = None): '''Defines a storage pool with the given name.''' - try: + if config is None: pool = virtinst.Storage.DirectoryPool(conn=self.__conn, name=name, target_path=DEFAULT_POOL_TARGET_PATH) - newpool = pool.install(build=True, create=True) + newpool = pool.install(build=True, create=True, meter=meter) newpool.setAutostart(True) - except Exception, error: - raise RuntimeError("Could not create pool: %s - %s", str(error)) + else: + pool = config.get_pool() + pool.target_path = config.get_target_path() + if config.needs_hostname(): + pool.host = config.get_hostname() + if config.needs_source_path(): + pool.source_path = config.get_source_path() + if config.needs_format(): + pool.format = config.get_format() + pool.conn = self.__conn + pool.get_xml_config() + newpool = pool.install(meter=meter, + build=True, # config.get_build_pool(), + create=True) + newpool.setAutostart(True) + + def undefine_storage_pool(self, name): + '''Undefines the specified storage pool.''' + pool = self.get_storage_pool(name) + pool.undefine() + + def get_storage_pool(self, name): + '''Returns the storage pool with the specified name.''' + return self.__conn.storagePoolLookupByName(name) + + def define_storage_volume(self, config, meter): + '''Defines a new storage volume.''' + self.create_storage_pool(config.get_pool().name()) + volume = config.create_volume() + volume.install(meter = meter) + + def remove_storage_volume(self, poolname, volumename): + '''Removes the specified storage volume.''' + pool = self.get_storage_pool(poolname) + volume = pool.storageVolLookupByName(volumename) + volume.delete(0) def list_bridges(self): '''Lists all defined and active bridges.''' diff --git a/nodeadmin/listpools.py b/nodeadmin/listpools.py new file mode 100644 index 0000000..686c42d --- /dev/null +++ b/nodeadmin/listpools.py @@ -0,0 +1,63 @@ +# listpools.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * + +from configscreen import * + +LIST_PAGE = 1 +DETAILS_PAGE = 2 + +class ListStoragePoolsConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "List Storage Pools") + + def get_elements_for_page(self, screen, page): + if page is LIST_PAGE: return self.get_storage_pool_list_page(screen) + elif page is DETAILS_PAGE: return self.get_pool_details_page(screen) + + def page_has_next(self, page): + if page is LIST_PAGE and self.has_selectable_pools(): + return True + return False + + def page_has_back(self, page): + if page is DETAILS_PAGE: return True + return False + + def get_pool_details_page(self, screen): + pool = self.get_libvirt().get_storage_pool(self.get_selected_pool()) + volumes = Listbox(0); + for name in pool.listVolumes(): + volume = pool.storageVolLookupByName(name) + volumes.append("%s (%0.1f G)" % (name, volume.info()[1] / 1024**3), name) + grid = Grid(2, 3) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(Label(pool.name()), 1, 0, anchorLeft = 1) + grid.setField(Label("Volumes:"), 0, 1, anchorRight = 1) + grid.setField(volumes, 1, 1, anchorLeft = 1) + grid.setField(Label("Autostart:"), 0, 2, anchorRight = 1) + label = "No" + if pool.autostart(): label = "Yes" + grid.setField(Label(label), 1, 2, anchorLeft = 1) + return [Label("Details For Storage Pool: %s" % self.get_selected_pool()), + grid] + +def ListStoragePools(): + screen = ListStoragePoolsConfigScreen() + screen.start() diff --git a/nodeadmin/mainmenu.py b/nodeadmin/mainmenu.py index 73501fa..52d9298 100755 --- a/nodeadmin/mainmenu.py +++ b/nodeadmin/mainmenu.py @@ -19,28 +19,32 @@ from snack import * import traceback -from menuscreen import MenuScreen -from nodemenu import NodeMenu -from netmenu import NetworkMenu +from menuscreen import MenuScreen +from nodemenu import NodeMenu +from netmenu import NetworkMenu +from storagemenu import StoragePoolMenu import utils import logging NODE_MENU = 1 NETWORK_MENU = 2 -EXIT_CONSOLE = 99 +STORAGE_MENU = 3 +EXIT_CONSOLE = 4 class MainMenuScreen(MenuScreen): def __init__(self): MenuScreen.__init__(self, "Main Menu") def get_menu_items(self): - return (("Node Administration", NODE_MENU), - ("Network Administration", NETWORK_MENU)) - - def handle_selection(self, page): - if page is NODE_MENU: NodeMenu() - elif page is NETWORK_MENU: NetworkMenu() + return (("Node Administration", NODE_MENU), + ("Network Administration", NETWORK_MENU), + ("Storage Pool Administration", STORAGE_MENU)) + + def handle_selection(self, item): + if item is NODE_MENU: NodeMenu() + elif item is NETWORK_MENU: NetworkMenu() + elif item is STORAGE_MENU: StoragePoolMenu() def MainMenu(): screen = MainMenuScreen() diff --git a/nodeadmin/poolconfig.py b/nodeadmin/poolconfig.py new file mode 100644 index 0000000..06af722 --- /dev/null +++ b/nodeadmin/poolconfig.py @@ -0,0 +1,137 @@ +# poolconfig.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from virtinst import Storage + +ROOT_TARGET_PATH="/var/lib/libvirt/images/%s" + +class PoolConfig: + def __init__(self, libvirt): + self.__libvirt = libvirt + self.__name = "" + self.set_type(None) + self.__format = None + self.__hostname = "" + self.__target_path = "" + self.__source_path = "" + self.__build_pool = False + + def get_pool(self): + return self.__pool + + def set_name(self, name): + self.__name = name + + def get_name(self): + return self.__name + + def set_type(self, pooltype): + self.__type = pooltype + self.__needs_target_path = False + self.__needs_format = False + self.__needs_hostname = False + self.__needs_source_path = False + self.__needs_build_pool = False + if pooltype is not None: + if pooltype is Storage.StoragePool.TYPE_DIR: + self.__needs_target_path = True + self.__target_path = ROOT_TARGET_PATH % self.__name + self.__build_pool = True + elif pooltype is Storage.StoragePool.TYPE_DISK: + self.__needs_target_path = True + self.__needs_format = True + self.__needs_source_path = True + self.__needs_build_pool = True + elif pooltype is Storage.StoragePool.TYPE_FS: + self.__needs_target_path = True + self.__needs_format = True + self.__needs_source_path = True + self.__build_pool = True + elif pooltype is Storage.StoragePool.TYPE_ISCSI: + self.__needs_target_path = True + self.__needs_hostname = True + self.__needs_source_path = True + self.__build_pool = False + elif pooltype is Storage.StoragePool.TYPE_LOGICAL: + self.__needs_target_path = True + self.__needs_source_path = True + self.__needs_build_pool = True + elif pooltype is Storage.StoragePool.TYPE_NETFS: + self.__needs_target_path = True + self.__needs_format = True + self.__needs_hostname = True + self.__needs_source_path = True + self.__build_pool = True + # create pool + pool_class = Storage.StoragePool.get_pool_class(self.__type) + self.__pool = pool_class(name = self.__name, + conn = self.__libvirt.get_connection()) + if self.__needs_format: + self.__format = self.__pool.formats[0] + else: + self.__type = Storage.StoragePool.get_pool_types()[0] + + def get_type(self): + return self.__type + + def needs_target_path(self): + return self.__needs_target_path + + def needs_format(self): + return self.__needs_format + + def needs_hostname(self): + return self.__needs_hostname + + def needs_source_path(self): + return self.__needs_source_path + + def needs_build_pool(self): + return self.__needs_build_pool + def set_target_path(self, path): + self.__target_path = path + + def get_target_path(self): + return self.__target_path + + def get_formats(self): + return self.__pool.formats + + def set_format(self, format): + self.__format = format + + def get_format(self): + return self.__format + + def set_hostname(self, hostname): + self.__hostname = hostname + + def get_hostname(self): + return self.__hostname + + def set_source_path(self, source_path): + self.__source_path = source_path + + def get_source_path(self): + return self.__source_path + + def set_build_pool(self, build_pool): + self.__build_pool = build_pool + + def get_build_pool(self): + return self.__build_pool diff --git a/nodeadmin/removepool.py b/nodeadmin/removepool.py new file mode 100644 index 0000000..7a7f46d --- /dev/null +++ b/nodeadmin/removepool.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# +# removepool.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +from configscreen import * + +LIST_POOLS_PAGE = 1 +CONFIRM_PAGE = 2 + +class RemoveStoragePoolConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Remove A Storage Pool") + + def get_elements_for_page(self, screen, page): + if page is LIST_POOLS_PAGE: return self.get_storage_pool_list_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + return page is LIST_POOLS_PAGE and self.has_selectable_pools() + + def page_has_back(self, page): + return False + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def validate_input(self, page, errors): + if page is LIST_POOLS_PAGE: + if self.get_selected_pool() is not None: + return True + else: + errors.append("Please select a storage pool to be removed.") + elif page is CONFIRM_PAGE: + if self.__confirm.value(): + return True + else: + errors.append("You must confirm removing a storage pool.") + return False + + def process_input(self, page): + if page is CONFIRM_PAGE: + self.get_libvirt().destroy_storage_pool(self.get_selected_pool()) + self.get_libvirt().undefine_storage_pool(self.get_selected_pool()) + self.set_finished() + + def get_confirm_page(self, screen): + self.__confirm = Checkbox("Check here to confirm deleting pool: %s" % self.get_selected_pool()) + grid = Grid(1, 1) + grid.setField(self.__confirm, 0, 0) + return [Label("Remove Selected Storage Pool"), + grid] + +def RemoveStoragePool(): + screen = RemoveStoragePoolConfigScreen() + screen.start() diff --git a/nodeadmin/removevolume.py b/nodeadmin/removevolume.py new file mode 100644 index 0000000..5ad3058 --- /dev/null +++ b/nodeadmin/removevolume.py @@ -0,0 +1,76 @@ +# removevolume.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +import traceback + +from createmeter import CreateMeter +from configscreen import * +from volumeconfig import StorageVolumeConfig +from utils import * + +SELECT_POOL_PAGE = 1 +SELECT_VOLUME_PAGE = 2 +CONFIRM_PAGE = 3 + +class RemoveVolumeConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Add A New Storage Volume") + self.__config = StorageVolumeConfig() + + def get_elements_for_page(self, screen, page): + if page is SELECT_POOL_PAGE: return self.get_storage_pool_list_page(screen) + elif page is SELECT_VOLUME_PAGE: return self.get_storage_volume_list_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + if page is SELECT_POOL_PAGE: return self.has_selectable_pools() + elif page is SELECT_VOLUME_PAGE: return self.has_selectable_volumes() + return False + + def validate_input(self, page, errors): + if page is SELECT_POOL_PAGE: return self.get_selected_pool() is not None + elif page is SELECT_VOLUME_PAGE: return self.get_selected_volume() is not None + elif page is CONFIRM_PAGE: + if self.__confirm.value(): + return True + else: + errors.append("You must confirm deleting a storage volume.") + return False + + def process_input(self, page): + if page is CONFIRM_PAGE: + self.get_libvirt().remove_storage_volume(self.get_selected_pool(), self.get_selected_volume()) + self.set_finished() + + def page_has_back(self, page): + return page > SELECT_POOL_PAGE + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def get_confirm_page(self, screen): + self.__confirm = Checkbox("Check here to confirm deleting volume: %s" % self.get_selected_volume()) + grid = Grid(1, 1) + grid.setField(self.__confirm, 0, 0) + return [Label("Remove Selected Storage Volume"), + grid] + +def RemoveStorageVolume(): + screen = RemoveVolumeConfigScreen() + screen.start() diff --git a/nodeadmin/setup.py.in b/nodeadmin/setup.py.in index 3635810..8a7fc7c 100644 --- a/nodeadmin/setup.py.in +++ b/nodeadmin/setup.py.in @@ -35,5 +35,12 @@ setup(name = "nodeadmin", 'createnet = nodeadmin.createnetwork:CreateNetwork', 'destroynet = nodeadmin.destroynetwork:DestroyNetwork', 'undefinenet = nodeadmin.undefinenetwork:UndefineNetwork', - 'listnets = nodeadmin.listnetworks:ListNetworks'] + 'listnets = nodeadmin.listnetworks:ListNetworks', + 'addpool = nodeadmin.addpool:AddStoragePool', + 'rmpool = nodeadmin.removepool:RemoveStoragePool', + 'startpool = nodeadmin.startpool:StartStoragePool', + 'stoppool = nodeadmin.stoppool:StopStoragePool', + 'addvolume = nodeadmin.addvolume:AddStorageVolume', + 'rmvolume = nodeadmin.removevolume:RemoveStorageVolume', + 'listpools = nodeadmin.listpools:ListPools'] }) diff --git a/nodeadmin/startpool.py b/nodeadmin/startpool.py new file mode 100644 index 0000000..8a84512 --- /dev/null +++ b/nodeadmin/startpool.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# +# startpool.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +from configscreen import * + +LIST_POOLS_PAGE = 1 +FINAL_PAGE = 2 + +class StartStoragePoolConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Start A Storage Pool") + + def get_elements_for_page(self, screen, page): + if page is LIST_POOLS_PAGE: return self.get_storage_pool_list_page(screen, created = False) + elif page is FINAL_PAGE: return self.get_final_page(screen) + + def page_has_next(self, page): + return page is LIST_POOLS_PAGE and self.has_selectable_pools() + + def page_has_back(self, page): + return False + + def page_has_finish(self, page): + return page is FINAL_PAGE + + def validate_input(self, page, errors): + if page is LIST_POOLS_PAGE: + if self.get_selected_pool() is not None: + return True + else: + errors.append("Please select a storage pool to be started.") + return False + + def process_input(self, page): + if page is LIST_POOLS_PAGE: + self.get_libvirt().create_storage_pool(self.get_selected_pool()) + self.set_finished() + + def get_final_page(self, screen): + return [Label("Storage pool started: %s" % self.get_selected_pool())] + +def StartStoragePool(): + screen = StartStoragePoolConfigScreen() + screen.start() diff --git a/nodeadmin/stoppool.py b/nodeadmin/stoppool.py new file mode 100644 index 0000000..0522b95 --- /dev/null +++ b/nodeadmin/stoppool.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# +# stoppool.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +from configscreen import * + +LIST_POOLS_PAGE = 1 +FINAL_PAGE = 2 + +class StopStoragePoolConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Stop A Storage Pool") + + def get_elements_for_page(self, screen, page): + if page is LIST_POOLS_PAGE: return self.get_storage_pool_list_page(screen, defined = False) + elif page is FINAL_PAGE: return self.get_final_page(screen) + + def page_has_next(self, page): + return page is LIST_POOLS_PAGE and self.has_selectable_pools() + + def page_has_back(self, page): + return False + + def page_has_finish(self, page): + return page is FINAL_PAGE + + def validate_input(self, page, errors): + if page is LIST_POOLS_PAGE: + if self.get_selected_pool() is not None: + return True + else: + errors.append("Please select a storage pool to be stopped.") + return False + + def process_input(self, page): + if page is LIST_POOLS_PAGE: + self.get_libvirt().destroy_storage_pool(self.get_selected_pool()) + self.set_finished() + + def get_final_page(self, screen): + return [Label("Storage pool stopped: %s" % self.get_selected_pool())] + +def StopStoragePool(): + screen = StopStoragePoolConfigScreen() + screen.start() diff --git a/nodeadmin/storagemenu.py b/nodeadmin/storagemenu.py new file mode 100644 index 0000000..0b56dae --- /dev/null +++ b/nodeadmin/storagemenu.py @@ -0,0 +1,63 @@ +# storagemenu.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +import traceback + +from menuscreen import MenuScreen +from addpool import AddStoragePool +from startpool import StartStoragePool +from stoppool import StopStoragePool +from removepool import RemoveStoragePool +from addvolume import AddStorageVolume +from removevolume import RemoveStorageVolume +from listpools import ListStoragePools + +ADD_POOL = 1 +START_POOL = 2 +STOP_POOL = 3 +REMOVE_POOL = 4 +ADD_VOLUME = 5 +REMOVE_VOLUME = 6 +LIST_POOLS = 7 + +class StoragePoolMenuScreen(MenuScreen): + def __init__(self): + MenuScreen.__init__(self, "Storage Pool Administration") + + def get_menu_items(self): + return (("Add A Storage Pool", ADD_POOL), + ("Start A Storage Pool", START_POOL), + ("Stop A Storage Pool", STOP_POOL), + ("Remove A Storage Pool", REMOVE_POOL), + ("Add A Storage Volume", ADD_VOLUME), + ("Remove A Storage Volume", REMOVE_VOLUME), + ("List Storage Pools", LIST_POOLS)) + + def handle_selection(self, item): + if item is ADD_POOL: AddStoragePool() + elif item is START_POOL: StartStoragePool() + elif item is STOP_POOL: StopStoragePool() + elif item is REMOVE_POOL: RemoveStoragePool() + elif item is ADD_VOLUME: AddStorageVolume() + elif item is REMOVE_VOLUME: RemoveStorageVolume() + elif item is LIST_POOLS: ListStoragePools() + +def StoragePoolMenu(): + screen = StoragePoolMenuScreen() + screen.start() diff --git a/nodeadmin/utils.py b/nodeadmin/utils.py index 55a838c..28ccb8b 100644 --- a/nodeadmin/utils.py +++ b/nodeadmin/utils.py @@ -17,9 +17,19 @@ # also available at http://www.gnu.org/copyleft/gpl.html. import logging +import re logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', filename='/var/log/ovirt-nodeadmin.log', filemode='w') + +def string_is_not_blank(value): + if len(value) > 0: return True + return False + +def string_has_no_spaces(value): + if re.match("^[a-zA-Z0-9_]*$", value): + return True + return False diff --git a/nodeadmin/volumeconfig.py b/nodeadmin/volumeconfig.py new file mode 100644 index 0000000..7741391 --- /dev/null +++ b/nodeadmin/volumeconfig.py @@ -0,0 +1,76 @@ +# volumeconfig.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +import virtinst +from virtinst import Storage + +class StorageVolumeConfig: + def __init__(self): + self.__pool = None + self.__name = "" + self.__formats = None + self.__format = None + self.__max_capacity = 10000 + self.__allocation = 0 + + def set_pool(self, pool): + self.__pool = pool + self.__formats = None + self.__pool_type = virtinst.util.get_xml_path(self.__pool.XMLDesc(0), '/pool/@type') + self.__volume_class = Storage.StoragePool.get_volume_for_pool(self.__pool_type) + + def get_pool(self): + return self.__pool + + def create_volume(self): + volume = self.__volume_class(name = self.__name + ".img", + allocation = self.__allocation * 1024**2, + capacity = self.__max_capacity * 1024**2, + pool = self.__pool) + volume.pool = self.__pool + volume.format = self.__format + return volume + + def set_name(self, name): + self.__name = name + + def get_name(self): + return self.__name + + def get_formats_for_pool(self): + if self.__formats is None: + self.__formats = self.__volume_class.formats + return self.__formats + + def set_format(self, format): + self.__format = format + + def get_format(self): + return self.__format + + def set_max_capacity(self, capacity): + self.__max_capacity = capacity + + def get_max_capacity(self): + return self.__max_capacity + + def set_allocation(self, allocation): + self.__allocation = allocation + + def get_allocation(self): + return self.__allocation diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index 2a6b7b6..c392123 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -182,23 +182,35 @@ cd - %{__install} -p -m0755 nodeadmin/nodeadmin.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/mainmenu.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0644 nodeadmin/nodemenu.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/definedomain.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/createdomain.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/definedomain.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/destroydomain.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/undefinedomain.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/listdomains.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/domainconfig.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/listdomains.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/nodemenu.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/undefinedomain.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0644 nodeadmin/netmenu.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0644 nodeadmin/networkconfig.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/definenet.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/createnetwork.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/definenet.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/destroynetwork.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/netmenu.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/networkconfig.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/undefinenetwork.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/addpool.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/addvolume.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/listpools.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/poolconfig.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/removepool.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/removevolume.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/startpool.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/stoppool.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/storagemenu.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/volumeconfig.py %{buildroot}%{python_sitelib}/nodeadmin + %{__install} -p -m0755 nodeadmin/createuser.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/createmeter.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/halworker.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/libvirtworker.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/userworker.py %{buildroot}%{python_sitelib}/nodeadmin @@ -368,18 +380,25 @@ fi %{_sbindir}/ovirt-awake %{_initrddir}/ovirt-functions %defattr(-,root,root,0644) -%{_bindir}/nodeadmin -%{_bindir}/definedom +%{_bindir}/addpool +%{_bindir}/addvolume %{_bindir}/createdom -%{_bindir}/destroydom -%{_bindir}/undefinedom -%{_bindir}/listdoms -%{_bindir}/definenet %{_bindir}/createnet +%{_bindir}/createuser +%{_bindir}/definedom +%{_bindir}/definenet +%{_bindir}/destroydom %{_bindir}/destroynet -%{_bindir}/undefinenet +%{_bindir}/listdoms %{_bindir}/listnets -%{_bindir}/createuser +%{_bindir}/listpools +%{_bindir}/nodeadmin +%{_bindir}/rmpool +%{_bindir}/rmvolume +%{_bindir}/startpool +%{_bindir}/stoppool +%{_bindir}/undefinedom +%{_bindir}/undefinenet %{_sysconfdir}/collectd.conf.in %{python_sitelib}/nodeadmin %{python_sitelib}/nodeadmin- at VERSION@-py2.6.egg-info -- 1.6.2.5 From dhuff at redhat.com Wed Oct 14 20:09:36 2009 From: dhuff at redhat.com (David Huff) Date: Wed, 14 Oct 2009 16:09:36 -0400 Subject: [Ovirt-devel] Refactor ovirt-node code base for inclusion in Fedora Message-ID: <1255550984-25188-1-git-send-email-dhuff@redhat.com> Main reason for refactor is no ovirt-node-image binary image allowed in fedora. Moves tools and kickstart files form ovirt-node-image to subpackage ovirt-node-recipe. Removes old sub packages form ovirt-node, stateless, logos, selinux. Modifies init scripts to meet Fedora packaging guidelines: added status, reload, and lockfile, rhbz: 514221 Added License file. From dhuff at redhat.com Wed Oct 14 20:09:37 2009 From: dhuff at redhat.com (David Huff) Date: Wed, 14 Oct 2009 16:09:37 -0400 Subject: [Ovirt-devel] [PATCH 01/10] Modified init scripts for inclusion in Fedora In-Reply-To: <1255550984-25188-1-git-send-email-dhuff@redhat.com> References: <1255550984-25188-1-git-send-email-dhuff@redhat.com> Message-ID: <1255550984-25188-2-git-send-email-dhuff@redhat.com> ovirt ovirt-early ovirt-firstboot ovirt-post Added reload to init functions Added status Added lockfile --- scripts/ovirt | 18 ++++++++++++++++++ scripts/ovirt-early | 16 ++++++++++++++++ scripts/ovirt-firstboot | 18 +++++++++++++++++- scripts/ovirt-post | 18 ++++++++++++++++++ 4 files changed, 69 insertions(+), 1 deletions(-) diff --git a/scripts/ovirt b/scripts/ovirt index 4ff03f2..9503c06 100755 --- a/scripts/ovirt +++ b/scripts/ovirt @@ -10,7 +10,13 @@ . /etc/init.d/functions . /etc/init.d/ovirt-functions +prog=ovirt +lockfile=/var/lock/subsys/$prog + start() { + + touch $lockfile + if is_standalone; then return 0 fi @@ -74,6 +80,8 @@ start() { else log "skipping libvirt-qpid and matahari configuration, could not find $libvirt_qpid_conf" fi + + rm -f $lockfile } case "$1" in @@ -89,6 +97,16 @@ case "$1" in test $? == 0 && success || failure echo ;; + status) + status $prog + ;; + reload) + stop + start + ;; + stop) + stop + ;; *) echo "Usage: ovirt {start}" exit 2 diff --git a/scripts/ovirt-early b/scripts/ovirt-early index cdd4afd..dcbcac4 100755 --- a/scripts/ovirt-early +++ b/scripts/ovirt-early @@ -10,6 +10,9 @@ . /etc/init.d/functions . /etc/init.d/ovirt-functions +prog=ovirt-early +lockfile=/var/lock/subsys/$prog + BONDING_MODCONF_FILE=/etc/modprobe.d/bonding AUGTOOL_CONFIG=/var/tmp/augtool-config @@ -130,6 +133,7 @@ find_disk() { start() { + touch $lockfile # oVirt boot parameters # BOOTIF=link|eth*| (appended by pxelinux) # ovirt_init=[usb|scsi[:serial#]|/dev/...] @@ -433,6 +437,8 @@ start() { fi fi fi + + rm -f $lockfile return 0 } @@ -481,6 +487,16 @@ case "$1" in test $? == 0 && success || failure echo ;; + status) + status $prog + ;; + reload) + stop + start + ;; + stop) + stop + ;; *) echo "Usage: ovirt-early {start}" exit 2 diff --git a/scripts/ovirt-firstboot b/scripts/ovirt-firstboot index bdafb33..335bdaa 100755 --- a/scripts/ovirt-firstboot +++ b/scripts/ovirt-firstboot @@ -27,6 +27,9 @@ . /etc/init.d/functions . /etc/init.d/ovirt-functions +prog=ovirt-firstboot +lockfile=/var/lock/subsys/$prog + trap '__st=$?; stop_log; exit $__st' 0 trap 'exit $?' 1 2 13 15 @@ -35,6 +38,8 @@ start () if ! is_firstboot && ! is_auto_install; then return fi + + touch $lockfile # by default in standalone, disable collectd until server is configured service collectd stop > /dev/null 2>&1 @@ -63,6 +68,8 @@ start () disable_firstboot ovirt_store_firstboot_config >> $OVIRT_LOGFILE 2>&1 + + rm -f $lockfile } case "$1" in @@ -78,7 +85,16 @@ case "$1" in test $? == 0 && success || failure echo ;; - + status) + status $prog + ;; + reload) + stop + start + ;; + stop) + stop + ;; *) echo "Usage: ovirt-firstboot {start}" exit 2 diff --git a/scripts/ovirt-post b/scripts/ovirt-post index 1fcfd6a..5cc8b8a 100755 --- a/scripts/ovirt-post +++ b/scripts/ovirt-post @@ -10,6 +10,9 @@ . /etc/init.d/functions . /etc/init.d/ovirt-functions +prog=ovirt-post +lockfile=/var/lock/subsys/$prog + start() { # wait for libvirt to finish initializing local count=0 @@ -24,6 +27,9 @@ start() { count=$(expr $count + 1) sleep 1 fi + + touch $lockfile + done BACKUP=$(mktemp) ISSUE=/etc/issue @@ -61,6 +67,8 @@ start() { # Removed ovirt-identify-node since it has now # been replaced with the matahari qmf agent. + + rm -f $lockfile } case "$1" in @@ -76,6 +84,16 @@ case "$1" in test $? == 0 && success || failure echo ;; + status) + status $prog + ;; + reload) + stop + start + ;; + stop) + stop + ;; *) echo "Usage: ovirt-post {start}" exit 2 -- 1.6.2.5 From dhuff at redhat.com Wed Oct 14 20:09:38 2009 From: dhuff at redhat.com (David Huff) Date: Wed, 14 Oct 2009 16:09:38 -0400 Subject: [Ovirt-devel] [PATCH 02/10] Added recipe dir and ks files In-Reply-To: <1255550984-25188-1-git-send-email-dhuff@redhat.com> References: <1255550984-25188-1-git-send-email-dhuff@redhat.com> Message-ID: <1255550984-25188-3-git-send-email-dhuff@redhat.com> --- recipe/common-blacklist.ks | 161 +++++++++++++++++++++++++++++++++++++++++ recipe/common-install.ks | 19 +++++ recipe/common-pkgs.ks | 75 +++++++++++++++++++ recipe/common-post.ks | 170 ++++++++++++++++++++++++++++++++++++++++++++ recipe/ovirt-node-image.ks | 118 ++++++++++++++++++++++++++++++ 5 files changed, 543 insertions(+), 0 deletions(-) create mode 100644 recipe/common-blacklist.ks create mode 100644 recipe/common-install.ks create mode 100644 recipe/common-pkgs.ks create mode 100644 recipe/common-post.ks create mode 100644 recipe/ovirt-node-image.ks diff --git a/recipe/common-blacklist.ks b/recipe/common-blacklist.ks new file mode 100644 index 0000000..81f46d6 --- /dev/null +++ b/recipe/common-blacklist.ks @@ -0,0 +1,161 @@ +# -*-Shell-script-*- +%post + +echo "Removing excess RPMs" + +# kernel pulls in mkinitrd which pulls in isomd5sum which pulls in python, +# and livecd-tools needs lokkit to configure SELinux. +# However, this is just an install-time dependency; we can remove +# it afterwards, which we do here +RPMS="system-config-firewall-tui system-config-network-tui rhpl \ + rpm-python kudzu libsemanage-python" + +RPMS="$RPMS mkinitrd isomd5sum dmraid checkpolicy" + +# Remove additional RPMs forcefully +RPMS="$RPMS gamin pm-utils kbd usermode vbetool ConsoleKit hdparm \ + efibootmgr linux-atm-libs fedora-release-notes \ + psmisc cryptsetup-luks pciutils mtools syslinux \ + wireless-tools radeontool libicu gnupg2 \ + fedora-logos" + +# cronie pulls in exim (sendmail) which pulls in all kinds of perl deps +RPMS="$RPMS exim perl-version perl-Pod-Simple perl-libs perl-Module-Pluggable \ + perl-Pod-Escapes perl" + +RPMS="$RPMS sysklogd" + +# workaround for gpxe issue with the virt-preview qemu on F11 host kernel +# https://bugzilla.redhat.com/show_bug.cgi?id=512358 +RPMS="$RPMS gpxe-roms-qemu" +ln -snf ../etherboot/e1000-82542.zrom /usr/share/qemu/pxe-e1000.bin +ln -snf ../etherboot/ne.zrom /usr/share/qemu/pxe-ne2k_pci.bin +ln -snf ../etherboot/pcnet32.zrom /usr/share/qemu/pxe-pcnet.bin +ln -snf ../etherboot/rtl8139.zrom /usr/share/qemu/pxe-rtl8139.bin +ln -snf ../etherboot/virtio-net.zrom /usr/share/qemu/pxe-virtio.bin + +# Things we could probably remove if libvirt didn't link against them +#RPMS="$RPMS avahi PolicyKit xen-libs" + +# Things we could probably remove if qemu-kvm didn't link against them +#RPMS="$RPMS SDL alsa-lib" + +# Pam complains when this is missing +#RPMS="$RPM ConsoleKit-libs" + +for rpm in $RPMS; do + rpm -v -e --nodeps $rpm 2> /dev/null +done + +# the following are lists of kernel modules we are pretty sure we won't need; +# note that these can be single files or whole directories. They are specified +# starting at $MODULES; so if you want to remove the NLS stuff from the +# fs subdir, your mods entry would be "fs/nls" +fs_mods="fs/nls fs/9p fs/affs fs/autofs fs/autofs4 fs/befs fs/bfs fs/cifs \ + fs/coda fs/cramfs fs/dlm fs/ecryptfs fs/efs fs/exportfs fs/ext4 \ + fs/freevxfs fs/gfs2 fs/hfs fs/hfsplus fs/jbd2 fs/jffs \ + fs/jffs2 fs/jfs fs/minix fs/ncpfs fs/ocfs2 fs/qnx4 fs/reiserfs \ + fs/romfs fs/sysv fs/udf fs/ufs fs/xfs" + +net_mods="net/9p net/appletalk net/atm net/ax25 \ + net/bluetooth net/dccp net/decnet net/ieee80211 net/ipx net/irda \ + net/mac80211 net/netrom net/rfkill net/rose net/sched net/tipc \ + net/wanrouter net/wireless" + +driver_mods="drivers/auxdisplay drivers/net/appletalk \ + drivers/net/hamradio drivers/net/pcmcia drivers/net/tokenring \ + drivers/net/wireless drivers/net/irda drivers/atm drivers/usb/atm \ + drivers/acpi drivers/char/drm drivers/char/mwave \ + drivers/char/ipmp drivers/char/pcmcia drivers/crypto drivers/dca \ + drivers/firmware drivers/memstick drivers/mmc drivers/mfs \ + drivers/parport drivers/video drivers/watchdog drivers/net/ppp* \ + drivers/usb/serial drivers/usb/misc drivers/usb/class \ + drivers/usb/image drivers/rtc drivers/char/lp*" + +misc_mods="drivers/bluetooth drivers/firewire drivers/i2c drivers/isdn \ + drivers/media drivers/misc drivers/leds drivers/mtd drivers/w1 sound \ + drivers/input drivers/pcmcia drivers/scsi/pcmcia" + +echo "Removing excess kernel modules" +MODULES="/lib/modules/*/kernel" +RM="rm -rf" + +for mods in $fs_mods $net_mods $misc_mods $driver_mods ; do + $RM $MODULES/$mods +done + +echo "Removing all timezones except for UTC" +find /usr/share/zoneinfo -regextype egrep -type f \ + ! -regex ".*/UTC|.*/GMT" -exec $RM {} \; + +echo "Removing blacklisted files and directories" +blacklist="/etc/alsa /etc/pki /usr/share/hwdata/MonitorsDB \ + /usr/share/hwdata/oui.txt /usr/share/hwdata/videoaliases \ + /usr/share/firstboot /usr/share/lua /usr/share/kde4 /usr/share/pixmaps \ + /usr/share/hwdata/videodrivers /usr/share/icons /usr/share/fedora-release \ + /usr/share/tabset /usr/share/libvirt /usr/share/augeas/lenses/tests \ + /usr/share/tc /usr/share/emacs /usr/share/info \ + /usr/src /usr/etc /usr/games /usr/include /usr/local \ + /usr/sbin/{dell*,sasldblistusers2,build-locale-archive,glibc_post_upgrade.*}" +blacklist_lib="/usr/{,lib64}/tc \ + /usr/lib{,64}/tls /usr/lib{,64}/sse2 \ + /usr/lib{,64}/pkgconfig /usr/lib{,64}/nss \ + /usr/lib{,64}/games /usr/lib{,64}/alsa-lib /usr/lib{,64}/fs/reiserfs \ + /usr/lib{,64}/krb5 /usr/lib{,64}/hal /usr/lib{,64}/gio \ + /usr/lib/locale /usr/lib/syslinux" +blacklist_pango="/usr/lib{,64}/pango /usr/lib{,64}/libpango* \ + /etc/pango /usr/bin/pango*" +blacklist_hal="/usr/bin/hal-disable-polling \ + /usr/bin/hal-is-caller-locked-out /usr/bin/hal-is-caller-privileged \ + /usr/bin/hal-lock /usr/bin/hal-set-property /usr/bin/hal-setup-keymap" +blacklist_ssh="/usr/bin/sftp /usr/bin/slogin /usr/bin/ssh /usr/bin/ssh-add \ + /usr/bin/ssh-agent /usr/bin/ssh-copy-id /usr/bin/ssh-keyscan" +blacklist_docs="/usr/share/omf /usr/share/gnome /usr/share/doc \ + /usr/share/locale /usr/share/libthai /usr/share/man \ + /usr/share/X11 /usr/share/i18n" + +eval $RM $blacklist $blacklist_lib $blacklist_pango $blacklist_hal \ + $blacklist_ssh $blacklist_docs + +echo "Cleanup empty directory structures in /usr/share" +find /usr/share -type d -exec rmdir {} \; > /dev/null 2>&1 + +echo "Cleanup excess selinux modules" +$RM /usr/share/selinux + +echo "Removing python source files" +find / -name '*.py' -exec rm -f {} \; +find / -name '*.pyo' -exec rm -f {} \; + +echo "Running image-minimizer..." +%end + +%post --nochroot --interpreter image-minimizer +drop /usr/lib/libboost* +keep /usr/lib/libboost_program_options.so* +keep /usr/lib/libboost_filesystem.so* +keep /usr/lib/libboost_thread-mt.so* +keep /usr/lib/libboost_system.so* +drop /usr/lib64/libboost* +keep /usr/lib64/libboost_program_options.so* +keep /usr/lib64/libboost_filesystem.so* +keep /usr/lib64/libboost_thread-mt.so* +keep /usr/lib64/libboost_system.so* +drop /usr/kerberos +keep /usr/kerberos/bin/kinit +keep /usr/kerberos/bin/klist +drop /lib/firmware +keep /lib/firmware/3com +keep /lib/firmware/acenic +keep /lib/firmware/adaptec +keep /lib/firmware/advansys +keep /lib/firmware/bnx2 +keep /lib/firmware/cxgb3 +keep /lib/firmware/e100 +keep /lib/firmware/myricom +keep /lib/firmware/qlogic +keep /lib/firmware/sun +keep /lib/firmware/tehuti +keep /lib/firmware/tigon +%end + diff --git a/recipe/common-install.ks b/recipe/common-install.ks new file mode 100644 index 0000000..d6620f7 --- /dev/null +++ b/recipe/common-install.ks @@ -0,0 +1,19 @@ +lang C +keyboard us +timezone --utc UTC +auth --useshadow --enablemd5 +selinux --enforcing +firewall --disabled +part / --size 650 --fstype ext2 +services --enabled=auditd,ntpd,ntpdate,collectd,iptables,network,rsyslog,libvirt-qpid,multipathd +# This requires a new fixed version of livecd-creator to honor the --append settings. +bootloader --timeout=30 --append="console=tty0 console=ttyS0,115200n8" + +# not included by default in Fedora 10 livecd initramfs +device virtio_blk +device virtio_pci +device scsi_wait_scan + +# multipath kmods +device dm-multipath +device dm-round-robin diff --git a/recipe/common-pkgs.ks b/recipe/common-pkgs.ks new file mode 100644 index 0000000..daff195 --- /dev/null +++ b/recipe/common-pkgs.ks @@ -0,0 +1,75 @@ +audit +bc +kernel +hwdata +passwd +policycoreutils +rootfiles +dhclient +openssh-clients +openssh-server +kvm +libmlx4 +ovirt-node +-selinux-policy-targeted +selinux-policy-minimum +vim-minimal +sudo +python +python-libs +python-setuptools +db4 +vconfig +python-virtinst +matahari +#debugging +hdparm +sos +gdb +ltrace +strace +sysstat +tcpdump +pstack +pciutils +numactl +file +lsof +newt-python +/usr/bin/kvmtrace +#remove +-audit-libs-python +-ustr +-authconfig +-wireless-tools +-setserial +-prelink +-newt-python +-newt +-kudzu +-libselinux-python +-rhpl +-kbd +-usermode +-fedora-logos +-dmraid +-gzip +-less +-which +-parted +-nash +-tar +-libuser +-mtools +-cpio +-sysklogd +/usr/sbin/lokkit +isomd5sum +irqbalance +cpuspeed +acpid +device-mapper-multipath +kpartx +# workaround for gpxe issue with the virt-preview qemu on F11 host kernel +# https://bugzilla.redhat.com/show_bug.cgi?id=512358 +etherboot-zroms-kvm diff --git a/recipe/common-post.ks b/recipe/common-post.ks new file mode 100644 index 0000000..7cebef0 --- /dev/null +++ b/recipe/common-post.ks @@ -0,0 +1,170 @@ +# -*-Shell-script-*- +echo "Starting Kickstart Post" +PATH=/sbin:/usr/sbin:/bin:/usr/bin +export PATH + +# Import SELinux Modules +echo "Enabling selinux modules" +SEMODULES="base automount avahi consolekit cyrus dhcp dnsmasq guest hal ipsec \ +iscsi kerberos kerneloops ldap lockdev logadm mozilla ntp ovirt-node-selinux \ +polkit portmap qemu rpcbind sasl snmp stunnel sysstat tcpd unprivuser \ +unconfined usbmodules userhelper virt" + +lokkit -v --selinuxtype=minimum +tmpdir=$(mktemp -d) + +for semodule in $SEMODULES; do + found=0 + pp_file=/usr/share/selinux/minimum/$semodule.pp + if [ -f $pp_file.bz2 ]; then + bzip2 -dc $pp_file.bz2 > "$tmpdir/$semodule.pp" + rm $pp_file.bz2 + found=1 + elif [ -f $pp_file ]; then + mv $pp_file "$tmpdir" + found=1 + fi + # Don't put "base.pp" on the list. + test $semodule = base \ + && continue + test $found=1 \ + && modules="$modules $semodule.pp" +done + +if test -n "$modules"; then + (cd "$tmpdir" \ + && test -f base.pp \ + && semodule -v -b base.pp -i $modules \ + && semodule -v -B ) +fi +rm -rf "$tmpdir" + +echo "Running ovirt-install-node-stateless" +ovirt-install-node-stateless + +echo "Creating shadow files" +# because we aren't installing authconfig, we aren't setting up shadow +# and gshadow properly. Do it by hand here +pwconv +grpconv + +echo "Forcing C locale" +# force logins (via ssh, etc) to use C locale, since we remove locales +cat >> /etc/profile << \EOF +# oVirt: force our locale to C since we don't have locale stuff' +export LC_ALL=C LANG=C +EOF + +echo "Configuring IPTables" +# here, we need to punch the appropriate holes in the firewall +cat > /etc/sysconfig/iptables << \EOF +# oVirt automatically generated firewall configuration +*filter +:INPUT ACCEPT [0:0] +:FORWARD ACCEPT [0:0] +:OUTPUT ACCEPT [0:0] +-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT +-A INPUT -p icmp -j ACCEPT +-A INPUT -i lo -j ACCEPT +# libvirt +-A INPUT -p tcp --dport 16509 -j ACCEPT +# SSH +-A INPUT -p tcp --dport 22 -j ACCEPT +# anyterm +-A INPUT -p tcp --dport 81 -j ACCEPT +# guest consoles +-A INPUT -p tcp -m multiport --dports 5800:6000 -j ACCEPT +# migration +-A INPUT -p tcp -m multiport --dports 49152:49216 -j ACCEPT +-A INPUT -j REJECT --reject-with icmp-host-prohibited +-A FORWARD -m physdev ! --physdev-is-bridged -j REJECT --reject-with icmp-host-prohibited +COMMIT +EOF +# configure IPv6 firewall, default is all ACCEPT +cat > /etc/sysconfig/ip6tables << \EOF +# oVirt automatically generated firewall configuration +*filter +:INPUT ACCEPT [0:0] +:FORWARD ACCEPT [0:0] +:OUTPUT ACCEPT [0:0] +-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT +-A INPUT -p ipv6-icmp -j ACCEPT +-A INPUT -i lo -j ACCEPT +# libvirt +-A INPUT -p tcp --dport 16509 -j ACCEPT +# SSH +-A INPUT -p tcp --dport 22 -j ACCEPT +# anyterm +-A INPUT -p tcp --dport 81 -j ACCEPT +# guest consoles +-A INPUT -p tcp -m multiport --dports 5800:6000 -j ACCEPT +# migration +-A INPUT -p tcp -m multiport --dports 49152:49216 -j ACCEPT +-A INPUT -j REJECT --reject-with icmp6-adm-prohibited +-A FORWARD -m physdev ! --physdev-is-bridged -j REJECT --reject-with icmp6-adm-prohibited +COMMIT +EOF + +# remove errors from /sbin/dhclient-script +DHSCRIPT=/sbin/dhclient-script +sed -i 's/mv /cp -p /g' $DHSCRIPT +sed -i '/rm -f.*${interface}/d' $DHSCRIPT +sed -i '/rm -f \/etc\/localtime/d' $DHSCRIPT +sed -i '/rm -f \/etc\/ntp.conf/d' $DHSCRIPT +sed -i '/rm -f \/etc\/yp.conf/d' $DHSCRIPT + +if rpm -q --qf '%{release}' ovirt-node | grep -q "^0\." ; then + echo "Building in developer mode, leaving root account unlocked" + augtool <<\EOF +set /files/etc/ssh/sshd_config/PermitEmptyPasswords yes +save +EOF +else + echo "Building in production mode, locking root account" + passwd -l root +fi + +# directories required in the image with the correct perms +# config persistance currently handles only regular files +mkdir -p /root/.ssh +chmod 700 /root/.ssh + +# fix iSCSI/LVM startup issue +sed -i 's/node\.session\.initial_login_retry_max.*/node.session.initial_login_retry_max = 60/' /etc/iscsi/iscsid.conf + +# root's bash profile +cat >> /root/.bashrc < /etc/rwtab.d/ovirt <> /etc/fstab +%end + +%post +# Create initial manifests +manifests=/tmp/manifests +mkdir -p $manifests +rpm -qa --qf '%{name}-%{version}-%{release}.%{arch}\n' | sort \ + > $manifests/rpm-manifest.txt +rpm -qa --qf '%{sourcerpm}\n' | sort -u > $manifests/srpm-manifest.txt +du -akx --exclude=/var/cache/yum / > $manifests/file-manifest.txt +du -x --exclude=/var/cache/yum / > $manifests/dir-manifest.txt +%end + +%include common-blacklist.ks + +%post --nochroot +if [ -f "ovirt-authorized_keys" ]; then + echo "Adding authorized_keys to Image" + mkdir -p $INSTALL_ROOT/root/.ssh + cp -v ovirt-authorized_keys $INSTALL_ROOT/root/.ssh/authorized_keys + chown -R root:root $INSTALL_ROOT/root/.ssh + chmod 755 $INSTALL_ROOT/root/.ssh + chmod 644 $INSTALL_ROOT/root/.ssh/authorized_keys +fi + +echo "Fixing boot menu" +# remove quiet from Node bootparams, added by livecd-creator +sed -i -e 's/ quiet//' $LIVE_ROOT/isolinux/isolinux.cfg + +# add stand-alone boot entry +awk ' +BEGIN { + # append additional default boot parameters + add_boot_params="check" +} +/^label linux0/ { linux0=1 } +linux0==1 && $1=="append" { + $0=$0 " " add_boot_params + append0=$0 +} +linux0==1 && $1=="label" && $2!="linux0" { + linux0=2 + print "label stand-alone" + print " menu label Boot in stand-alone mode" + print " kernel vmlinuz0" + gsub("console=tty0", "", append0) + print append0" ovirt_standalone console=tty0" +} +{ print } +' $LIVE_ROOT/isolinux/isolinux.cfg > $LIVE_ROOT/isolinux/isolinux.cfg.standalone +mv $LIVE_ROOT/isolinux/isolinux.cfg.standalone $LIVE_ROOT/isolinux/isolinux.cfg + +%end + +%post +# Create post-image processing manifests +manifests=/tmp/manifests +mkdir -p $manifests +rpm -qa --qf '%{name}-%{version}-%{release}.%{arch}\n' | sort \ + > $manifests/rpm-manifest-post.txt +rpm -qa --qf '%{sourcerpm}\n' | sort -u > $manifests/srpm-manifest-post.txt +du -akx --exclude=/var/cache/yum / > $manifests/file-manifest-post.txt +du -x --exclude=/var/cache/yum / > $manifests/dir-manifest-post.txt + +ver=$(rpm -q --qf '%{version}' ovirt-node) +rel=$(rpm -q --qf '%{release}' ovirt-node) +arch=$(rpm -q --qf '%{arch}' ovirt-node) +echo "oVirt Node release $ver-$rel-$arch" > $manifests/ovirt-release +tar -cvf ovirt-node-image-manifests-$ver-$rel.$arch.tar -C /tmp manifests +ln -nf ovirt-node-image-manifests-$ver-$rel.$arch.tar ovirt-node-image-manifests.tar +rm -Rf $manifests +%end + +%post --nochroot +# Move manifest tar to build directory +mv $INSTALL_ROOT/ovirt-node-image-manifests*.tar . + +# only works on x86, x86_64 +if [ "$(uname -i)" = "i386" -o "$(uname -i)" = "x86_64" ]; then + if [ ! -d $LIVE_ROOT/LiveOS ]; then mkdir -p $LIVE_ROOT/LiveOS ; fi + cp /usr/bin/livecd-iso-to-disk $LIVE_ROOT/LiveOS + cp /usr/bin/livecd-iso-to-pxeboot $LIVE_ROOT/LiveOS +fi +%end + -- 1.6.2.5 From dhuff at redhat.com Wed Oct 14 20:09:39 2009 From: dhuff at redhat.com (David Huff) Date: Wed, 14 Oct 2009 16:09:39 -0400 Subject: [Ovirt-devel] [PATCH 03/10] added tools dir and node-image-tools In-Reply-To: <1255550984-25188-1-git-send-email-dhuff@redhat.com> References: <1255550984-25188-1-git-send-email-dhuff@redhat.com> Message-ID: <1255550984-25188-4-git-send-email-dhuff@redhat.com> --- tools/create-ovirt-iso-nodes | 135 ++++++++++++++++++++++++++ tools/edit-livecd | 220 ++++++++++++++++++++++++++++++++++++++++++ tools/livecd-iso-to-iscsi | 201 ++++++++++++++++++++++++++++++++++++++ tools/livecd-rpms | 28 ++++++ tools/livecd-setauth | 50 ++++++++++ 5 files changed, 634 insertions(+), 0 deletions(-) create mode 100755 tools/create-ovirt-iso-nodes create mode 100755 tools/edit-livecd create mode 100755 tools/livecd-iso-to-iscsi create mode 100755 tools/livecd-rpms create mode 100755 tools/livecd-setauth diff --git a/tools/create-ovirt-iso-nodes b/tools/create-ovirt-iso-nodes new file mode 100755 index 0000000..fe2e7ab --- /dev/null +++ b/tools/create-ovirt-iso-nodes @@ -0,0 +1,135 @@ +#!/bin/bash +# +# Create fake oVirt Nodes for testing CDROM boot +# Copyright 2008 Red Hat, Inc. +# Written by Perry Myers +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Library General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +PATH=$PATH:/sbin:/usr/sbin + +ME=$(basename "$0") +warn() { printf '%s: %s\n' "$ME" "$*" >&2; } +try_h() { printf "Try \`$ME -h' for more information.\n" >&2; } +die() { warn "$@"; try_h; exit 1; } + +NET_DEFAULT=network:default +IMGDIR_DEFAULT=/var/lib/libvirt/images +imgdir=$IMGDIR_DEFAULT +NODEIMG_DEFAULT=/usr/share/ovirt-node-image/ovirt-node-image.iso +nodeimg=$NODEIMG_DEFAULT +NUM_DISKS_DEFAULT=1 +RANGE_DEFAULT="6-9" +RAM_DEFAULT=512 +VCPUS_DEFAULT=1 + +NODE_DISK_FMT=qcow2 +NODE_DISK_SIZE=6144M + +gen_fake_managed_node() { + local num=$1 + local src_nodeimg=$2 + local last_mac=$(( 54 + $num )) + + local os_variant=fedora10 + if [ "$no_virtio" = 1 ]; then + os_variant=fedora8 + fi + + echo "Creating fake node$num using $nodeimg..." + local dest_nodeimg="$imgdir/node${num}-$(basename $src_nodeimg)" + echo "$src_nodeimg -> $dest_nodeimg" + rsync -av $src_nodeimg $dest_nodeimg + + virsh destroy node$num > /dev/null 2>&1 + virsh undefine node$num > /dev/null 2>&1 + + local disks= + for ((i=0;i<$num_disks;i+=1)); do + qemu-img create -f $NODE_DISK_FMT \ + $imgdir/node${num}-${i}.$NODE_DISK_FMT $NODE_DISK_SIZE + disks="$disks --disk path=$imgdir/node${num}-${i}.$NODE_DISK_FMT" + done + + # FIXME: virt-install should be changed to have a --nostart parameter + # that just defines the VM w/o starting it. + virt-install --name=node$num --ram=$ram --vcpus=$vcpus $disks \ + --cdrom=$dest_nodeimg --livecd \ + --network=$net --mac=00:16:3e:12:34:$last_mac \ + --vnc --accelerate --hvm --noautoconsole \ + --os-type=linux --os-variant=$os_variant \ + --force --noreboot + virsh destroy node$num > /dev/null 2>&1 + echo "node$num created" +} + +usage() { + case $# in 1) warn "$1"; try_h; exit 1;; esac + cat < +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Library General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +#!/bin/bash + +PATH=$PATH:/sbin:/usr/sbin + +ME=$(basename "$0") +warn() { printf '%s: %s\n' "$ME" "$*" >&2; } +try_h() { printf "Try \`$ME -h' for more information.\n" >&2; } +die() { warn "$@"; try_h; exit 1; } + +NODEIMG_DEFAULT=/usr/share/ovirt-node-image/ovirt-node-image.iso +CD=$NODEIMG_DEFAULT + +usage() { + case $# in 1) warn "$1"; try_h; exit 1;; esac + cat < when done, and the script + re-packages the ISO. + -h display this help and exit + +EXAMPLES + + Example Script: + #!/bin/sh + touch etc/sysconfig/foo + Save as foo and make executable: + chmod a+x foo + Run this to create a file /etc/sysconfig/foo in the livecd filesystem + (note the use of "\$PWD/foo", not "./foo", since it will be run from a + different directory): + + $ME -i input.iso -o /tmp/result.iso -p "\$PWD/foo" + + or, equivalently, but without a separate script: + + $ME -i input.iso -o /tmp/result.iso -p 'touch etc/sysconfig/foo' + +EOF +} + +# exit after any error: +set -e + +CODE= +OUTPUT_FILE= + +err=0 help=0 +while getopts :b:hi:o:p: c; do + case $c in + i) CD=$OPTARG;; + b) PARAMS=$OPTARG;; + o) OUTPUT_FILE=$OPTARG;; + p) CODE=$OPTARG;; + h) help=1;; + '?') err=1; warn "invalid option: \`-$OPTARG'";; + :) err=1; warn "missing argument to \`-$OPTARG' option";; + *) err=1; warn "internal error: \`-$OPTARG' not handled";; + esac +done +test $err = 1 && { try_h; exit 1; } +test $help = 1 && { usage; exit 0; } + +# Require "-o OUTPUT_FILE" +test -z "$OUTPUT_FILE" \ + && { warn "no output file specified; use -o FILE.iso"; try_h; exit 1; } + +# Fail if there are any extra command-line arguments. +if test $OPTIND -le $#; then + bad_arg=$(eval "echo \$$OPTIND") + warn "extra argument '$bad_arg'"; try_h; exit 1 +fi + +# first, check to see we are root +if [ $( id -u ) -ne 0 ]; then + die "Must run as root" +fi + +# Check for some prerequisites. +# "type" prints "PROG not found" if it's not in $PATH. +type mkisofs +type mksquashfs +type sed +type implantisomd5 + +sane_name() +{ + case $1 in + *[^a-zA-Z0-9._,+:/@%=-]*) false;; + *) true;; + esac +} + +# Fail if names we'll use contain white space or shell meta-characters +sane_name "$PWD" || die "invalid working directory name: $PWD" +sane_name "$CD" || die "invalid ISO name: $CD" + +WDIR=`mktemp -d $PWD/livecd.XXXXXXXXXX` + +addExit() { + EXIT="$@ ; $EXIT" + trap "$EXIT" EXIT HUP TERM INT QUIT +} + +mnt() { + local margs="$1" ; shift + local mp="$WDIR/$1" + for D in "$@" ; do + mkdir -v -p "$WDIR/$D" + done + eval mount -v $margs "$mp" + addExit "df | grep $mp > /dev/null 2>&1 && umount -v $mp" +} + +addExit "rm -rf $WDIR" + +ID_FS_LABEL= # initialize, in case vol_id fails +eval "$(/lib/udev/vol_id $CD)" +LABEL=$ID_FS_LABEL + +# mount the CD image +mnt "-t iso9660 $CD -o loop,ro" cd + +# mount compressed filesystem +mnt "-t squashfs $WDIR/cd/LiveOS/squashfs.img -o ro,loop" sq + +# create writable copy of the new filesystem for the CD +cp -pr $WDIR/cd $WDIR/cd-w + +# create writable copy of the filesystem for the new compressed +# squashfs filesystem +cp -pr $WDIR/sq $WDIR/sq-w + +# mount root filesystem +mnt "-t ext2 $WDIR/sq-w/LiveOS/ext3fs.img -o rw,loop" ex + +echo ">>> Updating CD content" +if [ -n "$CODE" ]; then + ( + cd $WDIR/ex + set +e + eval "$CODE" + set -e + ) +else + echo "***" + echo "*** Pausing to allow manual changes. Press any key to continue." + echo "***" + read +fi + +# Try to unmount. But this is likely to fail, so let the user retry, +# e.g., if he forgot to "cd" out of $WDIR/ex. +while :; do + echo ">>> Unmounting ext3fs" + umount $WDIR/ex && break + echo ">>> Unmounting the working file system copy failed" + echo "***" + echo "*** Did you forget to 'cd' out of $WDIR/ex?" + echo "***" + echo "*** Press any key to repeat the attempt." + echo "***" + read +done + +echo ">>> Compressing filesystem" +mksquashfs $WDIR/sq-w/ $WDIR/cd-w/LiveOS/squashfs.img -noappend + +echo ">>> Recomputing MD5 sums" +( cd $WDIR/cd-w && find . -type f -not -name md5sum.txt \ + -not -path '*/isolinux/*' -print0 | xargs -0 -- md5sum > md5sum.txt ) + +if [ -n "$PARAMS" ]; then + case $PARAMS in + *@*) warn "PARAMS contains the @ sed delimiter, be sure it's escaped";; + esac + echo ">>> Appending boot parameters" + sed -i 's@^ append .*$@& '"$PARAMS@" "$WDIR/cd-w/isolinux/isolinux.cfg" +fi + +echo ">>> Creating ISO image $ISO" +mkisofs \ + -V "$LABEL" \ + -r -cache-inodes -J -l \ + -b isolinux/isolinux.bin \ + -c isolinux/boot.cat \ + -no-emul-boot -boot-load-size 4 -boot-info-table \ + -o "$OUTPUT_FILE" \ + $WDIR/cd-w + +echo ">>> Implanting ISO MD5 Sum" +implantisomd5 --force "$OUTPUT_FILE" + +# The trap ... callbacks will unmount everything. +set +e diff --git a/tools/livecd-iso-to-iscsi b/tools/livecd-iso-to-iscsi new file mode 100755 index 0000000..fd3934d --- /dev/null +++ b/tools/livecd-iso-to-iscsi @@ -0,0 +1,201 @@ +#!/usr/bin/python +# Convert a live CD iso into iscsi root bootable format +# iSCSI lun must be accessible via this script +# Copyright 2009 Red Hat, Inc. +# Written by Joey boggs +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Library General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +from optparse import OptionParser +from tempfile import mkdtemp +import dbus +import dbus.glib +import sys +import os +import subprocess +import shutil + +parser = OptionParser() +parser.add_option("--iso", dest="iso", help="LiveCD iso filename") +parser.add_option("--target", dest="target", help="iSCSI target ip address") +parser.add_option("--targetname", dest="targetname", help="iSCSI target lun") +parser.add_option("--targetport", dest="targetport", default="3260", help="iSCSI port number, defaults to 3260") +parser.add_option("--user", dest="user", help="Target username(optional)") +parser.add_option("--password", dest="password", help="Target password") +parser.add_option("--reverse_user", dest="reverse_user", help="Reverse CHAP username(optional)") +parser.add_option("--reverse_password", dest="reverse_password", help="Reverse CHAP password(optional)") +parser.add_option("--disk", dest="disk", help="iSCSI disk device name") +parser.add_option("--disk-label", dest="disk_label", default="ovirt-node-root", help="file system label") + +(options, args) = parser.parse_args() + +def fail(msg): + print(msg) + sys.exit(1) + +if os.geteuid () != 0: + fail("You must run as root") + +if options.iso is None: + fail("ERROR: iso file must be defined") +else: + options.iso = os.path.abspath(options.iso) + +if options.target is None: + fail("ERROR: iscsi target must be defined") + +if options.targetname is None: + fail("ERROR: iscsi targetname must be defined") + +if len(options.disk_label.strip()) > 15: + fail("ERROR: disk label must be 14 characters or less") + +try: + file = os.mkdir("tftpboot") +except OSError, e: + tftp_remove = raw_input("tftpboot directory exists, overwrite? (y/N)? ") + if tftp_remove.lower() == "y": + shutil.rmtree("tftpboot") + os.mkdir("tftpboot") + else: + print "Aborting" + sys.exit(1) + +if options.disk is None: + print "Below are the detected disks, if the iscsi disk is not shown, please ensure you are logged into the correct target\n" + bus = dbus.SystemBus () + hal_obj = bus.get_object ('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager') + hal = dbus.Interface (hal_obj, 'org.freedesktop.Hal.Manager') + udis = hal.FindDeviceByCapability ('storage') + dev_dict = {} + dev_count = 1 + for udi in udis: + dev_obj = bus.get_object ('org.freedesktop.Hal', udi) + dev = dbus.Interface (dev_obj, 'org.freedesktop.Hal.Device') + dev_bus=dev.GetProperty ('storage.bus') + dev_name=dev.GetProperty ('block.device') + dev_size=dev.GetProperty ('storage.size') + dev_size=(dev_size/1024/1024) + basename=os.path.basename(udi) + if dev_bus == "scsi": + print "%s. %s %sM %s \n" % (dev_count,dev_name,dev_size,basename) + dev_dict[str(dev_count)] = dev_name + dev_count = dev_count + 1 + print "Enter Q to Quit" + dev_choice = raw_input("Which device? ") + while not dev_dict.has_key(dev_choice): + if dev_choice.lower() == "q": + print "Aborting" + sys.exit(1) + else: + print "%s is an invalid choice" % dev_choice + dev_choice = raw_input("Which device? ") + options.disk = dev_dict[dev_choice] + +cont = raw_input("Creating file system on %s, do you wish to continue (y/N) " % options.disk) +if cont.lower() != "y": + print "Aborting" + sys.exit(1) + +isomount = mkdtemp() +isomount_ret = subprocess.call(["mount", "-o", "loop", options.iso, isomount]) +if isomount_ret != 0: + fail("Error mounting %s" % options.iso) + +kernel="%s/isolinux/vmlinuz0" % isomount +initrd="%s/isolinux/initrd0.img" % isomount +squashfs="%s/LiveOS/squashfs.img" % isomount +ext3fs="tftpboot/squashfs-root/LiveOS/ext3fs.img" +shutil.copy(kernel,"tftpboot") +shutil.copy(initrd,"tftpboot") + +unsquash = subprocess.call(["unsquashfs", squashfs]) + +# workaround until bug is fixed with squashfs -d option +shutil.move("squashfs-root","tftpboot/squashfs-root") + +print "Placing embedded file system on %s" % options.disk +dd_cmd="dd if=%s of=%s" % (ext3fs,options.disk) +copy_iscsi_ret = subprocess.call(dd_cmd, shell=True) +if copy_iscsi_ret != 0: + fail("Error copying to %s" % options.disk) + +umount_ret = subprocess.call(["umount", isomount]) +if umount_ret != 0: + fail("Error unmounting %s, continuing" % isomount) +else: + os.rmdir(isomount) +shutil.rmtree("tftpboot/squashfs-root") + +pxe_template = """ + +# pxelinux configuration. +DEFAULT pxeboot +TIMEOUT 20 +PROMPT 0 +LABEL ovirt-node-iscsi + KERNEL /vmlinuz0 + APPEND initrd=/initrd0.img ro root=LABEL=%(disk_label)s netroot=iscsi:%(user)s%(password)s@%(target)s::%(target_port)s::%(target_name)s ip=eth0:dhcp + ipappend 2 +ONERROR LOCALBOOT 0 +""" + +# insert empty values for unneeded variables in the pxe template +if not options.user is None: + options.user = options.user + ":" +else: + options.user = "" + +if not options.password is None: + options.password = options.password + ":" +else: + options.password = "" + +if not options.reverse_user is None: + options.reverse_user = options.reverse_user + ":" +else: + options.reverse_user = "" + +if not options.reverse_password is None: + options.reverse_password = options.reverse_password + ":" +else: + options.reverse_password = "" + +os.mkdir("tftpboot/pxelinux.cfg") +pxe_cfg = pxe_template % { + "disk_label": options.disk_label, + "target": options.target, + "target_port": options.targetport, + "target_name": options.targetname, + "user": options.user, + "password": options.password, + "reverse_user": options.reverse_user, + "reverse_password": options.reverse_password + } + +pxe_conf = open("tftpboot/pxelinux.cfg/default", 'w') +pxe_conf.write(pxe_cfg) +pxe_conf.close() + +if os.path.exists("/usr/share/syslinux/pxelinux.0"): + shutil.copy("/usr/share/syslinux/pxelinux.0","tftpboot") +elif os.path.exists("/usr/lib/syslinux/pxelinux.0"): + shutil.copy("/usr/lib/syslinux/pxelinux.0","tftpboot") +else: + print "Warning: You need to add pxelinux.0 to tftpboot/ subdirectory" + +print "Your iscsiroot has been setup on %s" % options.disk +print "" +print "Copy the tftpboot/ subdirectory to your tftpserver root directory" +print "Set up your DHCP, TFTP and PXE server to serve /tftpboot/.../pxeboot.0" diff --git a/tools/livecd-rpms b/tools/livecd-rpms new file mode 100755 index 0000000..0649cba --- /dev/null +++ b/tools/livecd-rpms @@ -0,0 +1,28 @@ +#!/bin/bash +# +# Script to install/update a livecd with a set of RPMS provided in a directory +# Copyright 2009 Red Hat, Inc. +# Written by Perry Myers +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Library General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +if [[ $# < 1 ]]; then + echo "Usage: $0 rpmdir" + exit 1 +fi + +RPMDIR=$1 + +rpm --root $PWD -Uvh $(find $RPMDIR -type f -name "*.rpm" -print) + diff --git a/tools/livecd-setauth b/tools/livecd-setauth new file mode 100755 index 0000000..eb8922f --- /dev/null +++ b/tools/livecd-setauth @@ -0,0 +1,50 @@ +#!/bin/bash +# +# Script to interactively add root password and authorized_keys file +# to a livecd +# Copyright 2008 Red Hat, Inc. +# Written by Perry Myers +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Library General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +DEFAULT_AUTH=~/.ssh/authorized_keys + +printf "Do you want to set a root password? [y/N]: " +read yesno +if [ "$yesno" = "y" -o "$yesno" = "Y" ]; then + chroot . passwd root +fi + +printf "Do you want to set an authorized_keys file? [y/N]: " +read yesno +if [ "$yesno" = "y" -o "$yesno" = "Y" ]; then + echo "Enter the location of the authorized_keys file [default: $DEFAULT_AUTH]: " + read -e authkeys + if [ -z "$authkeys" ]; then + authkeys=$DEFAULT_AUTH + fi + + authkeys=$(eval echo $authkeys) + if [ -f $authkeys ]; then + SSH=root/.ssh + AUTH=$SSH/authorized_keys + + mkdir -p $SSH + chmod 755 $SSH + cp -v $authkeys $AUTH + chmod 644 $AUTH + else + echo "$authkeys not found, skipping" + fi +fi -- 1.6.2.5 From dhuff at redhat.com Wed Oct 14 20:09:40 2009 From: dhuff at redhat.com (David Huff) Date: Wed, 14 Oct 2009 16:09:40 -0400 Subject: [Ovirt-devel] [PATCH 04/10] added README from ovirt-node-image In-Reply-To: <1255550984-25188-1-git-send-email-dhuff@redhat.com> References: <1255550984-25188-1-git-send-email-dhuff@redhat.com> Message-ID: <1255550984-25188-5-git-send-email-dhuff@redhat.com> --- README | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 45 insertions(+), 0 deletions(-) create mode 100644 README diff --git a/README b/README new file mode 100644 index 0000000..9c6b8da --- /dev/null +++ b/README @@ -0,0 +1,45 @@ +Some notes on node image deployment + +Nodes are provided in ISO format. These ISO images can be deployed by either: +1. Writing the image to a CD +2. Writing the image to USB flash using livecd-iso-to-disk utility +3. Creating pxe bootable files using livecd-iso-to-pxeboot utility + +To burn a Node image to a usb stick accessible as /dev/sdb: +livecd-iso-to-disk --format /usr/share/ovirt-node-image/ovirt-node-image.iso /dev/sdb + +To create vmlinuz and initrd images suitable for pxe booting: +livecd-iso-to-pxeboot /usr/share/ovirt-node-image/ovirt-node-image.iso + +The output of livecd-iso-to-pxeboot is a directory called tftpboot that has the +following files in it: +./pxelinux.0 +./pxelinux.cfg/default +./vmlinuz0 +./initrd0.img + +The vmlinuz0/initrd0.img files can be imported into a cobbler server or any +other PXE/tftp server. pxelinux.cfg/default provides a template for +configuring the pxe server to export the Node image: + +DEFAULT pxeboot +TIMEOUT 20 +PROMPT 0 +LABEL pxeboot + KERNEL vmlinuz0 + APPEND rootflags=loop initrd=initrd0.img root=/ovirt-node-image.iso rootfstype=auto ro liveimg check rootfstype=iso9660 elevator=deadline +ONERROR LOCALBOOT 0 + +In addition, PXE booted Nodes rely on the PXE server passing the mac address +of the pxe interface to the kernel. This is provided by using the IPAPPEND 2 +parameter as follows: + +DEFAULT pxeboot +TIMEOUT 20 +PROMPT 0 +LABEL pxeboot + KERNEL vmlinuz0 + IPAPPEND 2 + APPEND rootflags=loop initrd=initrd0.img root=/ovirt-node-image.iso rootfstype=auto ro liveimg check rootfstype=iso9660 elevator=deadline +ONERROR LOCALBOOT 0 + -- 1.6.2.5 From dhuff at redhat.com Wed Oct 14 20:09:41 2009 From: dhuff at redhat.com (David Huff) Date: Wed, 14 Oct 2009 16:09:41 -0400 Subject: [Ovirt-devel] [PATCH 05/10] added Makefile for recipe In-Reply-To: <1255550984-25188-1-git-send-email-dhuff@redhat.com> References: <1255550984-25188-1-git-send-email-dhuff@redhat.com> Message-ID: <1255550984-25188-6-git-send-email-dhuff@redhat.com> --- recipe/Makefile.am | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 46 insertions(+), 0 deletions(-) create mode 100644 recipe/Makefile.am diff --git a/recipe/Makefile.am b/recipe/Makefile.am new file mode 100644 index 0000000..7b64d55 --- /dev/null +++ b/recipe/Makefile.am @@ -0,0 +1,46 @@ +OVIRT_CACHE_DIR ?= $(HOME)/ovirt-cache +OVIRT_LOCAL_REPO ?= file://$(OVIRT_CACHE_DIR)/ovirt +OVIRT_URL ?= http://ovirt.org/repos/ovirt + +FEDORA_MIRROR = http://mirrors.fedoraproject.org/mirrorlist +CUR_RAWHIDE = 12 + +CUR_PREVIEW = 11 +PREVIEW_URL ?= http://markmc.fedorapeople.org/virt-preview/f$(CUR_PREVIEW)/$(ARCH) + +FEDORA = $(shell rpm --eval '%{fedora}') +ARCH = $(shell rpm --eval '%{_arch}') + +EXTRA_DIST = \ + repos.ks \ + ovirt-node-recipe.ks \ + common-blacklist.ks \ + common-install.ks \ + common-pkgs.ks \ + common-post.ks \ + ovirt-node-image.ks + +repos.ks: + ( \ + if [ 0$(FEDORA) == 0$(CUR_RAWHIDE) ]; then \ + FEDORA_REPO=rawhide ;\ + FEDORA_REPO_LOC="$(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/development/$(ARCH)/os,--mirrorlist=$(FEDORA_MIRROR)?repo=rawhide&arch=$(ARCH))" ;\ + OVIRT_DISTRO=development ;\ + else \ + FEDORA_REPO=f$(FEDORA) ;\ + FEDORA_REPO_LOC="$(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/releases/$(FEDORA)/Everything/${ARCH}/os,--mirrorlist=$(FEDORA_MIRROR)?repo=fedora-$(FEDORA)&arch=$(ARCH))" ;\ + OVIRT_DISTRO=$(FEDORA) ;\ + UPDATE_REPO_LINE="repo --name=$${FEDORA_REPO}-updates $(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/updates/$(FEDORA)/${ARCH},--mirrorlist=$(FEDORA_MIRROR)?repo=updates-released-f$(FEDORA)&arch=$(ARCH))\n" ;\ + if [ 0$(FEDORA) == 0$(CUR_PREVIEW) ]; then \ + UPDATE_REPO_LINE="$${UPDATE_REPO_LINE}repo --name=preview --baseurl=$(PREVIEW_URL)\n" ;\ + fi ;\ + fi ;\ + echo "repo --name=$${FEDORA_REPO} $${FEDORA_REPO_LOC}" > repos.ks ;\ + echo "repo --name=ovirt-org --baseurl=$(OVIRT_URL)/$${OVIRT_DISTRO}/$(ARCH)" >> repos.ks ;\ + printf "$${UPDATE_REPO_LINE}" >> repos.ks ;\ + echo "repo --name=ovirt-local --baseurl=$(OVIRT_LOCAL_REPO)" >> repos.ks ;\ + ) + +ovirt-node-recipe.ks: +# ksflatten ovirt-node-image.ks --output $@ + cp ovirt-node-image.ks ovirt-node-recipe.ks \ No newline at end of file -- 1.6.2.5 From dhuff at redhat.com Wed Oct 14 20:09:42 2009 From: dhuff at redhat.com (David Huff) Date: Wed, 14 Oct 2009 16:09:42 -0400 Subject: [Ovirt-devel] [PATCH 06/10] Modified Makefile for ovirt-node In-Reply-To: <1255550984-25188-1-git-send-email-dhuff@redhat.com> References: <1255550984-25188-1-git-send-email-dhuff@redhat.com> Message-ID: <1255550984-25188-7-git-send-email-dhuff@redhat.com> --- Makefile.am | 10 +++++++++- configure.ac | 1 + 2 files changed, 10 insertions(+), 1 deletions(-) diff --git a/Makefile.am b/Makefile.am index abb7c33..314ed8a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -16,7 +16,10 @@ # also available at http://www.gnu.org/copyleft/gpl.html. OVIRT_CACHE_DIR ?= $(HOME)/ovirt-cache -SUBDIRS = gptsync + +SUBDIRS = \ + gptsync \ + recipe EXTRA_DIST = \ .gitignore \ @@ -75,6 +78,11 @@ EXTRA_DIST = \ scripts/ovirt-post \ scripts/ovirt-process-config \ scripts/ovirt-uninstall-node-stateful \ + tools/create-ovirt-iso-nodes \ + tools/edit-livecd \ + tools/livecd-iso-to-iscsi \ + tools/livecd-rpms \ + tools/livecd-setauth \ kinit/ovirt-kinit \ logrotate/ovirt-logrotate \ logrotate/ovirt-logrotate.conf diff --git a/configure.ac b/configure.ac index 780b757..59593ae 100644 --- a/configure.ac +++ b/configure.ac @@ -10,6 +10,7 @@ test x"$ac_ct_CC:$CFLAGS" = 'xgcc:-g -O2' \ AC_CONFIG_FILES([Makefile nodeadmin/setup.py gptsync/Makefile + recipe/Makefile ovirt-node.spec ]) AC_OUTPUT -- 1.6.2.5 From dhuff at redhat.com Wed Oct 14 20:09:43 2009 From: dhuff at redhat.com (David Huff) Date: Wed, 14 Oct 2009 16:09:43 -0400 Subject: [Ovirt-devel] [PATCH 07/10] Modified spec file In-Reply-To: <1255550984-25188-1-git-send-email-dhuff@redhat.com> References: <1255550984-25188-1-git-send-email-dhuff@redhat.com> Message-ID: <1255550984-25188-8-git-send-email-dhuff@redhat.com> Removed stateful, and other old subpackages Added subpackage ovirt-node-tools Fixed issue with logrotate file name for fedora packaging. Fixed problem with conflicts between logos, added in approot install in ks post --- ovirt-node.spec.in | 234 +++++++++++++++++++--------------------------------- 1 files changed, 85 insertions(+), 149 deletions(-) diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index 2a6b7b6..513f095 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -11,12 +11,6 @@ Source0: %{name}-%{version}.tar.gz License: GPLv2+ Group: Applications/System -%define selinux_variants mls strict targeted minimum -%define selinux_policyver %(sed -n 's,.*selinux-policy-\([^/]*\)/.*,\1,p' /usr/share/selinux/devel/policyhelp) -%define modulename %{name}-selinux -Source1: %{modulename}.te -Source2: %{modulename}.fc - BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot URL: http://www.ovirt.org/ Requires(post): /sbin/chkconfig @@ -31,7 +25,8 @@ Requires: libvirt-qpid >= 0.2.14-3 Requires: hal Requires: collectd-virt Requires: wget -Requires: cyrus-sasl-gssapi cyrus-sasl cyrus-sasl-lib +Requires: cyrus-sasl-gssapi cyrus-sasl +Requires: cyrus-sasl-lib >= 2.1.22 Requires: iscsi-initiator-utils Requires: ntp Requires: nfs-utils @@ -49,9 +44,8 @@ Requires: grub Requires: /usr/sbin/crond Requires: anyterm Requires: newt-python -Requires: libuser-python +Requires: libuser-python >= 0.56.10 Requires: dbus-python -Requires: python-IPy ExclusiveArch: %{ix86} x86_64 @@ -59,89 +53,31 @@ ExclusiveArch: %{ix86} x86_64 %description Provides a series of daemons and support utilities to allow an -oVirt Node to interact with the oVirt Server. - -%package stateless -Summary: oVirt Node for running as embedded hypervisor -Group: Applications/System -Requires: %{name} = %{version}-%{release} -Conflicts: %{name}-stateful -ExclusiveArch: %{ix86} x86_64 - -%description stateless -Provides the oVirt Node functionality needed as part of the -ovirt-node-image creation. This provides a stateless oVirt Node -that runs as a livecd. +oVirt Node to interact with the oVirt Server. This package +should only be installed on the oVirt Node machine. -%package stateful -Summary: oVirt Node for running on Fedora Hosts +%package tools +Summary: oVirt Node tools for building and running an oVirt Node image Group: Applications/System -Requires: %{name} = %{version}-%{release} -Conflicts: %{name}-stateless -ExclusiveArch: %{ix86} x86_64 +BuildArch: noarch +BuildRequires: pykickstart >= 1.54 +Requires: livecd-tools >= 020-2 -%description stateful -Provides the oVirt Node functionality needed to convert an existing -host into a Node in a stateful manner. Presently intended for use on -the host running the oVirt Appliance. +%define tools_root %{_datadir}/ovirt-node-tools -%package logos -Summary: oVirt Node Logos -Group: System Environment/Base -BuildArch: noarch -Obsoletes: redhat-logos -Provides: redhat-logos = 10.0.1-1 -Provides: system-logos = 10.0.1-1 -Conflicts: fedora-logos -Conflicts: generic-logos -Conflicts: fedora-logos -Conflicts: anaconda-images <= 10 -Conflicts: redhat-artwork <= 5.0.5 - -%description logos -The ovirt-logos package contains various image files which can be -used by the bootloader, anaconda, and other related tools. - -%package release -Summary: %{product_family} release file -Group: System Environment/Base -Obsoletes: redhat-release -Provides: redhat-release -%description release -%{product_family} release files. - -%package selinux -Summary: SELinux policy module supporting ovirt-node -Group: System Environment/Base -BuildRequires: checkpolicy, selinux-policy-devel, hardlink -%if "%{selinux_policyver}" != "" -Requires: selinux-policy >= %{selinux_policyver} -%endif -Requires: %{name} = %{version}-%{release} -Requires(post): /usr/sbin/semodule, /sbin/restorecon -Requires(postun): /usr/sbin/semodule, /sbin/restorecon - -%description selinux -SELinux policy module supporting ovirt-node +%description tools +The oVirt-node-tools package provides recipe (ks files), client tools, +documentation for building and running an oVirt Node image. This package +is not to be installed on the oVirt-Node, however on a development machine +to help in deployment on the node. %prep %setup -q -mkdir SELinux -cp -p %{SOURCE1} %{SOURCE2} SELinux - %build %configure make -cd SELinux -for selinuxvariant in %{selinux_variants}; do - make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile - mv %{modulename}.pp %{modulename}.pp.${selinuxvariant} - make NAME=${selinuxvariant} -f /usr/share/selinux/devel/Makefile clean -done -cd - - %install %{__rm} -rf %{buildroot} # FIXME move installs into makefile @@ -167,9 +103,7 @@ cd - %{__install} -p -m0755 scripts/ovirt-config-storage %{buildroot}%{_sbindir} %{__install} -p -m0755 scripts/ovirt-config-uninstall %{buildroot}%{_sbindir} %{__install} -p -m0755 scripts/ovirt-process-config %{buildroot}%{_sbindir} -%{__install} -D -m0755 scripts/ovirt-install-node-stateful %{buildroot}%{_sbindir} %{__install} -D -m0755 scripts/ovirt-install-node-stateless %{buildroot}%{_sbindir} -%{__install} -D -m0755 scripts/ovirt-uninstall-node-stateful %{buildroot}%{_sbindir} %{__install} -D -m0755 scripts/ovirt-config-view-logs %{buildroot}%{_sbindir} %{__install} -p -m0755 scripts/persist %{buildroot}%{_sbindir} %{__install} -p -m0755 scripts/unpersist %{buildroot}%{_sbindir} @@ -177,7 +111,7 @@ cd - %{__install} -p -m0644 nodeadmin/__init__.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/configscreen.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/menuscreen.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/utils.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/utils.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/nodeadmin.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/mainmenu.py %{buildroot}%{python_sitelib}/nodeadmin @@ -189,10 +123,10 @@ cd - %{__install} -p -m0755 nodeadmin/undefinedomain.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/listdomains.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/domainconfig.py %{buildroot}%{python_sitelib}/nodeadmin - + %{__install} -p -m0644 nodeadmin/netmenu.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/networkconfig.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/definenet.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/definenet.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/createnetwork.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/destroynetwork.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/undefinenetwork.py %{buildroot}%{python_sitelib}/nodeadmin @@ -219,7 +153,7 @@ cd - %{__install} -p -m0755 kinit/ovirt-kinit %{buildroot}%{_sysconfdir}/cron.hourly %{__install} -p -m0644 logrotate/ovirt-logrotate %{buildroot}%{_sysconfdir}/cron.d -%{__install} -p -m0644 logrotate/ovirt-logrotate.conf %{buildroot}%{_sysconfdir}/logrotate.d +%{__install} -p -m0644 logrotate/ovirt-logrotate.conf %{buildroot}%{_sysconfdir}/logrotate.d/ovirt-node # install the admin tools python nodeadmin/setup.py install --root %{buildroot} @@ -227,25 +161,15 @@ python nodeadmin/setup.py install --root %{buildroot} echo "oVirt Node release %{version}-%{release}" > %{buildroot}%{_sysconfdir}/ovirt-release mkdir -p %{buildroot}/%{_sysconfdir}/default -touch %{buildroot}/%{_sysconfdir}/default/ovirt - -cd SELinux -for selinuxvariant in %{selinux_variants}; do - install -d %{buildroot}%{_datadir}/selinux/${selinuxvariant} - install -p -m 644 %{modulename}.pp.${selinuxvariant} \ - %{buildroot}%{_datadir}/selinux/${selinuxvariant}/%{modulename}.pp -done -cd - - -/usr/sbin/hardlink -cv %{buildroot}%{_datadir}/selinux +echo "# File where default partitioning info is kept" > %{buildroot}/%{_sysconfdir}/default/ovirt # ovirt-logos # should be ifarch i386 mkdir -p %{buildroot}/boot/grub -install -p -m 644 images/grub-splash.xpm.gz %{buildroot}/boot/grub/splash.xpm.gz +install -p -m 644 images/grub-splash.xpm.gz %{buildroot}%{app_root} # end i386 bits mkdir -p %{buildroot}/usr/lib/anaconda-runtime -install -p -m 644 images/syslinux-vesa-splash.jpg %{buildroot}/usr/lib/anaconda-runtime +install -p -m 644 images/syslinux-vesa-splash.jpg %{buildroot}%{app_root} # ovirt-logos # release files @@ -273,62 +197,78 @@ ln -s ovirt-release %{buildroot}/etc/system-release %{__ln_s} ../..%{_sbindir}/ovirt-config-uninstall %{buildroot}%{_sysconfdir}/ovirt-config-setup.d/"99_Uninstall node" +# ovirt-node-tools +%{__install} -d -m0755 %{buildroot}%{tools_root} +%{__install} -p -m0644 recipe/*.ks %{buildroot}%{tools_root} +%{__install} -p -m0755 tools/create-ovirt-iso-nodes %{buildroot}%{_sbindir} +%{__install} -p -m0755 tools/edit-livecd %{buildroot}%{_sbindir} +%{__install} -p -m0755 tools/livecd-setauth %{buildroot}%{_sbindir} +%{__install} -p -m0755 tools/livecd-rpms %{buildroot}%{_sbindir} +#%{__install} -p -m0755 tools/get-ovirt-node-image %{buildroot}%{_sbindir} +#%{__install} -p -m0755 tools/generate-ovirt-node-pxe-tree %{buildroot}%{_sbindir} + + %clean %{__rm} -rf %{buildroot} %post # Setup basic collectd configuration sed '//,/<\/Plugin>/d' /etc/collectd.conf.in > /etc/collectd.conf - -%post stateless /sbin/chkconfig --add ovirt-early /sbin/chkconfig --add ovirt-firstboot /sbin/chkconfig --add ovirt /sbin/chkconfig --add ovirt-post -%preun stateless -if [ "$1" = 0 ] ; then - /sbin/chkconfig --del ovirt-early - /sbin/chkconfig --del ovirt-firstboot - /sbin/chkconfig --del ovirt - /sbin/chkconfig --del ovirt-post +%preun +if [ $1 = 0 ] ; then + /sbin/service ovirt-early stop >/dev/null 2>&1 + /sbin/service ovirt-firstboor stop >/dev/null 2>&1 + /sbin/service ovirt stop >/dev/null 2>&1 + /sbin/service ovirt-post stop >/dev/null 2>&1 + /sbin/chkconfig --del ovirt-early + /sbin/chkconfig --del ovirt-firstboot + /sbin/chkconfig --del ovirt + /sbin/chkconfig --del ovirt-post fi -%post selinux -for selinuxvariant in %{selinux_variants}; do - /usr/sbin/semodule -s ${selinuxvariant} -i \ - %{_datadir}/selinux/${selinuxvariant}/%{modulename}.pp &> /dev/null || : -done - -%postun selinux -if [ $1 -eq 0 ] ; then - for selinuxvariant in %{selinux_variants}; do - /usr/sbin/semodule -s ${selinuxvariant} -r %{modulename} &> /dev/null || : - done -fi -%files release +%files tools +%defattr(0644,root,root,0755) +%doc README COPYING +%{tools_root}/*.ks + +%defattr(0755,root,root,0755) +%{_sbindir}/create-ovirt-iso-nodes +%{_sbindir}/edit-livecd +%{_sbindir}/livecd-setauth +%{_sbindir}/livecd-rpms +#%{_sbindir}/get-ovirt-node-image +#%{_sbindir}/generate-ovirt-node-pxe-tree + + +%files %defattr(-,root,root) -%attr(0644,root,root) /etc/ovirt-release -/etc/redhat-release -/etc/system-release +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/ovirt-release +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/default/ovirt +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/redhat-release +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/system-release %config(noreplace) %attr(0644,root,root) /etc/issue %config(noreplace) %attr(0644,root,root) /etc/issue.net -%files selinux -%defattr(-,root,root,0755) -%doc SELinux/* -%{_datadir}/selinux/*/%{modulename}.pp +%config(noreplace) %{_sysconfdir}/logrotate.d/ovirt-node +%config(noreplace) %{_sysconfdir}/cron.d/ovirt-logrotate + +%{_sysconfdir}/ovirt-config-boot.d +%{_sysconfdir}/ovirt-config-setup.d +%config(noreplace) %{_sysconfdir}/collectd.conf.in +%{_sysconfdir}/cron.hourly/ovirt-kinit -%files logos -%defattr(-, root, root) %doc COPYING # should be ifarch i386 -/boot/grub/splash.xpm.gz +%{app_root}/grub-splash.xpm.gz # end i386 bits -/usr/lib/anaconda-runtime/*.jpg +%{app_root}/syslinux-vesa-splash.jpg -%files stateless %defattr(-,root,root,0755) %{_sbindir}/ovirt-awake %{_sbindir}/ovirt-config-boot @@ -348,25 +288,13 @@ fi %{_sbindir}/showpart %{_sbindir}/persist %{_sbindir}/unpersist + %{_initrddir}/ovirt-early %{_initrddir}/ovirt-firstboot %{_initrddir}/ovirt %{_initrddir}/ovirt-post -%config %{_sysconfdir}/cron.hourly/ovirt-kinit -%config %{_sysconfdir}/logrotate.d/ovirt-logrotate.conf -%config %{_sysconfdir}/cron.d/ovirt-logrotate -%{_sysconfdir}/ovirt-config-boot.d -%{_sysconfdir}/ovirt-config-setup.d +%attr(0644,root,root) /etc/rc.d/init.d/ovirt-functions -%files stateful -%defattr(-,root,root,0755) -%{_sbindir}/ovirt-install-node-stateful -%{_sbindir}/ovirt-uninstall-node-stateful - -%files -%defattr(-,root,root,0755) -%{_sbindir}/ovirt-awake -%{_initrddir}/ovirt-functions %defattr(-,root,root,0644) %{_bindir}/nodeadmin %{_bindir}/definedom @@ -378,15 +306,23 @@ fi %{_bindir}/createnet %{_bindir}/destroynet %{_bindir}/undefinenet + %{_bindir}/listnets %{_bindir}/createuser -%{_sysconfdir}/collectd.conf.in + +%defattr(-,root,root,-) %{python_sitelib}/nodeadmin %{python_sitelib}/nodeadmin- at VERSION@-py2.6.egg-info -%config %attr(0644,root,root) %{_sysconfdir}/ovirt-release -%config %attr(0644,root,root) %{_sysconfdir}/default/ovirt + %changelog +* Wed Oct 07 2009 David huff - 1.0.3-4 +- Added ovirt-node-tools subpackage + +* Thu Jun 23 2009 David Huff - 1.0.3 +- Clean up spec for inclusion in Fedora +- Removed subpackages, stateful, stateless, logos, and selinux + * Thu Dec 11 2008 Perry Myers - 0.96 - Subpackage stateful/stateless to separate out functionality for embedded Node and Node running as part of already installed OS -- 1.6.2.5 From dhuff at redhat.com Wed Oct 14 20:09:44 2009 From: dhuff at redhat.com (David Huff) Date: Wed, 14 Oct 2009 16:09:44 -0400 Subject: [Ovirt-devel] [PATCH 08/10] remove old selinux modules, ovirt-node-selinux, polkit In-Reply-To: <1255550984-25188-1-git-send-email-dhuff@redhat.com> References: <1255550984-25188-1-git-send-email-dhuff@redhat.com> Message-ID: <1255550984-25188-9-git-send-email-dhuff@redhat.com> --- recipe/common-post.ks | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diff --git a/recipe/common-post.ks b/recipe/common-post.ks index 7cebef0..85242c5 100644 --- a/recipe/common-post.ks +++ b/recipe/common-post.ks @@ -6,8 +6,8 @@ export PATH # Import SELinux Modules echo "Enabling selinux modules" SEMODULES="base automount avahi consolekit cyrus dhcp dnsmasq guest hal ipsec \ -iscsi kerberos kerneloops ldap lockdev logadm mozilla ntp ovirt-node-selinux \ -polkit portmap qemu rpcbind sasl snmp stunnel sysstat tcpd unprivuser \ +iscsi kerberos kerneloops ldap lockdev logadm mozilla ntp \ +portmap qemu rpcbind sasl snmp stunnel sysstat tcpd unprivuser \ unconfined usbmodules userhelper virt" lokkit -v --selinuxtype=minimum -- 1.6.2.5 From dhuff at redhat.com Wed Oct 14 20:13:02 2009 From: dhuff at redhat.com (David Huff) Date: Wed, 14 Oct 2009 16:13:02 -0400 Subject: [Ovirt-devel] Refactor ovirt-node-image code base for inclusion in Fedora Message-ID: <1255551186-25333-1-git-send-email-dhuff@redhat.com> Main reason for refactor is no ovirt-node-image binary image allowed in fedora. Removed ks files, now in ovirt-node-recipe, which is subpackage of ovirt-node. Removed tools, these are now in ovirt-node-recipe which is subpackage of ovirt-node. From dhuff at redhat.com Wed Oct 14 20:13:03 2009 From: dhuff at redhat.com (David Huff) Date: Wed, 14 Oct 2009 16:13:03 -0400 Subject: [Ovirt-devel] [PATCH 1/4] Modified makefile for fedora In-Reply-To: <1255551186-25333-1-git-send-email-dhuff@redhat.com> References: <1255551186-25333-1-git-send-email-dhuff@redhat.com> Message-ID: <1255551186-25333-2-git-send-email-dhuff@redhat.com> --- Makefile.am | 61 ++++------------------------------------------------------ 1 files changed, 5 insertions(+), 56 deletions(-) diff --git a/Makefile.am b/Makefile.am index 306f49f..41741d2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -18,6 +18,7 @@ OVIRT_CACHE_DIR ?= $(HOME)/ovirt-cache OVIRT_LOCAL_REPO ?= file://$(OVIRT_CACHE_DIR)/ovirt OVIRT_URL ?= http://ovirt.org/repos/ovirt +OVIRT_NODE_RECIPE ?= /usr/share/ovirt-node-tools/ovirt-node-recipe.ks SUM ?= sha1sum PKG_FMT = iso SRC_FMT ?= tar @@ -37,17 +38,7 @@ NVR = $(PACKAGE)-$(VERSION)-$(ARCH) EXTRA_DIST = \ .gitignore \ $(PACKAGE).spec \ - $(PACKAGE).spec.in \ - common-blacklist.ks \ - common-install.ks \ - common-pkgs.ks \ - common-post.ks \ - $(PACKAGE).ks \ - create-ovirt-iso-nodes \ - edit-livecd \ - livecd-iso-to-iscsi \ - livecd-setauth \ - livecd-rpms \ + $(PACKAGE).spec.in \ README DISTCLEANFILES = $(PACKAGE)-$(VERSION).tar.gz \ @@ -70,48 +61,6 @@ RPM_FLAGS += $(if $(_ovirt_dev),--define "extra_release .$(GIT_RELEASE)") NODE_TMP = $(OVIRT_CACHE_DIR)/$(PACKAGE)-$(ARCH)-tmp SRC_KS = $(NODE_TMP)/src.ks -repos.ks: - ( \ - if [ 0$(FEDORA) == 0$(CUR_RAWHIDE) ]; then \ - FEDORA_REPO=rawhide ;\ - FEDORA_REPO_LOC="$(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/development/$(ARCH)/os,--mirrorlist=$(FEDORA_MIRROR)?repo=rawhide&arch=$(ARCH))" ;\ - OVIRT_DISTRO=development ;\ - else \ - FEDORA_REPO=f$(FEDORA) ;\ - FEDORA_REPO_LOC="$(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/releases/$(FEDORA)/Everything/${ARCH}/os,--mirrorlist=$(FEDORA_MIRROR)?repo=fedora-$(FEDORA)&arch=$(ARCH))" ;\ - OVIRT_DISTRO=$(FEDORA) ;\ - UPDATE_REPO_LINE="repo --name=$${FEDORA_REPO}-updates $(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/updates/$(FEDORA)/${ARCH},--mirrorlist=$(FEDORA_MIRROR)?repo=updates-released-f$(FEDORA)&arch=$(ARCH))\n" ;\ - if [ 0$(FEDORA) == 0$(CUR_PREVIEW) ]; then \ - UPDATE_REPO_LINE="$${UPDATE_REPO_LINE}repo --name=preview --baseurl=$(PREVIEW_URL)\n" ;\ - fi ;\ - fi ;\ - echo "repo --name=$${FEDORA_REPO} $${FEDORA_REPO_LOC}" > $@ ;\ - echo "repo --name=ovirt-org --baseurl=$(OVIRT_URL)/$${OVIRT_DISTRO}/$(ARCH)" >> $@ ;\ - printf "$${UPDATE_REPO_LINE}" >> $@ ;\ - echo "repo --name=ovirt-local --baseurl=$(OVIRT_LOCAL_REPO)" >> $@ \ - ) - -src.ks: repos.ks - mkdir -p $(NODE_TMP) - ( \ - cat repos.ks ;\ - if [ 0$(FEDORA) == 0$(CUR_RAWHIDE) ]; then \ - FEDORA_REPO=rawhide ;\ - FEDORA_REPO_LOC="$(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/development/source/SRPMS,--mirrorlist=$(FEDORA_MIRROR)?repo=rawhide-source&arch=source)" ;\ - OVIRT_DISTRO=development ;\ - else \ - FEDORA_REPO=f$(FEDORA) ;\ - FEDORA_REPO_LOC="$(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/releases/$(FEDORA)/Everything/source/SRPMS,--mirrorlist=$(FEDORA_MIRROR)?repo=fedora-source-$(FEDORA)&arch=source)" ;\ - OVIRT_DISTRO=$(FEDORA) ;\ - UPDATE_REPO_LINE="repo --name=$${FEDORA_REPO}-updates-source $(if $(FEDORA_URL),--baseurl=$(FEDORA_URL)/updates/$(FEDORA)/SRPMS,--mirrorlist=$(FEDORA_MIRROR)?repo=updates-released-source-f$(FEDORA)&arch=source)" ;\ - fi ;\ - echo "repo --name=$${FEDORA_REPO}-source $${FEDORA_REPO_LOC}" ;\ - echo "repo --name=ovirt-org-source --baseurl=$(OVIRT_URL)/$${OVIRT_DISTRO}/src" ;\ - echo "$${UPDATE_REPO_LINE}" ;\ - echo "%packages --nobase" ;\ - grep -v '^-' common-pkgs.ks ;\ - echo "%end" ;\ - ) > $(SRC_KS) keys: if [ "$(_ovirt_dev)" = 1 -a -f $(AUTH_KEYS) ]; then \ @@ -126,7 +75,7 @@ $(NVR).$(PKG_FMT): no_nscd repos.ks keys case $(SELINUX_ENFORCING) in \ Enforcing) sudo /usr/sbin/setenforce Permissive ;; \ Permissive) ;; \ - *) if ksflatten $(PACKAGE).ks 2>/dev/null \ + *) if cat $(OVIRT_NODE_RECIPE) \ | grep -q '^selinux --disabled'; then \ echo WARNING: SELinux disabled in kickstart ;\ else \ @@ -136,7 +85,7 @@ $(NVR).$(PKG_FMT): no_nscd repos.ks keys fi ;; \ esac ;\ ) - sudo livecd-creator --skip-minimize -c $(PACKAGE).ks \ + sudo livecd-creator --skip-minimize -c $(OVIRT_NODE_RECIPE) \ -f $(PACKAGE) \ --tmpdir='$(NODE_TMP)' \ --cache='$(OVIRT_CACHE_DIR)/yum-$(ARCH)' @@ -155,7 +104,7 @@ $(NVR).$(PKG_FMT).$(SUM): $(NVR).$(PKG_FMT) $(PACKAGE).$(PKG_FMT) node: $(NVR).$(PKG_FMT).$(SUM) PUNGI = $(NODE_TMP)/tree/pungi -source: src.ks +source: @sudo rm -Rf $(PUNGI) @mkdir -p $(PUNGI) -- 1.6.2.5 From dhuff at redhat.com Wed Oct 14 20:13:04 2009 From: dhuff at redhat.com (David Huff) Date: Wed, 14 Oct 2009 16:13:04 -0400 Subject: [Ovirt-devel] [PATCH 2/4] modified spec file for inclusion in Fedora In-Reply-To: <1255551186-25333-1-git-send-email-dhuff@redhat.com> References: <1255551186-25333-1-git-send-email-dhuff@redhat.com> Message-ID: <1255551186-25333-3-git-send-email-dhuff@redhat.com> --- ovirt-node-image.spec.in | 21 +++++++++++---------- 1 files changed, 11 insertions(+), 10 deletions(-) diff --git a/ovirt-node-image.spec.in b/ovirt-node-image.spec.in index 1f8c31b..e5ef318 100644 --- a/ovirt-node-image.spec.in +++ b/ovirt-node-image.spec.in @@ -23,6 +23,7 @@ URL: http://ovirt.org/ BuildRequires: livecd-tools >= 020-2 BuildRequires: appliance-tools >= 003.9 %endif +BuildRequires: ovirt-node-tools Requires: livecd-tools >= 020-2 %define app_root %{_datadir}/%{name} @@ -63,11 +64,11 @@ mkdir %{buildroot} %{__install} -d -m0755 %{buildroot}%{app_root} %{__install} -p -m0644 %{image_iso} %{buildroot}%{app_root} %{__install} -d -m0755 %{buildroot}%{_sbindir} -%{__install} -p -m0755 create-ovirt-iso-nodes %{buildroot}%{_sbindir} -%{__install} -p -m0755 edit-livecd %{buildroot}%{_sbindir} -%{__install} -p -m0755 livecd-iso-to-iscsi %{buildroot}%{_sbindir} -%{__install} -p -m0755 livecd-setauth %{buildroot}%{_sbindir} -%{__install} -p -m0755 livecd-rpms %{buildroot}%{_sbindir} +#%{__install} -p -m0755 create-ovirt-iso-nodes %{buildroot}%{_sbindir} +#%{__install} -p -m0755 edit-livecd %{buildroot}%{_sbindir} +#%{__install} -p -m0755 livecd-iso-to-iscsi %{buildroot}%{_sbindir} +#%{__install} -p -m0755 livecd-setauth %{buildroot}%{_sbindir} +#%{__install} -p -m0755 livecd-rpms %{buildroot}%{_sbindir} %{__tar} -xf %{image_manifests} -C %{buildroot}%{app_root} %clean @@ -93,11 +94,11 @@ cobbler sync > /dev/null 2>&1 || : %doc %{app_root}/manifests/ovirt-release %defattr(0755,root,root,0755) -%{_sbindir}/create-ovirt-iso-nodes -%{_sbindir}/edit-livecd -%{_sbindir}/livecd-iso-to-iscsi -%{_sbindir}/livecd-setauth -%{_sbindir}/livecd-rpms +#%{_sbindir}/create-ovirt-iso-nodes +#%{_sbindir}/edit-livecd +#%{_sbindir}/livecd-iso-to-iscsi +#%{_sbindir}/livecd-setauth +#%{_sbindir}/livecd-rpms %files pxe %defattr(0644,root,root,0755) -- 1.6.2.5 From dhuff at redhat.com Wed Oct 14 20:13:05 2009 From: dhuff at redhat.com (David Huff) Date: Wed, 14 Oct 2009 16:13:05 -0400 Subject: [Ovirt-devel] [PATCH 3/4] removed tools, these are now in ovirt-node-recipe which is subpackage of ovirt-node In-Reply-To: <1255551186-25333-1-git-send-email-dhuff@redhat.com> References: <1255551186-25333-1-git-send-email-dhuff@redhat.com> Message-ID: <1255551186-25333-4-git-send-email-dhuff@redhat.com> --- common-blacklist.ks | 161 ------------------------------------------------- common-install.ks | 19 ------ common-pkgs.ks | 78 ------------------------ common-post.ks | 167 --------------------------------------------------- ovirt-node-image.ks | 118 ------------------------------------ 5 files changed, 0 insertions(+), 543 deletions(-) delete mode 100644 common-blacklist.ks delete mode 100644 common-install.ks delete mode 100644 common-pkgs.ks delete mode 100644 common-post.ks delete mode 100644 ovirt-node-image.ks diff --git a/common-blacklist.ks b/common-blacklist.ks deleted file mode 100644 index 3499ccf..0000000 --- a/common-blacklist.ks +++ /dev/null @@ -1,161 +0,0 @@ -# -*-Shell-script-*- -%post - -echo "Removing excess RPMs" - -# kernel pulls in mkinitrd which pulls in isomd5sum which pulls in python, -# and livecd-tools needs lokkit to configure SELinux. -# However, this is just an install-time dependency; we can remove -# it afterwards, which we do here -RPMS="system-config-firewall-tui system-config-network-tui rhpl \ - rpm-python kudzu libsemanage-python" - -RPMS="$RPMS mkinitrd isomd5sum dmraid checkpolicy" - -# Remove additional RPMs forcefully -RPMS="$RPMS gamin pm-utils kbd usermode vbetool ConsoleKit hdparm \ - efibootmgr linux-atm-libs fedora-release-notes \ - psmisc cryptsetup-luks pciutils mtools syslinux \ - wireless-tools radeontool libicu gnupg2 \ - fedora-logos" - -# cronie pulls in exim (sendmail) which pulls in all kinds of perl deps -RPMS="$RPMS exim perl-version perl-Pod-Simple perl-libs perl-Module-Pluggable \ - perl-Pod-Escapes perl" - -RPMS="$RPMS sysklogd" - -# workaround for gpxe issue with the virt-preview qemu on F11 host kernel -# https://bugzilla.redhat.com/show_bug.cgi?id=512358 -RPMS="$RPMS gpxe-roms-qemu" -ln -snf ../etherboot/e1000-82542.zrom /usr/share/qemu/pxe-e1000.bin -ln -snf ../etherboot/ne.zrom /usr/share/qemu/pxe-ne2k_pci.bin -ln -snf ../etherboot/pcnet32.zrom /usr/share/qemu/pxe-pcnet.bin -ln -snf ../etherboot/rtl8139.zrom /usr/share/qemu/pxe-rtl8139.bin -ln -snf ../etherboot/virtio-net.zrom /usr/share/qemu/pxe-virtio.bin - -# Things we could probably remove if libvirt didn't link against them -#RPMS="$RPMS avahi PolicyKit xen-libs" - -# Things we could probably remove if qemu-kvm didn't link against them -#RPMS="$RPMS SDL alsa-lib" - -# Pam complains when this is missing -#RPMS="$RPM ConsoleKit-libs" - -for rpm in $RPMS; do - rpm -v -e --nodeps $rpm 2> /dev/null -done - -# the following are lists of kernel modules we are pretty sure we won't need; -# note that these can be single files or whole directories. They are specified -# starting at $MODULES; so if you want to remove the NLS stuff from the -# fs subdir, your mods entry would be "fs/nls" -fs_mods="fs/nls fs/9p fs/affs fs/autofs fs/autofs4 fs/befs fs/bfs fs/cifs \ - fs/coda fs/cramfs fs/dlm fs/ecryptfs fs/efs fs/exportfs fs/ext4 \ - fs/freevxfs fs/gfs2 fs/hfs fs/hfsplus fs/jbd2 fs/jffs \ - fs/jffs2 fs/jfs fs/minix fs/ncpfs fs/ocfs2 fs/qnx4 fs/reiserfs \ - fs/romfs fs/sysv fs/udf fs/ufs fs/xfs" - -net_mods="net/9p net/appletalk net/atm net/ax25 \ - net/bluetooth net/dccp net/decnet net/ieee80211 net/ipx net/irda \ - net/mac80211 net/netrom net/rfkill net/rose net/sched net/tipc \ - net/wanrouter net/wireless" - -driver_mods="drivers/auxdisplay drivers/net/appletalk \ - drivers/net/hamradio drivers/net/pcmcia drivers/net/tokenring \ - drivers/net/wireless drivers/net/irda drivers/atm drivers/usb/atm \ - drivers/acpi drivers/char/drm drivers/char/mwave \ - drivers/char/ipmp drivers/char/pcmcia drivers/crypto \ - drivers/firmware drivers/memstick drivers/mmc drivers/mfs \ - drivers/parport drivers/video drivers/watchdog drivers/net/ppp* \ - drivers/usb/serial drivers/usb/misc drivers/usb/class \ - drivers/usb/image drivers/rtc drivers/char/lp*" - -misc_mods="drivers/bluetooth drivers/firewire drivers/i2c drivers/isdn \ - drivers/media drivers/misc drivers/leds drivers/mtd drivers/w1 sound \ - drivers/input drivers/pcmcia drivers/scsi/pcmcia" - -echo "Removing excess kernel modules" -MODULES="/lib/modules/*/kernel" -RM="rm -rf" - -for mods in $fs_mods $net_mods $misc_mods $driver_mods ; do - $RM $MODULES/$mods -done - -echo "Removing all timezones except for UTC" -find /usr/share/zoneinfo -regextype egrep -type f \ - ! -regex ".*/UTC|.*/GMT" -exec $RM {} \; - -echo "Removing blacklisted files and directories" -blacklist="/etc/alsa /etc/pki /usr/share/hwdata/MonitorsDB \ - /usr/share/hwdata/oui.txt /usr/share/hwdata/videoaliases \ - /usr/share/firstboot /usr/share/lua /usr/share/kde4 /usr/share/pixmaps \ - /usr/share/hwdata/videodrivers /usr/share/icons /usr/share/fedora-release \ - /usr/share/tabset /usr/share/libvirt /usr/share/augeas/lenses/tests \ - /usr/share/tc /usr/share/emacs /usr/share/info \ - /usr/src /usr/etc /usr/games /usr/include /usr/local \ - /usr/sbin/{dell*,sasldblistusers2,build-locale-archive,glibc_post_upgrade.*}" -blacklist_lib="/usr/{,lib64}/tc \ - /usr/lib{,64}/tls /usr/lib{,64}/sse2 \ - /usr/lib{,64}/pkgconfig /usr/lib{,64}/nss \ - /usr/lib{,64}/games /usr/lib{,64}/alsa-lib /usr/lib{,64}/fs/reiserfs \ - /usr/lib{,64}/krb5 /usr/lib{,64}/hal /usr/lib{,64}/gio \ - /usr/lib/locale /usr/lib/syslinux" -blacklist_pango="/usr/lib{,64}/pango /usr/lib{,64}/libpango* \ - /etc/pango /usr/bin/pango*" -blacklist_hal="/usr/bin/hal-disable-polling \ - /usr/bin/hal-is-caller-locked-out /usr/bin/hal-is-caller-privileged \ - /usr/bin/hal-lock /usr/bin/hal-set-property /usr/bin/hal-setup-keymap" -blacklist_ssh="/usr/bin/sftp /usr/bin/slogin /usr/bin/ssh /usr/bin/ssh-add \ - /usr/bin/ssh-agent /usr/bin/ssh-copy-id /usr/bin/ssh-keyscan" -blacklist_docs="/usr/share/omf /usr/share/gnome /usr/share/doc \ - /usr/share/locale /usr/share/libthai /usr/share/man \ - /usr/share/X11 /usr/share/i18n" - -eval $RM $blacklist $blacklist_lib $blacklist_pango $blacklist_hal \ - $blacklist_ssh $blacklist_docs - -echo "Cleanup empty directory structures in /usr/share" -find /usr/share -type d -exec rmdir {} \; > /dev/null 2>&1 - -echo "Cleanup excess selinux modules" -$RM /usr/share/selinux - -echo "Removing python source files" -find / -name '*.py' -exec rm -f {} \; -find / -name '*.pyo' -exec rm -f {} \; - -echo "Running image-minimizer..." -%end - -%post --nochroot --interpreter image-minimizer -drop /usr/lib/libboost* -keep /usr/lib/libboost_program_options.so* -keep /usr/lib/libboost_filesystem.so* -keep /usr/lib/libboost_thread-mt.so* -keep /usr/lib/libboost_system.so* -drop /usr/lib64/libboost* -keep /usr/lib64/libboost_program_options.so* -keep /usr/lib64/libboost_filesystem.so* -keep /usr/lib64/libboost_thread-mt.so* -keep /usr/lib64/libboost_system.so* -drop /usr/kerberos -keep /usr/kerberos/bin/kinit -keep /usr/kerberos/bin/klist -drop /lib/firmware -keep /lib/firmware/3com -keep /lib/firmware/acenic -keep /lib/firmware/adaptec -keep /lib/firmware/advansys -keep /lib/firmware/bnx2 -keep /lib/firmware/cxgb3 -keep /lib/firmware/e100 -keep /lib/firmware/myricom -keep /lib/firmware/qlogic -keep /lib/firmware/sun -keep /lib/firmware/tehuti -keep /lib/firmware/tigon -%end - diff --git a/common-install.ks b/common-install.ks deleted file mode 100644 index d6620f7..0000000 --- a/common-install.ks +++ /dev/null @@ -1,19 +0,0 @@ -lang C -keyboard us -timezone --utc UTC -auth --useshadow --enablemd5 -selinux --enforcing -firewall --disabled -part / --size 650 --fstype ext2 -services --enabled=auditd,ntpd,ntpdate,collectd,iptables,network,rsyslog,libvirt-qpid,multipathd -# This requires a new fixed version of livecd-creator to honor the --append settings. -bootloader --timeout=30 --append="console=tty0 console=ttyS0,115200n8" - -# not included by default in Fedora 10 livecd initramfs -device virtio_blk -device virtio_pci -device scsi_wait_scan - -# multipath kmods -device dm-multipath -device dm-round-robin diff --git a/common-pkgs.ks b/common-pkgs.ks deleted file mode 100644 index d0d5170..0000000 --- a/common-pkgs.ks +++ /dev/null @@ -1,78 +0,0 @@ -audit -bc -kernel -hwdata -passwd -policycoreutils -rootfiles -dhclient -openssh-clients -openssh-server -kvm -libmlx4 -ovirt-node-stateless -ovirt-node-selinux -ovirt-node-logos -ovirt-node-release --selinux-policy-targeted -selinux-policy-minimum -vim-minimal -sudo -python -python-libs -python-setuptools -db4 -vconfig -python-virtinst -matahari -#debugging -hdparm -sos -gdb -ltrace -strace -sysstat -tcpdump -pstack -pciutils -numactl -file -lsof -newt-python -/usr/bin/kvmtrace -#remove --audit-libs-python --ustr --authconfig --wireless-tools --setserial --prelink --newt-python --newt --kudzu --libselinux-python --rhpl --kbd --usermode --fedora-logos --dmraid --gzip --less --which --parted --nash --tar --libuser --mtools --cpio --sysklogd -/usr/sbin/lokkit -isomd5sum -irqbalance -cpuspeed -acpid -device-mapper-multipath -kpartx -# workaround for gpxe issue with the virt-preview qemu on F11 host kernel -# https://bugzilla.redhat.com/show_bug.cgi?id=512358 -etherboot-zroms-kvm diff --git a/common-post.ks b/common-post.ks deleted file mode 100644 index f8e4a54..0000000 --- a/common-post.ks +++ /dev/null @@ -1,167 +0,0 @@ -# -*-Shell-script-*- -echo "Starting Kickstart Post" -PATH=/sbin:/usr/sbin:/bin:/usr/bin -export PATH - -# Import SELinux Modules -echo "Enabling selinux modules" -SEMODULES="base automount avahi consolekit cyrus dhcp dnsmasq guest hal ipsec \ -iscsi kerberos kerneloops ldap lockdev logadm mozilla ntp ovirt-node-selinux \ -polkit portmap qemu rpcbind sasl snmp stunnel sysstat tcpd unprivuser \ -unconfined usbmodules userhelper virt" - -lokkit -v --selinuxtype=minimum -tmpdir=$(mktemp -d) - -for semodule in $SEMODULES; do - found=0 - pp_file=/usr/share/selinux/minimum/$semodule.pp - if [ -f $pp_file.bz2 ]; then - bzip2 -dc $pp_file.bz2 > "$tmpdir/$semodule.pp" - rm $pp_file.bz2 - found=1 - elif [ -f $pp_file ]; then - mv $pp_file "$tmpdir" - found=1 - fi - # Don't put "base.pp" on the list. - test $semodule = base \ - && continue - test $found=1 \ - && modules="$modules $semodule.pp" -done - -if test -n "$modules"; then - (cd "$tmpdir" \ - && test -f base.pp \ - && semodule -v -b base.pp -i $modules \ - && semodule -v -B ) -fi -rm -rf "$tmpdir" - -echo "Running ovirt-install-node-stateless" -ovirt-install-node-stateless - -echo "Creating shadow files" -# because we aren't installing authconfig, we aren't setting up shadow -# and gshadow properly. Do it by hand here -pwconv -grpconv - -echo "Forcing C locale" -# force logins (via ssh, etc) to use C locale, since we remove locales -cat >> /etc/profile << \EOF -# oVirt: force our locale to C since we don't have locale stuff' -export LC_ALL=C LANG=C -EOF - -echo "Configuring IPTables" -# here, we need to punch the appropriate holes in the firewall -cat > /etc/sysconfig/iptables << \EOF -# oVirt automatically generated firewall configuration -*filter -:INPUT ACCEPT [0:0] -:FORWARD ACCEPT [0:0] -:OUTPUT ACCEPT [0:0] --A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT --A INPUT -p icmp -j ACCEPT --A INPUT -i lo -j ACCEPT -# libvirt --A INPUT -p tcp --dport 16509 -j ACCEPT -# SSH --A INPUT -p tcp --dport 22 -j ACCEPT -# anyterm --A INPUT -p tcp --dport 81 -j ACCEPT -# guest consoles --A INPUT -p tcp -m multiport --dports 5800:6000 -j ACCEPT -# migration --A INPUT -p tcp -m multiport --dports 49152:49216 -j ACCEPT --A INPUT -j REJECT --reject-with icmp-host-prohibited --A FORWARD -m physdev ! --physdev-is-bridged -j REJECT --reject-with icmp-host-prohibited -COMMIT -EOF -# configure IPv6 firewall, default is all ACCEPT -cat > /etc/sysconfig/ip6tables << \EOF -# oVirt automatically generated firewall configuration -*filter -:INPUT ACCEPT [0:0] -:FORWARD ACCEPT [0:0] -:OUTPUT ACCEPT [0:0] --A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT --A INPUT -p ipv6-icmp -j ACCEPT --A INPUT -i lo -j ACCEPT -# libvirt --A INPUT -p tcp --dport 16509 -j ACCEPT -# SSH --A INPUT -p tcp --dport 22 -j ACCEPT -# anyterm --A INPUT -p tcp --dport 81 -j ACCEPT -# guest consoles --A INPUT -p tcp -m multiport --dports 5800:6000 -j ACCEPT -# migration --A INPUT -p tcp -m multiport --dports 49152:49216 -j ACCEPT --A INPUT -j REJECT --reject-with icmp6-adm-prohibited --A FORWARD -m physdev ! --physdev-is-bridged -j REJECT --reject-with icmp6-adm-prohibited -COMMIT -EOF - -# remove errors from /sbin/dhclient-script -DHSCRIPT=/sbin/dhclient-script -sed -i 's/mv /cp -p /g' $DHSCRIPT -sed -i '/rm -f.*${interface}/d' $DHSCRIPT -sed -i '/rm -f \/etc\/localtime/d' $DHSCRIPT -sed -i '/rm -f \/etc\/ntp.conf/d' $DHSCRIPT -sed -i '/rm -f \/etc\/yp.conf/d' $DHSCRIPT - -if rpm -q --qf '%{release}' ovirt-node | grep -q "^0\." ; then - echo "Building in developer mode, leaving root account unlocked" - augtool <<\EOF -set /files/etc/ssh/sshd_config/PermitEmptyPasswords yes -save -EOF -else - echo "Building in production mode, locking root account" - passwd -l root -fi - -# directories required in the image with the correct perms -# config persistance currently handles only regular files -mkdir -p /root/.ssh -chmod 700 /root/.ssh - -# fix iSCSI/LVM startup issue -sed -i 's/node\.session\.initial_login_retry_max.*/node.session.initial_login_retry_max = 60/' /etc/iscsi/iscsid.conf - -# root's bash profile -cat >> /root/.bashrc < /etc/rwtab.d/ovirt <> /etc/fstab -%end - -%post -# Create initial manifests -manifests=/tmp/manifests -mkdir -p $manifests -rpm -qa --qf '%{name}-%{version}-%{release}.%{arch}\n' | sort \ - > $manifests/rpm-manifest.txt -rpm -qa --qf '%{sourcerpm}\n' | sort -u > $manifests/srpm-manifest.txt -du -akx --exclude=/var/cache/yum / > $manifests/file-manifest.txt -du -x --exclude=/var/cache/yum / > $manifests/dir-manifest.txt -%end - -%include common-blacklist.ks - -%post --nochroot -if [ -f "ovirt-authorized_keys" ]; then - echo "Adding authorized_keys to Image" - mkdir -p $INSTALL_ROOT/root/.ssh - cp -v ovirt-authorized_keys $INSTALL_ROOT/root/.ssh/authorized_keys - chown -R root:root $INSTALL_ROOT/root/.ssh - chmod 755 $INSTALL_ROOT/root/.ssh - chmod 644 $INSTALL_ROOT/root/.ssh/authorized_keys -fi - -echo "Fixing boot menu" -# remove quiet from Node bootparams, added by livecd-creator -sed -i -e 's/ quiet//' $LIVE_ROOT/isolinux/isolinux.cfg - -# add stand-alone boot entry -awk ' -BEGIN { - # append additional default boot parameters - add_boot_params="check" -} -/^label linux0/ { linux0=1 } -linux0==1 && $1=="append" { - $0=$0 " " add_boot_params - append0=$0 -} -linux0==1 && $1=="label" && $2!="linux0" { - linux0=2 - print "label stand-alone" - print " menu label Boot in stand-alone mode" - print " kernel vmlinuz0" - gsub("console=tty0", "", append0) - print append0" ovirt_standalone console=tty0" -} -{ print } -' $LIVE_ROOT/isolinux/isolinux.cfg > $LIVE_ROOT/isolinux/isolinux.cfg.standalone -mv $LIVE_ROOT/isolinux/isolinux.cfg.standalone $LIVE_ROOT/isolinux/isolinux.cfg - -%end - -%post -# Create post-image processing manifests -manifests=/tmp/manifests -mkdir -p $manifests -rpm -qa --qf '%{name}-%{version}-%{release}.%{arch}\n' | sort \ - > $manifests/rpm-manifest-post.txt -rpm -qa --qf '%{sourcerpm}\n' | sort -u > $manifests/srpm-manifest-post.txt -du -akx --exclude=/var/cache/yum / > $manifests/file-manifest-post.txt -du -x --exclude=/var/cache/yum / > $manifests/dir-manifest-post.txt - -ver=$(rpm -q --qf '%{version}' ovirt-node) -rel=$(rpm -q --qf '%{release}' ovirt-node) -arch=$(rpm -q --qf '%{arch}' ovirt-node) -echo "oVirt Node release $ver-$rel-$arch" > $manifests/ovirt-release -tar -cvf ovirt-node-image-manifests-$ver-$rel.$arch.tar -C /tmp manifests -ln -nf ovirt-node-image-manifests-$ver-$rel.$arch.tar ovirt-node-image-manifests.tar -rm -Rf $manifests -%end - -%post --nochroot -# Move manifest tar to build directory -mv $INSTALL_ROOT/ovirt-node-image-manifests*.tar . - -# only works on x86, x86_64 -if [ "$(uname -i)" = "i386" -o "$(uname -i)" = "x86_64" ]; then - if [ ! -d $LIVE_ROOT/LiveOS ]; then mkdir -p $LIVE_ROOT/LiveOS ; fi - cp /usr/bin/livecd-iso-to-disk $LIVE_ROOT/LiveOS - cp /usr/bin/livecd-iso-to-pxeboot $LIVE_ROOT/LiveOS -fi -%end - -- 1.6.2.5 From dhuff at redhat.com Wed Oct 14 20:13:06 2009 From: dhuff at redhat.com (David Huff) Date: Wed, 14 Oct 2009 16:13:06 -0400 Subject: [Ovirt-devel] [PATCH 4/4] removed ks files now in ovirt-node-recipe, which is subpackage of ovirt-node In-Reply-To: <1255551186-25333-1-git-send-email-dhuff@redhat.com> References: <1255551186-25333-1-git-send-email-dhuff@redhat.com> Message-ID: <1255551186-25333-5-git-send-email-dhuff@redhat.com> --- create-ovirt-iso-nodes | 135 ----------------------------- edit-livecd | 220 ------------------------------------------------ livecd-iso-to-iscsi | 201 ------------------------------------------- livecd-rpms | 28 ------ livecd-setauth | 50 ----------- 5 files changed, 0 insertions(+), 634 deletions(-) delete mode 100755 create-ovirt-iso-nodes delete mode 100755 edit-livecd delete mode 100755 livecd-iso-to-iscsi delete mode 100755 livecd-rpms delete mode 100755 livecd-setauth diff --git a/create-ovirt-iso-nodes b/create-ovirt-iso-nodes deleted file mode 100755 index fe2e7ab..0000000 --- a/create-ovirt-iso-nodes +++ /dev/null @@ -1,135 +0,0 @@ -#!/bin/bash -# -# Create fake oVirt Nodes for testing CDROM boot -# Copyright 2008 Red Hat, Inc. -# Written by Perry Myers -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Library General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -PATH=$PATH:/sbin:/usr/sbin - -ME=$(basename "$0") -warn() { printf '%s: %s\n' "$ME" "$*" >&2; } -try_h() { printf "Try \`$ME -h' for more information.\n" >&2; } -die() { warn "$@"; try_h; exit 1; } - -NET_DEFAULT=network:default -IMGDIR_DEFAULT=/var/lib/libvirt/images -imgdir=$IMGDIR_DEFAULT -NODEIMG_DEFAULT=/usr/share/ovirt-node-image/ovirt-node-image.iso -nodeimg=$NODEIMG_DEFAULT -NUM_DISKS_DEFAULT=1 -RANGE_DEFAULT="6-9" -RAM_DEFAULT=512 -VCPUS_DEFAULT=1 - -NODE_DISK_FMT=qcow2 -NODE_DISK_SIZE=6144M - -gen_fake_managed_node() { - local num=$1 - local src_nodeimg=$2 - local last_mac=$(( 54 + $num )) - - local os_variant=fedora10 - if [ "$no_virtio" = 1 ]; then - os_variant=fedora8 - fi - - echo "Creating fake node$num using $nodeimg..." - local dest_nodeimg="$imgdir/node${num}-$(basename $src_nodeimg)" - echo "$src_nodeimg -> $dest_nodeimg" - rsync -av $src_nodeimg $dest_nodeimg - - virsh destroy node$num > /dev/null 2>&1 - virsh undefine node$num > /dev/null 2>&1 - - local disks= - for ((i=0;i<$num_disks;i+=1)); do - qemu-img create -f $NODE_DISK_FMT \ - $imgdir/node${num}-${i}.$NODE_DISK_FMT $NODE_DISK_SIZE - disks="$disks --disk path=$imgdir/node${num}-${i}.$NODE_DISK_FMT" - done - - # FIXME: virt-install should be changed to have a --nostart parameter - # that just defines the VM w/o starting it. - virt-install --name=node$num --ram=$ram --vcpus=$vcpus $disks \ - --cdrom=$dest_nodeimg --livecd \ - --network=$net --mac=00:16:3e:12:34:$last_mac \ - --vnc --accelerate --hvm --noautoconsole \ - --os-type=linux --os-variant=$os_variant \ - --force --noreboot - virsh destroy node$num > /dev/null 2>&1 - echo "node$num created" -} - -usage() { - case $# in 1) warn "$1"; try_h; exit 1;; esac - cat < -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Library General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -#!/bin/bash - -PATH=$PATH:/sbin:/usr/sbin - -ME=$(basename "$0") -warn() { printf '%s: %s\n' "$ME" "$*" >&2; } -try_h() { printf "Try \`$ME -h' for more information.\n" >&2; } -die() { warn "$@"; try_h; exit 1; } - -NODEIMG_DEFAULT=/usr/share/ovirt-node-image/ovirt-node-image.iso -CD=$NODEIMG_DEFAULT - -usage() { - case $# in 1) warn "$1"; try_h; exit 1;; esac - cat < when done, and the script - re-packages the ISO. - -h display this help and exit - -EXAMPLES - - Example Script: - #!/bin/sh - touch etc/sysconfig/foo - Save as foo and make executable: - chmod a+x foo - Run this to create a file /etc/sysconfig/foo in the livecd filesystem - (note the use of "\$PWD/foo", not "./foo", since it will be run from a - different directory): - - $ME -i input.iso -o /tmp/result.iso -p "\$PWD/foo" - - or, equivalently, but without a separate script: - - $ME -i input.iso -o /tmp/result.iso -p 'touch etc/sysconfig/foo' - -EOF -} - -# exit after any error: -set -e - -CODE= -OUTPUT_FILE= - -err=0 help=0 -while getopts :b:hi:o:p: c; do - case $c in - i) CD=$OPTARG;; - b) PARAMS=$OPTARG;; - o) OUTPUT_FILE=$OPTARG;; - p) CODE=$OPTARG;; - h) help=1;; - '?') err=1; warn "invalid option: \`-$OPTARG'";; - :) err=1; warn "missing argument to \`-$OPTARG' option";; - *) err=1; warn "internal error: \`-$OPTARG' not handled";; - esac -done -test $err = 1 && { try_h; exit 1; } -test $help = 1 && { usage; exit 0; } - -# Require "-o OUTPUT_FILE" -test -z "$OUTPUT_FILE" \ - && { warn "no output file specified; use -o FILE.iso"; try_h; exit 1; } - -# Fail if there are any extra command-line arguments. -if test $OPTIND -le $#; then - bad_arg=$(eval "echo \$$OPTIND") - warn "extra argument '$bad_arg'"; try_h; exit 1 -fi - -# first, check to see we are root -if [ $( id -u ) -ne 0 ]; then - die "Must run as root" -fi - -# Check for some prerequisites. -# "type" prints "PROG not found" if it's not in $PATH. -type mkisofs -type mksquashfs -type sed -type implantisomd5 - -sane_name() -{ - case $1 in - *[^a-zA-Z0-9._,+:/@%=-]*) false;; - *) true;; - esac -} - -# Fail if names we'll use contain white space or shell meta-characters -sane_name "$PWD" || die "invalid working directory name: $PWD" -sane_name "$CD" || die "invalid ISO name: $CD" - -WDIR=`mktemp -d $PWD/livecd.XXXXXXXXXX` - -addExit() { - EXIT="$@ ; $EXIT" - trap "$EXIT" EXIT HUP TERM INT QUIT -} - -mnt() { - local margs="$1" ; shift - local mp="$WDIR/$1" - for D in "$@" ; do - mkdir -v -p "$WDIR/$D" - done - eval mount -v $margs "$mp" - addExit "df | grep $mp > /dev/null 2>&1 && umount -v $mp" -} - -addExit "rm -rf $WDIR" - -ID_FS_LABEL= # initialize, in case vol_id fails -eval "$(/lib/udev/vol_id $CD)" -LABEL=$ID_FS_LABEL - -# mount the CD image -mnt "-t iso9660 $CD -o loop,ro" cd - -# mount compressed filesystem -mnt "-t squashfs $WDIR/cd/LiveOS/squashfs.img -o ro,loop" sq - -# create writable copy of the new filesystem for the CD -cp -pr $WDIR/cd $WDIR/cd-w - -# create writable copy of the filesystem for the new compressed -# squashfs filesystem -cp -pr $WDIR/sq $WDIR/sq-w - -# mount root filesystem -mnt "-t ext2 $WDIR/sq-w/LiveOS/ext3fs.img -o rw,loop" ex - -echo ">>> Updating CD content" -if [ -n "$CODE" ]; then - ( - cd $WDIR/ex - set +e - eval "$CODE" - set -e - ) -else - echo "***" - echo "*** Pausing to allow manual changes. Press any key to continue." - echo "***" - read -fi - -# Try to unmount. But this is likely to fail, so let the user retry, -# e.g., if he forgot to "cd" out of $WDIR/ex. -while :; do - echo ">>> Unmounting ext3fs" - umount $WDIR/ex && break - echo ">>> Unmounting the working file system copy failed" - echo "***" - echo "*** Did you forget to 'cd' out of $WDIR/ex?" - echo "***" - echo "*** Press any key to repeat the attempt." - echo "***" - read -done - -echo ">>> Compressing filesystem" -mksquashfs $WDIR/sq-w/ $WDIR/cd-w/LiveOS/squashfs.img -noappend - -echo ">>> Recomputing MD5 sums" -( cd $WDIR/cd-w && find . -type f -not -name md5sum.txt \ - -not -path '*/isolinux/*' -print0 | xargs -0 -- md5sum > md5sum.txt ) - -if [ -n "$PARAMS" ]; then - case $PARAMS in - *@*) warn "PARAMS contains the @ sed delimiter, be sure it's escaped";; - esac - echo ">>> Appending boot parameters" - sed -i 's@^ append .*$@& '"$PARAMS@" "$WDIR/cd-w/isolinux/isolinux.cfg" -fi - -echo ">>> Creating ISO image $ISO" -mkisofs \ - -V "$LABEL" \ - -r -cache-inodes -J -l \ - -b isolinux/isolinux.bin \ - -c isolinux/boot.cat \ - -no-emul-boot -boot-load-size 4 -boot-info-table \ - -o "$OUTPUT_FILE" \ - $WDIR/cd-w - -echo ">>> Implanting ISO MD5 Sum" -implantisomd5 --force "$OUTPUT_FILE" - -# The trap ... callbacks will unmount everything. -set +e diff --git a/livecd-iso-to-iscsi b/livecd-iso-to-iscsi deleted file mode 100755 index fd3934d..0000000 --- a/livecd-iso-to-iscsi +++ /dev/null @@ -1,201 +0,0 @@ -#!/usr/bin/python -# Convert a live CD iso into iscsi root bootable format -# iSCSI lun must be accessible via this script -# Copyright 2009 Red Hat, Inc. -# Written by Joey boggs -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Library General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -from optparse import OptionParser -from tempfile import mkdtemp -import dbus -import dbus.glib -import sys -import os -import subprocess -import shutil - -parser = OptionParser() -parser.add_option("--iso", dest="iso", help="LiveCD iso filename") -parser.add_option("--target", dest="target", help="iSCSI target ip address") -parser.add_option("--targetname", dest="targetname", help="iSCSI target lun") -parser.add_option("--targetport", dest="targetport", default="3260", help="iSCSI port number, defaults to 3260") -parser.add_option("--user", dest="user", help="Target username(optional)") -parser.add_option("--password", dest="password", help="Target password") -parser.add_option("--reverse_user", dest="reverse_user", help="Reverse CHAP username(optional)") -parser.add_option("--reverse_password", dest="reverse_password", help="Reverse CHAP password(optional)") -parser.add_option("--disk", dest="disk", help="iSCSI disk device name") -parser.add_option("--disk-label", dest="disk_label", default="ovirt-node-root", help="file system label") - -(options, args) = parser.parse_args() - -def fail(msg): - print(msg) - sys.exit(1) - -if os.geteuid () != 0: - fail("You must run as root") - -if options.iso is None: - fail("ERROR: iso file must be defined") -else: - options.iso = os.path.abspath(options.iso) - -if options.target is None: - fail("ERROR: iscsi target must be defined") - -if options.targetname is None: - fail("ERROR: iscsi targetname must be defined") - -if len(options.disk_label.strip()) > 15: - fail("ERROR: disk label must be 14 characters or less") - -try: - file = os.mkdir("tftpboot") -except OSError, e: - tftp_remove = raw_input("tftpboot directory exists, overwrite? (y/N)? ") - if tftp_remove.lower() == "y": - shutil.rmtree("tftpboot") - os.mkdir("tftpboot") - else: - print "Aborting" - sys.exit(1) - -if options.disk is None: - print "Below are the detected disks, if the iscsi disk is not shown, please ensure you are logged into the correct target\n" - bus = dbus.SystemBus () - hal_obj = bus.get_object ('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager') - hal = dbus.Interface (hal_obj, 'org.freedesktop.Hal.Manager') - udis = hal.FindDeviceByCapability ('storage') - dev_dict = {} - dev_count = 1 - for udi in udis: - dev_obj = bus.get_object ('org.freedesktop.Hal', udi) - dev = dbus.Interface (dev_obj, 'org.freedesktop.Hal.Device') - dev_bus=dev.GetProperty ('storage.bus') - dev_name=dev.GetProperty ('block.device') - dev_size=dev.GetProperty ('storage.size') - dev_size=(dev_size/1024/1024) - basename=os.path.basename(udi) - if dev_bus == "scsi": - print "%s. %s %sM %s \n" % (dev_count,dev_name,dev_size,basename) - dev_dict[str(dev_count)] = dev_name - dev_count = dev_count + 1 - print "Enter Q to Quit" - dev_choice = raw_input("Which device? ") - while not dev_dict.has_key(dev_choice): - if dev_choice.lower() == "q": - print "Aborting" - sys.exit(1) - else: - print "%s is an invalid choice" % dev_choice - dev_choice = raw_input("Which device? ") - options.disk = dev_dict[dev_choice] - -cont = raw_input("Creating file system on %s, do you wish to continue (y/N) " % options.disk) -if cont.lower() != "y": - print "Aborting" - sys.exit(1) - -isomount = mkdtemp() -isomount_ret = subprocess.call(["mount", "-o", "loop", options.iso, isomount]) -if isomount_ret != 0: - fail("Error mounting %s" % options.iso) - -kernel="%s/isolinux/vmlinuz0" % isomount -initrd="%s/isolinux/initrd0.img" % isomount -squashfs="%s/LiveOS/squashfs.img" % isomount -ext3fs="tftpboot/squashfs-root/LiveOS/ext3fs.img" -shutil.copy(kernel,"tftpboot") -shutil.copy(initrd,"tftpboot") - -unsquash = subprocess.call(["unsquashfs", squashfs]) - -# workaround until bug is fixed with squashfs -d option -shutil.move("squashfs-root","tftpboot/squashfs-root") - -print "Placing embedded file system on %s" % options.disk -dd_cmd="dd if=%s of=%s" % (ext3fs,options.disk) -copy_iscsi_ret = subprocess.call(dd_cmd, shell=True) -if copy_iscsi_ret != 0: - fail("Error copying to %s" % options.disk) - -umount_ret = subprocess.call(["umount", isomount]) -if umount_ret != 0: - fail("Error unmounting %s, continuing" % isomount) -else: - os.rmdir(isomount) -shutil.rmtree("tftpboot/squashfs-root") - -pxe_template = """ - -# pxelinux configuration. -DEFAULT pxeboot -TIMEOUT 20 -PROMPT 0 -LABEL ovirt-node-iscsi - KERNEL /vmlinuz0 - APPEND initrd=/initrd0.img ro root=LABEL=%(disk_label)s netroot=iscsi:%(user)s%(password)s@%(target)s::%(target_port)s::%(target_name)s ip=eth0:dhcp - ipappend 2 -ONERROR LOCALBOOT 0 -""" - -# insert empty values for unneeded variables in the pxe template -if not options.user is None: - options.user = options.user + ":" -else: - options.user = "" - -if not options.password is None: - options.password = options.password + ":" -else: - options.password = "" - -if not options.reverse_user is None: - options.reverse_user = options.reverse_user + ":" -else: - options.reverse_user = "" - -if not options.reverse_password is None: - options.reverse_password = options.reverse_password + ":" -else: - options.reverse_password = "" - -os.mkdir("tftpboot/pxelinux.cfg") -pxe_cfg = pxe_template % { - "disk_label": options.disk_label, - "target": options.target, - "target_port": options.targetport, - "target_name": options.targetname, - "user": options.user, - "password": options.password, - "reverse_user": options.reverse_user, - "reverse_password": options.reverse_password - } - -pxe_conf = open("tftpboot/pxelinux.cfg/default", 'w') -pxe_conf.write(pxe_cfg) -pxe_conf.close() - -if os.path.exists("/usr/share/syslinux/pxelinux.0"): - shutil.copy("/usr/share/syslinux/pxelinux.0","tftpboot") -elif os.path.exists("/usr/lib/syslinux/pxelinux.0"): - shutil.copy("/usr/lib/syslinux/pxelinux.0","tftpboot") -else: - print "Warning: You need to add pxelinux.0 to tftpboot/ subdirectory" - -print "Your iscsiroot has been setup on %s" % options.disk -print "" -print "Copy the tftpboot/ subdirectory to your tftpserver root directory" -print "Set up your DHCP, TFTP and PXE server to serve /tftpboot/.../pxeboot.0" diff --git a/livecd-rpms b/livecd-rpms deleted file mode 100755 index 0649cba..0000000 --- a/livecd-rpms +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# -# Script to install/update a livecd with a set of RPMS provided in a directory -# Copyright 2009 Red Hat, Inc. -# Written by Perry Myers -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Library General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -if [[ $# < 1 ]]; then - echo "Usage: $0 rpmdir" - exit 1 -fi - -RPMDIR=$1 - -rpm --root $PWD -Uvh $(find $RPMDIR -type f -name "*.rpm" -print) - diff --git a/livecd-setauth b/livecd-setauth deleted file mode 100755 index eb8922f..0000000 --- a/livecd-setauth +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash -# -# Script to interactively add root password and authorized_keys file -# to a livecd -# Copyright 2008 Red Hat, Inc. -# Written by Perry Myers -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Library General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -DEFAULT_AUTH=~/.ssh/authorized_keys - -printf "Do you want to set a root password? [y/N]: " -read yesno -if [ "$yesno" = "y" -o "$yesno" = "Y" ]; then - chroot . passwd root -fi - -printf "Do you want to set an authorized_keys file? [y/N]: " -read yesno -if [ "$yesno" = "y" -o "$yesno" = "Y" ]; then - echo "Enter the location of the authorized_keys file [default: $DEFAULT_AUTH]: " - read -e authkeys - if [ -z "$authkeys" ]; then - authkeys=$DEFAULT_AUTH - fi - - authkeys=$(eval echo $authkeys) - if [ -f $authkeys ]; then - SSH=root/.ssh - AUTH=$SSH/authorized_keys - - mkdir -p $SSH - chmod 755 $SSH - cp -v $authkeys $AUTH - chmod 644 $AUTH - else - echo "$authkeys not found, skipping" - fi -fi -- 1.6.2.5 From mloiseleur at linagora.com Thu Oct 15 09:23:56 2009 From: mloiseleur at linagora.com (Loiseleur Michel) Date: Thu, 15 Oct 2009 09:23:56 +0000 Subject: [Ovirt-devel] [PATCH] enhance & fix foreigner plugin to manage correctly named foreign key In-Reply-To: <1254168512-4166-1-git-send-email-mloiseleur@linagora.com> References: <1254168512-4166-1-git-send-email-mloiseleur@linagora.com> Message-ID: <1255598636-13401-1-git-send-email-mloiseleur@linagora.com> This patch fixes current foreigner plugins in order to : 1) be able to add constraint on a named table 2) be able to drop fk with postgres Signed-off-by: Loiseleur Michel --- .../abstract/schema_definitions.rb | 2 +- .../connection_adapters/postgresql_adapter.rb | 15 ++++++++++++- .../lib/foreigner/connection_adapters/sql_2003.rb | 21 +++++++++---------- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/abstract/schema_definitions.rb b/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/abstract/schema_definitions.rb index e417c5b..3598d05 100644 --- a/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/abstract/schema_definitions.rb +++ b/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/abstract/schema_definitions.rb @@ -18,7 +18,7 @@ module Foreigner module TableDefinition class ForeignKey < Struct.new(:base, :to_table, :options) def to_sql - base.foreign_key_definition(to_table, options) + base.foreign_key_definition(@table_name, to_table, options) end alias to_s :to_sql end diff --git a/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/postgresql_adapter.rb b/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/postgresql_adapter.rb index be662b3..fec81cd 100644 --- a/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/postgresql_adapter.rb +++ b/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/postgresql_adapter.rb @@ -23,7 +23,7 @@ module Foreigner } fk_info.map do |row| - options = {:column => row['column'], :name => row['name'], :primary_key = row['primary_key']} + options = {:column => row['column'], :name => row['name'], :primary_key => row['primary_key']} if row['dependency'] == 'CASCADE' options[:dependent] = :delete @@ -33,6 +33,17 @@ module Foreigner ForeignKeyDefinition.new(table_name, row['to_table'], options) end end + + def remove_foreign_key(table, options) + if Hash === options + foreign_key_name = foreign_key_name(table, options[:column], options) + else + foreign_key_name = foreign_key_name(table, "#{options.to_s.singularize}_id") + end + + execute "ALTER TABLE #{quote_table_name(table)} DROP CONSTRAINT #{quote_column_name(foreign_key_name)}" + end + end end end @@ -43,4 +54,4 @@ module ActiveRecord include Foreigner::ConnectionAdapters::PostgreSQLAdapter end end -end \ No newline at end of file +end diff --git a/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/sql_2003.rb b/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/sql_2003.rb index 1a1019b..957111b 100644 --- a/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/sql_2003.rb +++ b/src/vendor/plugins/foreigner/lib/foreigner/connection_adapters/sql_2003.rb @@ -4,25 +4,24 @@ module Foreigner def supports_foreign_keys? true end - + def add_foreign_key(from_table, to_table, options = {}) column = options[:column] || "#{to_table.to_s.singularize}_id" - foreign_key_name = foreign_key_name(from_table, column, options) - sql = - "ALTER TABLE #{quote_table_name(from_table)} " + - "ADD CONSTRAINT #{quote_column_name(foreign_key_name)} " + - foreign_key_definition(to_table, options) - + sql = "ALTER TABLE #{quote_table_name(from_table)} ADD " << + foreign_key_definition(from_table, to_table, options) + execute(sql) end - - def foreign_key_definition(to_table, options = {}) + + def foreign_key_definition(from_table, to_table, options = {}) column = options[:column] || "#{to_table.to_s.singularize}_id" + foreign_key_name = foreign_key_name(from_table, column, options) primary_key = options[:primary_key] || "id" dependency = dependency_sql(options[:dependent]) - sql = "FOREIGN KEY (#{quote_column_name(column)}) REFERENCES #{quote_table_name(to_table)}(#{primary_key})" + sql = "CONSTRAINT #{quote_column_name(foreign_key_name)} " + sql << "FOREIGN KEY (#{quote_column_name(column)}) REFERENCES #{quote_table_name(to_table)}(#{primary_key})" sql << " #{dependency}" unless dependency.blank? sql end @@ -36,7 +35,7 @@ module Foreigner execute "ALTER TABLE #{quote_table_name(table)} DROP FOREIGN KEY #{quote_column_name(foreign_key_name)}" end - + private def foreign_key_name(table, column, options = {}) if options[:name] -- 1.6.2.5 From mloiseleur at linagora.com Thu Oct 15 09:26:26 2009 From: mloiseleur at linagora.com (Michel Loiseleur) Date: Thu, 15 Oct 2009 11:26:26 +0200 Subject: [Ovirt-devel] [PATCH 3/3] enhance migrations in order to use the new system. It should be backward compatible with existing database, since it uses the same fk name & index. In-Reply-To: <4AD62428.6060006@redhat.com> References: <4AC1173D.7070408@linagora.com> <1254168512-4166-1-git-send-email-mloiseleur@linagora.com> <1254168512-4166-2-git-send-email-mloiseleur@linagora.com> <1254168512-4166-3-git-send-email-mloiseleur@linagora.com> <4AD62428.6060006@redhat.com> Message-ID: <4AD6EAC2.4080106@linagora.com> Mohammed Morsi a ?crit : > [...] > > The error I got was: > [...] > Rake stops at this point, so I'm not sure if any other migrations are > broken. Does this work when trying it against postgres locally? In fact, I had not any postgres under the hand when I made this patch. Since I have one currently, I was able to reproduce it and fix the second patch. It was just a matter of using "DROP CONSTRAINT" in postgres instead of "DROP FOREIGN KEY" in MySQL. Regards, -- Loiseleur Michel Responsable Technique OSSA Linagora / 27, rue de Berri / 75008 PARIS Tel/Fax : 01 58 18 68 28 / 01 58 18 68 29 http://job.linagora.com/ | http://www.tosca-project.net "Ce n'est pas le logiciel qui est libre, c'est vous" -------------- next part -------------- An HTML attachment was scrubbed... URL: From mloiseleur at linagora.com Thu Oct 15 09:32:01 2009 From: mloiseleur at linagora.com (Michel Loiseleur) Date: Thu, 15 Oct 2009 11:32:01 +0200 Subject: [Ovirt-devel] [PATCH 2/3] enhance & fix foreigner plugin to manage correctly named foreign key In-Reply-To: <4AD62320.4010301@redhat.com> References: <4AC1173D.7070408@linagora.com> <1254168512-4166-1-git-send-email-mloiseleur@linagora.com> <1254168512-4166-2-git-send-email-mloiseleur@linagora.com> <4AD62320.4010301@redhat.com> Message-ID: <4AD6EC11.5040408@linagora.com> Hi mo, > A couple things. First I notice alot of whitespace changes, ideally > these wouldn't be here, or even be in their own formatting patch, as > they kinda clutter up the actual content changes. > You're right, see the update of this specific patch I just sent today : there's far less spaces. > Second I was just wondering if you could provide a little more detail as > to what this patch does as it's not immediately apparent looking at it. > If this is a known foreigner plugin fix could you link the thread of > discussion detailing it, or if we need it for our project perhaps a > blurb below the patch summary as to the changes needed to foreigner to > adapt it to ovirt. > I have submitted the fix to foreigner upstream, see http://github.com/matthuhiggins/foreigner/issues#issue/5 http://github.com/matthuhiggins/foreigner/issues/#issue/7 Basically, these are bugfixes to foreigner. Foreigner API is complete, but not its source code. Regards, -- Loiseleur Michel Responsable Technique OSSA Linagora / 27, rue de Berri / 75008 PARIS Tel/Fax : 01 58 18 68 28 / 01 58 18 68 29 http://job.linagora.com/ | http://www.tosca-project.net "Ce n'est pas le logiciel qui est libre, c'est vous" From imain at redhat.com Thu Oct 15 16:08:18 2009 From: imain at redhat.com (Ian Main) Date: Thu, 15 Oct 2009 09:08:18 -0700 Subject: [Ovirt-devel] [PATCH server] remove init scripts from being logrotated In-Reply-To: <1255461261-17005-1-git-send-email-jboggs@redhat.com> References: <1255461261-17005-1-git-send-email-jboggs@redhat.com> Message-ID: <20091015090818.3424510b@tp.mains.priv> ACK On Tue, 13 Oct 2009 15:14:21 -0400 Joey Boggs wrote: > ovirt-host-register and ovirt-host-collect init scripts are being set as log files and are being rotated, this removes them from the list. > > --- > conf/ovirt-server.logrotate | 3 --- > 1 files changed, 0 insertions(+), 3 deletions(-) > > diff --git a/conf/ovirt-server.logrotate b/conf/ovirt-server.logrotate > index 3239783..d7c3b0c 100644 > --- a/conf/ovirt-server.logrotate > +++ b/conf/ovirt-server.logrotate > @@ -4,9 +4,6 @@ compress > missingok > notifempty > > -/etc/init.d/ovirt-host-register > -/etc/init.d/ovirt-host-collect > - > /var/log/ovirt-server/taskomatic.log { > postrotate > /etc/init.d/ovirt-taskomatic restart > -- > 1.6.2.5 > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel From jboggs at redhat.com Thu Oct 15 18:15:18 2009 From: jboggs at redhat.com (Joey Boggs) Date: Thu, 15 Oct 2009 14:15:18 -0400 Subject: [Ovirt-devel] [PATCH server] remove init scripts from being logrotated In-Reply-To: <20091015090818.3424510b@tp.mains.priv> References: <1255461261-17005-1-git-send-email-jboggs@redhat.com> <20091015090818.3424510b@tp.mains.priv> Message-ID: <4AD766B6.5020206@redhat.com> Ian Main wrote: > ACK > > On Tue, 13 Oct 2009 15:14:21 -0400 > Joey Boggs wrote: > > >> ovirt-host-register and ovirt-host-collect init scripts are being set as log files and are being rotated, this removes them from the list. >> >> --- >> conf/ovirt-server.logrotate | 3 --- >> 1 files changed, 0 insertions(+), 3 deletions(-) >> >> diff --git a/conf/ovirt-server.logrotate b/conf/ovirt-server.logrotate >> index 3239783..d7c3b0c 100644 >> --- a/conf/ovirt-server.logrotate >> +++ b/conf/ovirt-server.logrotate >> @@ -4,9 +4,6 @@ compress >> missingok >> notifempty >> >> -/etc/init.d/ovirt-host-register >> -/etc/init.d/ovirt-host-collect >> - >> /var/log/ovirt-server/taskomatic.log { >> postrotate >> /etc/init.d/ovirt-taskomatic restart >> -- >> 1.6.2.5 >> >> _______________________________________________ >> Ovirt-devel mailing list >> Ovirt-devel at redhat.com >> https://www.redhat.com/mailman/listinfo/ovirt-devel >> > > _______________________________________________ > Ovirt-devel mailing list > Ovirt-devel at redhat.com > https://www.redhat.com/mailman/listinfo/ovirt-devel > pushed From dpierce at redhat.com Thu Oct 15 19:24:46 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 15 Oct 2009 15:24:46 -0400 Subject: [Ovirt-devel] Patch depends on the previous storage patch... Message-ID: <1255634687-18776-1-git-send-email-dpierce@redhat.com> This patch is dependant on the previously submitted storage admin patch. From dpierce at redhat.com Thu Oct 15 19:24:47 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 15 Oct 2009 15:24:47 -0400 Subject: [Ovirt-devel] [PATCH node] Refactor domain storage setup to use pool and volume selection screens. In-Reply-To: <1255634687-18776-1-git-send-email-dpierce@redhat.com> References: <1255634687-18776-1-git-send-email-dpierce@redhat.com> Message-ID: <1255634687-18776-2-git-send-email-dpierce@redhat.com> Now, when the user elects to use managed storage, they're show the list of available storage pools. Then, after selecting one, the user is shown the list of volumes on that pool. These are then used to create the domain. Signed-off-by: Darryl L. Pierce --- nodeadmin/definedomain.py | 186 ++++++++++++++++++++++++++------------------ nodeadmin/domainconfig.py | 17 +++- nodeadmin/libvirtworker.py | 34 ++++---- 3 files changed, 141 insertions(+), 96 deletions(-) diff --git a/nodeadmin/definedomain.py b/nodeadmin/definedomain.py index 791c278..0086e59 100755 --- a/nodeadmin/definedomain.py +++ b/nodeadmin/definedomain.py @@ -37,10 +37,11 @@ OS_VARIANT_PAGE = 12 RAM_CPU_PAGE = 13 ENABLE_STORAGE_PAGE = 14 LOCAL_STORAGE_PAGE = 15 -MANAGED_STORAGE_PAGE = 16 -BRIDGE_PAGE = 17 -VIRT_DETAILS_PAGE = 18 -CONFIRM_PAGE = 19 +SELECT_POOL_PAGE = 16 +SELECT_VOLUME_PAGE = 17 +BRIDGE_PAGE = 18 +VIRT_DETAILS_PAGE = 19 +CONFIRM_PAGE = 20 LOCATION="location" KICKSTART="kickstart" @@ -58,24 +59,25 @@ class DomainConfigScreen(ConfigScreen): self.__config.set_virt_type(self.get_libvirt().get_default_virt_type()) def get_elements_for_page(self, screen, page): - if page == VM_DETAILS_PAGE: return self.get_vm_details_page(screen) - elif page == LOCAL_INSTALL_PAGE: return self.get_local_install_page(screen) - elif page == SELECT_CDROM_PAGE: return self.get_select_cdrom_page(screen) - elif page == SELECT_ISO_PAGE: return self.get_select_iso_page(screen) - elif page == NETWORK_INSTALL_PAGE: return self.get_network_install_page(screen) - elif page == OS_TYPE_PAGE: return self.get_os_type_page(screen) - elif page == OS_VARIANT_PAGE: return self.get_os_variant_page(screen) - elif page == RAM_CPU_PAGE: return self.get_ram_and_cpu_page(screen) - elif page == ENABLE_STORAGE_PAGE: return self.get_enable_storage_page(screen) - elif page == LOCAL_STORAGE_PAGE: return self.get_local_storage_page(screen) - elif page == MANAGED_STORAGE_PAGE: return self.get_managed_storage_page(screen) - elif page == BRIDGE_PAGE: return self.get_bridge_page(screen) - elif page == VIRT_DETAILS_PAGE: return self.get_virt_details_page(screen) - elif page == CONFIRM_PAGE: return self.get_confirm_page(screen) + if page is VM_DETAILS_PAGE: return self.get_vm_details_page(screen) + elif page is LOCAL_INSTALL_PAGE: return self.get_local_install_page(screen) + elif page is SELECT_CDROM_PAGE: return self.get_select_cdrom_page(screen) + elif page is SELECT_ISO_PAGE: return self.get_select_iso_page(screen) + elif page is NETWORK_INSTALL_PAGE: return self.get_network_install_page(screen) + elif page is OS_TYPE_PAGE: return self.get_os_type_page(screen) + elif page is OS_VARIANT_PAGE: return self.get_os_variant_page(screen) + elif page is RAM_CPU_PAGE: return self.get_ram_and_cpu_page(screen) + elif page is ENABLE_STORAGE_PAGE: return self.get_enable_storage_page(screen) + elif page is LOCAL_STORAGE_PAGE: return self.get_local_storage_page(screen) + elif page is SELECT_POOL_PAGE: return self.get_select_pool_page(screen) + elif page is SELECT_VOLUME_PAGE: return self.get_select_volume_page(screen) + elif page is BRIDGE_PAGE: return self.get_bridge_page(screen) + elif page is VIRT_DETAILS_PAGE: return self.get_virt_details_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) return [] def validate_input(self, page, errors): - if page == VM_DETAILS_PAGE: + if page is VM_DETAILS_PAGE: if len(self.__guest_name.value()) > 0: if self.get_libvirt().domain_exists(self.__guest_name.value()): errors.append("Guest name '%s' is already in use." % self.__guest_name.value()) @@ -83,12 +85,12 @@ class DomainConfigScreen(ConfigScreen): return True else: errors.append("Guest name must be a string between 0 and 50 characters.") - elif page == LOCAL_INSTALL_PAGE: + elif page is LOCAL_INSTALL_PAGE: if self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM: return True elif self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_ISO: return True - elif page == SELECT_CDROM_PAGE: + elif page is SELECT_CDROM_PAGE: if self.__install_media.getSelection() != None: if len(self.get_hal().list_installable_volumes()) == 0: errors.append("No installable media is available.") @@ -96,7 +98,7 @@ class DomainConfigScreen(ConfigScreen): return True else: errors.append("You must select an install media.") - elif page == SELECT_ISO_PAGE: + elif page is SELECT_ISO_PAGE: if len(self.__iso_path.value()) > 0: if os.path.exists(self.__iso_path.value()): if os.path.isfile(self.__iso_path.value()): @@ -108,14 +110,14 @@ class DomainConfigScreen(ConfigScreen): errors.append(self.__iso_path.value()) else: errors.append("An install media selection is required.") - elif page == NETWORK_INSTALL_PAGE: + elif page is NETWORK_INSTALL_PAGE: if len(self.__install_url.value()) > 0: return True else: errors.append("An install tree is required.") - elif page == OS_TYPE_PAGE: return True - elif page == OS_VARIANT_PAGE: return True - elif page == RAM_CPU_PAGE: + elif page is OS_TYPE_PAGE: return True + elif page is OS_VARIANT_PAGE: return True + elif page is RAM_CPU_PAGE: if (len(self.__memory.value()) > 0 and len(self.__cpus.value()) > 0) \ and (int(self.__memory.value()) > 0 and int(self.__cpus.value()) > 0): return True @@ -128,8 +130,8 @@ class DomainConfigScreen(ConfigScreen): errors.append("A value must be entered for CPUs.") elif int(self.__cpus.value()) <= 0: errors.append("A positive integer value must be entered for memory.") - elif page == ENABLE_STORAGE_PAGE: return True - elif page == LOCAL_STORAGE_PAGE: + elif page is ENABLE_STORAGE_PAGE: return True + elif page is LOCAL_STORAGE_PAGE: if len(self.__storage_size.value()) > 0: if float(self.__storage_size.value()) > 0: return True @@ -137,12 +139,17 @@ class DomainConfigScreen(ConfigScreen): errors.append("A positive value must be entered for the storage size.") else: errors.append("A value must be entered for the storage size.") - elif page == MANAGED_STORAGE_PAGE: - if self.__existing_storage.getSelection() is not None: + elif page is SELECT_POOL_PAGE: + if self.__storage_pool.getSelection() is not None: + return True + else: + errors.append("Please select a storage pool.") + elif page is SELECT_VOLUME_PAGE: + if self.__storage_volume.getSelection() is not None: return True else: errors.append("Please select a storage volume.") - elif page == BRIDGE_PAGE: + elif page is BRIDGE_PAGE: if self.__network_bridges.getSelection() != None: if len(self.__mac_address.value()) > 0: # TODO: regex check the format @@ -151,62 +158,66 @@ class DomainConfigScreen(ConfigScreen): errors.append("MAC address must be supplied.") else: errors.append("A network bridge must be selected.") - elif page == VIRT_DETAILS_PAGE: + elif page is VIRT_DETAILS_PAGE: if self.__virt_types.getSelection() != None and self.__architectures.getSelection() != None: return True if self.__virt_types.getSelection() is None: errors.append("Please select a virtualization type.") if self.__architectures.getSelection() is None: errors.append("Please selection an architecture.") - elif page == CONFIRM_PAGE: return True + elif page is CONFIRM_PAGE: return True return False def process_input(self, page): - if page == VM_DETAILS_PAGE: + if page is VM_DETAILS_PAGE: self.__config.set_guest_name(self.__guest_name.value()) self.__config.set_install_type(self.__install_type.getSelection()) - elif page == LOCAL_INSTALL_PAGE: + elif page is LOCAL_INSTALL_PAGE: self.__config.set_use_cdrom_source(self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM) - elif page == SELECT_CDROM_PAGE: + elif page is SELECT_CDROM_PAGE: self.__config.set_install_media(self.__install_media.getSelection()) - elif page == SELECT_ISO_PAGE: + elif page is SELECT_ISO_PAGE: self.__config.set_iso_path(self.__iso_path.value()) - elif page == NETWORK_INSTALL_PAGE: + elif page is NETWORK_INSTALL_PAGE: self.__config.set_install_url(self.__install_url.value()) self.__config.set_kickstart_url(self.__kickstart_url.value()) self.__config.set_kernel_options(self.__kernel_options.value()) - elif page == OS_TYPE_PAGE: + elif page is OS_TYPE_PAGE: self.__config.set_os_type(self.__os_types.getSelection()) - elif page == OS_VARIANT_PAGE: + elif page is OS_VARIANT_PAGE: self.__config.set_os_variant(self.__os_variants.getSelection()) - elif page == RAM_CPU_PAGE: + elif page is RAM_CPU_PAGE: self.__config.set_memory(int(self.__memory.value())) self.__config.set_cpus(int(self.__cpus.value())) - elif page == ENABLE_STORAGE_PAGE: + elif page is ENABLE_STORAGE_PAGE: self.__config.set_enable_storage(self.__enable_storage.value()) if self.__storage_type.getSelection() == DomainConfig.NEW_STORAGE: self.__config.set_use_local_storage(True) elif self.__storage_type.getSelection() == DomainConfig.EXISTING_STORAGE: self.__config.set_use_local_storage(False) - elif page == LOCAL_STORAGE_PAGE: + elif page is LOCAL_STORAGE_PAGE: self.__config.set_storage_size(float(self.__storage_size.value())) self.__config.set_allocate_storage(self.__allocate_storage.value()) - elif page == MANAGED_STORAGE_PAGE: + elif page is SELECT_POOL_PAGE: self.__config.set_use_local_storage(False) - self.__config.set_existing_storage(self.__existing_storage.getSelection()) - self.__config.set_storage_size(self.get_libvirt().get_storage_size(self.__existing_storage.getSelection())) - elif page == BRIDGE_PAGE: + self.__config.set_storage_pool(self.__storage_pool.getSelection()) + elif page is SELECT_VOLUME_PAGE: + self.__config.set_storage_volume(self.__storage_volume.getSelection()) + volume = self.get_libvirt().get_storage_volume(self.__config.get_storage_pool(), + self.__config.get_storage_volume()) + self.__config.set_storage_size(volume.info()[1] / 1024.0 ** 3) + elif page is BRIDGE_PAGE: self.__config.set_network_bridge(self.__network_bridges.getSelection()) - elif page == VIRT_DETAILS_PAGE: + elif page is VIRT_DETAILS_PAGE: self.__config.set_virt_type(self.__virt_types.getSelection()) self.__config.set_architecture(self.__architectures.getSelection()) - elif page == CONFIRM_PAGE: + elif page is CONFIRM_PAGE: self.get_libvirt().define_domain(self.__config, CreateMeter()) self.set_finished() def get_back_page(self, page): result = page - if page == OS_TYPE_PAGE: + if page is OS_TYPE_PAGE: install_type = self.__config.get_install_type() if install_type == DomainConfig.LOCAL_INSTALL: if self.__config.get_use_cdrom_source(): @@ -217,24 +228,26 @@ class DomainConfigScreen(ConfigScreen): result = NETWORK_INSTALL_PAGE elif install_type == DomainConfig.PXE_INSTALL: result = VM_DETAILS_PAGE - elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: + elif page is LOCAL_STORAGE_PAGE or page is SELECT_VOLUME_PAGE: result = ENABLE_STORAGE_PAGE - elif page == NETWORK_INSTALL_PAGE: + elif page is SELECT_POOL_PAGE: + result = ENABLE_STORAGE_PAGE + elif page is NETWORK_INSTALL_PAGE: result = VM_DETAILS_PAGE - elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: + elif page is SELECT_CDROM_PAGE or page is SELECT_ISO_PAGE: result = LOCAL_INSTALL_PAGE - elif page == BRIDGE_PAGE: + elif page is BRIDGE_PAGE: if self.__config.get_use_local_storage(): result = LOCAL_STORAGE_PAGE else: - result = MANAGED_STORAGE_PAGE + result = SELECT_VOLUME_PAGE else: if page > 1: result = page - 1 return result def get_next_page(self, page): result = page - if page == VM_DETAILS_PAGE: + if page is VM_DETAILS_PAGE: install_type = self.__config.get_install_type() if install_type == DomainConfig.LOCAL_INSTALL: result = LOCAL_INSTALL_PAGE @@ -242,34 +255,36 @@ class DomainConfigScreen(ConfigScreen): result = NETWORK_INSTALL_PAGE elif install_type == DomainConfig.PXE_INSTALL: result = OS_TYPE_PAGE - elif page == LOCAL_INSTALL_PAGE: + elif page is LOCAL_INSTALL_PAGE: if self.__config.get_use_cdrom_source(): result = SELECT_CDROM_PAGE else: result = SELECT_ISO_PAGE - elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: + elif page is SELECT_CDROM_PAGE or page is SELECT_ISO_PAGE: result = OS_TYPE_PAGE - elif page == NETWORK_INSTALL_PAGE: + elif page is NETWORK_INSTALL_PAGE: result = OS_TYPE_PAGE - elif page == ENABLE_STORAGE_PAGE: + elif page is ENABLE_STORAGE_PAGE: result = BRIDGE_PAGE if self.__config.get_enable_storage(): if self.__config.get_use_local_storage(): result = LOCAL_STORAGE_PAGE else: - result = MANAGED_STORAGE_PAGE - elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: + result = SELECT_POOL_PAGE + elif page is LOCAL_STORAGE_PAGE: result = BRIDGE_PAGE else: result = page + 1 return result def page_has_finish(self, page): - if page == CONFIRM_PAGE: return True + if page is CONFIRM_PAGE: return True return False def page_has_next(self, page): - if page < CONFIRM_PAGE: + if page is SELECT_POOL_PAGE: return self.__has_pools + elif page is SELECT_VOLUME_PAGE: return self.__has_volumes + elif page < CONFIRM_PAGE: return True def get_vm_details_page(self, screen): @@ -393,17 +408,36 @@ class DomainConfigScreen(ConfigScreen): return [Label("Configure local storage"), grid] - def get_managed_storage_page(self, screen): + def get_select_pool_page(self, screen): + pools = [] + for pool in self.get_libvirt().list_storage_pools(): + pools.append([pool, pool, pool == self.__config.get_storage_pool()]) + if len(pools) > 0: + self.__storage_pool = RadioBar(screen, (pools)) + grid = Grid(2, 1) + grid.setField(Label("Storage pool:"), 0, 0, anchorTop = 1) + grid.setField(self.__storage_pool, 1, 0) + self.__has_pools = True + else: + grid = Label("There are no storage pools available.") + self.__has_pools = False + return [Label("Configure Managed Storage: Select A Pool"), + grid] + + def get_select_volume_page(self, screen): volumes = [] - for volume in self.get_libvirt().list_storage_volumes(): - volumes.append(["%s (%d GB)" % (volume.name(), volume.info()[1] / (1024 ** 3)), - volume.name(), - self.__config.is_existing_storage(volume.name())]) - self.__existing_storage = RadioBar(screen, (volumes)) - grid = Grid(2, 1) - grid.setField(Label("Existing storage:"), 0, 0) - grid.setField(self.__existing_storage, 1, 0) - return [Label("Configure managed storage"), + for volume in self.get_libvirt().list_storage_volumes(self.__config.get_storage_pool()): + volumes.append([volume, volume, volume == self.__config.get_storage_volume()]) + if len(volumes) > 0: + self.__storage_volume = RadioBar(screen, (volumes)) + grid = Grid(2, 1) + grid.setField(Label("Storage volumes:"), 0, 0, anchorTop = 1) + grid.setField(self.__storage_volume, 1, 0) + self.__has_volumes = True + else: + grid = Label("This storage pool has no defined volumes.") + self.__has_volumes = False + return [Label("Configure Managed Storage: Select A Volume"), grid] def get_bridge_page(self, screen): @@ -448,7 +482,9 @@ class DomainConfigScreen(ConfigScreen): grid.setField(Label("CPUs:"), 0, 3, anchorRight = 1) grid.setField(Label("%d" % self.__config.get_cpus()), 1, 3, anchorLeft = 1) grid.setField(Label("Storage:"), 0, 4, anchorRight = 1) - grid.setField(Label(self.__config.get_existing_storage()), 1, 4, anchorLeft = 1) + grid.setField(Label("%s (on %s)" % (self.__config.get_storage_volume(), + self.__config.get_storage_pool())), + 1, 4, anchorLeft = 1) grid.setField(Label("Network:"), 0, 5, anchorRight = 1) grid.setField(Label(self.__config.get_network_bridge()), 1, 5, anchorLeft = 1) return [Label("Ready to begin installation of %s" % self.__config.get_guest_name()), diff --git a/nodeadmin/domainconfig.py b/nodeadmin/domainconfig.py index ef39fe0..4466e67 100644 --- a/nodeadmin/domainconfig.py +++ b/nodeadmin/domainconfig.py @@ -50,7 +50,8 @@ class DomainConfig: self.__use_local_storage = True self.__storage_size = 8.0 self.__allocate_storage = True - self.__existing_storage = "" + self.__storage_pool = "" + self.__storage_volume = "" self.__network_bridge = None self.__mac_address = None self.__virt_type = None @@ -177,11 +178,17 @@ class DomainConfig: def get_allocate_storage(self): return self.__allocate_storage - def set_existing_storage(self, storage): - self.__existing_storage = storage + def set_storage_pool(self, pool): + self.__storage_pool = pool - def get_existing_storage(self): - return self.__existing_storage + def get_storage_pool(self): + return self.__storage_pool + + def set_storage_volume(self, volume): + self.__storage_volume = volume + + def get_storage_volume(self): + return self.__storage_volume def is_existing_storage(self, storage): return self.__existing_storage == storage diff --git a/nodeadmin/libvirtworker.py b/nodeadmin/libvirtworker.py index b2acabe..f31266c 100644 --- a/nodeadmin/libvirtworker.py +++ b/nodeadmin/libvirtworker.py @@ -196,6 +196,11 @@ class LibvirtWorker: '''Returns the storage pool with the specified name.''' return self.__conn.storagePoolLookupByName(name) + def list_storage_volumes(self, poolname): + '''Returns the list of all defined storage volumes for a given pool.''' + pool = self.get_storage_pool(poolname) + return pool.listVolumes() + def define_storage_volume(self, config, meter): '''Defines a new storage volume.''' self.create_storage_pool(config.get_pool().name()) @@ -204,10 +209,15 @@ class LibvirtWorker: def remove_storage_volume(self, poolname, volumename): '''Removes the specified storage volume.''' - pool = self.get_storage_pool(poolname) - volume = pool.storageVolLookupByName(volumename) + volume = self.get_storage_volume(poolname, volumename) volume.delete(0) + def get_storage_volume(self, poolname, volumename): + '''Returns a reference to the specified storage volume.''' + pool =self.get_storage_pool(poolname) + volume = pool.storageVolLookupByName(volumename) + return volume + def list_bridges(self): '''Lists all defined and active bridges.''' bridges = self.__conn.listNetworks() @@ -221,21 +231,9 @@ class LibvirtWorker: def generate_mac_address(self): return self.__net.macaddr - def list_storage_volumes(self): - '''Lists all defined storage volumes.''' - pools = self.__conn.listStoragePools() - pools.extend(self.__conn.listDefinedStoragePools()) - result = [] - for name in pools: - pool = self.__conn.storagePoolLookupByName(name) - for volname in pool.listVolumes(): - volume = self.__conn.storageVolLookupByPath("/var/lib/libvirt/images/%s" % volname) - result.append(volume) - return result - - def get_storage_size(self, name): + def get_storage_size(self, poolname, volumename): '''Returns the size of the specified storage volume.''' - volume = self.__conn.storageVolLookupByPath("/var/lib/libvirt/images/%s" % name) + volume = self.get_storage_volume(poolname, volumename) return volume.info()[1] / (1024.0 ** 3) def get_virt_types(self): @@ -381,6 +379,10 @@ class LibvirtWorker: pool_object = pool, suffix = ".img") path = os.path.join(DEFAULT_POOL_TARGET_PATH, path) + else: + volume = self.get_storage_volume(config.get_storage_pool(), + config.get_storage_volume()) + path = volume.path() if path is not None: storage= virtinst.VirtualDisk(conn = self.__conn, -- 1.6.2.5 From pmialon at linagora.com Fri Oct 16 12:36:39 2009 From: pmialon at linagora.com (Pierre-Gilles Mialon) Date: Fri, 16 Oct 2009 14:36:39 +0200 Subject: [Ovirt-devel] Issue with LVM and iSCSI lun Message-ID: <200910161436.39535.pmialon@linagora.com> Hi all, Imagine the following situation, you have : - one ISCSI storage server providing : lun0 and lun1 - the lun0 and lun1 contains a standard installation of Fedora : VM1 and VM2 - the standards installations define two VG VolGroup00 ... We didn't define LogicalVolume on the node and we attached directly the iSCSI lun to the VM without making partition or LV in Ovirt. On the node that run VM1 and VM2 you have lun0 and lun1 seen as /dev/sda and /dev/sdb, but when you run vgscan it hangs, because you have conflict and the local LVM try to manage "virtual LVM". When you refresh a iSCSI pool, Ovirt add a task to taskomatic that ask a node to launch a vgscan, vgscan hang and the pool is never refreshed. In fact the lun is seen as PV in the VM and for the node, but the version of LVM running on the node and the VM is not always the same. You have a conflict between the LVM instance of the lun0 and the local lvm of your node. This situation gave to us a lot of desease with the disk access of our VMs. Do you see a solution to isolate lun containing LVM from the node LVM instance. It could be possible to filter the iSCSI devices in /etc/lvm/lvm.conf, but it would be difficult to continue to manage LVM volume on iSCSI lun and excluding them. The easiest way to fix it, would be to remove the capicity of doing lvm action on the node. We use one iSCSI lun for each VM and didn't need the ability to share the storage space of a lun between VM. I think that a choice should be done, between a lun managed directly by the VM and a lun shared between VM with Ovirt, because trying to keep both will produce weird interaction... -- Pierre-Gilles Mialon Responsable h?bergement :: Head of Hosting services pmialon at linagora.com :: +33.1 58 18 65 46 Linagora :: http://www.linagora.com 27 rue de Berri :: 75008 PARIS -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 198 bytes Desc: This is a digitally signed message part. URL: From mmorsi at redhat.com Mon Oct 19 18:49:11 2009 From: mmorsi at redhat.com (Mohammed Morsi) Date: Mon, 19 Oct 2009 14:49:11 -0400 Subject: [Ovirt-devel] Multi-databases support In-Reply-To: <4AD622C7.6010701@redhat.com> References: <4AC1173D.7070408@linagora.com> <4AD622C7.6010701@redhat.com> Message-ID: <4ADCB4A7.5020104@redhat.com> Mohammed Morsi wrote: > Loiseleur Michel wrote: > >> Hi, >> >> While I was hacking ovirt-server, I have found that it's currently >> restricted to Postgres DB. Even if I like postgres for serious work on >> a server, I really prefer to hack/dev locally on a Sqlite or MySQL DB. >> >> I have googled on rails in order to find a good answer for the >> "foreign key problem" which forces OVirt to stay on pg. I have found a >> plugin on this particular problem, named foreigner : >> http://github.com/matthuhiggins/foreigner >> >> It provides a rails' syntax to common operation on them. For instance : >> >> add_foreign_key(from_table, to_table, options) >> remove_foreign_key(from_table, options) >> foreign_keys(table_name) >> >> I really like to keep my development computer to run as fast as >> possible (ie without a database service), so I have even tried to >> integrate this plugin into OVirt, in a transparent way for existing db. >> >> After a couple of patch on the plugin itself and some hack on >> migrations, it seems that I manage to do it. The 3 patchs following >> this email allows OVirt to support multiple open source database >> without losing functionality, without losing existing databases and >> with a small readability enhancement on db migrations. >> >> What do you think of it ? Feel free to contact me by email, on this >> list or by irc (nick: Coren`) >> >> Regards, >> > Thank you greatly for this. I agree, and am sure many others do that > multiple database support would be great. I applied all three patches > but ran into some problems when running them. Comments are in line in > the patches. > > -Mo > > Excellent, with your latest patches, everything works. I verified it w/ a running system, against the dev/test env (also ran the tests), and when applied before a production install. I also ran through the code and nothing looks offhand glancing at it. I only tested this against our current oVirt setup w/ the postgres db, and not w/ mysql or any others, but postgres support now doesn't break, so its committable. ACK and pushed. Thanks for this and again sorry for the delay. Its up to you, but if you want to write a patch adding instructions for mysql or any other db to the top of src/config/databases.yml, as we have w/ postgresql there, I would ack it. Also if you wanted/needed to add support to the oVirt installer/rpms permitting mysql or any other db to be selected and configured instead of postgresql, I can also review / push those. As I said, up to you, and once again thanks for this patchset. -Mo From dpierce at redhat.com Wed Oct 21 19:48:02 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 21 Oct 2009 15:48:02 -0400 Subject: [Ovirt-devel] [PATCH node] Renamed files and menu items for node administration: Message-ID: <1256154482-9174-1-git-send-email-dpierce@redhat.com> define domain -> add virtual machine (addvm) undefine domain -> remove virtual machine (rmvm) create domain -> start virtual machine (startvm) destroy domain -> stop virtual machine (stopvm) list domains -> list virtual machiens (listvms) Signed-off-by: Darryl L. Pierce --- Makefile.am | 8 +- nodeadmin/adddomain.py | 470 +++++++++++++++++++++++++++++++++++++++++++ nodeadmin/createdomain.py | 65 ------ nodeadmin/definedomain.py | 470 ------------------------------------------- nodeadmin/destroydomain.py | 66 ------ nodeadmin/listdomains.py | 4 +- nodeadmin/nodemenu.py | 42 ++-- nodeadmin/removedomain.py | 83 ++++++++ nodeadmin/setup.py.in | 10 +- nodeadmin/startdomain.py | 65 ++++++ nodeadmin/stopdomain.py | 66 ++++++ nodeadmin/undefinedomain.py | 83 -------- ovirt-node.spec.in | 18 +- 13 files changed, 725 insertions(+), 725 deletions(-) create mode 100755 nodeadmin/adddomain.py delete mode 100755 nodeadmin/createdomain.py delete mode 100755 nodeadmin/definedomain.py delete mode 100755 nodeadmin/destroydomain.py create mode 100755 nodeadmin/removedomain.py create mode 100755 nodeadmin/startdomain.py create mode 100755 nodeadmin/stopdomain.py delete mode 100755 nodeadmin/undefinedomain.py diff --git a/Makefile.am b/Makefile.am index abb7c33..3ce24c1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -27,10 +27,10 @@ EXTRA_DIST = \ images/grub-splash.xpm.gz \ images/syslinux-vesa-splash.jpg \ nodeadmin/__init__.py \ + nodeadmin/adddomain.py \ nodeadmin/configscreen.py \ nodeadmin/createnetwork.py \ nodeadmin/createuser.py \ - nodeadmin/destroydomain.py \ nodeadmin/destroynetwork.py \ nodeadmin/halworker.py \ nodeadmin/libvirtworker.py \ @@ -39,10 +39,10 @@ EXTRA_DIST = \ nodeadmin/menuscreen.py \ nodeadmin/netmenu.py \ nodeadmin/nodemenu.py \ - nodeadmin/undefinedomain.py \ + nodeadmin/removedomain.py \ nodeadmin/undefinenetwork.py \ - nodeadmin/createdomain.py \ - nodeadmin/definedomain.py \ + nodeadmin/startdomain.py \ + nodeadmin/stopdomain.py \ nodeadmin/definenet.py \ nodeadmin/domainconfig.py \ nodeadmin/networkconfig.py \ diff --git a/nodeadmin/adddomain.py b/nodeadmin/adddomain.py new file mode 100755 index 0000000..70a2011 --- /dev/null +++ b/nodeadmin/adddomain.py @@ -0,0 +1,470 @@ +#!/usr/bin/env python +# +# adddomain.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +import os +from domainconfig import DomainConfig +from configscreen import ConfigScreen +import urlgrabber.progress as progress +import utils +import logging + +from virtinst import * + +VM_DETAILS_PAGE = 1 +LOCAL_INSTALL_PAGE = 2 +SELECT_CDROM_PAGE = 3 +SELECT_ISO_PAGE = 4 +NETWORK_INSTALL_PAGE = 10 +OS_TYPE_PAGE = 11 +OS_VARIANT_PAGE = 12 +RAM_CPU_PAGE = 13 +ENABLE_STORAGE_PAGE = 14 +LOCAL_STORAGE_PAGE = 15 +MANAGED_STORAGE_PAGE = 16 +BRIDGE_PAGE = 17 +VIRT_DETAILS_PAGE = 18 +CONFIRM_PAGE = 19 + +LOCATION="location" +KICKSTART="kickstart" +KERNELOPTS="kernel.options" +OS_TYPE="os.type" +OS_VARIANT="os.variant" +MEMORY="memory" +CPUS="cpus" + +class DummyMeter(progress.BaseMeter): + def _do_start(self, now = None): + logging.info("Starting...") + + def _do_end(self, amount_read, now = None): + logging.info("Ending: read=%d" % amount_read) + + def _do_update(self, amount_read, now = None): + logging.info("Update: read=%d" % amount_read) + +class DomainConfigScreen(ConfigScreen): + def __init__(self): + ConfigScreen.__init__(self, "Create A New Virtual Machine") + self.__config = DomainConfig() + self.__config.set_architecture(self.get_libvirt().get_default_architecture()) + self.__config.set_virt_type(self.get_libvirt().get_default_virt_type()) + + def get_elements_for_page(self, screen, page): + if page == VM_DETAILS_PAGE: return self.get_vm_details_page(screen) + elif page == LOCAL_INSTALL_PAGE: return self.get_local_install_page(screen) + elif page == SELECT_CDROM_PAGE: return self.get_select_cdrom_page(screen) + elif page == SELECT_ISO_PAGE: return self.get_select_iso_page(screen) + elif page == NETWORK_INSTALL_PAGE: return self.get_network_install_page(screen) + elif page == OS_TYPE_PAGE: return self.get_os_type_page(screen) + elif page == OS_VARIANT_PAGE: return self.get_os_variant_page(screen) + elif page == RAM_CPU_PAGE: return self.get_ram_and_cpu_page(screen) + elif page == ENABLE_STORAGE_PAGE: return self.get_enable_storage_page(screen) + elif page == LOCAL_STORAGE_PAGE: return self.get_local_storage_page(screen) + elif page == MANAGED_STORAGE_PAGE: return self.get_managed_storage_page(screen) + elif page == BRIDGE_PAGE: return self.get_bridge_page(screen) + elif page == VIRT_DETAILS_PAGE: return self.get_virt_details_page(screen) + elif page == CONFIRM_PAGE: return self.get_confirm_page(screen) + return [] + + def validate_input(self, page, errors): + if page == VM_DETAILS_PAGE: + if len(self.__guest_name.value()) > 0: + if self.get_libvirt().domain_exists(self.__guest_name.value()): + errors.append("Guest name '%s' is already in use." % self.__guest_name.value()) + else: + return True + else: + errors.append("Guest name must be a string between 0 and 50 characters.") + elif page == LOCAL_INSTALL_PAGE: + if self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM: + return True + elif self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_ISO: + return True + elif page == SELECT_CDROM_PAGE: + if self.__install_media.getSelection() != None: + if len(self.get_hal().list_installable_volumes()) == 0: + errors.append("No installable media is available.") + else: + return True + else: + errors.append("You must select an install media.") + elif page == SELECT_ISO_PAGE: + if len(self.__iso_path.value()) > 0: + if os.path.exists(self.__iso_path.value()): + if os.path.isfile(self.__iso_path.value()): + return True + else: + errors.append("%s is not a file." % self.__iso_path.value()) + else: + errors.append("No such install media exists:") + errors.append(self.__iso_path.value()) + else: + errors.append("An install media selection is required.") + elif page == NETWORK_INSTALL_PAGE: + if len(self.__install_url.value()) > 0: + return True + else: + errors.append("An install tree is required.") + elif page == OS_TYPE_PAGE: return True + elif page == OS_VARIANT_PAGE: return True + elif page == RAM_CPU_PAGE: + if (len(self.__memory.value()) > 0 and len(self.__cpus.value()) > 0) \ + and (int(self.__memory.value()) > 0 and int(self.__cpus.value()) > 0): + return True + else: + if len(self.__memory.value()) == 0: + errors.append("A value must be entered for memory.") + elif int(self.__memory.value()) <= 0: + errors.append("A positive integer value must be entered for memory.") + if len(self.__cpus.value()) == 0: + errors.append("A value must be entered for CPUs.") + elif int(self.__cpus.value()) <= 0: + errors.append("A positive integer value must be entered for memory.") + elif page == ENABLE_STORAGE_PAGE: return True + elif page == LOCAL_STORAGE_PAGE: + if len(self.__storage_size.value()) > 0: + if float(self.__storage_size.value()) > 0: + return True + else: + errors.append("A positive value must be entered for the storage size.") + else: + errors.append("A value must be entered for the storage size.") + elif page == MANAGED_STORAGE_PAGE: + if self.__existing_storage.getSelection() is not None: + return True + else: + errors.append("Please select a storage volume.") + elif page == BRIDGE_PAGE: + if self.__network_bridges.getSelection() != None: + if len(self.__mac_address.value()) > 0: + # TODO: regex check the format + return True + else: + errors.append("MAC address must be supplied.") + else: + errors.append("A network bridge must be selected.") + elif page == VIRT_DETAILS_PAGE: + if self.__virt_types.getSelection() != None and self.__architectures.getSelection() != None: + return True + if self.__virt_types.getSelection() is None: + errors.append("Please select a virtualization type.") + if self.__architectures.getSelection() is None: + errors.append("Please selection an architecture.") + elif page == CONFIRM_PAGE: return True + return False + + def process_input(self, page): + if page == VM_DETAILS_PAGE: + self.__config.set_guest_name(self.__guest_name.value()) + self.__config.set_install_type(self.__install_type.getSelection()) + elif page == LOCAL_INSTALL_PAGE: + self.__config.set_use_cdrom_source(self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM) + elif page == SELECT_CDROM_PAGE: + self.__config.set_install_media(self.__install_media.getSelection()) + elif page == SELECT_ISO_PAGE: + self.__config.set_iso_path(self.__iso_path.value()) + elif page == NETWORK_INSTALL_PAGE: + self.__config.set_install_url(self.__install_url.value()) + self.__config.set_kickstart_url(self.__kickstart_url.value()) + self.__config.set_kernel_options(self.__kernel_options.value()) + elif page == OS_TYPE_PAGE: + self.__config.set_os_type(self.__os_types.getSelection()) + elif page == OS_VARIANT_PAGE: + self.__config.set_os_variant(self.__os_variants.getSelection()) + elif page == RAM_CPU_PAGE: + self.__config.set_memory(int(self.__memory.value())) + self.__config.set_cpus(int(self.__cpus.value())) + elif page == ENABLE_STORAGE_PAGE: + self.__config.set_enable_storage(self.__enable_storage.value()) + if self.__storage_type.getSelection() == DomainConfig.NEW_STORAGE: + self.__config.set_use_local_storage(True) + elif self.__storage_type.getSelection() == DomainConfig.EXISTING_STORAGE: + self.__config.set_use_local_storage(False) + elif page == LOCAL_STORAGE_PAGE: + self.__config.set_storage_size(float(self.__storage_size.value())) + self.__config.set_allocate_storage(self.__allocate_storage.value()) + elif page == MANAGED_STORAGE_PAGE: + self.__config.set_use_local_storage(False) + self.__config.set_existing_storage(self.__existing_storage.getSelection()) + self.__config.set_storage_size(self.get_libvirt().get_storage_size(self.__existing_storage.getSelection())) + elif page == BRIDGE_PAGE: + self.__config.set_network_bridge(self.__network_bridges.getSelection()) + elif page == VIRT_DETAILS_PAGE: + self.__config.set_virt_type(self.__virt_types.getSelection()) + self.__config.set_architecture(self.__architectures.getSelection()) + elif page == CONFIRM_PAGE: + self.get_libvirt().define_domain(self.__config, DummyMeter()) + self.set_finished() + + def get_back_page(self, page): + result = page + if page == OS_TYPE_PAGE: + install_type = self.__config.get_install_type() + if install_type == DomainConfig.LOCAL_INSTALL: + if self.__config.get_use_cdrom_source(): + result = SELECT_CDROM_PAGE + else: + result = SELECT_ISO_PAGE + elif install_type == DomainConfig.NETWORK_INSTALL: + result = NETWORK_INSTALL_PAGE + elif install_type == DomainConfig.PXE_INSTALL: + result = VM_DETAILS_PAGE + elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: + result = ENABLE_STORAGE_PAGE + elif page == NETWORK_INSTALL_PAGE: + result = VM_DETAILS_PAGE + elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: + result = LOCAL_INSTALL_PAGE + elif page == BRIDGE_PAGE: + if self.__config.get_use_local_storage(): + result = LOCAL_STORAGE_PAGE + else: + result = MANAGED_STORAGE_PAGE + else: + if page > 1: result = page - 1 + return result + + def get_next_page(self, page): + result = page + if page == VM_DETAILS_PAGE: + install_type = self.__config.get_install_type() + if install_type == DomainConfig.LOCAL_INSTALL: + result = LOCAL_INSTALL_PAGE + elif install_type == DomainConfig.NETWORK_INSTALL: + result = NETWORK_INSTALL_PAGE + elif install_type == DomainConfig.PXE_INSTALL: + result = OS_TYPE_PAGE + elif page == LOCAL_INSTALL_PAGE: + if self.__config.get_use_cdrom_source(): + result = SELECT_CDROM_PAGE + else: + result = SELECT_ISO_PAGE + elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: + result = OS_TYPE_PAGE + elif page == NETWORK_INSTALL_PAGE: + result = OS_TYPE_PAGE + elif page == ENABLE_STORAGE_PAGE: + result = BRIDGE_PAGE + if self.__config.get_enable_storage(): + if self.__config.get_use_local_storage(): + result = LOCAL_STORAGE_PAGE + else: + result = MANAGED_STORAGE_PAGE + elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: + result = BRIDGE_PAGE + else: + result = page + 1 + return result + + def page_has_finish(self, page): + if page == CONFIRM_PAGE: return True + return False + + def page_has_next(self, page): + if page < CONFIRM_PAGE: + return True + + def get_vm_details_page(self, screen): + self.__guest_name = Entry(50, self.__config.get_guest_name()) + self.__install_type = RadioBar(screen, (("Local install media (ISO image or CDROM)", + DomainConfig.LOCAL_INSTALL, + self.__config.is_install_type(DomainConfig.LOCAL_INSTALL)), + ("Network Install (HTTP, FTP, or NFS)", + DomainConfig.NETWORK_INSTALL, + self.__config.is_install_type(DomainConfig.NETWORK_INSTALL)), + ("Network Boot (PXE)", + DomainConfig.PXE_INSTALL, + self.__config.is_install_type(DomainConfig.PXE_INSTALL)))) + grid = Grid(2,3) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(self.__guest_name, 1, 0, anchorLeft = 1) + grid.setField(Label("Choose how you would like to install the operating system"), 1, 1, + anchorLeft = 1, anchorTop = 1) + grid.setField(self.__install_type, 1, 2, anchorLeft = 1) + return [Label("Enter your machine details"), + grid] + + def get_local_install_page(self, screen): + self.__install_source = RadioBar(screen, (("Use CDROM or DVD", + DomainConfig.INSTALL_SOURCE_CDROM, + self.__config.get_use_cdrom_source()), + ("Use ISO image", + DomainConfig.INSTALL_SOURCE_ISO, + self.__config.get_use_cdrom_source() is False))) + grid = Grid(1,1) + grid.setField(self.__install_source, 0, 0, anchorLeft = 1) + return [Label("Locate your install media"), + grid] + + def get_select_cdrom_page(self, screen): + drives = [] + media = self.get_hal().list_installable_volumes() + for drive in media.keys(): + drives.append([media[drive], drive, self.__config.is_install_media(drive)]) + self.__install_media = RadioBar(screen, (drives)) + grid = Grid(1, 1) + grid.setField(self.__install_media, 0, 0) + return [Label("Select the install media"), + grid] + + def get_select_iso_page(self, screen): + self.__iso_path = Entry(50, self.__config.get_iso_path()) + grid = Grid(1, 2) + grid.setField(Label("Enter ISO path:"), 0, 0, anchorLeft = 1) + grid.setField(self.__iso_path, 0, 1, anchorLeft = 1) + return [Label("Enter the full path to an install ISO"), + grid] + + def get_network_install_page(self, screen): + self.__install_url = Entry(50, self.__config.get_install_url()) + self.__kickstart_url = Entry(50, self.__config.get_kickstart_url()) + self.__kernel_options = Entry(50, self.__config.get_kernel_options()) + grid = Grid(2,3) + grid.setField(Label("URL:"), 0, 0, anchorRight = 1) + grid.setField(self.__install_url, 1, 0, anchorLeft = 1) + grid.setField(Label("Kickstart URL:"), 0, 1, anchorRight = 1) + grid.setField(self.__kickstart_url, 1, 1, anchorLeft = 1) + grid.setField(Label("Kernel Options:"), 0, 2, anchorRight = 1) + grid.setField(self.__kernel_options, 1, 2, anchorLeft = 1) + return [Label("Provide the operating system URL"), + grid] + + def get_os_type_page(self, screen): + types = [] + for type in Guest.list_os_types(): + types.append([Guest.get_os_type_label(type), type, self.__config.is_os_type(type)]) + self.__os_types = RadioBar(screen, types) + grid = Grid(1, 1) + grid.setField(self.__os_types, 0, 0, anchorLeft = 1) + return [Label("Choose the operating system type"), + grid] + + def get_os_variant_page(self, screen): + variants = [] + type = self.__config.get_os_type() + for variant in Guest.list_os_variants(type): + variants.append([Guest.get_os_variant_label(type, variant), variant, self.__config.is_os_variant(variant)]) + self.__os_variants = RadioBar(screen, variants) + grid = Grid(1, 1) + grid.setField(self.__os_variants, 0, 0, anchorLeft = 1) + return [Label("Choose the operating system version"), + grid] + + def get_ram_and_cpu_page(self, screen): + self.__memory = Entry(10, str(self.__config.get_memory())) + self.__cpus = Entry(10, str(self.__config.get_cpus())) + grid = Grid(2,2) + grid.setField(Label("Memory (RAM):"), 0, 0, anchorRight = 1) + grid.setField(self.__memory, 1, 0, anchorLeft = 1) + grid.setField(Label("CPUs:"), 0, 1, anchorRight = 1) + grid.setField(self.__cpus, 1, 1, anchorLeft = 1) + return [Label("Choose memory and CPU settings"), + grid] + + def get_enable_storage_page(self, screen): + self.__enable_storage = Checkbox("Enable storage for this virtual machine", self.__config.get_enable_storage()) + self.__storage_type = RadioBar(screen,((["Create a disk image on the computer's hard disk", + DomainConfig.NEW_STORAGE, + self.__config.get_use_local_storage()]), + (["Select managed or other existing storage", + DomainConfig.EXISTING_STORAGE, + self.__config.get_use_local_storage() is False]))) + grid = Grid(1,2) + grid.setField(self.__enable_storage, 0, 0, anchorLeft = 1) + grid.setField(self.__storage_type, 0, 1, anchorLeft = 1) + return [Label("Configure storage"), + grid] + + def get_local_storage_page(self, screen): + self.__storage_size = Entry(6, str(self.__config.get_storage_size())) + self.__allocate_storage = Checkbox("Allocate entire disk now", self.__config.get_allocate_storage()) + grid = Grid(2, 2) + grid.setField(self.__allocate_storage, 0, 0, growx = 1, anchorLeft = 1) + grid.setField(Label("Storage size (GB):"), 0, 1, anchorLeft = 1) + grid.setField(self.__storage_size, 1, 1) + return [Label("Configure local storage"), + grid] + + def get_managed_storage_page(self, screen): + volumes = [] + for volume in self.get_libvirt().list_storage_volumes(): + volumes.append(["%s (%d GB)" % (volume.name(), volume.info()[1] / (1024 ** 3)), + volume.name(), + self.__config.is_existing_storage(volume.name())]) + self.__existing_storage = RadioBar(screen, (volumes)) + grid = Grid(2, 1) + grid.setField(Label("Existing storage:"), 0, 0) + grid.setField(self.__existing_storage, 1, 0) + return [Label("Configure managed storage"), + grid] + + def get_bridge_page(self, screen): + bridges = [] + for bridge in self.get_libvirt().list_bridges(): + bridges.append(["Virtual network '%s'" % bridge.name(), bridge.name(), self.__config.get_network_bridge() == bridge.name()]) + self.__network_bridges = RadioBar(screen, (bridges)) + if self.__config.get_mac_address() == None: + self.__config.set_mac_address(self.get_libvirt().generate_mac_address()) + self.__mac_address = Entry(20, self.__config.get_mac_address()) + grid = Grid(1, 1) + grid.setField(self.__network_bridges, 0, 0) + return [Label("Select an existing bridge"), + grid] + + def get_virt_details_page(self, screen): + virt_types = [] + for type in self.get_libvirt().list_virt_types(): + virt_types.append([type, type, self.__config.is_virt_type(type)]) + self.__virt_types = RadioBar(screen, (virt_types)) + archs = [] + for arch in self.get_libvirt().list_architectures(): + archs.append([arch, arch, self.__config.is_architecture(arch)]) + self.__architectures = RadioBar(screen, (archs)) + grid = Grid(2, 2) + grid.setField(Label("Virt Type:"), 0, 0, anchorRight = 1, anchorTop = 1) + grid.setField(self.__virt_types, 1, 0, anchorLeft = 1) + grid.setField(Label("Architecture:"), 0, 1, anchorRight = 1, anchorTop = 1) + grid.setField(self.__architectures, 1, 1, anchorLeft = 1) + return [Label("Configure virtualization details"), + grid] + + def get_confirm_page(self, screen): + grid = Grid(2, 6) + grid.setField(Label("OS:"), 0, 0, anchorRight = 1) + grid.setField(Label(Guest.get_os_variant_label(self.__config.get_os_type(), + self.__config.get_os_variant())), 1, 0, anchorLeft = 1) + grid.setField(Label("Install:"), 0, 1, anchorRight = 1) + grid.setField(Label(self.__config.get_install_type_text()), 1, 1, anchorLeft = 1) + grid.setField(Label("Memory:"), 0, 2, anchorRight = 1) + grid.setField(Label("%s MB" % self.__config.get_memory()), 1, 2, anchorLeft = 1) + grid.setField(Label("CPUs:"), 0, 3, anchorRight = 1) + grid.setField(Label("%d" % self.__config.get_cpus()), 1, 3, anchorLeft = 1) + grid.setField(Label("Storage:"), 0, 4, anchorRight = 1) + grid.setField(Label(self.__config.get_existing_storage()), 1, 4, anchorLeft = 1) + grid.setField(Label("Network:"), 0, 5, anchorRight = 1) + grid.setField(Label(self.__config.get_network_bridge()), 1, 5, anchorLeft = 1) + return [Label("Ready to begin installation of %s" % self.__config.get_guest_name()), + grid] + +def AddDomain(): + screen = DomainConfigScreen() + screen.start() diff --git a/nodeadmin/createdomain.py b/nodeadmin/createdomain.py deleted file mode 100755 index 6f10b44..0000000 --- a/nodeadmin/createdomain.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python -# -# createdomain.py - Copyright (C) 2009 Red Hat, Inc. -# Written by Darryl L. Pierce -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. A copy of the GNU General Public License is -# also available at http://www.gnu.org/copyleft/gpl.html. - -from snack import * -from configscreen import * - -class CreateDomainConfigScreen(DomainListConfigScreen): - LIST_PAGE = 1 - CREATE_PAGE = 2 - - def __init__(self): - DomainListConfigScreen.__init__(self, "Create A Domain") - - def get_elements_for_page(self, screen, page): - if page is self.LIST_PAGE: - return self.get_domain_list_page(screen, created = False) - elif page is self.CREATE_PAGE: - return self.get_create_domain_page(screen) - - def page_has_next(self, page): - if page is self.LIST_PAGE: return self.has_selectable_domains() - return False - - def page_has_back(self, page): - if page is self.CREATE_PAGE: return True - return False - - def validate_input(self, page, errors): - if page is self.LIST_PAGE: - if self.get_selected_domain() is not None: - domain = self.get_selected_domain() - try: - self.get_libvirt().create_domain(domain) - return True - except Exception, error: - errors.append("There was an error creating the domain: %s" % domain) - errors.append(str(error)) - else: - errors.append("You must first select a domain to create.") - - def get_create_domain_page(self, screen): - grid = Grid(1, 1) - grid.setField(Label("%s was successfully created." % self.get_selected_domain()), 0, 0) - return [grid] - -def CreateDomain(): - screen = CreateDomainConfigScreen() - screen.start() diff --git a/nodeadmin/definedomain.py b/nodeadmin/definedomain.py deleted file mode 100755 index 3fffca2..0000000 --- a/nodeadmin/definedomain.py +++ /dev/null @@ -1,470 +0,0 @@ -#!/usr/bin/env python -# -# definedomain.py - Copyright (C) 2009 Red Hat, Inc. -# Written by Darryl L. Pierce -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. A copy of the GNU General Public License is -# also available at http://www.gnu.org/copyleft/gpl.html. - -from snack import * -import os -from domainconfig import DomainConfig -from configscreen import ConfigScreen -import urlgrabber.progress as progress -import utils -import logging - -from virtinst import * - -VM_DETAILS_PAGE = 1 -LOCAL_INSTALL_PAGE = 2 -SELECT_CDROM_PAGE = 3 -SELECT_ISO_PAGE = 4 -NETWORK_INSTALL_PAGE = 10 -OS_TYPE_PAGE = 11 -OS_VARIANT_PAGE = 12 -RAM_CPU_PAGE = 13 -ENABLE_STORAGE_PAGE = 14 -LOCAL_STORAGE_PAGE = 15 -MANAGED_STORAGE_PAGE = 16 -BRIDGE_PAGE = 17 -VIRT_DETAILS_PAGE = 18 -CONFIRM_PAGE = 19 - -LOCATION="location" -KICKSTART="kickstart" -KERNELOPTS="kernel.options" -OS_TYPE="os.type" -OS_VARIANT="os.variant" -MEMORY="memory" -CPUS="cpus" - -class DummyMeter(progress.BaseMeter): - def _do_start(self, now = None): - logging.info("Starting...") - - def _do_end(self, amount_read, now = None): - logging.info("Ending: read=%d" % amount_read) - - def _do_update(self, amount_read, now = None): - logging.info("Update: read=%d" % amount_read) - -class DomainConfigScreen(ConfigScreen): - def __init__(self): - ConfigScreen.__init__(self, "Create A New Virtual Machine") - self.__config = DomainConfig() - self.__config.set_architecture(self.get_libvirt().get_default_architecture()) - self.__config.set_virt_type(self.get_libvirt().get_default_virt_type()) - - def get_elements_for_page(self, screen, page): - if page == VM_DETAILS_PAGE: return self.get_vm_details_page(screen) - elif page == LOCAL_INSTALL_PAGE: return self.get_local_install_page(screen) - elif page == SELECT_CDROM_PAGE: return self.get_select_cdrom_page(screen) - elif page == SELECT_ISO_PAGE: return self.get_select_iso_page(screen) - elif page == NETWORK_INSTALL_PAGE: return self.get_network_install_page(screen) - elif page == OS_TYPE_PAGE: return self.get_os_type_page(screen) - elif page == OS_VARIANT_PAGE: return self.get_os_variant_page(screen) - elif page == RAM_CPU_PAGE: return self.get_ram_and_cpu_page(screen) - elif page == ENABLE_STORAGE_PAGE: return self.get_enable_storage_page(screen) - elif page == LOCAL_STORAGE_PAGE: return self.get_local_storage_page(screen) - elif page == MANAGED_STORAGE_PAGE: return self.get_managed_storage_page(screen) - elif page == BRIDGE_PAGE: return self.get_bridge_page(screen) - elif page == VIRT_DETAILS_PAGE: return self.get_virt_details_page(screen) - elif page == CONFIRM_PAGE: return self.get_confirm_page(screen) - return [] - - def validate_input(self, page, errors): - if page == VM_DETAILS_PAGE: - if len(self.__guest_name.value()) > 0: - if self.get_libvirt().domain_exists(self.__guest_name.value()): - errors.append("Guest name '%s' is already in use." % self.__guest_name.value()) - else: - return True - else: - errors.append("Guest name must be a string between 0 and 50 characters.") - elif page == LOCAL_INSTALL_PAGE: - if self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM: - return True - elif self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_ISO: - return True - elif page == SELECT_CDROM_PAGE: - if self.__install_media.getSelection() != None: - if len(self.get_hal().list_installable_volumes()) == 0: - errors.append("No installable media is available.") - else: - return True - else: - errors.append("You must select an install media.") - elif page == SELECT_ISO_PAGE: - if len(self.__iso_path.value()) > 0: - if os.path.exists(self.__iso_path.value()): - if os.path.isfile(self.__iso_path.value()): - return True - else: - errors.append("%s is not a file." % self.__iso_path.value()) - else: - errors.append("No such install media exists:") - errors.append(self.__iso_path.value()) - else: - errors.append("An install media selection is required.") - elif page == NETWORK_INSTALL_PAGE: - if len(self.__install_url.value()) > 0: - return True - else: - errors.append("An install tree is required.") - elif page == OS_TYPE_PAGE: return True - elif page == OS_VARIANT_PAGE: return True - elif page == RAM_CPU_PAGE: - if (len(self.__memory.value()) > 0 and len(self.__cpus.value()) > 0) \ - and (int(self.__memory.value()) > 0 and int(self.__cpus.value()) > 0): - return True - else: - if len(self.__memory.value()) == 0: - errors.append("A value must be entered for memory.") - elif int(self.__memory.value()) <= 0: - errors.append("A positive integer value must be entered for memory.") - if len(self.__cpus.value()) == 0: - errors.append("A value must be entered for CPUs.") - elif int(self.__cpus.value()) <= 0: - errors.append("A positive integer value must be entered for memory.") - elif page == ENABLE_STORAGE_PAGE: return True - elif page == LOCAL_STORAGE_PAGE: - if len(self.__storage_size.value()) > 0: - if float(self.__storage_size.value()) > 0: - return True - else: - errors.append("A positive value must be entered for the storage size.") - else: - errors.append("A value must be entered for the storage size.") - elif page == MANAGED_STORAGE_PAGE: - if self.__existing_storage.getSelection() is not None: - return True - else: - errors.append("Please select a storage volume.") - elif page == BRIDGE_PAGE: - if self.__network_bridges.getSelection() != None: - if len(self.__mac_address.value()) > 0: - # TODO: regex check the format - return True - else: - errors.append("MAC address must be supplied.") - else: - errors.append("A network bridge must be selected.") - elif page == VIRT_DETAILS_PAGE: - if self.__virt_types.getSelection() != None and self.__architectures.getSelection() != None: - return True - if self.__virt_types.getSelection() is None: - errors.append("Please select a virtualization type.") - if self.__architectures.getSelection() is None: - errors.append("Please selection an architecture.") - elif page == CONFIRM_PAGE: return True - return False - - def process_input(self, page): - if page == VM_DETAILS_PAGE: - self.__config.set_guest_name(self.__guest_name.value()) - self.__config.set_install_type(self.__install_type.getSelection()) - elif page == LOCAL_INSTALL_PAGE: - self.__config.set_use_cdrom_source(self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM) - elif page == SELECT_CDROM_PAGE: - self.__config.set_install_media(self.__install_media.getSelection()) - elif page == SELECT_ISO_PAGE: - self.__config.set_iso_path(self.__iso_path.value()) - elif page == NETWORK_INSTALL_PAGE: - self.__config.set_install_url(self.__install_url.value()) - self.__config.set_kickstart_url(self.__kickstart_url.value()) - self.__config.set_kernel_options(self.__kernel_options.value()) - elif page == OS_TYPE_PAGE: - self.__config.set_os_type(self.__os_types.getSelection()) - elif page == OS_VARIANT_PAGE: - self.__config.set_os_variant(self.__os_variants.getSelection()) - elif page == RAM_CPU_PAGE: - self.__config.set_memory(int(self.__memory.value())) - self.__config.set_cpus(int(self.__cpus.value())) - elif page == ENABLE_STORAGE_PAGE: - self.__config.set_enable_storage(self.__enable_storage.value()) - if self.__storage_type.getSelection() == DomainConfig.NEW_STORAGE: - self.__config.set_use_local_storage(True) - elif self.__storage_type.getSelection() == DomainConfig.EXISTING_STORAGE: - self.__config.set_use_local_storage(False) - elif page == LOCAL_STORAGE_PAGE: - self.__config.set_storage_size(float(self.__storage_size.value())) - self.__config.set_allocate_storage(self.__allocate_storage.value()) - elif page == MANAGED_STORAGE_PAGE: - self.__config.set_use_local_storage(False) - self.__config.set_existing_storage(self.__existing_storage.getSelection()) - self.__config.set_storage_size(self.get_libvirt().get_storage_size(self.__existing_storage.getSelection())) - elif page == BRIDGE_PAGE: - self.__config.set_network_bridge(self.__network_bridges.getSelection()) - elif page == VIRT_DETAILS_PAGE: - self.__config.set_virt_type(self.__virt_types.getSelection()) - self.__config.set_architecture(self.__architectures.getSelection()) - elif page == CONFIRM_PAGE: - self.get_libvirt().define_domain(self.__config, DummyMeter()) - self.set_finished() - - def get_back_page(self, page): - result = page - if page == OS_TYPE_PAGE: - install_type = self.__config.get_install_type() - if install_type == DomainConfig.LOCAL_INSTALL: - if self.__config.get_use_cdrom_source(): - result = SELECT_CDROM_PAGE - else: - result = SELECT_ISO_PAGE - elif install_type == DomainConfig.NETWORK_INSTALL: - result = NETWORK_INSTALL_PAGE - elif install_type == DomainConfig.PXE_INSTALL: - result = VM_DETAILS_PAGE - elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: - result = ENABLE_STORAGE_PAGE - elif page == NETWORK_INSTALL_PAGE: - result = VM_DETAILS_PAGE - elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: - result = LOCAL_INSTALL_PAGE - elif page == BRIDGE_PAGE: - if self.__config.get_use_local_storage(): - result = LOCAL_STORAGE_PAGE - else: - result = MANAGED_STORAGE_PAGE - else: - if page > 1: result = page - 1 - return result - - def get_next_page(self, page): - result = page - if page == VM_DETAILS_PAGE: - install_type = self.__config.get_install_type() - if install_type == DomainConfig.LOCAL_INSTALL: - result = LOCAL_INSTALL_PAGE - elif install_type == DomainConfig.NETWORK_INSTALL: - result = NETWORK_INSTALL_PAGE - elif install_type == DomainConfig.PXE_INSTALL: - result = OS_TYPE_PAGE - elif page == LOCAL_INSTALL_PAGE: - if self.__config.get_use_cdrom_source(): - result = SELECT_CDROM_PAGE - else: - result = SELECT_ISO_PAGE - elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: - result = OS_TYPE_PAGE - elif page == NETWORK_INSTALL_PAGE: - result = OS_TYPE_PAGE - elif page == ENABLE_STORAGE_PAGE: - result = BRIDGE_PAGE - if self.__config.get_enable_storage(): - if self.__config.get_use_local_storage(): - result = LOCAL_STORAGE_PAGE - else: - result = MANAGED_STORAGE_PAGE - elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: - result = BRIDGE_PAGE - else: - result = page + 1 - return result - - def page_has_finish(self, page): - if page == CONFIRM_PAGE: return True - return False - - def page_has_next(self, page): - if page < CONFIRM_PAGE: - return True - - def get_vm_details_page(self, screen): - self.__guest_name = Entry(50, self.__config.get_guest_name()) - self.__install_type = RadioBar(screen, (("Local install media (ISO image or CDROM)", - DomainConfig.LOCAL_INSTALL, - self.__config.is_install_type(DomainConfig.LOCAL_INSTALL)), - ("Network Install (HTTP, FTP, or NFS)", - DomainConfig.NETWORK_INSTALL, - self.__config.is_install_type(DomainConfig.NETWORK_INSTALL)), - ("Network Boot (PXE)", - DomainConfig.PXE_INSTALL, - self.__config.is_install_type(DomainConfig.PXE_INSTALL)))) - grid = Grid(2,3) - grid.setField(Label("Name:"), 0, 0, anchorRight = 1) - grid.setField(self.__guest_name, 1, 0, anchorLeft = 1) - grid.setField(Label("Choose how you would like to install the operating system"), 1, 1, - anchorLeft = 1, anchorTop = 1) - grid.setField(self.__install_type, 1, 2, anchorLeft = 1) - return [Label("Enter your machine details"), - grid] - - def get_local_install_page(self, screen): - self.__install_source = RadioBar(screen, (("Use CDROM or DVD", - DomainConfig.INSTALL_SOURCE_CDROM, - self.__config.get_use_cdrom_source()), - ("Use ISO image", - DomainConfig.INSTALL_SOURCE_ISO, - self.__config.get_use_cdrom_source() is False))) - grid = Grid(1,1) - grid.setField(self.__install_source, 0, 0, anchorLeft = 1) - return [Label("Locate your install media"), - grid] - - def get_select_cdrom_page(self, screen): - drives = [] - media = self.get_hal().list_installable_volumes() - for drive in media.keys(): - drives.append([media[drive], drive, self.__config.is_install_media(drive)]) - self.__install_media = RadioBar(screen, (drives)) - grid = Grid(1, 1) - grid.setField(self.__install_media, 0, 0) - return [Label("Select the install media"), - grid] - - def get_select_iso_page(self, screen): - self.__iso_path = Entry(50, self.__config.get_iso_path()) - grid = Grid(1, 2) - grid.setField(Label("Enter ISO path:"), 0, 0, anchorLeft = 1) - grid.setField(self.__iso_path, 0, 1, anchorLeft = 1) - return [Label("Enter the full path to an install ISO"), - grid] - - def get_network_install_page(self, screen): - self.__install_url = Entry(50, self.__config.get_install_url()) - self.__kickstart_url = Entry(50, self.__config.get_kickstart_url()) - self.__kernel_options = Entry(50, self.__config.get_kernel_options()) - grid = Grid(2,3) - grid.setField(Label("URL:"), 0, 0, anchorRight = 1) - grid.setField(self.__install_url, 1, 0, anchorLeft = 1) - grid.setField(Label("Kickstart URL:"), 0, 1, anchorRight = 1) - grid.setField(self.__kickstart_url, 1, 1, anchorLeft = 1) - grid.setField(Label("Kernel Options:"), 0, 2, anchorRight = 1) - grid.setField(self.__kernel_options, 1, 2, anchorLeft = 1) - return [Label("Provide the operating system URL"), - grid] - - def get_os_type_page(self, screen): - types = [] - for type in Guest.list_os_types(): - types.append([Guest.get_os_type_label(type), type, self.__config.is_os_type(type)]) - self.__os_types = RadioBar(screen, types) - grid = Grid(1, 1) - grid.setField(self.__os_types, 0, 0, anchorLeft = 1) - return [Label("Choose the operating system type"), - grid] - - def get_os_variant_page(self, screen): - variants = [] - type = self.__config.get_os_type() - for variant in Guest.list_os_variants(type): - variants.append([Guest.get_os_variant_label(type, variant), variant, self.__config.is_os_variant(variant)]) - self.__os_variants = RadioBar(screen, variants) - grid = Grid(1, 1) - grid.setField(self.__os_variants, 0, 0, anchorLeft = 1) - return [Label("Choose the operating system version"), - grid] - - def get_ram_and_cpu_page(self, screen): - self.__memory = Entry(10, str(self.__config.get_memory())) - self.__cpus = Entry(10, str(self.__config.get_cpus())) - grid = Grid(2,2) - grid.setField(Label("Memory (RAM):"), 0, 0, anchorRight = 1) - grid.setField(self.__memory, 1, 0, anchorLeft = 1) - grid.setField(Label("CPUs:"), 0, 1, anchorRight = 1) - grid.setField(self.__cpus, 1, 1, anchorLeft = 1) - return [Label("Choose memory and CPU settings"), - grid] - - def get_enable_storage_page(self, screen): - self.__enable_storage = Checkbox("Enable storage for this virtual machine", self.__config.get_enable_storage()) - self.__storage_type = RadioBar(screen,((["Create a disk image on the computer's hard disk", - DomainConfig.NEW_STORAGE, - self.__config.get_use_local_storage()]), - (["Select managed or other existing storage", - DomainConfig.EXISTING_STORAGE, - self.__config.get_use_local_storage() is False]))) - grid = Grid(1,2) - grid.setField(self.__enable_storage, 0, 0, anchorLeft = 1) - grid.setField(self.__storage_type, 0, 1, anchorLeft = 1) - return [Label("Configure storage"), - grid] - - def get_local_storage_page(self, screen): - self.__storage_size = Entry(6, str(self.__config.get_storage_size())) - self.__allocate_storage = Checkbox("Allocate entire disk now", self.__config.get_allocate_storage()) - grid = Grid(2, 2) - grid.setField(self.__allocate_storage, 0, 0, growx = 1, anchorLeft = 1) - grid.setField(Label("Storage size (GB):"), 0, 1, anchorLeft = 1) - grid.setField(self.__storage_size, 1, 1) - return [Label("Configure local storage"), - grid] - - def get_managed_storage_page(self, screen): - volumes = [] - for volume in self.get_libvirt().list_storage_volumes(): - volumes.append(["%s (%d GB)" % (volume.name(), volume.info()[1] / (1024 ** 3)), - volume.name(), - self.__config.is_existing_storage(volume.name())]) - self.__existing_storage = RadioBar(screen, (volumes)) - grid = Grid(2, 1) - grid.setField(Label("Existing storage:"), 0, 0) - grid.setField(self.__existing_storage, 1, 0) - return [Label("Configure managed storage"), - grid] - - def get_bridge_page(self, screen): - bridges = [] - for bridge in self.get_libvirt().list_bridges(): - bridges.append(["Virtual network '%s'" % bridge.name(), bridge.name(), self.__config.get_network_bridge() == bridge.name()]) - self.__network_bridges = RadioBar(screen, (bridges)) - if self.__config.get_mac_address() == None: - self.__config.set_mac_address(self.get_libvirt().generate_mac_address()) - self.__mac_address = Entry(20, self.__config.get_mac_address()) - grid = Grid(1, 1) - grid.setField(self.__network_bridges, 0, 0) - return [Label("Select an existing bridge"), - grid] - - def get_virt_details_page(self, screen): - virt_types = [] - for type in self.get_libvirt().list_virt_types(): - virt_types.append([type, type, self.__config.is_virt_type(type)]) - self.__virt_types = RadioBar(screen, (virt_types)) - archs = [] - for arch in self.get_libvirt().list_architectures(): - archs.append([arch, arch, self.__config.is_architecture(arch)]) - self.__architectures = RadioBar(screen, (archs)) - grid = Grid(2, 2) - grid.setField(Label("Virt Type:"), 0, 0, anchorRight = 1, anchorTop = 1) - grid.setField(self.__virt_types, 1, 0, anchorLeft = 1) - grid.setField(Label("Architecture:"), 0, 1, anchorRight = 1, anchorTop = 1) - grid.setField(self.__architectures, 1, 1, anchorLeft = 1) - return [Label("Configure virtualization details"), - grid] - - def get_confirm_page(self, screen): - grid = Grid(2, 6) - grid.setField(Label("OS:"), 0, 0, anchorRight = 1) - grid.setField(Label(Guest.get_os_variant_label(self.__config.get_os_type(), - self.__config.get_os_variant())), 1, 0, anchorLeft = 1) - grid.setField(Label("Install:"), 0, 1, anchorRight = 1) - grid.setField(Label(self.__config.get_install_type_text()), 1, 1, anchorLeft = 1) - grid.setField(Label("Memory:"), 0, 2, anchorRight = 1) - grid.setField(Label("%s MB" % self.__config.get_memory()), 1, 2, anchorLeft = 1) - grid.setField(Label("CPUs:"), 0, 3, anchorRight = 1) - grid.setField(Label("%d" % self.__config.get_cpus()), 1, 3, anchorLeft = 1) - grid.setField(Label("Storage:"), 0, 4, anchorRight = 1) - grid.setField(Label(self.__config.get_existing_storage()), 1, 4, anchorLeft = 1) - grid.setField(Label("Network:"), 0, 5, anchorRight = 1) - grid.setField(Label(self.__config.get_network_bridge()), 1, 5, anchorLeft = 1) - return [Label("Ready to begin installation of %s" % self.__config.get_guest_name()), - grid] - -def DefineDomain(): - screen = DomainConfigScreen() - screen.start() diff --git a/nodeadmin/destroydomain.py b/nodeadmin/destroydomain.py deleted file mode 100755 index 350c32e..0000000 --- a/nodeadmin/destroydomain.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python -# -# destroydomain.py - Copyright (C) 2009 Red Hat, Inc. -# Written by Darryl L. Pierce -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. A copy of the GNU General Public License is -# also available at http://www.gnu.org/copyleft/gpl.html. - -from snack import * -from configscreen import * - -class DestroyDomainConfigScreen(DomainListConfigScreen): - LIST_PAGE = 1 - DESTROY_PAGE = 2 - - def __init__(self): - DomainListConfigScreen.__init__(self, "Destroy A Domain") - - def get_elements_for_page(self, screen, page): - if page is self.LIST_PAGE: - return self.get_domain_list_page(screen, defined = False) - elif page is self.DESTROY_PAGE: - return self.get_destroy_page(screen) - - def page_has_next(self, page): - if page is self.LIST_PAGE: return self.has_selectable_domains() - return False - - def page_has_back(self, page): - if page is self.DESTROY_PAGE: return True - return False - - def validate_input(self, page, errors): - if page is self.LIST_PAGE: - if self.get_selected_domain() is not None: - domain = self.get_selected_domain() - try: - self.get_libvirt().destroy_domain(domain) - return True - except Exception, error: - errors.append("There was an error destroy the domain: %s" % domain) - errors.append(str(error)) - else: - errors.append("You must first select a domain to destroy.") - return False - - def get_destroy_page(self, screen): - grid = Grid(1, 1) - grid.setField(Label("%s was successfully destroyed." % self.get_selected_domain()), 0, 0) - return [grid] - -def DestroyDomain(): - screen = DestroyDomainConfigScreen() - screen.start() diff --git a/nodeadmin/listdomains.py b/nodeadmin/listdomains.py index 1b51ee2..7468bcf 100755 --- a/nodeadmin/listdomains.py +++ b/nodeadmin/listdomains.py @@ -27,7 +27,7 @@ class ListDomainsConfigScreen(DomainListConfigScreen): DETAIL_PAGE = 2 def __init__(self): - DomainListConfigScreen.__init__(self, 'List Domains') + DomainListConfigScreen.__init__(self, 'List Virtual Machines') def page_has_next(self, page): return (page == self.LIST_PAGE) @@ -38,7 +38,7 @@ class ListDomainsConfigScreen(DomainListConfigScreen): def validate_input(self, page, errors): if page == self.LIST_PAGE: if self.get_selected_domain() is None: - errors.append("Please select a domain to view.") + errors.append("Please select a virtual machine to view.") else: return True diff --git a/nodeadmin/nodemenu.py b/nodeadmin/nodemenu.py index 9e339ff..0503b2e 100755 --- a/nodeadmin/nodemenu.py +++ b/nodeadmin/nodemenu.py @@ -21,42 +21,42 @@ import traceback from menuscreen import MenuScreen from configscreen import ConfigScreen -from definedomain import DefineDomain +from adddomain import AddDomain from createdomain import CreateDomain -from destroydomain import DestroyDomain -from undefinedomain import UndefineDomain +from stopdomain import StopDomain +from removedomain import RemoveDomain from listdomains import ListDomains from createuser import CreateUser import utils import logging -DEFINE_DOMAIN = 1 -CREATE_DOMAIN = 2 -DESTROY_DOMAIN = 3 -UNDEFINE_DOMAIN = 4 -LIST_DOMAINS = 5 -CREATE_USER = 6 +ADD_DOMAIN = 1 +CREATE_DOMAIN = 2 +STOP_DOMAIN = 3 +REMOVE_DOMAIN = 4 +LIST_DOMAINS = 5 +CREATE_USER = 6 class NodeMenuScreen(MenuScreen): def __init__(self): MenuScreen.__init__(self, "Node Administration") def get_menu_items(self): - return (("Define A Domain", DEFINE_DOMAIN), - ("Create A Domain", CREATE_DOMAIN), - ("Destroy A Domain", DESTROY_DOMAIN), - ("Undefine A Domain", UNDEFINE_DOMAIN), - ("List All Domains", LIST_DOMAINS), - ("Create A User", CREATE_USER)) + return (("Add A Virtual Machine", ADD_DOMAIN), + ("Create A Virtual Machine", CREATE_DOMAIN), + ("Stop A Virtual Machine", STOP_DOMAIN), + ("Remove A Virtual Machine", REMOVE_DOMAIN), + ("List All Virtual Machines", LIST_DOMAINS), + ("Create A User", CREATE_USER)) def handle_selection(self, item): - if item is DEFINE_DOMAIN: DefineDomain() - elif item is CREATE_DOMAIN: CreateDomain() - elif item is DESTROY_DOMAIN: DestroyDomain() - elif item is UNDEFINE_DOMAIN: UndefineDomain() - elif item is LIST_DOMAINS: ListDomains() - elif item is CREATE_USER: CreateUser() + if item is ADD_DOMAIN: AddDomain() + elif item is CREATE_DOMAIN: CreateDomain() + elif item is STOP_DOMAIN: StopDomain() + elif item is REMOVE_DOMAIN: RemoveDomain() + elif item is LIST_DOMAINS: ListDomains() + elif item is CREATE_USER: CreateUser() def NodeMenu(): screen = NodeMenuScreen() diff --git a/nodeadmin/removedomain.py b/nodeadmin/removedomain.py new file mode 100755 index 0000000..4e31428 --- /dev/null +++ b/nodeadmin/removedomain.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# +# removedomain.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +from configscreen import * + +class RemoveDomainConfigScreen(DomainListConfigScreen): + LIST_PAGE = 1 + CONFIRM_PAGE = 2 + REMOVE_PAGE = 3 + + def __init__(self): + DomainListConfigScreen.__init__(self, "Remove A Domain") + + def get_elements_for_page(self, screen, page): + if page is self.LIST_PAGE: return self.get_domain_list_page(screen) + elif page is self.CONFIRM_PAGE: return self.get_confirm_page(screen) + elif page is self.REMOVE_PAGE: return self.get_remove_page(screen) + + def page_has_next(self, page): + if page is self.LIST_PAGE: return self.has_selectable_domains() + elif page is self.CONFIRM_PAGE: return True + return False + + def page_has_back(self, page): + if page is self.CONFIRM_PAGE: return True + elif page is self.REMOVE_PAGE: return True + return False + + def get_back_page(self, page): + if page is self.CONFIRM_PAGE: return self.LIST_PAGE + elif page is self.REMOVE_PAGE: return self.LIST_PAGE + + def validate_input(self, page, errors): + if page is self.LIST_PAGE: + if self.get_selected_domain() is not None: + return True + else: + errors.append("You must first select a domain.") + elif page is self.CONFIRM_PAGE: + if self.__confirm_remove.value(): + domain = self.get_selected_domain() + try: + self.get_libvirt().remove_domain(domain) + return True + except Exception, error: + errors.append("Failed to remove %s." % domain) + errors.append(str(error)) + else: + errors.append("You must confirm undefining the domain to proceed.") + return False + + def get_confirm_page(self, screen): + self.__confirm_remove = Checkbox("Check here to confirm undefining %s." % self.get_selected_domain(), 0) + grid = Grid(1, 1) + grid.setField(self.__confirm_remove, 0, 0) + return [grid] + + def get_remove_page(self, screen): + grid = Grid(1, 1) + grid.setField(Label("%s has been removed." % self.get_selected_domain()), 0, 0) + return [grid] + +def RemoveDomain(): + screen = RemoveDomainConfigScreen() + screen.start() diff --git a/nodeadmin/setup.py.in b/nodeadmin/setup.py.in index 3635810..1e6e028 100644 --- a/nodeadmin/setup.py.in +++ b/nodeadmin/setup.py.in @@ -25,12 +25,12 @@ setup(name = "nodeadmin", entry_points = { 'console_scripts': [ 'nodeadmin = nodeadmin.nodeadmin:NodeAdmin', - 'definedom = nodeadmin.definedomain:DefineDomain', - 'createdom = nodeadmin.createdomain:CreateDomain', - 'destroydom = nodeadmin.destroydomain:DestroyDomain', - 'undefinedom = nodeadmin.undefinedomain:UndefineDomain', + 'addvm = nodeadmin.adddomain:AddDomain', + 'startvm = nodeadmin.startdomain:StartDomain', + 'stopvm = nodeadmin.stopdomain:StopDomain', + 'rmvm = nodeadmin.removedomain:RemoveDomain', 'createuser = nodeadmin.createuser:CreateUser', - 'listdoms = nodeadmin.listdomains:ListDomains', + 'listvms = nodeadmin.listdomains:ListDomains', 'definenet = nodeadmin.definenet:DefineNetwork', 'createnet = nodeadmin.createnetwork:CreateNetwork', 'destroynet = nodeadmin.destroynetwork:DestroyNetwork', diff --git a/nodeadmin/startdomain.py b/nodeadmin/startdomain.py new file mode 100755 index 0000000..082ac0d --- /dev/null +++ b/nodeadmin/startdomain.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# +# startdomain.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +from configscreen import * + +class StartDomainConfigScreen(DomainListConfigScreen): + LIST_PAGE = 1 + START_PAGE = 2 + + def __init__(self): + DomainListConfigScreen.__init__(self, "Start A Domain") + + def get_elements_for_page(self, screen, page): + if page is self.LIST_PAGE: + return self.get_domain_list_page(screen, started = False) + elif page is self.START_PAGE: + return self.get_start_domain_page(screen) + + def page_has_next(self, page): + if page is self.LIST_PAGE: return self.has_selectable_domains() + return False + + def page_has_back(self, page): + if page is self.START_PAGE: return True + return False + + def validate_input(self, page, errors): + if page is self.LIST_PAGE: + if self.get_selected_domain() is not None: + domain = self.get_selected_domain() + try: + self.get_libvirt().start_domain(domain) + return True + except Exception, error: + errors.append("There was an error creating the domain: %s" % domain) + errors.append(str(error)) + else: + errors.append("You must first select a domain to start.") + + def get_start_domain_page(self, screen): + grid = Grid(1, 1) + grid.setField(Label("%s was successfully started." % self.get_selected_domain()), 0, 0) + return [grid] + +def StartDomain(): + screen = StartDomainConfigScreen() + screen.start() diff --git a/nodeadmin/stopdomain.py b/nodeadmin/stopdomain.py new file mode 100755 index 0000000..3ddd681 --- /dev/null +++ b/nodeadmin/stopdomain.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# +# stopdomain.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +from configscreen import * + +class StopDomainConfigScreen(DomainListConfigScreen): + LIST_PAGE = 1 + STOP_PAGE = 2 + + def __init__(self): + DomainListConfigScreen.__init__(self, "Stop A Domain") + + def get_elements_for_page(self, screen, page): + if page is self.LIST_PAGE: + return self.get_domain_list_page(screen, defined = False) + elif page is self.STOP_PAGE: + return self.get_stop_page(screen) + + def page_has_next(self, page): + if page is self.LIST_PAGE: return self.has_selectable_domains() + return False + + def page_has_back(self, page): + if page is self.STOP_PAGE: return True + return False + + def validate_input(self, page, errors): + if page is self.LIST_PAGE: + if self.get_selected_domain() is not None: + domain = self.get_selected_domain() + try: + self.get_libvirt().stop_domain(domain) + return True + except Exception, error: + errors.append("There was an error stop the domain: %s" % domain) + errors.append(str(error)) + else: + errors.append("You must first select a domain to stop.") + return False + + def get_stop_page(self, screen): + grid = Grid(1, 1) + grid.setField(Label("%s was successfully stoped." % self.get_selected_domain()), 0, 0) + return [grid] + +def StopDomain(): + screen = StopDomainConfigScreen() + screen.start() diff --git a/nodeadmin/undefinedomain.py b/nodeadmin/undefinedomain.py deleted file mode 100755 index 2620540..0000000 --- a/nodeadmin/undefinedomain.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python -# -# undefinedomain.py - Copyright (C) 2009 Red Hat, Inc. -# Written by Darryl L. Pierce -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; version 2 of the License. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. A copy of the GNU General Public License is -# also available at http://www.gnu.org/copyleft/gpl.html. - -from snack import * -from configscreen import * - -class UndefineDomainConfigScreen(DomainListConfigScreen): - LIST_PAGE = 1 - CONFIRM_PAGE = 2 - UNDEFINE_PAGE = 3 - - def __init__(self): - DomainListConfigScreen.__init__(self, "Undefine A Domain") - - def get_elements_for_page(self, screen, page): - if page is self.LIST_PAGE: return self.get_domain_list_page(screen) - elif page is self.CONFIRM_PAGE: return self.get_confirm_page(screen) - elif page is self.UNDEFINE_PAGE: return self.get_undefine_page(screen) - - def page_has_next(self, page): - if page is self.LIST_PAGE: return self.has_selectable_domains() - elif page is self.CONFIRM_PAGE: return True - return False - - def page_has_back(self, page): - if page is self.CONFIRM_PAGE: return True - elif page is self.UNDEFINE_PAGE: return True - return False - - def get_back_page(self, page): - if page is self.CONFIRM_PAGE: return self.LIST_PAGE - elif page is self.UNDEFINE_PAGE: return self.LIST_PAGE - - def validate_input(self, page, errors): - if page is self.LIST_PAGE: - if self.get_selected_domain() is not None: - return True - else: - errors.append("You must first select a domain.") - elif page is self.CONFIRM_PAGE: - if self.__confirm_undefine.value(): - domain = self.get_selected_domain() - try: - self.get_libvirt().undefine_domain(domain) - return True - except Exception, error: - errors.append("Failed to undefine %s." % domain) - errors.append(str(error)) - else: - errors.append("You must confirm undefining the domain to proceed.") - return False - - def get_confirm_page(self, screen): - self.__confirm_undefine = Checkbox("Check here to confirm undefining %s." % self.get_selected_domain(), 0) - grid = Grid(1, 1) - grid.setField(self.__confirm_undefine, 0, 0) - return [grid] - - def get_undefine_page(self, screen): - grid = Grid(1, 1) - grid.setField(Label("%s has been undefined." % self.get_selected_domain()), 0, 0) - return [grid] - -def UndefineDomain(): - screen = UndefineDomainConfigScreen() - screen.start() diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index 2a6b7b6..49f41cb 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -183,10 +183,10 @@ cd - %{__install} -p -m0644 nodeadmin/mainmenu.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/nodemenu.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/definedomain.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/createdomain.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/destroydomain.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/undefinedomain.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/adddomain.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/startdomain.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/stopdomain.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/removedomain.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/listdomains.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/domainconfig.py %{buildroot}%{python_sitelib}/nodeadmin @@ -369,11 +369,11 @@ fi %{_initrddir}/ovirt-functions %defattr(-,root,root,0644) %{_bindir}/nodeadmin -%{_bindir}/definedom -%{_bindir}/createdom -%{_bindir}/destroydom -%{_bindir}/undefinedom -%{_bindir}/listdoms +%{_bindir}/addvm +%{_bindir}/startvm +%{_bindir}/stopvm +%{_bindir}/rmvm +%{_bindir}/listvms %{_bindir}/definenet %{_bindir}/createnet %{_bindir}/destroynet -- 1.6.2.5 From dpierce at redhat.com Wed Oct 21 19:50:46 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 21 Oct 2009 15:50:46 -0400 Subject: [Ovirt-devel] Storage administration and refactoring of domain admin Message-ID: <1256154648-9387-1-git-send-email-dpierce@redhat.com> This pair of patches provide a new storage admin interface. Then, on top of that, it refactors the domain administration pieces to now properly use storage pools and volumes when defining a new VM. From dpierce at redhat.com Wed Oct 21 19:50:47 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 21 Oct 2009 15:50:47 -0400 Subject: [Ovirt-devel] [PATCH node 1/2] Provides a new storage administration system to the managed node. In-Reply-To: <1256154648-9387-1-git-send-email-dpierce@redhat.com> References: <1256154648-9387-1-git-send-email-dpierce@redhat.com> Message-ID: <1256154648-9387-2-git-send-email-dpierce@redhat.com> Users can now: * Add a new storage pool. * Delete a storage pool. * Start and stop storage pools. * Add a new storage volume. * Delete a storage volume. * List existing storage pools, with details. Signed-off-by: Darryl L. Pierce --- Makefile.am | 31 +++++--- nodeadmin/addpool.py | 182 ++++++++++++++++++++++++++++++++++++++++++++ nodeadmin/addvolume.py | 160 ++++++++++++++++++++++++++++++++++++++ nodeadmin/configscreen.py | 52 +++++++++++++ nodeadmin/createmeter.py | 30 +++++++ nodeadmin/definedomain.py | 15 +--- nodeadmin/libvirtworker.py | 67 +++++++++++++++-- nodeadmin/listpools.py | 63 +++++++++++++++ nodeadmin/mainmenu.py | 24 ++++--- nodeadmin/poolconfig.py | 137 +++++++++++++++++++++++++++++++++ nodeadmin/removepool.py | 72 +++++++++++++++++ nodeadmin/removevolume.py | 76 ++++++++++++++++++ nodeadmin/setup.py.in | 9 ++- nodeadmin/startpool.py | 62 +++++++++++++++ nodeadmin/stoppool.py | 62 +++++++++++++++ nodeadmin/storagemenu.py | 63 +++++++++++++++ nodeadmin/utils.py | 10 +++ nodeadmin/volumeconfig.py | 76 ++++++++++++++++++ ovirt-node.spec.in | 49 ++++++++---- 19 files changed, 1184 insertions(+), 56 deletions(-) create mode 100644 nodeadmin/addpool.py create mode 100644 nodeadmin/addvolume.py create mode 100644 nodeadmin/createmeter.py create mode 100644 nodeadmin/listpools.py create mode 100644 nodeadmin/poolconfig.py create mode 100644 nodeadmin/removepool.py create mode 100644 nodeadmin/removevolume.py create mode 100644 nodeadmin/startpool.py create mode 100644 nodeadmin/stoppool.py create mode 100644 nodeadmin/storagemenu.py create mode 100644 nodeadmin/volumeconfig.py diff --git a/Makefile.am b/Makefile.am index abb7c33..14b8ccb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -27,30 +27,41 @@ EXTRA_DIST = \ images/grub-splash.xpm.gz \ images/syslinux-vesa-splash.jpg \ nodeadmin/__init__.py \ + nodeadmin/addpool.py \ + nodeadmin/addvolume.py \ nodeadmin/configscreen.py \ + nodeadmin/createdomain.py \ + nodeadmin/createmeter.py \ nodeadmin/createnetwork.py \ nodeadmin/createuser.py \ + nodeadmin/definedomain.py \ + nodeadmin/definenet.py \ nodeadmin/destroydomain.py \ nodeadmin/destroynetwork.py \ + nodeadmin/domainconfig.py \ nodeadmin/halworker.py \ nodeadmin/libvirtworker.py \ - nodeadmin/userworker.py \ + nodeadmin/listdomains.py \ + nodeadmin/listnetworks.py \ + nodeadmin/listpools.py \ nodeadmin/mainmenu.py \ nodeadmin/menuscreen.py \ nodeadmin/netmenu.py \ - nodeadmin/nodemenu.py \ - nodeadmin/undefinedomain.py \ - nodeadmin/undefinenetwork.py \ - nodeadmin/createdomain.py \ - nodeadmin/definedomain.py \ - nodeadmin/definenet.py \ - nodeadmin/domainconfig.py \ nodeadmin/networkconfig.py \ - nodeadmin/listdomains.py \ - nodeadmin/listnetworks.py \ nodeadmin/nodeadmin.py \ + nodeadmin/nodemenu.py \ + nodeadmin/poolconfig.py \ + nodeadmin/removepool.py \ + nodeadmin/removevolume.py \ nodeadmin/setup.py \ + nodeadmin/startpool.py \ + nodeadmin/stoppool.py \ + nodeadmin/storagemenu.py \ + nodeadmin/undefinedomain.py \ + nodeadmin/undefinenetwork.py \ + nodeadmin/userworker.py \ nodeadmin/utils.py \ + nodeadmin/volumeconfig.py \ scripts/collectd.conf.in \ scripts/ovirt \ scripts/ovirt-awake \ diff --git a/nodeadmin/addpool.py b/nodeadmin/addpool.py new file mode 100644 index 0000000..389be52 --- /dev/null +++ b/nodeadmin/addpool.py @@ -0,0 +1,182 @@ +# addstorage.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +import traceback +import utils + +from configscreen import * +from poolconfig import PoolConfig +from virtinst import Storage + +POOL_NAME_PAGE = 1 +POOL_DETAILS_PAGE = 2 +CONFIRM_PAGE = 3 + +class AddStoragePoolConfigScreen(ConfigScreen): + def __init__(self): + ConfigScreen.__init__(self, "Add A Storage Pool") + self.__config = PoolConfig(self.get_libvirt()) + + def get_elements_for_page(self, screen, page): + if page is POOL_NAME_PAGE: return self.get_pool_name_page(screen) + elif page is POOL_DETAILS_PAGE: return self.get_pool_details_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + return page < CONFIRM_PAGE + + def page_has_back(self, page): + return page > POOL_NAME_PAGE + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def validate_input(self, page, errors): + if page is POOL_NAME_PAGE: + if utils.string_is_not_blank(self.__name.value()): + if self.get_libvirt().storage_pool_exists(self.__name.value()): + errors.append("Name '%s' already in use by another pool." % self.__name.value()) + else: + return True + else: + errors.append("Storage object name must be a string between 0 and 50 characters.") + elif page is POOL_DETAILS_PAGE: + result = True + if self.__config.needs_target_path(): + if utils.string_is_not_blank(self.__target_path.value()): + if self.__target_path.value()[0:1] is not '/': + errors.append("'%s' is not an absolute path." % self.__target_path.value()) + result = False + else: + errors.append("You must enter a target path.") + result = False + if self.__config.needs_format(): + if self.__formats.getSelection() is None: + errors.append("You must select a pool format.") + result = False + if self.__config.needs_hostname(): + if utils.string_is_not_blank(self.__hostname.value()): + errors.append("You must enter a hostname.") + result = False + if self.__config.needs_source_path(): + if utils.string_is_not_blank(self.__source_path.value()): + if self.__source_path.value()[0:1] is not '/': + errors.append("'%s' is not an absolute path." % self.__source_path.value()) + result = False + else: + errors.append("you must enter a source path.") + result = False + return result + elif page is CONFIRM_PAGE: return True + return False + + def process_input(self, page): + if page is POOL_NAME_PAGE: + self.__config.set_name(self.__name.value()) + self.__config.set_type(self.__type.getSelection()) + #self._reset_flags(self.__type.current()) + elif page is POOL_DETAILS_PAGE: + if self.__config.needs_target_path(): + self.__config.set_target_path(self.__target_path.value()) + if self.__config.needs_format(): + self.__config.set_format(self.__formats.getSelection()) + if self.__config.needs_hostname(): + self.__config.set_hostname(self.__hostname.value()) + if self.__config.needs_source_path(): + self.__config.set_source_path(self.__source_path.value()) + if self.__config.needs_build_pool(): + self.__config.set_build_pool(self.__build_pool.value()) + elif page is CONFIRM_PAGE: + self.get_libvirt().define_storage_pool(self.__config.get_name(), config = self.__config) + self.get_libvirt().create_storage_pool(self.__config.get_name()) + self.set_finished() + + def get_pool_name_page(self, screen): + self.__name = Entry(50, self.__config.get_name()) + pooltypes = [] + for pooltype in Storage.StoragePool.get_pool_types(): + pooltypes.append(["%s: %s" % (pooltype, Storage.StoragePool.get_pool_type_desc(pooltype)), + pooltype, + self.__config.get_type() is pooltype]) + self.__type = RadioBar(screen, pooltypes) + grid = Grid(2, 2) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(self.__name, 1, 0, anchorLeft = 1) + grid.setField(Label("Type:"), 0, 1, anchorRight = 1, anchorTop = 1) + grid.setField(self.__type, 1, 1, anchorLeft = 1) + return [Label("Add Storage Pool"), + grid] + + def get_pool_details_page(self, screen): + rows = 0 + if self.__config.needs_target_path(): + self.__target_path = Entry(50, self.__config.get_target_path()) + rows += 1 + if self.__config.needs_format(): + formats = [] + for format in self.__config.get_formats(): + formats.append([format, format, format is self.__config.get_format()]) + self.__formats = RadioBar(screen, formats) + rows += 1 + if self.__config.needs_hostname(): + self.__hostname = Entry(50, self.__config.get_hostname()) + rows += 1 + if self.__config.needs_source_path(): + self.__source_path = Entry(50, self.__config.get_source_path()) + rows += 1 + if self.__config.needs_build_pool(): + self.__build_pool = Checkbox("Build Pool", self.__config.get_build_pool()) + rows += 1 + grid = Grid(2, rows) + currentrow = 0 + if self.__config.needs_target_path(): + grid.setField(Label("Target Path:"), 0, currentrow, anchorRight = 1) + grid.setField(self.__target_path, 1, currentrow, anchorLeft = 1) + currentrow += 1 + if self.__config.needs_format(): + grid.setField(Label("Format:"), 0, currentrow, anchorRight = 1, anchorTop = 1) + grid.setField(self.__formats, 1, currentrow, anchorLeft = 1) + currentrow += 1 + if self.__config.needs_hostname(): + grid.setField(Label("Host Name:"), 0, currentrow, anchorRight = 1) + grid.setField(self.__hostname, 1, currentrow, anchorRight = 1) + currentrow += 1 + if self.__config.needs_source_path(): + grid.setField(Label("Source Path:"), 0, currentrow, anchorRight = 1) + grid.setField(self.__source_path, 1, currentrow, anchorLeft = 1) + currentrow += 1 + if self.__config.needs_build_pool(): + grid.setField(Label(" "), 0, currentrow, anchorRight = 1) + grid.setField(self.__build_pool, 1, currentrow, anchorLeft = 1) + currentrow += 1 + return [Label("Specify a storage location to be later split into virtual machine storage"), + grid] + + def get_confirm_page(self, screen): + grid = Grid(2, 2) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(Label(self.__config.get_name()), 1, 0, anchorLeft = 1) + grid.setField(Label("Target Path:"), 0, 1, anchorRight = 1) + grid.setField(Label(self.__config.get_target_path()), 1, 1, anchorLeft = 1) + return [Label("Confirm Pool Details"), + grid] + +def AddStoragePool(): + screen = AddStoragePoolConfigScreen() + screen.start() diff --git a/nodeadmin/addvolume.py b/nodeadmin/addvolume.py new file mode 100644 index 0000000..82c014c --- /dev/null +++ b/nodeadmin/addvolume.py @@ -0,0 +1,160 @@ +# addvolume.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +import traceback + +from createmeter import CreateMeter +from configscreen import * +from volumeconfig import StorageVolumeConfig +from utils import * + +SELECT_POOL_PAGE = 1 +VOLUME_NAME_PAGE = 2 +VOLUME_FORMAT_PAGE = 3 +MAX_CAPACITY_PAGE = 4 +CONFIRM_PAGE = 5 + +class AddVolumeConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Add A New Storage Volume") + self.__config = StorageVolumeConfig() + + def get_elements_for_page(self, screen, page): + if page is SELECT_POOL_PAGE: return self.get_storage_pool_list_page(screen) + elif page is VOLUME_NAME_PAGE: return self.get_volume_name_page(screen) + elif page is VOLUME_FORMAT_PAGE: return self.get_volume_format_page(screen) + elif page is MAX_CAPACITY_PAGE: return self.get_max_capacity_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + if page is SELECT_POOL_PAGE: + return self.has_selectable_pools() + else: + if page < CONFIRM_PAGE: return True + return False + + def page_has_back(self, page): + if page > SELECT_POOL_PAGE: return True + return False + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def validate_input(self, page, errors): + if page is SELECT_POOL_PAGE: + if self.get_selected_pool() is not None: + return True + else: + errors.append("You must select a storage pool.") + elif page is VOLUME_NAME_PAGE: + if string_is_not_blank(self.__name.value()): + return True + else: + errors.append("Storage object name can only contain alphanumeric, '_', '.', or '-' characters.") + elif page is VOLUME_FORMAT_PAGE: + if self.__formats.current() is not None: + return True + else: + errors.append("You must select a volume format.") + elif page is MAX_CAPACITY_PAGE: + if string_is_not_blank(self.__capacity.value()): + if string_is_not_blank(self.__allocation.value()): + capacity = int(self.__capacity.value()) + allocation = int(self.__allocation.value()) + if capacity > 0: + if capacity <= self.__config.get_pool().info()[3] / 1024**2: + if allocation >= 0: + if allocation <= capacity: + return True + else: + errors.append("Allocation cannot exceed the maximum capacity.") + else: + errors.append("The allocation must be greater than or equal to 0.") + else: + errors.append("The maximum capacity cannot exceed the storage pool size.") + else: + errors.append("The capacity must be greater than zero.") + else: + errors.append("An allocation value must be entered.") + else: + errors.append("A maximum volume capacity must be entered.") + elif page is CONFIRM_PAGE: return True + return False + + def process_input(self, page): + if page is SELECT_POOL_PAGE: + self.__config.set_pool(self.get_libvirt().get_storage_pool(self.get_selected_pool())) + elif page is VOLUME_NAME_PAGE: + self.__config.set_name(self.__name.value()) + elif page is VOLUME_FORMAT_PAGE: + self.__config.set_format(self.__formats.current()) + elif page is MAX_CAPACITY_PAGE: + self.__config.set_max_capacity(int(self.__capacity.value())) + self.__config.set_allocation(int(self.__allocation.value())) + elif page is CONFIRM_PAGE: + self.get_libvirt().define_storage_volume(self.__config, CreateMeter()) + self.set_finished() + + def get_volume_name_page(self, screen): + self.__name = Entry(50, self.__config.get_name()) + grid = Grid(2, 1) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(self.__name, 1, 0, anchorLeft = 1) + return [Label("New Storage Volume"), + grid, + Label("Name of the volume to create. File extension may be appended.")] + + def get_volume_format_page(self, screen): + self.__formats = Listbox(0) + for format in self.__config.get_formats_for_pool(): + self.__formats.append(format, format) + grid = Grid(1, 1) + grid.setField(self.__formats, 0, 0) + return [Label("Select The Volume Format"), + grid] + + def get_max_capacity_page(self, screen): + self.__capacity = Entry(6, str(self.__config.get_max_capacity())) + self.__allocation = Entry(6, str(self.__config.get_allocation())) + grid = Grid(2, 2) + grid.setField(Label("Max. Capacity (MB):"), 0, 0, anchorRight = 1) + grid.setField(self.__capacity, 1, 0, anchorLeft = 1) + grid.setField(Label("Allocation (MB):"), 0, 1, anchorRight = 1) + grid.setField(self.__allocation, 1, 1, anchorLeft = 1) + return [Label("Storage Volume Quota"), + Label("%s's available space: %0.2f GB" % (self.__config.get_pool().name(), + self.__config.get_pool().info()[3] / 1024.0**3)), + grid] + + def get_confirm_page(self, screen): + grid = Grid(2, 5) + grid.setField(Label("Volume Name:"), 0, 0, anchorRight = 1) + grid.setField(Label("%s (%s)" % (self.__config.get_name(), self.__config.get_pool().name())), 1, 0, anchorLeft = 1) + grid.setField(Label("Format:"), 0, 1, anchorRight = 1) + grid.setField(Label(self.__config.get_format()), 1, 1, anchorLeft = 1) + grid.setField(Label("Max. Capacity:"), 0, 2, anchorRight = 1) + grid.setField(Label("%0.2f GB" % (self.__config.get_max_capacity() / 1024.0)), 1, 2, anchorLeft = 1) + grid.setField(Label("Allocation:"), 0, 3, anchorRight = 1) + grid.setField(Label("%0.2f GB" % (self.__config.get_allocation() / 1024.0)), 1, 3, anchorLeft = 1) + return [Label("Ready To Allocation New Storage Volume"), + grid] + +def AddStorageVolume(): + screen = AddVolumeConfigScreen() + screen.start() diff --git a/nodeadmin/configscreen.py b/nodeadmin/configscreen.py index f214aea..7654697 100644 --- a/nodeadmin/configscreen.py +++ b/nodeadmin/configscreen.py @@ -179,3 +179,55 @@ class NetworkListConfigScreen(ConfigScreen): def has_selectable_networks(self): return self.__has_networks + +class StorageListConfigScreen(ConfigScreen): + '''Provides a base class for any configuration screen that deals with storage pool lists.''' + + def __init__(self, title): + ConfigScreen.__init__(self, title) + + def get_storage_pool_list_page(self, screen, defined=True, created=True): + pools = self.get_libvirt().list_storage_pools(defined=defined, created=created) + if len(pools) > 0: + self.__has_pools = True + self.__pools_list = Listbox(0) + for pool in pools: + self.__pools_list.append(pool, pool) + result = self.__pools_list + else: + self.__has_pools = False + result = Label("There are no storage pools available.") + grid = Grid(1, 1) + grid.setField(result, 0, 0) + return [Label("Storage Pool List"), + grid] + + def get_selected_pool(self): + return self.__pools_list.current() + + def has_selectable_pools(self): + return self.__has_pools + + def get_storage_volume_list_page(self, screen): + '''Requires that self.__pools_list have a selected element.''' + pool = self.get_libvirt().get_storage_pool(self.get_selected_pool()) + if len(pool.listVolumes()) > 0: + self.__has_volumes = True + self.__volumes_list = Listbox(0) + for volname in pool.listVolumes(): + volume = pool.storageVolLookupByName(volname) + self.__volumes_list.append("%s (%0.2f GB)" % (volume.name(), volume.info()[2] / 1024**3), volume.name()) + result = self.__volumes_list + else: + self.__has_volumes = False + result = Label("There are no storage volumes available.") + grid = Grid(1, 1) + grid.setField(result, 0, 0) + return [Label("Storage Volume List"), + grid] + + def get_selected_volume(self): + return self.__volumes_list.current() + + def has_selectable_volumes(self): + return self.__has_volumes diff --git a/nodeadmin/createmeter.py b/nodeadmin/createmeter.py new file mode 100644 index 0000000..521e7d8 --- /dev/null +++ b/nodeadmin/createmeter.py @@ -0,0 +1,30 @@ +# createmeter.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +import urlgrabber.progress as progress +import logging + +class CreateMeter(progress.BaseMeter): + def _do_start(self, now = None): + logging.info("Starting...") + + def _do_end(self, amount_read, now = None): + logging.info("Ending: read=%d" % amount_read) + + def _do_update(self, amount_read, now = None): + logging.info("Update: read=%d" % amount_read) diff --git a/nodeadmin/definedomain.py b/nodeadmin/definedomain.py index 3fffca2..791c278 100755 --- a/nodeadmin/definedomain.py +++ b/nodeadmin/definedomain.py @@ -20,11 +20,10 @@ from snack import * import os +from createmeter import CreateMeter from domainconfig import DomainConfig from configscreen import ConfigScreen -import urlgrabber.progress as progress import utils -import logging from virtinst import * @@ -51,16 +50,6 @@ OS_VARIANT="os.variant" MEMORY="memory" CPUS="cpus" -class DummyMeter(progress.BaseMeter): - def _do_start(self, now = None): - logging.info("Starting...") - - def _do_end(self, amount_read, now = None): - logging.info("Ending: read=%d" % amount_read) - - def _do_update(self, amount_read, now = None): - logging.info("Update: read=%d" % amount_read) - class DomainConfigScreen(ConfigScreen): def __init__(self): ConfigScreen.__init__(self, "Create A New Virtual Machine") @@ -212,7 +201,7 @@ class DomainConfigScreen(ConfigScreen): self.__config.set_virt_type(self.__virt_types.getSelection()) self.__config.set_architecture(self.__architectures.getSelection()) elif page == CONFIRM_PAGE: - self.get_libvirt().define_domain(self.__config, DummyMeter()) + self.get_libvirt().define_domain(self.__config, CreateMeter()) self.set_finished() def get_back_page(self, page): diff --git a/nodeadmin/libvirtworker.py b/nodeadmin/libvirtworker.py index ba07605..b2acabe 100644 --- a/nodeadmin/libvirtworker.py +++ b/nodeadmin/libvirtworker.py @@ -35,6 +35,10 @@ class LibvirtWorker: self.__net.setup(self.__conn) (self.__new_guest, self.__new_domain) = virtinst.CapabilitiesParser.guest_lookup(conn = self.__conn) + def get_connection(self): + '''Returns the underlying connection.''' + return self.__conn + def list_domains(self, defined = True, started = True): '''Lists all domains.''' result = [] @@ -134,9 +138,12 @@ class LibvirtWorker: network = self.get_network(name) network.undefine() - def list_storage_pools(self): + def list_storage_pools(self, defined=True, created=True): '''Returns the list of all defined storage pools.''' - return self.__conn.listStoragePools() + pools = [] + if defined: pools.extend(self.__conn.listDefinedStoragePools()) + if created: pools.extend(self.__conn.listStoragePools()) + return pools def storage_pool_exists(self, name): '''Returns whether a storage pool exists.''' @@ -144,16 +151,62 @@ class LibvirtWorker: if name in pools: return True return False - def define_storage_pool(self, name): + def create_storage_pool(self, name): + '''Starts the named storage pool if it is not currently started.''' + if name not in self.list_storage_pools(defined = False): + pool = self.get_storage_pool(name) + pool.create(0) + + def destroy_storage_pool(self, name): + '''Stops the specified storage pool.''' + if name in self.list_storage_pools(defined = False): + pool = self.get_storage_pool(name) + pool.destroy() + + def define_storage_pool(self, name, config = None, meter = None): '''Defines a storage pool with the given name.''' - try: + if config is None: pool = virtinst.Storage.DirectoryPool(conn=self.__conn, name=name, target_path=DEFAULT_POOL_TARGET_PATH) - newpool = pool.install(build=True, create=True) + newpool = pool.install(build=True, create=True, meter=meter) newpool.setAutostart(True) - except Exception, error: - raise RuntimeError("Could not create pool: %s - %s", str(error)) + else: + pool = config.get_pool() + pool.target_path = config.get_target_path() + if config.needs_hostname(): + pool.host = config.get_hostname() + if config.needs_source_path(): + pool.source_path = config.get_source_path() + if config.needs_format(): + pool.format = config.get_format() + pool.conn = self.__conn + pool.get_xml_config() + newpool = pool.install(meter=meter, + build=True, # config.get_build_pool(), + create=True) + newpool.setAutostart(True) + + def undefine_storage_pool(self, name): + '''Undefines the specified storage pool.''' + pool = self.get_storage_pool(name) + pool.undefine() + + def get_storage_pool(self, name): + '''Returns the storage pool with the specified name.''' + return self.__conn.storagePoolLookupByName(name) + + def define_storage_volume(self, config, meter): + '''Defines a new storage volume.''' + self.create_storage_pool(config.get_pool().name()) + volume = config.create_volume() + volume.install(meter = meter) + + def remove_storage_volume(self, poolname, volumename): + '''Removes the specified storage volume.''' + pool = self.get_storage_pool(poolname) + volume = pool.storageVolLookupByName(volumename) + volume.delete(0) def list_bridges(self): '''Lists all defined and active bridges.''' diff --git a/nodeadmin/listpools.py b/nodeadmin/listpools.py new file mode 100644 index 0000000..686c42d --- /dev/null +++ b/nodeadmin/listpools.py @@ -0,0 +1,63 @@ +# listpools.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * + +from configscreen import * + +LIST_PAGE = 1 +DETAILS_PAGE = 2 + +class ListStoragePoolsConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "List Storage Pools") + + def get_elements_for_page(self, screen, page): + if page is LIST_PAGE: return self.get_storage_pool_list_page(screen) + elif page is DETAILS_PAGE: return self.get_pool_details_page(screen) + + def page_has_next(self, page): + if page is LIST_PAGE and self.has_selectable_pools(): + return True + return False + + def page_has_back(self, page): + if page is DETAILS_PAGE: return True + return False + + def get_pool_details_page(self, screen): + pool = self.get_libvirt().get_storage_pool(self.get_selected_pool()) + volumes = Listbox(0); + for name in pool.listVolumes(): + volume = pool.storageVolLookupByName(name) + volumes.append("%s (%0.1f G)" % (name, volume.info()[1] / 1024**3), name) + grid = Grid(2, 3) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(Label(pool.name()), 1, 0, anchorLeft = 1) + grid.setField(Label("Volumes:"), 0, 1, anchorRight = 1) + grid.setField(volumes, 1, 1, anchorLeft = 1) + grid.setField(Label("Autostart:"), 0, 2, anchorRight = 1) + label = "No" + if pool.autostart(): label = "Yes" + grid.setField(Label(label), 1, 2, anchorLeft = 1) + return [Label("Details For Storage Pool: %s" % self.get_selected_pool()), + grid] + +def ListStoragePools(): + screen = ListStoragePoolsConfigScreen() + screen.start() diff --git a/nodeadmin/mainmenu.py b/nodeadmin/mainmenu.py index 73501fa..52d9298 100755 --- a/nodeadmin/mainmenu.py +++ b/nodeadmin/mainmenu.py @@ -19,28 +19,32 @@ from snack import * import traceback -from menuscreen import MenuScreen -from nodemenu import NodeMenu -from netmenu import NetworkMenu +from menuscreen import MenuScreen +from nodemenu import NodeMenu +from netmenu import NetworkMenu +from storagemenu import StoragePoolMenu import utils import logging NODE_MENU = 1 NETWORK_MENU = 2 -EXIT_CONSOLE = 99 +STORAGE_MENU = 3 +EXIT_CONSOLE = 4 class MainMenuScreen(MenuScreen): def __init__(self): MenuScreen.__init__(self, "Main Menu") def get_menu_items(self): - return (("Node Administration", NODE_MENU), - ("Network Administration", NETWORK_MENU)) - - def handle_selection(self, page): - if page is NODE_MENU: NodeMenu() - elif page is NETWORK_MENU: NetworkMenu() + return (("Node Administration", NODE_MENU), + ("Network Administration", NETWORK_MENU), + ("Storage Pool Administration", STORAGE_MENU)) + + def handle_selection(self, item): + if item is NODE_MENU: NodeMenu() + elif item is NETWORK_MENU: NetworkMenu() + elif item is STORAGE_MENU: StoragePoolMenu() def MainMenu(): screen = MainMenuScreen() diff --git a/nodeadmin/poolconfig.py b/nodeadmin/poolconfig.py new file mode 100644 index 0000000..06af722 --- /dev/null +++ b/nodeadmin/poolconfig.py @@ -0,0 +1,137 @@ +# poolconfig.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from virtinst import Storage + +ROOT_TARGET_PATH="/var/lib/libvirt/images/%s" + +class PoolConfig: + def __init__(self, libvirt): + self.__libvirt = libvirt + self.__name = "" + self.set_type(None) + self.__format = None + self.__hostname = "" + self.__target_path = "" + self.__source_path = "" + self.__build_pool = False + + def get_pool(self): + return self.__pool + + def set_name(self, name): + self.__name = name + + def get_name(self): + return self.__name + + def set_type(self, pooltype): + self.__type = pooltype + self.__needs_target_path = False + self.__needs_format = False + self.__needs_hostname = False + self.__needs_source_path = False + self.__needs_build_pool = False + if pooltype is not None: + if pooltype is Storage.StoragePool.TYPE_DIR: + self.__needs_target_path = True + self.__target_path = ROOT_TARGET_PATH % self.__name + self.__build_pool = True + elif pooltype is Storage.StoragePool.TYPE_DISK: + self.__needs_target_path = True + self.__needs_format = True + self.__needs_source_path = True + self.__needs_build_pool = True + elif pooltype is Storage.StoragePool.TYPE_FS: + self.__needs_target_path = True + self.__needs_format = True + self.__needs_source_path = True + self.__build_pool = True + elif pooltype is Storage.StoragePool.TYPE_ISCSI: + self.__needs_target_path = True + self.__needs_hostname = True + self.__needs_source_path = True + self.__build_pool = False + elif pooltype is Storage.StoragePool.TYPE_LOGICAL: + self.__needs_target_path = True + self.__needs_source_path = True + self.__needs_build_pool = True + elif pooltype is Storage.StoragePool.TYPE_NETFS: + self.__needs_target_path = True + self.__needs_format = True + self.__needs_hostname = True + self.__needs_source_path = True + self.__build_pool = True + # create pool + pool_class = Storage.StoragePool.get_pool_class(self.__type) + self.__pool = pool_class(name = self.__name, + conn = self.__libvirt.get_connection()) + if self.__needs_format: + self.__format = self.__pool.formats[0] + else: + self.__type = Storage.StoragePool.get_pool_types()[0] + + def get_type(self): + return self.__type + + def needs_target_path(self): + return self.__needs_target_path + + def needs_format(self): + return self.__needs_format + + def needs_hostname(self): + return self.__needs_hostname + + def needs_source_path(self): + return self.__needs_source_path + + def needs_build_pool(self): + return self.__needs_build_pool + def set_target_path(self, path): + self.__target_path = path + + def get_target_path(self): + return self.__target_path + + def get_formats(self): + return self.__pool.formats + + def set_format(self, format): + self.__format = format + + def get_format(self): + return self.__format + + def set_hostname(self, hostname): + self.__hostname = hostname + + def get_hostname(self): + return self.__hostname + + def set_source_path(self, source_path): + self.__source_path = source_path + + def get_source_path(self): + return self.__source_path + + def set_build_pool(self, build_pool): + self.__build_pool = build_pool + + def get_build_pool(self): + return self.__build_pool diff --git a/nodeadmin/removepool.py b/nodeadmin/removepool.py new file mode 100644 index 0000000..7a7f46d --- /dev/null +++ b/nodeadmin/removepool.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# +# removepool.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +from configscreen import * + +LIST_POOLS_PAGE = 1 +CONFIRM_PAGE = 2 + +class RemoveStoragePoolConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Remove A Storage Pool") + + def get_elements_for_page(self, screen, page): + if page is LIST_POOLS_PAGE: return self.get_storage_pool_list_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + return page is LIST_POOLS_PAGE and self.has_selectable_pools() + + def page_has_back(self, page): + return False + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def validate_input(self, page, errors): + if page is LIST_POOLS_PAGE: + if self.get_selected_pool() is not None: + return True + else: + errors.append("Please select a storage pool to be removed.") + elif page is CONFIRM_PAGE: + if self.__confirm.value(): + return True + else: + errors.append("You must confirm removing a storage pool.") + return False + + def process_input(self, page): + if page is CONFIRM_PAGE: + self.get_libvirt().destroy_storage_pool(self.get_selected_pool()) + self.get_libvirt().undefine_storage_pool(self.get_selected_pool()) + self.set_finished() + + def get_confirm_page(self, screen): + self.__confirm = Checkbox("Check here to confirm deleting pool: %s" % self.get_selected_pool()) + grid = Grid(1, 1) + grid.setField(self.__confirm, 0, 0) + return [Label("Remove Selected Storage Pool"), + grid] + +def RemoveStoragePool(): + screen = RemoveStoragePoolConfigScreen() + screen.start() diff --git a/nodeadmin/removevolume.py b/nodeadmin/removevolume.py new file mode 100644 index 0000000..5ad3058 --- /dev/null +++ b/nodeadmin/removevolume.py @@ -0,0 +1,76 @@ +# removevolume.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +import traceback + +from createmeter import CreateMeter +from configscreen import * +from volumeconfig import StorageVolumeConfig +from utils import * + +SELECT_POOL_PAGE = 1 +SELECT_VOLUME_PAGE = 2 +CONFIRM_PAGE = 3 + +class RemoveVolumeConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Add A New Storage Volume") + self.__config = StorageVolumeConfig() + + def get_elements_for_page(self, screen, page): + if page is SELECT_POOL_PAGE: return self.get_storage_pool_list_page(screen) + elif page is SELECT_VOLUME_PAGE: return self.get_storage_volume_list_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + if page is SELECT_POOL_PAGE: return self.has_selectable_pools() + elif page is SELECT_VOLUME_PAGE: return self.has_selectable_volumes() + return False + + def validate_input(self, page, errors): + if page is SELECT_POOL_PAGE: return self.get_selected_pool() is not None + elif page is SELECT_VOLUME_PAGE: return self.get_selected_volume() is not None + elif page is CONFIRM_PAGE: + if self.__confirm.value(): + return True + else: + errors.append("You must confirm deleting a storage volume.") + return False + + def process_input(self, page): + if page is CONFIRM_PAGE: + self.get_libvirt().remove_storage_volume(self.get_selected_pool(), self.get_selected_volume()) + self.set_finished() + + def page_has_back(self, page): + return page > SELECT_POOL_PAGE + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def get_confirm_page(self, screen): + self.__confirm = Checkbox("Check here to confirm deleting volume: %s" % self.get_selected_volume()) + grid = Grid(1, 1) + grid.setField(self.__confirm, 0, 0) + return [Label("Remove Selected Storage Volume"), + grid] + +def RemoveStorageVolume(): + screen = RemoveVolumeConfigScreen() + screen.start() diff --git a/nodeadmin/setup.py.in b/nodeadmin/setup.py.in index 3635810..8a7fc7c 100644 --- a/nodeadmin/setup.py.in +++ b/nodeadmin/setup.py.in @@ -35,5 +35,12 @@ setup(name = "nodeadmin", 'createnet = nodeadmin.createnetwork:CreateNetwork', 'destroynet = nodeadmin.destroynetwork:DestroyNetwork', 'undefinenet = nodeadmin.undefinenetwork:UndefineNetwork', - 'listnets = nodeadmin.listnetworks:ListNetworks'] + 'listnets = nodeadmin.listnetworks:ListNetworks', + 'addpool = nodeadmin.addpool:AddStoragePool', + 'rmpool = nodeadmin.removepool:RemoveStoragePool', + 'startpool = nodeadmin.startpool:StartStoragePool', + 'stoppool = nodeadmin.stoppool:StopStoragePool', + 'addvolume = nodeadmin.addvolume:AddStorageVolume', + 'rmvolume = nodeadmin.removevolume:RemoveStorageVolume', + 'listpools = nodeadmin.listpools:ListPools'] }) diff --git a/nodeadmin/startpool.py b/nodeadmin/startpool.py new file mode 100644 index 0000000..8a84512 --- /dev/null +++ b/nodeadmin/startpool.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# +# startpool.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +from configscreen import * + +LIST_POOLS_PAGE = 1 +FINAL_PAGE = 2 + +class StartStoragePoolConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Start A Storage Pool") + + def get_elements_for_page(self, screen, page): + if page is LIST_POOLS_PAGE: return self.get_storage_pool_list_page(screen, created = False) + elif page is FINAL_PAGE: return self.get_final_page(screen) + + def page_has_next(self, page): + return page is LIST_POOLS_PAGE and self.has_selectable_pools() + + def page_has_back(self, page): + return False + + def page_has_finish(self, page): + return page is FINAL_PAGE + + def validate_input(self, page, errors): + if page is LIST_POOLS_PAGE: + if self.get_selected_pool() is not None: + return True + else: + errors.append("Please select a storage pool to be started.") + return False + + def process_input(self, page): + if page is LIST_POOLS_PAGE: + self.get_libvirt().create_storage_pool(self.get_selected_pool()) + self.set_finished() + + def get_final_page(self, screen): + return [Label("Storage pool started: %s" % self.get_selected_pool())] + +def StartStoragePool(): + screen = StartStoragePoolConfigScreen() + screen.start() diff --git a/nodeadmin/stoppool.py b/nodeadmin/stoppool.py new file mode 100644 index 0000000..0522b95 --- /dev/null +++ b/nodeadmin/stoppool.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# +# stoppool.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +from configscreen import * + +LIST_POOLS_PAGE = 1 +FINAL_PAGE = 2 + +class StopStoragePoolConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Stop A Storage Pool") + + def get_elements_for_page(self, screen, page): + if page is LIST_POOLS_PAGE: return self.get_storage_pool_list_page(screen, defined = False) + elif page is FINAL_PAGE: return self.get_final_page(screen) + + def page_has_next(self, page): + return page is LIST_POOLS_PAGE and self.has_selectable_pools() + + def page_has_back(self, page): + return False + + def page_has_finish(self, page): + return page is FINAL_PAGE + + def validate_input(self, page, errors): + if page is LIST_POOLS_PAGE: + if self.get_selected_pool() is not None: + return True + else: + errors.append("Please select a storage pool to be stopped.") + return False + + def process_input(self, page): + if page is LIST_POOLS_PAGE: + self.get_libvirt().destroy_storage_pool(self.get_selected_pool()) + self.set_finished() + + def get_final_page(self, screen): + return [Label("Storage pool stopped: %s" % self.get_selected_pool())] + +def StopStoragePool(): + screen = StopStoragePoolConfigScreen() + screen.start() diff --git a/nodeadmin/storagemenu.py b/nodeadmin/storagemenu.py new file mode 100644 index 0000000..0b56dae --- /dev/null +++ b/nodeadmin/storagemenu.py @@ -0,0 +1,63 @@ +# storagemenu.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +import traceback + +from menuscreen import MenuScreen +from addpool import AddStoragePool +from startpool import StartStoragePool +from stoppool import StopStoragePool +from removepool import RemoveStoragePool +from addvolume import AddStorageVolume +from removevolume import RemoveStorageVolume +from listpools import ListStoragePools + +ADD_POOL = 1 +START_POOL = 2 +STOP_POOL = 3 +REMOVE_POOL = 4 +ADD_VOLUME = 5 +REMOVE_VOLUME = 6 +LIST_POOLS = 7 + +class StoragePoolMenuScreen(MenuScreen): + def __init__(self): + MenuScreen.__init__(self, "Storage Pool Administration") + + def get_menu_items(self): + return (("Add A Storage Pool", ADD_POOL), + ("Start A Storage Pool", START_POOL), + ("Stop A Storage Pool", STOP_POOL), + ("Remove A Storage Pool", REMOVE_POOL), + ("Add A Storage Volume", ADD_VOLUME), + ("Remove A Storage Volume", REMOVE_VOLUME), + ("List Storage Pools", LIST_POOLS)) + + def handle_selection(self, item): + if item is ADD_POOL: AddStoragePool() + elif item is START_POOL: StartStoragePool() + elif item is STOP_POOL: StopStoragePool() + elif item is REMOVE_POOL: RemoveStoragePool() + elif item is ADD_VOLUME: AddStorageVolume() + elif item is REMOVE_VOLUME: RemoveStorageVolume() + elif item is LIST_POOLS: ListStoragePools() + +def StoragePoolMenu(): + screen = StoragePoolMenuScreen() + screen.start() diff --git a/nodeadmin/utils.py b/nodeadmin/utils.py index 55a838c..28ccb8b 100644 --- a/nodeadmin/utils.py +++ b/nodeadmin/utils.py @@ -17,9 +17,19 @@ # also available at http://www.gnu.org/copyleft/gpl.html. import logging +import re logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', filename='/var/log/ovirt-nodeadmin.log', filemode='w') + +def string_is_not_blank(value): + if len(value) > 0: return True + return False + +def string_has_no_spaces(value): + if re.match("^[a-zA-Z0-9_]*$", value): + return True + return False diff --git a/nodeadmin/volumeconfig.py b/nodeadmin/volumeconfig.py new file mode 100644 index 0000000..7741391 --- /dev/null +++ b/nodeadmin/volumeconfig.py @@ -0,0 +1,76 @@ +# volumeconfig.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +import virtinst +from virtinst import Storage + +class StorageVolumeConfig: + def __init__(self): + self.__pool = None + self.__name = "" + self.__formats = None + self.__format = None + self.__max_capacity = 10000 + self.__allocation = 0 + + def set_pool(self, pool): + self.__pool = pool + self.__formats = None + self.__pool_type = virtinst.util.get_xml_path(self.__pool.XMLDesc(0), '/pool/@type') + self.__volume_class = Storage.StoragePool.get_volume_for_pool(self.__pool_type) + + def get_pool(self): + return self.__pool + + def create_volume(self): + volume = self.__volume_class(name = self.__name + ".img", + allocation = self.__allocation * 1024**2, + capacity = self.__max_capacity * 1024**2, + pool = self.__pool) + volume.pool = self.__pool + volume.format = self.__format + return volume + + def set_name(self, name): + self.__name = name + + def get_name(self): + return self.__name + + def get_formats_for_pool(self): + if self.__formats is None: + self.__formats = self.__volume_class.formats + return self.__formats + + def set_format(self, format): + self.__format = format + + def get_format(self): + return self.__format + + def set_max_capacity(self, capacity): + self.__max_capacity = capacity + + def get_max_capacity(self): + return self.__max_capacity + + def set_allocation(self, allocation): + self.__allocation = allocation + + def get_allocation(self): + return self.__allocation diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index 2a6b7b6..c392123 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -182,23 +182,35 @@ cd - %{__install} -p -m0755 nodeadmin/nodeadmin.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/mainmenu.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0644 nodeadmin/nodemenu.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/definedomain.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/createdomain.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/definedomain.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/destroydomain.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/undefinedomain.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/listdomains.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/domainconfig.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/listdomains.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/nodemenu.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/undefinedomain.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0644 nodeadmin/netmenu.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0644 nodeadmin/networkconfig.py %{buildroot}%{python_sitelib}/nodeadmin -%{__install} -p -m0755 nodeadmin/definenet.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/createnetwork.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/definenet.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/destroynetwork.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/netmenu.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/networkconfig.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/undefinenetwork.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/addpool.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/addvolume.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/listpools.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/poolconfig.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/removepool.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/removevolume.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/startpool.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/stoppool.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/storagemenu.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/volumeconfig.py %{buildroot}%{python_sitelib}/nodeadmin + %{__install} -p -m0755 nodeadmin/createuser.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/createmeter.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/halworker.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/libvirtworker.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/userworker.py %{buildroot}%{python_sitelib}/nodeadmin @@ -368,18 +380,25 @@ fi %{_sbindir}/ovirt-awake %{_initrddir}/ovirt-functions %defattr(-,root,root,0644) -%{_bindir}/nodeadmin -%{_bindir}/definedom +%{_bindir}/addpool +%{_bindir}/addvolume %{_bindir}/createdom -%{_bindir}/destroydom -%{_bindir}/undefinedom -%{_bindir}/listdoms -%{_bindir}/definenet %{_bindir}/createnet +%{_bindir}/createuser +%{_bindir}/definedom +%{_bindir}/definenet +%{_bindir}/destroydom %{_bindir}/destroynet -%{_bindir}/undefinenet +%{_bindir}/listdoms %{_bindir}/listnets -%{_bindir}/createuser +%{_bindir}/listpools +%{_bindir}/nodeadmin +%{_bindir}/rmpool +%{_bindir}/rmvolume +%{_bindir}/startpool +%{_bindir}/stoppool +%{_bindir}/undefinedom +%{_bindir}/undefinenet %{_sysconfdir}/collectd.conf.in %{python_sitelib}/nodeadmin %{python_sitelib}/nodeadmin- at VERSION@-py2.6.egg-info -- 1.6.2.5 From dpierce at redhat.com Wed Oct 21 19:50:48 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 21 Oct 2009 15:50:48 -0400 Subject: [Ovirt-devel] [PATCH node 2/2] Refactor domain storage setup to use pool and volume selection screens. In-Reply-To: <1256154648-9387-2-git-send-email-dpierce@redhat.com> References: <1256154648-9387-1-git-send-email-dpierce@redhat.com> <1256154648-9387-2-git-send-email-dpierce@redhat.com> Message-ID: <1256154648-9387-3-git-send-email-dpierce@redhat.com> Now, when the user elects to use managed storage, they're show the list of available storage pools. Then, after selecting one, the user is shown the list of volumes on that pool. These are then used to create the domain. Signed-off-by: Darryl L. Pierce --- nodeadmin/definedomain.py | 186 ++++++++++++++++++++++++++------------------ nodeadmin/domainconfig.py | 17 +++- nodeadmin/libvirtworker.py | 34 ++++---- 3 files changed, 141 insertions(+), 96 deletions(-) diff --git a/nodeadmin/definedomain.py b/nodeadmin/definedomain.py index 791c278..0086e59 100755 --- a/nodeadmin/definedomain.py +++ b/nodeadmin/definedomain.py @@ -37,10 +37,11 @@ OS_VARIANT_PAGE = 12 RAM_CPU_PAGE = 13 ENABLE_STORAGE_PAGE = 14 LOCAL_STORAGE_PAGE = 15 -MANAGED_STORAGE_PAGE = 16 -BRIDGE_PAGE = 17 -VIRT_DETAILS_PAGE = 18 -CONFIRM_PAGE = 19 +SELECT_POOL_PAGE = 16 +SELECT_VOLUME_PAGE = 17 +BRIDGE_PAGE = 18 +VIRT_DETAILS_PAGE = 19 +CONFIRM_PAGE = 20 LOCATION="location" KICKSTART="kickstart" @@ -58,24 +59,25 @@ class DomainConfigScreen(ConfigScreen): self.__config.set_virt_type(self.get_libvirt().get_default_virt_type()) def get_elements_for_page(self, screen, page): - if page == VM_DETAILS_PAGE: return self.get_vm_details_page(screen) - elif page == LOCAL_INSTALL_PAGE: return self.get_local_install_page(screen) - elif page == SELECT_CDROM_PAGE: return self.get_select_cdrom_page(screen) - elif page == SELECT_ISO_PAGE: return self.get_select_iso_page(screen) - elif page == NETWORK_INSTALL_PAGE: return self.get_network_install_page(screen) - elif page == OS_TYPE_PAGE: return self.get_os_type_page(screen) - elif page == OS_VARIANT_PAGE: return self.get_os_variant_page(screen) - elif page == RAM_CPU_PAGE: return self.get_ram_and_cpu_page(screen) - elif page == ENABLE_STORAGE_PAGE: return self.get_enable_storage_page(screen) - elif page == LOCAL_STORAGE_PAGE: return self.get_local_storage_page(screen) - elif page == MANAGED_STORAGE_PAGE: return self.get_managed_storage_page(screen) - elif page == BRIDGE_PAGE: return self.get_bridge_page(screen) - elif page == VIRT_DETAILS_PAGE: return self.get_virt_details_page(screen) - elif page == CONFIRM_PAGE: return self.get_confirm_page(screen) + if page is VM_DETAILS_PAGE: return self.get_vm_details_page(screen) + elif page is LOCAL_INSTALL_PAGE: return self.get_local_install_page(screen) + elif page is SELECT_CDROM_PAGE: return self.get_select_cdrom_page(screen) + elif page is SELECT_ISO_PAGE: return self.get_select_iso_page(screen) + elif page is NETWORK_INSTALL_PAGE: return self.get_network_install_page(screen) + elif page is OS_TYPE_PAGE: return self.get_os_type_page(screen) + elif page is OS_VARIANT_PAGE: return self.get_os_variant_page(screen) + elif page is RAM_CPU_PAGE: return self.get_ram_and_cpu_page(screen) + elif page is ENABLE_STORAGE_PAGE: return self.get_enable_storage_page(screen) + elif page is LOCAL_STORAGE_PAGE: return self.get_local_storage_page(screen) + elif page is SELECT_POOL_PAGE: return self.get_select_pool_page(screen) + elif page is SELECT_VOLUME_PAGE: return self.get_select_volume_page(screen) + elif page is BRIDGE_PAGE: return self.get_bridge_page(screen) + elif page is VIRT_DETAILS_PAGE: return self.get_virt_details_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) return [] def validate_input(self, page, errors): - if page == VM_DETAILS_PAGE: + if page is VM_DETAILS_PAGE: if len(self.__guest_name.value()) > 0: if self.get_libvirt().domain_exists(self.__guest_name.value()): errors.append("Guest name '%s' is already in use." % self.__guest_name.value()) @@ -83,12 +85,12 @@ class DomainConfigScreen(ConfigScreen): return True else: errors.append("Guest name must be a string between 0 and 50 characters.") - elif page == LOCAL_INSTALL_PAGE: + elif page is LOCAL_INSTALL_PAGE: if self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM: return True elif self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_ISO: return True - elif page == SELECT_CDROM_PAGE: + elif page is SELECT_CDROM_PAGE: if self.__install_media.getSelection() != None: if len(self.get_hal().list_installable_volumes()) == 0: errors.append("No installable media is available.") @@ -96,7 +98,7 @@ class DomainConfigScreen(ConfigScreen): return True else: errors.append("You must select an install media.") - elif page == SELECT_ISO_PAGE: + elif page is SELECT_ISO_PAGE: if len(self.__iso_path.value()) > 0: if os.path.exists(self.__iso_path.value()): if os.path.isfile(self.__iso_path.value()): @@ -108,14 +110,14 @@ class DomainConfigScreen(ConfigScreen): errors.append(self.__iso_path.value()) else: errors.append("An install media selection is required.") - elif page == NETWORK_INSTALL_PAGE: + elif page is NETWORK_INSTALL_PAGE: if len(self.__install_url.value()) > 0: return True else: errors.append("An install tree is required.") - elif page == OS_TYPE_PAGE: return True - elif page == OS_VARIANT_PAGE: return True - elif page == RAM_CPU_PAGE: + elif page is OS_TYPE_PAGE: return True + elif page is OS_VARIANT_PAGE: return True + elif page is RAM_CPU_PAGE: if (len(self.__memory.value()) > 0 and len(self.__cpus.value()) > 0) \ and (int(self.__memory.value()) > 0 and int(self.__cpus.value()) > 0): return True @@ -128,8 +130,8 @@ class DomainConfigScreen(ConfigScreen): errors.append("A value must be entered for CPUs.") elif int(self.__cpus.value()) <= 0: errors.append("A positive integer value must be entered for memory.") - elif page == ENABLE_STORAGE_PAGE: return True - elif page == LOCAL_STORAGE_PAGE: + elif page is ENABLE_STORAGE_PAGE: return True + elif page is LOCAL_STORAGE_PAGE: if len(self.__storage_size.value()) > 0: if float(self.__storage_size.value()) > 0: return True @@ -137,12 +139,17 @@ class DomainConfigScreen(ConfigScreen): errors.append("A positive value must be entered for the storage size.") else: errors.append("A value must be entered for the storage size.") - elif page == MANAGED_STORAGE_PAGE: - if self.__existing_storage.getSelection() is not None: + elif page is SELECT_POOL_PAGE: + if self.__storage_pool.getSelection() is not None: + return True + else: + errors.append("Please select a storage pool.") + elif page is SELECT_VOLUME_PAGE: + if self.__storage_volume.getSelection() is not None: return True else: errors.append("Please select a storage volume.") - elif page == BRIDGE_PAGE: + elif page is BRIDGE_PAGE: if self.__network_bridges.getSelection() != None: if len(self.__mac_address.value()) > 0: # TODO: regex check the format @@ -151,62 +158,66 @@ class DomainConfigScreen(ConfigScreen): errors.append("MAC address must be supplied.") else: errors.append("A network bridge must be selected.") - elif page == VIRT_DETAILS_PAGE: + elif page is VIRT_DETAILS_PAGE: if self.__virt_types.getSelection() != None and self.__architectures.getSelection() != None: return True if self.__virt_types.getSelection() is None: errors.append("Please select a virtualization type.") if self.__architectures.getSelection() is None: errors.append("Please selection an architecture.") - elif page == CONFIRM_PAGE: return True + elif page is CONFIRM_PAGE: return True return False def process_input(self, page): - if page == VM_DETAILS_PAGE: + if page is VM_DETAILS_PAGE: self.__config.set_guest_name(self.__guest_name.value()) self.__config.set_install_type(self.__install_type.getSelection()) - elif page == LOCAL_INSTALL_PAGE: + elif page is LOCAL_INSTALL_PAGE: self.__config.set_use_cdrom_source(self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM) - elif page == SELECT_CDROM_PAGE: + elif page is SELECT_CDROM_PAGE: self.__config.set_install_media(self.__install_media.getSelection()) - elif page == SELECT_ISO_PAGE: + elif page is SELECT_ISO_PAGE: self.__config.set_iso_path(self.__iso_path.value()) - elif page == NETWORK_INSTALL_PAGE: + elif page is NETWORK_INSTALL_PAGE: self.__config.set_install_url(self.__install_url.value()) self.__config.set_kickstart_url(self.__kickstart_url.value()) self.__config.set_kernel_options(self.__kernel_options.value()) - elif page == OS_TYPE_PAGE: + elif page is OS_TYPE_PAGE: self.__config.set_os_type(self.__os_types.getSelection()) - elif page == OS_VARIANT_PAGE: + elif page is OS_VARIANT_PAGE: self.__config.set_os_variant(self.__os_variants.getSelection()) - elif page == RAM_CPU_PAGE: + elif page is RAM_CPU_PAGE: self.__config.set_memory(int(self.__memory.value())) self.__config.set_cpus(int(self.__cpus.value())) - elif page == ENABLE_STORAGE_PAGE: + elif page is ENABLE_STORAGE_PAGE: self.__config.set_enable_storage(self.__enable_storage.value()) if self.__storage_type.getSelection() == DomainConfig.NEW_STORAGE: self.__config.set_use_local_storage(True) elif self.__storage_type.getSelection() == DomainConfig.EXISTING_STORAGE: self.__config.set_use_local_storage(False) - elif page == LOCAL_STORAGE_PAGE: + elif page is LOCAL_STORAGE_PAGE: self.__config.set_storage_size(float(self.__storage_size.value())) self.__config.set_allocate_storage(self.__allocate_storage.value()) - elif page == MANAGED_STORAGE_PAGE: + elif page is SELECT_POOL_PAGE: self.__config.set_use_local_storage(False) - self.__config.set_existing_storage(self.__existing_storage.getSelection()) - self.__config.set_storage_size(self.get_libvirt().get_storage_size(self.__existing_storage.getSelection())) - elif page == BRIDGE_PAGE: + self.__config.set_storage_pool(self.__storage_pool.getSelection()) + elif page is SELECT_VOLUME_PAGE: + self.__config.set_storage_volume(self.__storage_volume.getSelection()) + volume = self.get_libvirt().get_storage_volume(self.__config.get_storage_pool(), + self.__config.get_storage_volume()) + self.__config.set_storage_size(volume.info()[1] / 1024.0 ** 3) + elif page is BRIDGE_PAGE: self.__config.set_network_bridge(self.__network_bridges.getSelection()) - elif page == VIRT_DETAILS_PAGE: + elif page is VIRT_DETAILS_PAGE: self.__config.set_virt_type(self.__virt_types.getSelection()) self.__config.set_architecture(self.__architectures.getSelection()) - elif page == CONFIRM_PAGE: + elif page is CONFIRM_PAGE: self.get_libvirt().define_domain(self.__config, CreateMeter()) self.set_finished() def get_back_page(self, page): result = page - if page == OS_TYPE_PAGE: + if page is OS_TYPE_PAGE: install_type = self.__config.get_install_type() if install_type == DomainConfig.LOCAL_INSTALL: if self.__config.get_use_cdrom_source(): @@ -217,24 +228,26 @@ class DomainConfigScreen(ConfigScreen): result = NETWORK_INSTALL_PAGE elif install_type == DomainConfig.PXE_INSTALL: result = VM_DETAILS_PAGE - elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: + elif page is LOCAL_STORAGE_PAGE or page is SELECT_VOLUME_PAGE: result = ENABLE_STORAGE_PAGE - elif page == NETWORK_INSTALL_PAGE: + elif page is SELECT_POOL_PAGE: + result = ENABLE_STORAGE_PAGE + elif page is NETWORK_INSTALL_PAGE: result = VM_DETAILS_PAGE - elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: + elif page is SELECT_CDROM_PAGE or page is SELECT_ISO_PAGE: result = LOCAL_INSTALL_PAGE - elif page == BRIDGE_PAGE: + elif page is BRIDGE_PAGE: if self.__config.get_use_local_storage(): result = LOCAL_STORAGE_PAGE else: - result = MANAGED_STORAGE_PAGE + result = SELECT_VOLUME_PAGE else: if page > 1: result = page - 1 return result def get_next_page(self, page): result = page - if page == VM_DETAILS_PAGE: + if page is VM_DETAILS_PAGE: install_type = self.__config.get_install_type() if install_type == DomainConfig.LOCAL_INSTALL: result = LOCAL_INSTALL_PAGE @@ -242,34 +255,36 @@ class DomainConfigScreen(ConfigScreen): result = NETWORK_INSTALL_PAGE elif install_type == DomainConfig.PXE_INSTALL: result = OS_TYPE_PAGE - elif page == LOCAL_INSTALL_PAGE: + elif page is LOCAL_INSTALL_PAGE: if self.__config.get_use_cdrom_source(): result = SELECT_CDROM_PAGE else: result = SELECT_ISO_PAGE - elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: + elif page is SELECT_CDROM_PAGE or page is SELECT_ISO_PAGE: result = OS_TYPE_PAGE - elif page == NETWORK_INSTALL_PAGE: + elif page is NETWORK_INSTALL_PAGE: result = OS_TYPE_PAGE - elif page == ENABLE_STORAGE_PAGE: + elif page is ENABLE_STORAGE_PAGE: result = BRIDGE_PAGE if self.__config.get_enable_storage(): if self.__config.get_use_local_storage(): result = LOCAL_STORAGE_PAGE else: - result = MANAGED_STORAGE_PAGE - elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: + result = SELECT_POOL_PAGE + elif page is LOCAL_STORAGE_PAGE: result = BRIDGE_PAGE else: result = page + 1 return result def page_has_finish(self, page): - if page == CONFIRM_PAGE: return True + if page is CONFIRM_PAGE: return True return False def page_has_next(self, page): - if page < CONFIRM_PAGE: + if page is SELECT_POOL_PAGE: return self.__has_pools + elif page is SELECT_VOLUME_PAGE: return self.__has_volumes + elif page < CONFIRM_PAGE: return True def get_vm_details_page(self, screen): @@ -393,17 +408,36 @@ class DomainConfigScreen(ConfigScreen): return [Label("Configure local storage"), grid] - def get_managed_storage_page(self, screen): + def get_select_pool_page(self, screen): + pools = [] + for pool in self.get_libvirt().list_storage_pools(): + pools.append([pool, pool, pool == self.__config.get_storage_pool()]) + if len(pools) > 0: + self.__storage_pool = RadioBar(screen, (pools)) + grid = Grid(2, 1) + grid.setField(Label("Storage pool:"), 0, 0, anchorTop = 1) + grid.setField(self.__storage_pool, 1, 0) + self.__has_pools = True + else: + grid = Label("There are no storage pools available.") + self.__has_pools = False + return [Label("Configure Managed Storage: Select A Pool"), + grid] + + def get_select_volume_page(self, screen): volumes = [] - for volume in self.get_libvirt().list_storage_volumes(): - volumes.append(["%s (%d GB)" % (volume.name(), volume.info()[1] / (1024 ** 3)), - volume.name(), - self.__config.is_existing_storage(volume.name())]) - self.__existing_storage = RadioBar(screen, (volumes)) - grid = Grid(2, 1) - grid.setField(Label("Existing storage:"), 0, 0) - grid.setField(self.__existing_storage, 1, 0) - return [Label("Configure managed storage"), + for volume in self.get_libvirt().list_storage_volumes(self.__config.get_storage_pool()): + volumes.append([volume, volume, volume == self.__config.get_storage_volume()]) + if len(volumes) > 0: + self.__storage_volume = RadioBar(screen, (volumes)) + grid = Grid(2, 1) + grid.setField(Label("Storage volumes:"), 0, 0, anchorTop = 1) + grid.setField(self.__storage_volume, 1, 0) + self.__has_volumes = True + else: + grid = Label("This storage pool has no defined volumes.") + self.__has_volumes = False + return [Label("Configure Managed Storage: Select A Volume"), grid] def get_bridge_page(self, screen): @@ -448,7 +482,9 @@ class DomainConfigScreen(ConfigScreen): grid.setField(Label("CPUs:"), 0, 3, anchorRight = 1) grid.setField(Label("%d" % self.__config.get_cpus()), 1, 3, anchorLeft = 1) grid.setField(Label("Storage:"), 0, 4, anchorRight = 1) - grid.setField(Label(self.__config.get_existing_storage()), 1, 4, anchorLeft = 1) + grid.setField(Label("%s (on %s)" % (self.__config.get_storage_volume(), + self.__config.get_storage_pool())), + 1, 4, anchorLeft = 1) grid.setField(Label("Network:"), 0, 5, anchorRight = 1) grid.setField(Label(self.__config.get_network_bridge()), 1, 5, anchorLeft = 1) return [Label("Ready to begin installation of %s" % self.__config.get_guest_name()), diff --git a/nodeadmin/domainconfig.py b/nodeadmin/domainconfig.py index ef39fe0..4466e67 100644 --- a/nodeadmin/domainconfig.py +++ b/nodeadmin/domainconfig.py @@ -50,7 +50,8 @@ class DomainConfig: self.__use_local_storage = True self.__storage_size = 8.0 self.__allocate_storage = True - self.__existing_storage = "" + self.__storage_pool = "" + self.__storage_volume = "" self.__network_bridge = None self.__mac_address = None self.__virt_type = None @@ -177,11 +178,17 @@ class DomainConfig: def get_allocate_storage(self): return self.__allocate_storage - def set_existing_storage(self, storage): - self.__existing_storage = storage + def set_storage_pool(self, pool): + self.__storage_pool = pool - def get_existing_storage(self): - return self.__existing_storage + def get_storage_pool(self): + return self.__storage_pool + + def set_storage_volume(self, volume): + self.__storage_volume = volume + + def get_storage_volume(self): + return self.__storage_volume def is_existing_storage(self, storage): return self.__existing_storage == storage diff --git a/nodeadmin/libvirtworker.py b/nodeadmin/libvirtworker.py index b2acabe..f31266c 100644 --- a/nodeadmin/libvirtworker.py +++ b/nodeadmin/libvirtworker.py @@ -196,6 +196,11 @@ class LibvirtWorker: '''Returns the storage pool with the specified name.''' return self.__conn.storagePoolLookupByName(name) + def list_storage_volumes(self, poolname): + '''Returns the list of all defined storage volumes for a given pool.''' + pool = self.get_storage_pool(poolname) + return pool.listVolumes() + def define_storage_volume(self, config, meter): '''Defines a new storage volume.''' self.create_storage_pool(config.get_pool().name()) @@ -204,10 +209,15 @@ class LibvirtWorker: def remove_storage_volume(self, poolname, volumename): '''Removes the specified storage volume.''' - pool = self.get_storage_pool(poolname) - volume = pool.storageVolLookupByName(volumename) + volume = self.get_storage_volume(poolname, volumename) volume.delete(0) + def get_storage_volume(self, poolname, volumename): + '''Returns a reference to the specified storage volume.''' + pool =self.get_storage_pool(poolname) + volume = pool.storageVolLookupByName(volumename) + return volume + def list_bridges(self): '''Lists all defined and active bridges.''' bridges = self.__conn.listNetworks() @@ -221,21 +231,9 @@ class LibvirtWorker: def generate_mac_address(self): return self.__net.macaddr - def list_storage_volumes(self): - '''Lists all defined storage volumes.''' - pools = self.__conn.listStoragePools() - pools.extend(self.__conn.listDefinedStoragePools()) - result = [] - for name in pools: - pool = self.__conn.storagePoolLookupByName(name) - for volname in pool.listVolumes(): - volume = self.__conn.storageVolLookupByPath("/var/lib/libvirt/images/%s" % volname) - result.append(volume) - return result - - def get_storage_size(self, name): + def get_storage_size(self, poolname, volumename): '''Returns the size of the specified storage volume.''' - volume = self.__conn.storageVolLookupByPath("/var/lib/libvirt/images/%s" % name) + volume = self.get_storage_volume(poolname, volumename) return volume.info()[1] / (1024.0 ** 3) def get_virt_types(self): @@ -381,6 +379,10 @@ class LibvirtWorker: pool_object = pool, suffix = ".img") path = os.path.join(DEFAULT_POOL_TARGET_PATH, path) + else: + volume = self.get_storage_volume(config.get_storage_pool(), + config.get_storage_volume()) + path = volume.path() if path is not None: storage= virtinst.VirtualDisk(conn = self.__conn, -- 1.6.2.5 From jboggs at redhat.com Thu Oct 22 18:52:30 2009 From: jboggs at redhat.com (Joey Boggs) Date: Thu, 22 Oct 2009 14:52:30 -0400 Subject: [Ovirt-devel] [PATCH node] merge Root and /boot partitions Message-ID: <1256237550-19352-1-git-send-email-jboggs@redhat.com> This completes the switch to ext3 backed root partitions. /boot is now contained on each Root/RootBackup partition. On install grub is updated to read and boot from the correct partition as well. --- scripts/ovirt-config-boot | 29 ++++++++++++------------ scripts/ovirt-config-storage | 47 +++++++++++---------------------------- scripts/ovirt-config-uninstall | 5 +--- scripts/ovirt-functions | 12 +++++----- 4 files changed, 34 insertions(+), 59 deletions(-) diff --git a/scripts/ovirt-config-boot b/scripts/ovirt-config-boot index dd53988..9ab06bd 100755 --- a/scripts/ovirt-config-boot +++ b/scripts/ovirt-config-boot @@ -29,9 +29,9 @@ ovirt_boot_setup() { local disk2 local partN=-1 log "installing the image." - mount_boot + mount_liveos # check that /boot mounted ok and find partition number for GRUB - eval $(readlink -f /dev/disk/by-label/BOOT|awk {' + eval $(readlink -f /dev/disk/by-label/RootBackup|awk {' print "disk=" substr($1,1,length($1)-1); print "disk2=" substr($1,1,length($1)-2); partN=substr($1,length($1),1); partN--; @@ -39,13 +39,13 @@ ovirt_boot_setup() { }') rc=$? if [ $rc -ne 0 -o $partN -lt 0 ]; then - log "unable to determine boot partition" + log "unable to determine Root partition" return 1 fi - mountpoint /boot + mountpoint /liveos if [ $? -ne 0 ] ; then - log "boot partition not available" + log "Root partition not available" return 1 fi @@ -87,12 +87,12 @@ ovirt_boot_setup() { else syslinux= fi - rm -rf /boot/grub + rm -rf /liveos/boot/grub rm -rf /liveos/LiveOS - mkdir -p /boot/grub + mkdir -p /liveos/boot/grub mkdir -p /liveos/LiveOS cp -p $live/LiveOS/squashfs.img /liveos/LiveOS \ - && cp -p $live/$syslinux/vmlinuz0 /boot + && cp -p $live/$syslinux/vmlinuz0 /liveos rc=$? if [ $rc -ne 0 ]; then log "image copy failed." @@ -142,14 +142,14 @@ set -e\ find $init_script bin/lvm lib$bit -type f | cpio -H newc --quiet -o | gzip -9 | - cat $live/$syslinux/initrd0.img - > /boot/initrd0.img + cat $live/$syslinux/initrd0.img - > /liveos/initrd0.img version=$(rpm -q --qf '%{version}' ovirt-node) release=$(rpm -q --qf '%{release}' ovirt-node) # reorder tty0 to allow both serial and phys console after installation bootparams="ro root=LABEL=Root roottypefs=ext3 console=tty0 \ $(echo $bootparams | sed s/console=tty0//g)" - cat > /boot/grub/grub.conf << EOF + cat > /liveos/boot/grub/grub.conf << EOF default=0 timeout=5 hiddenmenu @@ -158,11 +158,11 @@ title oVirt Node (${version}-${release}) kernel /vmlinuz0 $bootparams initrd /initrd0.img EOF - echo "(hd0) $disk" > /boot/grub/device.map - ( cd /usr/share/grub/*; cp -p stage? e2fs_stage1_5 /boot/grub ) - grub --device-map=/boot/grub/device.map < /liveos/boot/grub/device.map + ( cd /usr/share/grub/*; cp -p stage? e2fs_stage1_5 /liveos/boot/grub ) + grub --device-map=/liveos/boot/grub/device.map < /dev/null || udevsettle @@ -366,12 +356,10 @@ perform_partitioning() gptsync $DRIVE fi - partboot=${DRIVE}1 - partpv=${DRIVE}4 + partpv=${DRIVE}3 if [ ! -e "$partpv" ]; then # e.g. /dev/cciss/c0d0p2 - partboot=${DRIVE}p1 - partpv=${DRIVE}p4 + partpv=${DRIVE}p3 fi log "Creating physical volume" if [ ! -e "$partpv" ]; then @@ -383,15 +371,7 @@ perform_partitioning() log "Creating volume group" vgcreate /dev/HostVG "${partpv}" - log "Creating boot filesystem" - if [ ! -e "$partboot" ]; then - log "$partboot is not available!" - exit 1 - fi - mke2fs -j "${partboot}" -L "BOOT" - tune2fs -c 0 -i 0 "${partboot}" mkdir -p /dev/disk/by-label - ln -snf "${partboot}" /dev/disk/by-label/BOOT if [ "$SWAP_SIZE" -gt 0 ]; then log "Creating swap partition" @@ -512,7 +492,6 @@ fi CALC_SWAP_SIZE=$(echo "scale=0; ${BASE_SWAP_SIZE} + ${OVERCOMMIT_SWAP_SIZE};" | bc -l) SWAP_SIZE=${OVIRT_VOL_SWAP_SIZE:-$CALC_SWAP_SIZE} -BOOT_SIZE=${OVIRT_VOL_BOOT_SIZE:-$default_boot_size} ROOT_SIZE=${OVIRT_VOL_ROOT_SIZE:-$default_root_size} CONFIG_SIZE=${OVIRT_VOL_CONFIG_SIZE:-$default_config_size} LOGGING_SIZE=${OVIRT_VOL_LOGGING_SIZE:-$default_logging_size} diff --git a/scripts/ovirt-config-uninstall b/scripts/ovirt-config-uninstall index 22633a0..820eead 100755 --- a/scripts/ovirt-config-uninstall +++ b/scripts/ovirt-config-uninstall @@ -34,16 +34,13 @@ if ask_yes_or_no "Do you wish to continue and uninstall this node ([Y]es/[N]o)?" unmount_logging log "Removing volume group" wipe_volume_group "HostVG" - partition=$(findfs LABEL=BOOT) + partition=$(findfs LABEL=Root) if [ -n "$partition" ]; then - log "Unmounting boot partition" - umount $partition log "Removing partitions" drive=$(echo $partition | awk '{ print substr($0, 1, length($0) - 1) }') parted -s $drive "rm 1" parted -s $drive "rm 2" parted -s $drive "rm 3" - parted -s $drive "rm 4" fi printf "Finished uninstalling node." else diff --git a/scripts/ovirt-functions b/scripts/ovirt-functions index f24cb07..6e76ce3 100644 --- a/scripts/ovirt-functions +++ b/scripts/ovirt-functions @@ -260,14 +260,14 @@ mount_live() { mount -r $live_dev /live || mount $live_dev /live } -# mount boot partition -# boot loader + kernel + initrd -mount_boot() { - if grep -q " /boot " /etc/mtab; then +# mount root partition +# boot loader + kernel + initrd + LiveOS +mount_liveos() { + if grep -q " /liveos " /etc/mtab; then return 0 fi - mkdir -p /boot - mount /dev/disk/by-label/BOOT /boot + mkdir -p /liveos + mount /dev/disk/by-label/Root /liveos } # mount config partition -- 1.6.2.5 From jboggs at redhat.com Fri Oct 23 19:53:33 2009 From: jboggs at redhat.com (Joey Boggs) Date: Fri, 23 Oct 2009 15:53:33 -0400 Subject: [Ovirt-devel] [PATCH node] Add iSCSI initiator setup option Message-ID: <1256327613-20260-1-git-send-email-jboggs@redhat.com> This add an iscsi initiator name setup option to the main menu in ovirt-config-setup and can also be accessed via ovirt-config-iscsi. Also accepts AUTO setup by running ovirt-config-iscsi AUTO and generates a random name as before --- Makefile.am | 1 + ovirt-node.spec.in | 3 ++ scripts/ovirt-config-boot | 4 --- scripts/ovirt-config-iscsi | 56 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 4 deletions(-) create mode 100755 scripts/ovirt-config-iscsi diff --git a/Makefile.am b/Makefile.am index abb7c33..b2419cd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -58,6 +58,7 @@ EXTRA_DIST = \ scripts/ovirt-config-boot-wrapper \ scripts/ovirt-config-collectd \ scripts/ovirt-config-hostname \ + scripts/ovirt-config-iscsi \ scripts/ovirt-config-logging \ scripts/ovirt-config-networking \ scripts/ovirt-config-password \ diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index 2a6b7b6..4a68c44 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -160,6 +160,7 @@ cd - %{__install} -p -m0755 scripts/ovirt-config-boot-wrapper %{buildroot}%{_sbindir} %{__install} -p -m0755 scripts/ovirt-config-collectd %{buildroot}%{_sbindir} %{__install} -p -m0755 scripts/ovirt-config-hostname %{buildroot}%{_sbindir} +%{__install} -p -m0755 scripts/ovirt-config-iscsi %{buildroot}%{_sbindir} %{__install} -p -m0755 scripts/ovirt-config-logging %{buildroot}%{_sbindir} %{__install} -p -m0755 scripts/ovirt-config-networking %{buildroot}%{_sbindir} %{__install} -p -m0755 scripts/ovirt-config-password %{buildroot}%{_sbindir} @@ -265,6 +266,7 @@ ln -s ovirt-release %{buildroot}/etc/system-release %{__ln_s} ../..%{_sbindir}/ovirt-config-storage %{buildroot}%{_sysconfdir}/ovirt-config-setup.d/"00_Disk Partitioning" %{__ln_s} ../..%{_sbindir}/ovirt-config-password %{buildroot}%{_sysconfdir}/ovirt-config-setup.d/"05_Administrator Password" %{__ln_s} ../..%{_sbindir}/ovirt-config-hostname %{buildroot}%{_sysconfdir}/ovirt-config-setup.d/"10_Set Hostname" +%{__ln_s} ../..%{_sbindir}/ovirt-config-iscsi %{buildroot}%{_sysconfdir}/ovirt-config-setup.d/"12_iSCSI Initiator Setup" %{__ln_s} ../..%{_sbindir}/ovirt-config-networking %{buildroot}%{_sysconfdir}/ovirt-config-setup.d/"15_Networking Setup" %{__ln_s} ../..%{_sbindir}/ovirt-config-logging %{buildroot}%{_sysconfdir}/ovirt-config-setup.d/"30_Logging Setup" %{__ln_s} ../..%{_sbindir}/ovirt-config-collectd %{buildroot}%{_sysconfdir}/ovirt-config-setup.d/"35_Collectd Setup" @@ -334,6 +336,7 @@ fi %{_sbindir}/ovirt-config-boot %{_sbindir}/ovirt-config-boot-wrapper %{_sbindir}/ovirt-config-collectd +%{_sbindir}/ovirt-config-iscsi %{_sbindir}/ovirt-config-hostname %{_sbindir}/ovirt-config-logging %{_sbindir}/ovirt-config-networking diff --git a/scripts/ovirt-config-boot b/scripts/ovirt-config-boot index dd53988..b2efe71 100755 --- a/scripts/ovirt-config-boot +++ b/scripts/ovirt-config-boot @@ -170,10 +170,6 @@ EOF return $rc fi - # install iscsi initiator name for this machine - echo "InitiatorName=$(iscsi-iname)" > /etc/iscsi/initiatorname.iscsi - ovirt_store_config /etc/iscsi/initiatorname.iscsi - umount /liveos umount /boot # mark new Root ready to go, reboot() in ovirt-function switches it to active diff --git a/scripts/ovirt-config-iscsi b/scripts/ovirt-config-iscsi new file mode 100755 index 0000000..8e1bbaf --- /dev/null +++ b/scripts/ovirt-config-iscsi @@ -0,0 +1,56 @@ +#!/bin/bash +# +# Configures the hostname file based on kernel cmdline or user prompt +# Source functions library +. /etc/init.d/functions +. /etc/init.d/ovirt-functions + +trap '__st=$?; stop_log; exit $__st' 0 +trap 'exit $?' 1 2 13 15 + +warn() { printf '%s\n' "$*" >&2; } + +if ! is_local_storage_configured; then + warn "Local storage must be configured prior to setting the iSCSI Initiator Name." + exit 99 +fi + +INITIATOR_FILE="/etc/iscsi/initiatorname.iscsi" + +function prompt_user { + printf "\n" + printf "Enter iSCSI Initiator Name (If blank one will be automatically generated)\n" + printf "Enter Q to quit\n" + read REPLY + if [[ $REPLY == "q" || $REPLY == "Q" ]]; then + printf "Aborting due to user request" + return + fi + + set_initiator $REPLY +} + +function set_initiator { + if [ -z "$1" ]; then + INITIATOR_NAME=$(iscsi-iname) + else + INITIATOR_NAME=$1 + fi + + echo "InitiatorName=$INITIATOR_NAME" > $INITIATOR_FILE + ovirt_store_config $INITIATOR_FILE + rc=$? + if [ $rc = 0 ]; then + printf "Initiator name set as: $INITIATOR_NAME\n" + else + printf "Setting initiator name failed\n" + fi +} + +# AUTO for auto-install +if [ "$1" = "AUTO" ]; then + set_initiator +else + printf "\n\n iSCSI Initiator Configuration\n\n" + prompt_user +fi -- 1.6.2.5 From dpierce at redhat.com Mon Oct 26 21:03:02 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Mon, 26 Oct 2009 17:03:02 -0400 Subject: [Ovirt-devel] [PATCH node] Users can now work with remote libvirt hosts. Message-ID: <1256590982-12127-1-git-send-email-dpierce@redhat.com> The user can: * select a remote machine * add a remote machine * remove a remote machine Signed-off-by: Darryl L. Pierce --- nodeadmin/addhost.py | 129 ++++++++++++++++++++++++++++++++++++++++++++ nodeadmin/changehost.py | 58 ++++++++++++++++++++ nodeadmin/configscreen.py | 36 ++++++++++++- nodeadmin/definenet.py | 1 + nodeadmin/hostconnect.py | 29 ++++++++++ nodeadmin/hostmenu.py | 46 ++++++++++++++++ nodeadmin/libvirtworker.py | 48 ++++++++++++++++- nodeadmin/mainmenu.py | 14 +++-- nodeadmin/removehost.py | 66 ++++++++++++++++++++++ 9 files changed, 419 insertions(+), 8 deletions(-) create mode 100644 nodeadmin/addhost.py create mode 100644 nodeadmin/changehost.py create mode 100644 nodeadmin/hostconnect.py create mode 100644 nodeadmin/hostmenu.py create mode 100644 nodeadmin/removehost.py diff --git a/nodeadmin/addhost.py b/nodeadmin/addhost.py new file mode 100644 index 0000000..ef35b7d --- /dev/null +++ b/nodeadmin/addhost.py @@ -0,0 +1,129 @@ +# addhost.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * + +from configscreen import * + +DETAILS_PAGE = 1 +CONFIRM_PAGE = 2 + +HYPERVISOR_XEN = "xen" +HYPERVISOR_KVM = "kvm" + +HYPERVISORS = {HYPERVISOR_XEN : "Xen", + HYPERVISOR_KVM : "QEMU/KVM"} + +CONNECTION_LOCAL = "local" +CONNECTION_KERBEROS = "kerberos" +CONNECTION_SSL = "ssl" +CONNECTION_SSH = "ssh" + +CONNECTIONS = {CONNECTION_LOCAL : "Local", + CONNECTION_KERBEROS : "Remote Password or Kerberos", + CONNECTION_SSL : "Remote SSL/TLS with x509 certificate", + CONNECTION_SSH : "Remote tunnel over SSH"} + +class AddHostConfigScreen(ConfigScreen): + def __init__(self): + ConfigScreen.__init__(self, "Add A Remote Host") + self.__configured = False + + def get_elements_for_page(self, screen, page): + if page is DETAILS_PAGE: return self.get_details_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + return page < CONFIRM_PAGE + + def page_has_back(self, page): + return page > DETAILS_PAGE + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def validate_input(self, page, errors): + if page is DETAILS_PAGE: + if len(self.__hostname.value()) > 0: + return True + else: + errors.append("You must enter a remote hostname.") + elif page is CONFIRM_PAGE: return True + return False + + def process_input(self, page): + if page is CONFIRM_PAGE: + hv = self.__hypervisor.getSelection() + conn = self.__connection.getSelection() + hostname = self.__hostname.value() + + if hv is HYPERVISOR_XEN: + if conn is CONNECTION_LOCAL: url = "xen:///" + elif conn is CONNECTION_KERBEROS: url = "xen+tcp:///" + hostname + "/" + elif conn is CONNECTION_SSL: url = "xen+tls:///" + hostname + "/" + elif conn is CONNECTION_SSH: url = "xen+ssh:///" + hostname + "/" + elif hv is HYPERVISOR_KVM: + if conn is CONNECTION_LOCAL: url = "qemu:///system" + elif conn is CONNECTION_KERBEROS: url = "qemu+tcp://" + hostname + "/system" + elif conn is CONNECTION_SSL: url = "qemu+tls://" + hostname + "/system" + elif conn is CONNECTION_SSH: url = "qemu+ssh://" + hostname + "/system" + + self.get_virt_manager_config().add_connection(url) + self.set_finished() + + def get_details_page(self, screen): + if not self.__configured: + self.__hypervisor = RadioBar(screen, ((HYPERVISORS[HYPERVISOR_XEN], HYPERVISOR_XEN, True), + (HYPERVISORS[HYPERVISOR_KVM], HYPERVISOR_KVM, False))) + self.__connection = RadioBar(screen, ((CONNECTIONS[CONNECTION_LOCAL], CONNECTION_LOCAL, True), + (CONNECTIONS[CONNECTION_KERBEROS], CONNECTION_KERBEROS, False), + (CONNECTIONS[CONNECTION_SSL], CONNECTION_SSL, False), + (CONNECTIONS[CONNECTION_SSH], CONNECTION_SSH, False))) + self.__hostname = Entry(50, "") + self.__autoconnect = Checkbox("Autoconnect on Startup") + self.__configured = True + grid = Grid(2, 4) + grid.setField(Label("Hypervisor:"), 0, 0, anchorRight = 1, anchorTop = 1) + grid.setField(self.__hypervisor, 1, 0, anchorLeft = 1) + grid.setField(Label("Connection:"), 0, 1, anchorRight = 1, anchorTop = 1) + grid.setField(self.__connection, 1, 1, anchorLeft = 1) + grid.setField(Label("Hostname:"), 0, 2, anchorRight = 1) + grid.setField(self.__hostname, 1, 2, anchorLeft = 1) + grid.setField(Label(""), 0, 3, anchorRight = 1) + grid.setField(self.__autoconnect, 1, 3, anchorLeft = 1) + return [Label("Add Connection"), + grid] + + def get_confirm_page(self, screen): + grid = Grid(2, 4) + grid.setField(Label("Hypervisor:"), 0, 0, anchorRight = 1) + grid.setField(Label(HYPERVISORS[self.__hypervisor.getSelection()]), 1, 0, anchorLeft = 1) + grid.setField(Label("Connection:"), 0, 1, anchorRight = 1) + grid.setField(Label(CONNECTIONS[self.__connection.getSelection()]), 1, 1, anchorLeft = 1) + grid.setField(Label("Hostname:"), 0, 2, anchorRight = 1) + grid.setField(Label(self.__hostname.value()), 1, 2, anchorLeft = 1) + grid.setField(Label("Autoconnect on Startup:"), 0, 3, anchorRight = 1) + label = "Yes" + if not self.__autoconnect.value(): label = "No" + grid.setField(Label(label), 1, 3, anchorLeft = 1) + return [Label("Confirm Connection"), + grid] + +def AddHost(): + screen = AddHostConfigScreen() + screen.start() diff --git a/nodeadmin/changehost.py b/nodeadmin/changehost.py new file mode 100644 index 0000000..23e6854 --- /dev/null +++ b/nodeadmin/changehost.py @@ -0,0 +1,58 @@ +# changehost.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * + +import logging +import libvirtworker +from configscreen import * + +CONNECTION_LIST_PAGE = 1 +CONNECTED_PAGE = 2 + +class ChangeHostConfigScreen(HostListConfigScreen): + def __init__(self): + HostListConfigScreen.__init__(self, "Change Host") + + def get_elements_for_page(self, screen, page): + if page is CONNECTION_LIST_PAGE: return self.get_connection_list_page(screen) + elif page is CONNECTED_PAGE: return self.get_connected_page(screen) + + def process_input(self, page): + if page is CONNECTION_LIST_PAGE: + logging.info("Changing libvirt connection to %s" % self.get_selected_connection()) + libvirtworker.set_default_url(self.get_selected_connection()) + self.get_libvirt().open_connection(self.get_selected_connection()) + elif page is CONNECTED_PAGE: self.set_finished() + + def page_has_next(self, page): + if page is CONNECTION_LIST_PAGE: return self.has_selectable_connections() + return False + + def page_has_back(self, page): + return page > CONNECTION_LIST_PAGE + + def page_has_finish(self, page): + return page is CONNECTED_PAGE + + def get_connected_page(self, screen): + return [Label("Connected to %s" % self.get_selected_connection())] + +def ChangeHost(): + screen = ChangeHostConfigScreen() + screen.start() diff --git a/nodeadmin/configscreen.py b/nodeadmin/configscreen.py index f214aea..98e0338 100644 --- a/nodeadmin/configscreen.py +++ b/nodeadmin/configscreen.py @@ -18,7 +18,7 @@ from snack import * from halworker import HALWorker -from libvirtworker import LibvirtWorker +from libvirtworker import * import traceback BACK_BUTTON = "back" @@ -35,6 +35,7 @@ class ConfigScreen: self.__finished = False self.__hal = HALWorker() self.__libvirt = LibvirtWorker() + self.__vm_config = VirtManagerConfig() def get_hal(self): return self.__hal @@ -42,6 +43,9 @@ class ConfigScreen: def get_libvirt(self): return self.__libvirt + def get_virt_manager_config(self): + return self.__vm_config + def set_finished(self): self.__finished = True @@ -179,3 +183,33 @@ class NetworkListConfigScreen(ConfigScreen): def has_selectable_networks(self): return self.__has_networks + +class HostListConfigScreen(ConfigScreen): + '''Provides a base class for working with lists of libvirt hosts.''' + + def __init__(self, title): + ConfigScreen.__init__(self, title) + + def get_connection_list_page(self, screen): + connections = self.get_virt_manager_config().get_connection_list() + result = None + + if len(connections) > 0: + self.__has_connections = True + self.__connection_list = Listbox(0) + for connection in connections: + self.__connection_list.append(connection, connection) + result = self.__connection_list + else: + self.__has_connections = False + result = Label("There are no defined connections.") + grid = Grid(1, 1) + grid.setField(result, 0, 0) + return [Label("Host List"), + grid] + + def get_selected_connection(self): + return self.__connection_list.current() + + def has_selectable_connections(self): + return self.__has_connections diff --git a/nodeadmin/definenet.py b/nodeadmin/definenet.py index 4aa37d5..6dff18f 100644 --- a/nodeadmin/definenet.py +++ b/nodeadmin/definenet.py @@ -20,6 +20,7 @@ from snack import * from IPy import IP import traceback import logging +import re from configscreen import ConfigScreen from networkconfig import NetworkConfig diff --git a/nodeadmin/hostconnect.py b/nodeadmin/hostconnect.py new file mode 100644 index 0000000..a1be569 --- /dev/null +++ b/nodeadmin/hostconnect.py @@ -0,0 +1,29 @@ +# hostconnect.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * + +from configscreen import * + +class HostConnectConfigScreen(ConfigScreen): + def __init__(self): + ConfigScree + +def HostConnect(): + screen = HostConnectConfigScreen() + screen.start() diff --git a/nodeadmin/hostmenu.py b/nodeadmin/hostmenu.py new file mode 100644 index 0000000..4054d6b --- /dev/null +++ b/nodeadmin/hostmenu.py @@ -0,0 +1,46 @@ +# hostmenu.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * + +from menuscreen import MenuScreen +from changehost import ChangeHost +from addhost import AddHost +from removehost import RemoveHost + +SELECT_HOST = 1 +ADD_HOST = 2 +REMOVE_HOST = 3 + +class HostMenuScreen(MenuScreen): + def __init__(self): + MenuScreen.__init__(self, "Host Menu Screen") + + def get_menu_items(self): + return (("Select A Host", SELECT_HOST), + ("Add A Host", ADD_HOST), + ("Remove A Host", REMOVE_HOST)) + + def handle_selection(self, item): + if item is SELECT_HOST: ChangeHost() + elif item is ADD_HOST: AddHost() + elif item is REMOVE_HOST: RemoveHost() + +def HostMenu(): + screen = HostMenuScreen() + screen.start() diff --git a/nodeadmin/libvirtworker.py b/nodeadmin/libvirtworker.py index ba07605..9963f1d 100644 --- a/nodeadmin/libvirtworker.py +++ b/nodeadmin/libvirtworker.py @@ -21,20 +21,64 @@ import libvirt import os import virtinst import utils +import logging + +import gconf from domainconfig import DomainConfig DEFAULT_POOL_TARGET_PATH="/var/lib/libvirt/images" +DEFAULT_URL="qemu:///system" + +default_url = DEFAULT_URL + +def set_default_url(url): + logging.info("Changing DEFAULT_URL to %s" % url) + global default_url + + default_url = url + +def get_default_url(): + logging.info("Returning default URL of %s" % default_url) + return default_url + +class VirtManagerConfig: + def __init__(self): + self.__conf = gconf.client_get_default() + self.__conf.add_dir("/apps/virt-manager", gconf.CLIENT_PRELOAD_NONE) + + def get_connection_list(self): + return self.__conf.get_list("/apps/virt-manager/connections/uris", gconf.VALUE_STRING) + + def add_connection(self, connection): + connections = self.get_connection_list() + if connections.count(connection) is 0: + connections.append(connection) + self.__conf.set_list("/apps/virt-manager/connections/uris", gconf.VALUE_STRING, connections) + self.__conf.suggest_sync() + + def remove_connection(self, connection): + connections = self.get_connection_list() + if connections.count(connection) > 0: + connections.remove(connection) + self.__conf.set_list("/apps/virt-manager/connections/uris", gconf.VALUE_STRING, connections) + self.__conf.suggest_sync() class LibvirtWorker: '''Provides utilities for interfacing with libvirt.''' - def __init__(self, url = "qemu:///system"): - self.__conn = libvirt.open(url) + def __init__(self, url = None): + if url is None: url = get_default_url() + logging.info("Connecting to libvirt: %s" % url) + self.open_connection(url) self.__capabilities = virtinst.CapabilitiesParser.parse(self.__conn.getCapabilities()) self.__net = virtinst.VirtualNetworkInterface(conn = self.__conn) self.__net.setup(self.__conn) (self.__new_guest, self.__new_domain) = virtinst.CapabilitiesParser.guest_lookup(conn = self.__conn) + def open_connection(self, url): + '''Lets the user change the url for the connection.''' + self.__conn = libvirt.open(url) + def list_domains(self, defined = True, started = True): '''Lists all domains.''' result = [] diff --git a/nodeadmin/mainmenu.py b/nodeadmin/mainmenu.py index 73501fa..944ffeb 100755 --- a/nodeadmin/mainmenu.py +++ b/nodeadmin/mainmenu.py @@ -19,15 +19,17 @@ from snack import * import traceback -from menuscreen import MenuScreen -from nodemenu import NodeMenu -from netmenu import NetworkMenu +from menuscreen import MenuScreen +from nodemenu import NodeMenu +from netmenu import NetworkMenu +from hostmenu import HostMenu import utils import logging NODE_MENU = 1 NETWORK_MENU = 2 +HOST_MENU = 3 EXIT_CONSOLE = 99 class MainMenuScreen(MenuScreen): @@ -35,12 +37,14 @@ class MainMenuScreen(MenuScreen): MenuScreen.__init__(self, "Main Menu") def get_menu_items(self): - return (("Node Administration", NODE_MENU), - ("Network Administration", NETWORK_MENU)) + return (("Node Administration", NODE_MENU), + ("Network Administration", NETWORK_MENU), + ("Host Administration", HOST_MENU)) def handle_selection(self, page): if page is NODE_MENU: NodeMenu() elif page is NETWORK_MENU: NetworkMenu() + elif page is HOST_MENU: HostMenu() def MainMenu(): screen = MainMenuScreen() diff --git a/nodeadmin/removehost.py b/nodeadmin/removehost.py new file mode 100644 index 0000000..cf3c46c --- /dev/null +++ b/nodeadmin/removehost.py @@ -0,0 +1,66 @@ +# removehost.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * + +from configscreen import * + +SELECT_HOST_PAGE = 1 +CONFIRM_REMOVE_PAGE = 2 + +class RemoveHostConfigScreen(HostListConfigScreen): + def __init__(self): + HostListConfigScreen.__init__(self, "Remove Host Connection") + + def get_elements_for_page(self, screen, page): + if page is SELECT_HOST_PAGE: return self.get_connection_list_page(screen) + elif page is CONFIRM_REMOVE_PAGE: return self.get_confirm_remove_page(screen) + + def page_has_next(self, page): + return page is SELECT_HOST_PAGE and self.has_selectable_connections() + + def page_has_back(self, page): + return page is CONFIRM_REMOVE_PAGE + + def page_has_finish(self, page): + return page is CONFIRM_REMOVE_PAGE + + def validate_input(self, page, errors): + if page is SELECT_HOST_PAGE: return True + elif page is CONFIRM_REMOVE_PAGE: + if self.__confirm.value(): + return True + else: + errors.append("You must confirm removing the connection.") + return False + + def process_input(self, page): + if page is CONFIRM_REMOVE_PAGE: + self.get_virt_manager_config().remove_connection(self.get_selected_connection()) + self.set_finished() + + def get_confirm_remove_page(self, screen): + self.__confirm = Checkbox("Remove this connection: %s" % self.get_selected_connection(), 0) + grid = Grid(1, 1) + grid.setField(self.__confirm, 0, 0) + return [Label("Remove Host Connection"), + grid] + +def RemoveHost(): + screen = RemoveHostConfigScreen() + screen.start() -- 1.6.2.5 From jboggs at redhat.com Mon Oct 26 22:20:01 2009 From: jboggs at redhat.com (Joey Boggs) Date: Mon, 26 Oct 2009 18:20:01 -0400 Subject: [Ovirt-devel] [PATCH release] add python-devel and python-setuptools into ovirt-build spec Message-ID: <1256595601-7825-1-git-send-email-jboggs@redhat.com> --- ovirt-release.spec.in | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/ovirt-release.spec.in b/ovirt-release.spec.in index dda3d24..6ad401e 100644 --- a/ovirt-release.spec.in +++ b/ovirt-release.spec.in @@ -33,6 +33,7 @@ Requires: wget Requires: publican-ovirt Requires: python-virtinst >= 0.400 Requires: hal-devel selinux-policy-devel hardlink +Requires: python-devel python-setuptools %description -n ovirt-build Developer makefile and scripts to help automate the building of -- 1.6.2.5 From danken at redhat.com Tue Oct 27 09:07:14 2009 From: danken at redhat.com (Dan Kenigsberg) Date: Tue, 27 Oct 2009 11:07:14 +0200 Subject: [Ovirt-devel] [PATCH] inhibit error when sourced by a login shell Message-ID: <1256634434-3572-1-git-send-email-danken@redhat.com> if $0 is -bash, basename croaks and spits an annoying error. --- scripts/ovirt-functions | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/scripts/ovirt-functions b/scripts/ovirt-functions index e89898d..d25e6a2 100755 --- a/scripts/ovirt-functions +++ b/scripts/ovirt-functions @@ -605,7 +605,7 @@ chkconfig_persist() { # execute a function if called as a script, e.g. # ovirt-functions ovirt_store_config /etc/hosts -if [ "$(basename "$0")" = "ovirt-functions" ]; then +if [ "$(basename -- "$0")" = "ovirt-functions" ]; then "$@" fi -- 1.6.2.5 From jboggs at redhat.com Tue Oct 27 14:20:49 2009 From: jboggs at redhat.com (Joey Boggs) Date: Tue, 27 Oct 2009 10:20:49 -0400 Subject: [Ovirt-devel] [PATCH node] Renamed files and menu items for node administration: In-Reply-To: <1256154482-9174-1-git-send-email-dpierce@redhat.com> References: <1256154482-9174-1-git-send-email-dpierce@redhat.com> Message-ID: <4AE701C1.7020207@redhat.com> Darryl L. Pierce wrote: > define domain -> add virtual machine (addvm) > undefine domain -> remove virtual machine (rmvm) > create domain -> start virtual machine (startvm) > destroy domain -> stop virtual machine (stopvm) > list domains -> list virtual machiens (listvms) > > Signed-off-by: Darryl L. Pierce > --- > Makefile.am | 8 +- > nodeadmin/adddomain.py | 470 +++++++++++++++++++++++++++++++++++++++++++ > nodeadmin/createdomain.py | 65 ------ > nodeadmin/definedomain.py | 470 ------------------------------------------- > nodeadmin/destroydomain.py | 66 ------ > nodeadmin/listdomains.py | 4 +- > nodeadmin/nodemenu.py | 42 ++-- > nodeadmin/removedomain.py | 83 ++++++++ > nodeadmin/setup.py.in | 10 +- > nodeadmin/startdomain.py | 65 ++++++ > nodeadmin/stopdomain.py | 66 ++++++ > nodeadmin/undefinedomain.py | 83 -------- > ovirt-node.spec.in | 18 +- > 13 files changed, 725 insertions(+), 725 deletions(-) > create mode 100755 nodeadmin/adddomain.py > delete mode 100755 nodeadmin/createdomain.py > delete mode 100755 nodeadmin/definedomain.py > delete mode 100755 nodeadmin/destroydomain.py > create mode 100755 nodeadmin/removedomain.py > create mode 100755 nodeadmin/startdomain.py > create mode 100755 nodeadmin/stopdomain.py > delete mode 100755 nodeadmin/undefinedomain.py > > diff --git a/Makefile.am b/Makefile.am > index abb7c33..3ce24c1 100644 > --- a/Makefile.am > +++ b/Makefile.am > @@ -27,10 +27,10 @@ EXTRA_DIST = \ > images/grub-splash.xpm.gz \ > images/syslinux-vesa-splash.jpg \ > nodeadmin/__init__.py \ > + nodeadmin/adddomain.py \ > nodeadmin/configscreen.py \ > nodeadmin/createnetwork.py \ > nodeadmin/createuser.py \ > - nodeadmin/destroydomain.py \ > nodeadmin/destroynetwork.py \ > nodeadmin/halworker.py \ > nodeadmin/libvirtworker.py \ > @@ -39,10 +39,10 @@ EXTRA_DIST = \ > nodeadmin/menuscreen.py \ > nodeadmin/netmenu.py \ > nodeadmin/nodemenu.py \ > - nodeadmin/undefinedomain.py \ > + nodeadmin/removedomain.py \ > nodeadmin/undefinenetwork.py \ > - nodeadmin/createdomain.py \ > - nodeadmin/definedomain.py \ > + nodeadmin/startdomain.py \ > + nodeadmin/stopdomain.py \ > nodeadmin/definenet.py \ > nodeadmin/domainconfig.py \ > nodeadmin/networkconfig.py \ > diff --git a/nodeadmin/adddomain.py b/nodeadmin/adddomain.py > new file mode 100755 > index 0000000..70a2011 > --- /dev/null > +++ b/nodeadmin/adddomain.py > @@ -0,0 +1,470 @@ > +#!/usr/bin/env python > +# > +# adddomain.py - Copyright (C) 2009 Red Hat, Inc. > +# Written by Darryl L. Pierce > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +from snack import * > +import os > +from domainconfig import DomainConfig > +from configscreen import ConfigScreen > +import urlgrabber.progress as progress > +import utils > +import logging > + > +from virtinst import * > + > +VM_DETAILS_PAGE = 1 > +LOCAL_INSTALL_PAGE = 2 > +SELECT_CDROM_PAGE = 3 > +SELECT_ISO_PAGE = 4 > +NETWORK_INSTALL_PAGE = 10 > +OS_TYPE_PAGE = 11 > +OS_VARIANT_PAGE = 12 > +RAM_CPU_PAGE = 13 > +ENABLE_STORAGE_PAGE = 14 > +LOCAL_STORAGE_PAGE = 15 > +MANAGED_STORAGE_PAGE = 16 > +BRIDGE_PAGE = 17 > +VIRT_DETAILS_PAGE = 18 > +CONFIRM_PAGE = 19 > + > +LOCATION="location" > +KICKSTART="kickstart" > +KERNELOPTS="kernel.options" > +OS_TYPE="os.type" > +OS_VARIANT="os.variant" > +MEMORY="memory" > +CPUS="cpus" > + > +class DummyMeter(progress.BaseMeter): > + def _do_start(self, now = None): > + logging.info("Starting...") > + > + def _do_end(self, amount_read, now = None): > + logging.info("Ending: read=%d" % amount_read) > + > + def _do_update(self, amount_read, now = None): > + logging.info("Update: read=%d" % amount_read) > + > +class DomainConfigScreen(ConfigScreen): > + def __init__(self): > + ConfigScreen.__init__(self, "Create A New Virtual Machine") > + self.__config = DomainConfig() > + self.__config.set_architecture(self.get_libvirt().get_default_architecture()) > + self.__config.set_virt_type(self.get_libvirt().get_default_virt_type()) > + > + def get_elements_for_page(self, screen, page): > + if page == VM_DETAILS_PAGE: return self.get_vm_details_page(screen) > + elif page == LOCAL_INSTALL_PAGE: return self.get_local_install_page(screen) > + elif page == SELECT_CDROM_PAGE: return self.get_select_cdrom_page(screen) > + elif page == SELECT_ISO_PAGE: return self.get_select_iso_page(screen) > + elif page == NETWORK_INSTALL_PAGE: return self.get_network_install_page(screen) > + elif page == OS_TYPE_PAGE: return self.get_os_type_page(screen) > + elif page == OS_VARIANT_PAGE: return self.get_os_variant_page(screen) > + elif page == RAM_CPU_PAGE: return self.get_ram_and_cpu_page(screen) > + elif page == ENABLE_STORAGE_PAGE: return self.get_enable_storage_page(screen) > + elif page == LOCAL_STORAGE_PAGE: return self.get_local_storage_page(screen) > + elif page == MANAGED_STORAGE_PAGE: return self.get_managed_storage_page(screen) > + elif page == BRIDGE_PAGE: return self.get_bridge_page(screen) > + elif page == VIRT_DETAILS_PAGE: return self.get_virt_details_page(screen) > + elif page == CONFIRM_PAGE: return self.get_confirm_page(screen) > + return [] > + > + def validate_input(self, page, errors): > + if page == VM_DETAILS_PAGE: > + if len(self.__guest_name.value()) > 0: > + if self.get_libvirt().domain_exists(self.__guest_name.value()): > + errors.append("Guest name '%s' is already in use." % self.__guest_name.value()) > + else: > + return True > + else: > + errors.append("Guest name must be a string between 0 and 50 characters.") > + elif page == LOCAL_INSTALL_PAGE: > + if self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM: > + return True > + elif self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_ISO: > + return True > + elif page == SELECT_CDROM_PAGE: > + if self.__install_media.getSelection() != None: > + if len(self.get_hal().list_installable_volumes()) == 0: > + errors.append("No installable media is available.") > + else: > + return True > + else: > + errors.append("You must select an install media.") > + elif page == SELECT_ISO_PAGE: > + if len(self.__iso_path.value()) > 0: > + if os.path.exists(self.__iso_path.value()): > + if os.path.isfile(self.__iso_path.value()): > + return True > + else: > + errors.append("%s is not a file." % self.__iso_path.value()) > + else: > + errors.append("No such install media exists:") > + errors.append(self.__iso_path.value()) > + else: > + errors.append("An install media selection is required.") > + elif page == NETWORK_INSTALL_PAGE: > + if len(self.__install_url.value()) > 0: > + return True > + else: > + errors.append("An install tree is required.") > + elif page == OS_TYPE_PAGE: return True > + elif page == OS_VARIANT_PAGE: return True > + elif page == RAM_CPU_PAGE: > + if (len(self.__memory.value()) > 0 and len(self.__cpus.value()) > 0) \ > + and (int(self.__memory.value()) > 0 and int(self.__cpus.value()) > 0): > + return True > + else: > + if len(self.__memory.value()) == 0: > + errors.append("A value must be entered for memory.") > + elif int(self.__memory.value()) <= 0: > + errors.append("A positive integer value must be entered for memory.") > + if len(self.__cpus.value()) == 0: > + errors.append("A value must be entered for CPUs.") > + elif int(self.__cpus.value()) <= 0: > + errors.append("A positive integer value must be entered for memory.") > + elif page == ENABLE_STORAGE_PAGE: return True > + elif page == LOCAL_STORAGE_PAGE: > + if len(self.__storage_size.value()) > 0: > + if float(self.__storage_size.value()) > 0: > + return True > + else: > + errors.append("A positive value must be entered for the storage size.") > + else: > + errors.append("A value must be entered for the storage size.") > + elif page == MANAGED_STORAGE_PAGE: > + if self.__existing_storage.getSelection() is not None: > + return True > + else: > + errors.append("Please select a storage volume.") > + elif page == BRIDGE_PAGE: > + if self.__network_bridges.getSelection() != None: > + if len(self.__mac_address.value()) > 0: > + # TODO: regex check the format > + return True > + else: > + errors.append("MAC address must be supplied.") > + else: > + errors.append("A network bridge must be selected.") > + elif page == VIRT_DETAILS_PAGE: > + if self.__virt_types.getSelection() != None and self.__architectures.getSelection() != None: > + return True > + if self.__virt_types.getSelection() is None: > + errors.append("Please select a virtualization type.") > + if self.__architectures.getSelection() is None: > + errors.append("Please selection an architecture.") > + elif page == CONFIRM_PAGE: return True > + return False > + > + def process_input(self, page): > + if page == VM_DETAILS_PAGE: > + self.__config.set_guest_name(self.__guest_name.value()) > + self.__config.set_install_type(self.__install_type.getSelection()) > + elif page == LOCAL_INSTALL_PAGE: > + self.__config.set_use_cdrom_source(self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM) > + elif page == SELECT_CDROM_PAGE: > + self.__config.set_install_media(self.__install_media.getSelection()) > + elif page == SELECT_ISO_PAGE: > + self.__config.set_iso_path(self.__iso_path.value()) > + elif page == NETWORK_INSTALL_PAGE: > + self.__config.set_install_url(self.__install_url.value()) > + self.__config.set_kickstart_url(self.__kickstart_url.value()) > + self.__config.set_kernel_options(self.__kernel_options.value()) > + elif page == OS_TYPE_PAGE: > + self.__config.set_os_type(self.__os_types.getSelection()) > + elif page == OS_VARIANT_PAGE: > + self.__config.set_os_variant(self.__os_variants.getSelection()) > + elif page == RAM_CPU_PAGE: > + self.__config.set_memory(int(self.__memory.value())) > + self.__config.set_cpus(int(self.__cpus.value())) > + elif page == ENABLE_STORAGE_PAGE: > + self.__config.set_enable_storage(self.__enable_storage.value()) > + if self.__storage_type.getSelection() == DomainConfig.NEW_STORAGE: > + self.__config.set_use_local_storage(True) > + elif self.__storage_type.getSelection() == DomainConfig.EXISTING_STORAGE: > + self.__config.set_use_local_storage(False) > + elif page == LOCAL_STORAGE_PAGE: > + self.__config.set_storage_size(float(self.__storage_size.value())) > + self.__config.set_allocate_storage(self.__allocate_storage.value()) > + elif page == MANAGED_STORAGE_PAGE: > + self.__config.set_use_local_storage(False) > + self.__config.set_existing_storage(self.__existing_storage.getSelection()) > + self.__config.set_storage_size(self.get_libvirt().get_storage_size(self.__existing_storage.getSelection())) > + elif page == BRIDGE_PAGE: > + self.__config.set_network_bridge(self.__network_bridges.getSelection()) > + elif page == VIRT_DETAILS_PAGE: > + self.__config.set_virt_type(self.__virt_types.getSelection()) > + self.__config.set_architecture(self.__architectures.getSelection()) > + elif page == CONFIRM_PAGE: > + self.get_libvirt().define_domain(self.__config, DummyMeter()) > + self.set_finished() > + > + def get_back_page(self, page): > + result = page > + if page == OS_TYPE_PAGE: > + install_type = self.__config.get_install_type() > + if install_type == DomainConfig.LOCAL_INSTALL: > + if self.__config.get_use_cdrom_source(): > + result = SELECT_CDROM_PAGE > + else: > + result = SELECT_ISO_PAGE > + elif install_type == DomainConfig.NETWORK_INSTALL: > + result = NETWORK_INSTALL_PAGE > + elif install_type == DomainConfig.PXE_INSTALL: > + result = VM_DETAILS_PAGE > + elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: > + result = ENABLE_STORAGE_PAGE > + elif page == NETWORK_INSTALL_PAGE: > + result = VM_DETAILS_PAGE > + elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: > + result = LOCAL_INSTALL_PAGE > + elif page == BRIDGE_PAGE: > + if self.__config.get_use_local_storage(): > + result = LOCAL_STORAGE_PAGE > + else: > + result = MANAGED_STORAGE_PAGE > + else: > + if page > 1: result = page - 1 > + return result > + > + def get_next_page(self, page): > + result = page > + if page == VM_DETAILS_PAGE: > + install_type = self.__config.get_install_type() > + if install_type == DomainConfig.LOCAL_INSTALL: > + result = LOCAL_INSTALL_PAGE > + elif install_type == DomainConfig.NETWORK_INSTALL: > + result = NETWORK_INSTALL_PAGE > + elif install_type == DomainConfig.PXE_INSTALL: > + result = OS_TYPE_PAGE > + elif page == LOCAL_INSTALL_PAGE: > + if self.__config.get_use_cdrom_source(): > + result = SELECT_CDROM_PAGE > + else: > + result = SELECT_ISO_PAGE > + elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: > + result = OS_TYPE_PAGE > + elif page == NETWORK_INSTALL_PAGE: > + result = OS_TYPE_PAGE > + elif page == ENABLE_STORAGE_PAGE: > + result = BRIDGE_PAGE > + if self.__config.get_enable_storage(): > + if self.__config.get_use_local_storage(): > + result = LOCAL_STORAGE_PAGE > + else: > + result = MANAGED_STORAGE_PAGE > + elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: > + result = BRIDGE_PAGE > + else: > + result = page + 1 > + return result > + > + def page_has_finish(self, page): > + if page == CONFIRM_PAGE: return True > + return False > + > + def page_has_next(self, page): > + if page < CONFIRM_PAGE: > + return True > + > + def get_vm_details_page(self, screen): > + self.__guest_name = Entry(50, self.__config.get_guest_name()) > + self.__install_type = RadioBar(screen, (("Local install media (ISO image or CDROM)", > + DomainConfig.LOCAL_INSTALL, > + self.__config.is_install_type(DomainConfig.LOCAL_INSTALL)), > + ("Network Install (HTTP, FTP, or NFS)", > + DomainConfig.NETWORK_INSTALL, > + self.__config.is_install_type(DomainConfig.NETWORK_INSTALL)), > + ("Network Boot (PXE)", > + DomainConfig.PXE_INSTALL, > + self.__config.is_install_type(DomainConfig.PXE_INSTALL)))) > + grid = Grid(2,3) > + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) > + grid.setField(self.__guest_name, 1, 0, anchorLeft = 1) > + grid.setField(Label("Choose how you would like to install the operating system"), 1, 1, > + anchorLeft = 1, anchorTop = 1) > + grid.setField(self.__install_type, 1, 2, anchorLeft = 1) > + return [Label("Enter your machine details"), > + grid] > + > + def get_local_install_page(self, screen): > + self.__install_source = RadioBar(screen, (("Use CDROM or DVD", > + DomainConfig.INSTALL_SOURCE_CDROM, > + self.__config.get_use_cdrom_source()), > + ("Use ISO image", > + DomainConfig.INSTALL_SOURCE_ISO, > + self.__config.get_use_cdrom_source() is False))) > + grid = Grid(1,1) > + grid.setField(self.__install_source, 0, 0, anchorLeft = 1) > + return [Label("Locate your install media"), > + grid] > + > + def get_select_cdrom_page(self, screen): > + drives = [] > + media = self.get_hal().list_installable_volumes() > + for drive in media.keys(): > + drives.append([media[drive], drive, self.__config.is_install_media(drive)]) > + self.__install_media = RadioBar(screen, (drives)) > + grid = Grid(1, 1) > + grid.setField(self.__install_media, 0, 0) > + return [Label("Select the install media"), > + grid] > + > + def get_select_iso_page(self, screen): > + self.__iso_path = Entry(50, self.__config.get_iso_path()) > + grid = Grid(1, 2) > + grid.setField(Label("Enter ISO path:"), 0, 0, anchorLeft = 1) > + grid.setField(self.__iso_path, 0, 1, anchorLeft = 1) > + return [Label("Enter the full path to an install ISO"), > + grid] > + > + def get_network_install_page(self, screen): > + self.__install_url = Entry(50, self.__config.get_install_url()) > + self.__kickstart_url = Entry(50, self.__config.get_kickstart_url()) > + self.__kernel_options = Entry(50, self.__config.get_kernel_options()) > + grid = Grid(2,3) > + grid.setField(Label("URL:"), 0, 0, anchorRight = 1) > + grid.setField(self.__install_url, 1, 0, anchorLeft = 1) > + grid.setField(Label("Kickstart URL:"), 0, 1, anchorRight = 1) > + grid.setField(self.__kickstart_url, 1, 1, anchorLeft = 1) > + grid.setField(Label("Kernel Options:"), 0, 2, anchorRight = 1) > + grid.setField(self.__kernel_options, 1, 2, anchorLeft = 1) > + return [Label("Provide the operating system URL"), > + grid] > + > + def get_os_type_page(self, screen): > + types = [] > + for type in Guest.list_os_types(): > + types.append([Guest.get_os_type_label(type), type, self.__config.is_os_type(type)]) > + self.__os_types = RadioBar(screen, types) > + grid = Grid(1, 1) > + grid.setField(self.__os_types, 0, 0, anchorLeft = 1) > + return [Label("Choose the operating system type"), > + grid] > + > + def get_os_variant_page(self, screen): > + variants = [] > + type = self.__config.get_os_type() > + for variant in Guest.list_os_variants(type): > + variants.append([Guest.get_os_variant_label(type, variant), variant, self.__config.is_os_variant(variant)]) > + self.__os_variants = RadioBar(screen, variants) > + grid = Grid(1, 1) > + grid.setField(self.__os_variants, 0, 0, anchorLeft = 1) > + return [Label("Choose the operating system version"), > + grid] > + > + def get_ram_and_cpu_page(self, screen): > + self.__memory = Entry(10, str(self.__config.get_memory())) > + self.__cpus = Entry(10, str(self.__config.get_cpus())) > + grid = Grid(2,2) > + grid.setField(Label("Memory (RAM):"), 0, 0, anchorRight = 1) > + grid.setField(self.__memory, 1, 0, anchorLeft = 1) > + grid.setField(Label("CPUs:"), 0, 1, anchorRight = 1) > + grid.setField(self.__cpus, 1, 1, anchorLeft = 1) > + return [Label("Choose memory and CPU settings"), > + grid] > + > + def get_enable_storage_page(self, screen): > + self.__enable_storage = Checkbox("Enable storage for this virtual machine", self.__config.get_enable_storage()) > + self.__storage_type = RadioBar(screen,((["Create a disk image on the computer's hard disk", > + DomainConfig.NEW_STORAGE, > + self.__config.get_use_local_storage()]), > + (["Select managed or other existing storage", > + DomainConfig.EXISTING_STORAGE, > + self.__config.get_use_local_storage() is False]))) > + grid = Grid(1,2) > + grid.setField(self.__enable_storage, 0, 0, anchorLeft = 1) > + grid.setField(self.__storage_type, 0, 1, anchorLeft = 1) > + return [Label("Configure storage"), > + grid] > + > + def get_local_storage_page(self, screen): > + self.__storage_size = Entry(6, str(self.__config.get_storage_size())) > + self.__allocate_storage = Checkbox("Allocate entire disk now", self.__config.get_allocate_storage()) > + grid = Grid(2, 2) > + grid.setField(self.__allocate_storage, 0, 0, growx = 1, anchorLeft = 1) > + grid.setField(Label("Storage size (GB):"), 0, 1, anchorLeft = 1) > + grid.setField(self.__storage_size, 1, 1) > + return [Label("Configure local storage"), > + grid] > + > + def get_managed_storage_page(self, screen): > + volumes = [] > + for volume in self.get_libvirt().list_storage_volumes(): > + volumes.append(["%s (%d GB)" % (volume.name(), volume.info()[1] / (1024 ** 3)), > + volume.name(), > + self.__config.is_existing_storage(volume.name())]) > + self.__existing_storage = RadioBar(screen, (volumes)) > + grid = Grid(2, 1) > + grid.setField(Label("Existing storage:"), 0, 0) > + grid.setField(self.__existing_storage, 1, 0) > + return [Label("Configure managed storage"), > + grid] > + > + def get_bridge_page(self, screen): > + bridges = [] > + for bridge in self.get_libvirt().list_bridges(): > + bridges.append(["Virtual network '%s'" % bridge.name(), bridge.name(), self.__config.get_network_bridge() == bridge.name()]) > + self.__network_bridges = RadioBar(screen, (bridges)) > + if self.__config.get_mac_address() == None: > + self.__config.set_mac_address(self.get_libvirt().generate_mac_address()) > + self.__mac_address = Entry(20, self.__config.get_mac_address()) > + grid = Grid(1, 1) > + grid.setField(self.__network_bridges, 0, 0) > + return [Label("Select an existing bridge"), > + grid] > + > + def get_virt_details_page(self, screen): > + virt_types = [] > + for type in self.get_libvirt().list_virt_types(): > + virt_types.append([type, type, self.__config.is_virt_type(type)]) > + self.__virt_types = RadioBar(screen, (virt_types)) > + archs = [] > + for arch in self.get_libvirt().list_architectures(): > + archs.append([arch, arch, self.__config.is_architecture(arch)]) > + self.__architectures = RadioBar(screen, (archs)) > + grid = Grid(2, 2) > + grid.setField(Label("Virt Type:"), 0, 0, anchorRight = 1, anchorTop = 1) > + grid.setField(self.__virt_types, 1, 0, anchorLeft = 1) > + grid.setField(Label("Architecture:"), 0, 1, anchorRight = 1, anchorTop = 1) > + grid.setField(self.__architectures, 1, 1, anchorLeft = 1) > + return [Label("Configure virtualization details"), > + grid] > + > + def get_confirm_page(self, screen): > + grid = Grid(2, 6) > + grid.setField(Label("OS:"), 0, 0, anchorRight = 1) > + grid.setField(Label(Guest.get_os_variant_label(self.__config.get_os_type(), > + self.__config.get_os_variant())), 1, 0, anchorLeft = 1) > + grid.setField(Label("Install:"), 0, 1, anchorRight = 1) > + grid.setField(Label(self.__config.get_install_type_text()), 1, 1, anchorLeft = 1) > + grid.setField(Label("Memory:"), 0, 2, anchorRight = 1) > + grid.setField(Label("%s MB" % self.__config.get_memory()), 1, 2, anchorLeft = 1) > + grid.setField(Label("CPUs:"), 0, 3, anchorRight = 1) > + grid.setField(Label("%d" % self.__config.get_cpus()), 1, 3, anchorLeft = 1) > + grid.setField(Label("Storage:"), 0, 4, anchorRight = 1) > + grid.setField(Label(self.__config.get_existing_storage()), 1, 4, anchorLeft = 1) > + grid.setField(Label("Network:"), 0, 5, anchorRight = 1) > + grid.setField(Label(self.__config.get_network_bridge()), 1, 5, anchorLeft = 1) > + return [Label("Ready to begin installation of %s" % self.__config.get_guest_name()), > + grid] > + > +def AddDomain(): > + screen = DomainConfigScreen() > + screen.start() > diff --git a/nodeadmin/createdomain.py b/nodeadmin/createdomain.py > deleted file mode 100755 > index 6f10b44..0000000 > --- a/nodeadmin/createdomain.py > +++ /dev/null > @@ -1,65 +0,0 @@ > -#!/usr/bin/env python > -# > -# createdomain.py - Copyright (C) 2009 Red Hat, Inc. > -# Written by Darryl L. Pierce > -# > -# This program is free software; you can redistribute it and/or modify > -# it under the terms of the GNU General Public License as published by > -# the Free Software Foundation; version 2 of the License. > -# > -# This program is distributed in the hope that it will be useful, > -# but WITHOUT ANY WARRANTY; without even the implied warranty of > -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > -# GNU General Public License for more details. > -# > -# You should have received a copy of the GNU General Public License > -# along with this program; if not, write to the Free Software > -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > -# MA 02110-1301, USA. A copy of the GNU General Public License is > -# also available at http://www.gnu.org/copyleft/gpl.html. > - > -from snack import * > -from configscreen import * > - > -class CreateDomainConfigScreen(DomainListConfigScreen): > - LIST_PAGE = 1 > - CREATE_PAGE = 2 > - > - def __init__(self): > - DomainListConfigScreen.__init__(self, "Create A Domain") > - > - def get_elements_for_page(self, screen, page): > - if page is self.LIST_PAGE: > - return self.get_domain_list_page(screen, created = False) > - elif page is self.CREATE_PAGE: > - return self.get_create_domain_page(screen) > - > - def page_has_next(self, page): > - if page is self.LIST_PAGE: return self.has_selectable_domains() > - return False > - > - def page_has_back(self, page): > - if page is self.CREATE_PAGE: return True > - return False > - > - def validate_input(self, page, errors): > - if page is self.LIST_PAGE: > - if self.get_selected_domain() is not None: > - domain = self.get_selected_domain() > - try: > - self.get_libvirt().create_domain(domain) > - return True > - except Exception, error: > - errors.append("There was an error creating the domain: %s" % domain) > - errors.append(str(error)) > - else: > - errors.append("You must first select a domain to create.") > - > - def get_create_domain_page(self, screen): > - grid = Grid(1, 1) > - grid.setField(Label("%s was successfully created." % self.get_selected_domain()), 0, 0) > - return [grid] > - > -def CreateDomain(): > - screen = CreateDomainConfigScreen() > - screen.start() > diff --git a/nodeadmin/definedomain.py b/nodeadmin/definedomain.py > deleted file mode 100755 > index 3fffca2..0000000 > --- a/nodeadmin/definedomain.py > +++ /dev/null > @@ -1,470 +0,0 @@ > -#!/usr/bin/env python > -# > -# definedomain.py - Copyright (C) 2009 Red Hat, Inc. > -# Written by Darryl L. Pierce > -# > -# This program is free software; you can redistribute it and/or modify > -# it under the terms of the GNU General Public License as published by > -# the Free Software Foundation; version 2 of the License. > -# > -# This program is distributed in the hope that it will be useful, > -# but WITHOUT ANY WARRANTY; without even the implied warranty of > -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > -# GNU General Public License for more details. > -# > -# You should have received a copy of the GNU General Public License > -# along with this program; if not, write to the Free Software > -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > -# MA 02110-1301, USA. A copy of the GNU General Public License is > -# also available at http://www.gnu.org/copyleft/gpl.html. > - > -from snack import * > -import os > -from domainconfig import DomainConfig > -from configscreen import ConfigScreen > -import urlgrabber.progress as progress > -import utils > -import logging > - > -from virtinst import * > - > -VM_DETAILS_PAGE = 1 > -LOCAL_INSTALL_PAGE = 2 > -SELECT_CDROM_PAGE = 3 > -SELECT_ISO_PAGE = 4 > -NETWORK_INSTALL_PAGE = 10 > -OS_TYPE_PAGE = 11 > -OS_VARIANT_PAGE = 12 > -RAM_CPU_PAGE = 13 > -ENABLE_STORAGE_PAGE = 14 > -LOCAL_STORAGE_PAGE = 15 > -MANAGED_STORAGE_PAGE = 16 > -BRIDGE_PAGE = 17 > -VIRT_DETAILS_PAGE = 18 > -CONFIRM_PAGE = 19 > - > -LOCATION="location" > -KICKSTART="kickstart" > -KERNELOPTS="kernel.options" > -OS_TYPE="os.type" > -OS_VARIANT="os.variant" > -MEMORY="memory" > -CPUS="cpus" > - > -class DummyMeter(progress.BaseMeter): > - def _do_start(self, now = None): > - logging.info("Starting...") > - > - def _do_end(self, amount_read, now = None): > - logging.info("Ending: read=%d" % amount_read) > - > - def _do_update(self, amount_read, now = None): > - logging.info("Update: read=%d" % amount_read) > - > -class DomainConfigScreen(ConfigScreen): > - def __init__(self): > - ConfigScreen.__init__(self, "Create A New Virtual Machine") > - self.__config = DomainConfig() > - self.__config.set_architecture(self.get_libvirt().get_default_architecture()) > - self.__config.set_virt_type(self.get_libvirt().get_default_virt_type()) > - > - def get_elements_for_page(self, screen, page): > - if page == VM_DETAILS_PAGE: return self.get_vm_details_page(screen) > - elif page == LOCAL_INSTALL_PAGE: return self.get_local_install_page(screen) > - elif page == SELECT_CDROM_PAGE: return self.get_select_cdrom_page(screen) > - elif page == SELECT_ISO_PAGE: return self.get_select_iso_page(screen) > - elif page == NETWORK_INSTALL_PAGE: return self.get_network_install_page(screen) > - elif page == OS_TYPE_PAGE: return self.get_os_type_page(screen) > - elif page == OS_VARIANT_PAGE: return self.get_os_variant_page(screen) > - elif page == RAM_CPU_PAGE: return self.get_ram_and_cpu_page(screen) > - elif page == ENABLE_STORAGE_PAGE: return self.get_enable_storage_page(screen) > - elif page == LOCAL_STORAGE_PAGE: return self.get_local_storage_page(screen) > - elif page == MANAGED_STORAGE_PAGE: return self.get_managed_storage_page(screen) > - elif page == BRIDGE_PAGE: return self.get_bridge_page(screen) > - elif page == VIRT_DETAILS_PAGE: return self.get_virt_details_page(screen) > - elif page == CONFIRM_PAGE: return self.get_confirm_page(screen) > - return [] > - > - def validate_input(self, page, errors): > - if page == VM_DETAILS_PAGE: > - if len(self.__guest_name.value()) > 0: > - if self.get_libvirt().domain_exists(self.__guest_name.value()): > - errors.append("Guest name '%s' is already in use." % self.__guest_name.value()) > - else: > - return True > - else: > - errors.append("Guest name must be a string between 0 and 50 characters.") > - elif page == LOCAL_INSTALL_PAGE: > - if self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM: > - return True > - elif self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_ISO: > - return True > - elif page == SELECT_CDROM_PAGE: > - if self.__install_media.getSelection() != None: > - if len(self.get_hal().list_installable_volumes()) == 0: > - errors.append("No installable media is available.") > - else: > - return True > - else: > - errors.append("You must select an install media.") > - elif page == SELECT_ISO_PAGE: > - if len(self.__iso_path.value()) > 0: > - if os.path.exists(self.__iso_path.value()): > - if os.path.isfile(self.__iso_path.value()): > - return True > - else: > - errors.append("%s is not a file." % self.__iso_path.value()) > - else: > - errors.append("No such install media exists:") > - errors.append(self.__iso_path.value()) > - else: > - errors.append("An install media selection is required.") > - elif page == NETWORK_INSTALL_PAGE: > - if len(self.__install_url.value()) > 0: > - return True > - else: > - errors.append("An install tree is required.") > - elif page == OS_TYPE_PAGE: return True > - elif page == OS_VARIANT_PAGE: return True > - elif page == RAM_CPU_PAGE: > - if (len(self.__memory.value()) > 0 and len(self.__cpus.value()) > 0) \ > - and (int(self.__memory.value()) > 0 and int(self.__cpus.value()) > 0): > - return True > - else: > - if len(self.__memory.value()) == 0: > - errors.append("A value must be entered for memory.") > - elif int(self.__memory.value()) <= 0: > - errors.append("A positive integer value must be entered for memory.") > - if len(self.__cpus.value()) == 0: > - errors.append("A value must be entered for CPUs.") > - elif int(self.__cpus.value()) <= 0: > - errors.append("A positive integer value must be entered for memory.") > - elif page == ENABLE_STORAGE_PAGE: return True > - elif page == LOCAL_STORAGE_PAGE: > - if len(self.__storage_size.value()) > 0: > - if float(self.__storage_size.value()) > 0: > - return True > - else: > - errors.append("A positive value must be entered for the storage size.") > - else: > - errors.append("A value must be entered for the storage size.") > - elif page == MANAGED_STORAGE_PAGE: > - if self.__existing_storage.getSelection() is not None: > - return True > - else: > - errors.append("Please select a storage volume.") > - elif page == BRIDGE_PAGE: > - if self.__network_bridges.getSelection() != None: > - if len(self.__mac_address.value()) > 0: > - # TODO: regex check the format > - return True > - else: > - errors.append("MAC address must be supplied.") > - else: > - errors.append("A network bridge must be selected.") > - elif page == VIRT_DETAILS_PAGE: > - if self.__virt_types.getSelection() != None and self.__architectures.getSelection() != None: > - return True > - if self.__virt_types.getSelection() is None: > - errors.append("Please select a virtualization type.") > - if self.__architectures.getSelection() is None: > - errors.append("Please selection an architecture.") > - elif page == CONFIRM_PAGE: return True > - return False > - > - def process_input(self, page): > - if page == VM_DETAILS_PAGE: > - self.__config.set_guest_name(self.__guest_name.value()) > - self.__config.set_install_type(self.__install_type.getSelection()) > - elif page == LOCAL_INSTALL_PAGE: > - self.__config.set_use_cdrom_source(self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM) > - elif page == SELECT_CDROM_PAGE: > - self.__config.set_install_media(self.__install_media.getSelection()) > - elif page == SELECT_ISO_PAGE: > - self.__config.set_iso_path(self.__iso_path.value()) > - elif page == NETWORK_INSTALL_PAGE: > - self.__config.set_install_url(self.__install_url.value()) > - self.__config.set_kickstart_url(self.__kickstart_url.value()) > - self.__config.set_kernel_options(self.__kernel_options.value()) > - elif page == OS_TYPE_PAGE: > - self.__config.set_os_type(self.__os_types.getSelection()) > - elif page == OS_VARIANT_PAGE: > - self.__config.set_os_variant(self.__os_variants.getSelection()) > - elif page == RAM_CPU_PAGE: > - self.__config.set_memory(int(self.__memory.value())) > - self.__config.set_cpus(int(self.__cpus.value())) > - elif page == ENABLE_STORAGE_PAGE: > - self.__config.set_enable_storage(self.__enable_storage.value()) > - if self.__storage_type.getSelection() == DomainConfig.NEW_STORAGE: > - self.__config.set_use_local_storage(True) > - elif self.__storage_type.getSelection() == DomainConfig.EXISTING_STORAGE: > - self.__config.set_use_local_storage(False) > - elif page == LOCAL_STORAGE_PAGE: > - self.__config.set_storage_size(float(self.__storage_size.value())) > - self.__config.set_allocate_storage(self.__allocate_storage.value()) > - elif page == MANAGED_STORAGE_PAGE: > - self.__config.set_use_local_storage(False) > - self.__config.set_existing_storage(self.__existing_storage.getSelection()) > - self.__config.set_storage_size(self.get_libvirt().get_storage_size(self.__existing_storage.getSelection())) > - elif page == BRIDGE_PAGE: > - self.__config.set_network_bridge(self.__network_bridges.getSelection()) > - elif page == VIRT_DETAILS_PAGE: > - self.__config.set_virt_type(self.__virt_types.getSelection()) > - self.__config.set_architecture(self.__architectures.getSelection()) > - elif page == CONFIRM_PAGE: > - self.get_libvirt().define_domain(self.__config, DummyMeter()) > - self.set_finished() > - > - def get_back_page(self, page): > - result = page > - if page == OS_TYPE_PAGE: > - install_type = self.__config.get_install_type() > - if install_type == DomainConfig.LOCAL_INSTALL: > - if self.__config.get_use_cdrom_source(): > - result = SELECT_CDROM_PAGE > - else: > - result = SELECT_ISO_PAGE > - elif install_type == DomainConfig.NETWORK_INSTALL: > - result = NETWORK_INSTALL_PAGE > - elif install_type == DomainConfig.PXE_INSTALL: > - result = VM_DETAILS_PAGE > - elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: > - result = ENABLE_STORAGE_PAGE > - elif page == NETWORK_INSTALL_PAGE: > - result = VM_DETAILS_PAGE > - elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: > - result = LOCAL_INSTALL_PAGE > - elif page == BRIDGE_PAGE: > - if self.__config.get_use_local_storage(): > - result = LOCAL_STORAGE_PAGE > - else: > - result = MANAGED_STORAGE_PAGE > - else: > - if page > 1: result = page - 1 > - return result > - > - def get_next_page(self, page): > - result = page > - if page == VM_DETAILS_PAGE: > - install_type = self.__config.get_install_type() > - if install_type == DomainConfig.LOCAL_INSTALL: > - result = LOCAL_INSTALL_PAGE > - elif install_type == DomainConfig.NETWORK_INSTALL: > - result = NETWORK_INSTALL_PAGE > - elif install_type == DomainConfig.PXE_INSTALL: > - result = OS_TYPE_PAGE > - elif page == LOCAL_INSTALL_PAGE: > - if self.__config.get_use_cdrom_source(): > - result = SELECT_CDROM_PAGE > - else: > - result = SELECT_ISO_PAGE > - elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: > - result = OS_TYPE_PAGE > - elif page == NETWORK_INSTALL_PAGE: > - result = OS_TYPE_PAGE > - elif page == ENABLE_STORAGE_PAGE: > - result = BRIDGE_PAGE > - if self.__config.get_enable_storage(): > - if self.__config.get_use_local_storage(): > - result = LOCAL_STORAGE_PAGE > - else: > - result = MANAGED_STORAGE_PAGE > - elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: > - result = BRIDGE_PAGE > - else: > - result = page + 1 > - return result > - > - def page_has_finish(self, page): > - if page == CONFIRM_PAGE: return True > - return False > - > - def page_has_next(self, page): > - if page < CONFIRM_PAGE: > - return True > - > - def get_vm_details_page(self, screen): > - self.__guest_name = Entry(50, self.__config.get_guest_name()) > - self.__install_type = RadioBar(screen, (("Local install media (ISO image or CDROM)", > - DomainConfig.LOCAL_INSTALL, > - self.__config.is_install_type(DomainConfig.LOCAL_INSTALL)), > - ("Network Install (HTTP, FTP, or NFS)", > - DomainConfig.NETWORK_INSTALL, > - self.__config.is_install_type(DomainConfig.NETWORK_INSTALL)), > - ("Network Boot (PXE)", > - DomainConfig.PXE_INSTALL, > - self.__config.is_install_type(DomainConfig.PXE_INSTALL)))) > - grid = Grid(2,3) > - grid.setField(Label("Name:"), 0, 0, anchorRight = 1) > - grid.setField(self.__guest_name, 1, 0, anchorLeft = 1) > - grid.setField(Label("Choose how you would like to install the operating system"), 1, 1, > - anchorLeft = 1, anchorTop = 1) > - grid.setField(self.__install_type, 1, 2, anchorLeft = 1) > - return [Label("Enter your machine details"), > - grid] > - > - def get_local_install_page(self, screen): > - self.__install_source = RadioBar(screen, (("Use CDROM or DVD", > - DomainConfig.INSTALL_SOURCE_CDROM, > - self.__config.get_use_cdrom_source()), > - ("Use ISO image", > - DomainConfig.INSTALL_SOURCE_ISO, > - self.__config.get_use_cdrom_source() is False))) > - grid = Grid(1,1) > - grid.setField(self.__install_source, 0, 0, anchorLeft = 1) > - return [Label("Locate your install media"), > - grid] > - > - def get_select_cdrom_page(self, screen): > - drives = [] > - media = self.get_hal().list_installable_volumes() > - for drive in media.keys(): > - drives.append([media[drive], drive, self.__config.is_install_media(drive)]) > - self.__install_media = RadioBar(screen, (drives)) > - grid = Grid(1, 1) > - grid.setField(self.__install_media, 0, 0) > - return [Label("Select the install media"), > - grid] > - > - def get_select_iso_page(self, screen): > - self.__iso_path = Entry(50, self.__config.get_iso_path()) > - grid = Grid(1, 2) > - grid.setField(Label("Enter ISO path:"), 0, 0, anchorLeft = 1) > - grid.setField(self.__iso_path, 0, 1, anchorLeft = 1) > - return [Label("Enter the full path to an install ISO"), > - grid] > - > - def get_network_install_page(self, screen): > - self.__install_url = Entry(50, self.__config.get_install_url()) > - self.__kickstart_url = Entry(50, self.__config.get_kickstart_url()) > - self.__kernel_options = Entry(50, self.__config.get_kernel_options()) > - grid = Grid(2,3) > - grid.setField(Label("URL:"), 0, 0, anchorRight = 1) > - grid.setField(self.__install_url, 1, 0, anchorLeft = 1) > - grid.setField(Label("Kickstart URL:"), 0, 1, anchorRight = 1) > - grid.setField(self.__kickstart_url, 1, 1, anchorLeft = 1) > - grid.setField(Label("Kernel Options:"), 0, 2, anchorRight = 1) > - grid.setField(self.__kernel_options, 1, 2, anchorLeft = 1) > - return [Label("Provide the operating system URL"), > - grid] > - > - def get_os_type_page(self, screen): > - types = [] > - for type in Guest.list_os_types(): > - types.append([Guest.get_os_type_label(type), type, self.__config.is_os_type(type)]) > - self.__os_types = RadioBar(screen, types) > - grid = Grid(1, 1) > - grid.setField(self.__os_types, 0, 0, anchorLeft = 1) > - return [Label("Choose the operating system type"), > - grid] > - > - def get_os_variant_page(self, screen): > - variants = [] > - type = self.__config.get_os_type() > - for variant in Guest.list_os_variants(type): > - variants.append([Guest.get_os_variant_label(type, variant), variant, self.__config.is_os_variant(variant)]) > - self.__os_variants = RadioBar(screen, variants) > - grid = Grid(1, 1) > - grid.setField(self.__os_variants, 0, 0, anchorLeft = 1) > - return [Label("Choose the operating system version"), > - grid] > - > - def get_ram_and_cpu_page(self, screen): > - self.__memory = Entry(10, str(self.__config.get_memory())) > - self.__cpus = Entry(10, str(self.__config.get_cpus())) > - grid = Grid(2,2) > - grid.setField(Label("Memory (RAM):"), 0, 0, anchorRight = 1) > - grid.setField(self.__memory, 1, 0, anchorLeft = 1) > - grid.setField(Label("CPUs:"), 0, 1, anchorRight = 1) > - grid.setField(self.__cpus, 1, 1, anchorLeft = 1) > - return [Label("Choose memory and CPU settings"), > - grid] > - > - def get_enable_storage_page(self, screen): > - self.__enable_storage = Checkbox("Enable storage for this virtual machine", self.__config.get_enable_storage()) > - self.__storage_type = RadioBar(screen,((["Create a disk image on the computer's hard disk", > - DomainConfig.NEW_STORAGE, > - self.__config.get_use_local_storage()]), > - (["Select managed or other existing storage", > - DomainConfig.EXISTING_STORAGE, > - self.__config.get_use_local_storage() is False]))) > - grid = Grid(1,2) > - grid.setField(self.__enable_storage, 0, 0, anchorLeft = 1) > - grid.setField(self.__storage_type, 0, 1, anchorLeft = 1) > - return [Label("Configure storage"), > - grid] > - > - def get_local_storage_page(self, screen): > - self.__storage_size = Entry(6, str(self.__config.get_storage_size())) > - self.__allocate_storage = Checkbox("Allocate entire disk now", self.__config.get_allocate_storage()) > - grid = Grid(2, 2) > - grid.setField(self.__allocate_storage, 0, 0, growx = 1, anchorLeft = 1) > - grid.setField(Label("Storage size (GB):"), 0, 1, anchorLeft = 1) > - grid.setField(self.__storage_size, 1, 1) > - return [Label("Configure local storage"), > - grid] > - > - def get_managed_storage_page(self, screen): > - volumes = [] > - for volume in self.get_libvirt().list_storage_volumes(): > - volumes.append(["%s (%d GB)" % (volume.name(), volume.info()[1] / (1024 ** 3)), > - volume.name(), > - self.__config.is_existing_storage(volume.name())]) > - self.__existing_storage = RadioBar(screen, (volumes)) > - grid = Grid(2, 1) > - grid.setField(Label("Existing storage:"), 0, 0) > - grid.setField(self.__existing_storage, 1, 0) > - return [Label("Configure managed storage"), > - grid] > - > - def get_bridge_page(self, screen): > - bridges = [] > - for bridge in self.get_libvirt().list_bridges(): > - bridges.append(["Virtual network '%s'" % bridge.name(), bridge.name(), self.__config.get_network_bridge() == bridge.name()]) > - self.__network_bridges = RadioBar(screen, (bridges)) > - if self.__config.get_mac_address() == None: > - self.__config.set_mac_address(self.get_libvirt().generate_mac_address()) > - self.__mac_address = Entry(20, self.__config.get_mac_address()) > - grid = Grid(1, 1) > - grid.setField(self.__network_bridges, 0, 0) > - return [Label("Select an existing bridge"), > - grid] > - > - def get_virt_details_page(self, screen): > - virt_types = [] > - for type in self.get_libvirt().list_virt_types(): > - virt_types.append([type, type, self.__config.is_virt_type(type)]) > - self.__virt_types = RadioBar(screen, (virt_types)) > - archs = [] > - for arch in self.get_libvirt().list_architectures(): > - archs.append([arch, arch, self.__config.is_architecture(arch)]) > - self.__architectures = RadioBar(screen, (archs)) > - grid = Grid(2, 2) > - grid.setField(Label("Virt Type:"), 0, 0, anchorRight = 1, anchorTop = 1) > - grid.setField(self.__virt_types, 1, 0, anchorLeft = 1) > - grid.setField(Label("Architecture:"), 0, 1, anchorRight = 1, anchorTop = 1) > - grid.setField(self.__architectures, 1, 1, anchorLeft = 1) > - return [Label("Configure virtualization details"), > - grid] > - > - def get_confirm_page(self, screen): > - grid = Grid(2, 6) > - grid.setField(Label("OS:"), 0, 0, anchorRight = 1) > - grid.setField(Label(Guest.get_os_variant_label(self.__config.get_os_type(), > - self.__config.get_os_variant())), 1, 0, anchorLeft = 1) > - grid.setField(Label("Install:"), 0, 1, anchorRight = 1) > - grid.setField(Label(self.__config.get_install_type_text()), 1, 1, anchorLeft = 1) > - grid.setField(Label("Memory:"), 0, 2, anchorRight = 1) > - grid.setField(Label("%s MB" % self.__config.get_memory()), 1, 2, anchorLeft = 1) > - grid.setField(Label("CPUs:"), 0, 3, anchorRight = 1) > - grid.setField(Label("%d" % self.__config.get_cpus()), 1, 3, anchorLeft = 1) > - grid.setField(Label("Storage:"), 0, 4, anchorRight = 1) > - grid.setField(Label(self.__config.get_existing_storage()), 1, 4, anchorLeft = 1) > - grid.setField(Label("Network:"), 0, 5, anchorRight = 1) > - grid.setField(Label(self.__config.get_network_bridge()), 1, 5, anchorLeft = 1) > - return [Label("Ready to begin installation of %s" % self.__config.get_guest_name()), > - grid] > - > -def DefineDomain(): > - screen = DomainConfigScreen() > - screen.start() > diff --git a/nodeadmin/destroydomain.py b/nodeadmin/destroydomain.py > deleted file mode 100755 > index 350c32e..0000000 > --- a/nodeadmin/destroydomain.py > +++ /dev/null > @@ -1,66 +0,0 @@ > -#!/usr/bin/env python > -# > -# destroydomain.py - Copyright (C) 2009 Red Hat, Inc. > -# Written by Darryl L. Pierce > -# > -# This program is free software; you can redistribute it and/or modify > -# it under the terms of the GNU General Public License as published by > -# the Free Software Foundation; version 2 of the License. > -# > -# This program is distributed in the hope that it will be useful, > -# but WITHOUT ANY WARRANTY; without even the implied warranty of > -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > -# GNU General Public License for more details. > -# > -# You should have received a copy of the GNU General Public License > -# along with this program; if not, write to the Free Software > -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > -# MA 02110-1301, USA. A copy of the GNU General Public License is > -# also available at http://www.gnu.org/copyleft/gpl.html. > - > -from snack import * > -from configscreen import * > - > -class DestroyDomainConfigScreen(DomainListConfigScreen): > - LIST_PAGE = 1 > - DESTROY_PAGE = 2 > - > - def __init__(self): > - DomainListConfigScreen.__init__(self, "Destroy A Domain") > - > - def get_elements_for_page(self, screen, page): > - if page is self.LIST_PAGE: > - return self.get_domain_list_page(screen, defined = False) > - elif page is self.DESTROY_PAGE: > - return self.get_destroy_page(screen) > - > - def page_has_next(self, page): > - if page is self.LIST_PAGE: return self.has_selectable_domains() > - return False > - > - def page_has_back(self, page): > - if page is self.DESTROY_PAGE: return True > - return False > - > - def validate_input(self, page, errors): > - if page is self.LIST_PAGE: > - if self.get_selected_domain() is not None: > - domain = self.get_selected_domain() > - try: > - self.get_libvirt().destroy_domain(domain) > - return True > - except Exception, error: > - errors.append("There was an error destroy the domain: %s" % domain) > - errors.append(str(error)) > - else: > - errors.append("You must first select a domain to destroy.") > - return False > - > - def get_destroy_page(self, screen): > - grid = Grid(1, 1) > - grid.setField(Label("%s was successfully destroyed." % self.get_selected_domain()), 0, 0) > - return [grid] > - > -def DestroyDomain(): > - screen = DestroyDomainConfigScreen() > - screen.start() > diff --git a/nodeadmin/listdomains.py b/nodeadmin/listdomains.py > index 1b51ee2..7468bcf 100755 > --- a/nodeadmin/listdomains.py > +++ b/nodeadmin/listdomains.py > @@ -27,7 +27,7 @@ class ListDomainsConfigScreen(DomainListConfigScreen): > DETAIL_PAGE = 2 > > def __init__(self): > - DomainListConfigScreen.__init__(self, 'List Domains') > + DomainListConfigScreen.__init__(self, 'List Virtual Machines') > > def page_has_next(self, page): > return (page == self.LIST_PAGE) > @@ -38,7 +38,7 @@ class ListDomainsConfigScreen(DomainListConfigScreen): > def validate_input(self, page, errors): > if page == self.LIST_PAGE: > if self.get_selected_domain() is None: > - errors.append("Please select a domain to view.") > + errors.append("Please select a virtual machine to view.") > else: > return True > > diff --git a/nodeadmin/nodemenu.py b/nodeadmin/nodemenu.py > index 9e339ff..0503b2e 100755 > --- a/nodeadmin/nodemenu.py > +++ b/nodeadmin/nodemenu.py > @@ -21,42 +21,42 @@ import traceback > > from menuscreen import MenuScreen > from configscreen import ConfigScreen > -from definedomain import DefineDomain > +from adddomain import AddDomain > from createdomain import CreateDomain > -from destroydomain import DestroyDomain > -from undefinedomain import UndefineDomain > +from stopdomain import StopDomain > +from removedomain import RemoveDomain > from listdomains import ListDomains > from createuser import CreateUser > > import utils > import logging > > -DEFINE_DOMAIN = 1 > -CREATE_DOMAIN = 2 > -DESTROY_DOMAIN = 3 > -UNDEFINE_DOMAIN = 4 > -LIST_DOMAINS = 5 > -CREATE_USER = 6 > +ADD_DOMAIN = 1 > +CREATE_DOMAIN = 2 > +STOP_DOMAIN = 3 > +REMOVE_DOMAIN = 4 > +LIST_DOMAINS = 5 > +CREATE_USER = 6 > > class NodeMenuScreen(MenuScreen): > def __init__(self): > MenuScreen.__init__(self, "Node Administration") > > def get_menu_items(self): > - return (("Define A Domain", DEFINE_DOMAIN), > - ("Create A Domain", CREATE_DOMAIN), > - ("Destroy A Domain", DESTROY_DOMAIN), > - ("Undefine A Domain", UNDEFINE_DOMAIN), > - ("List All Domains", LIST_DOMAINS), > - ("Create A User", CREATE_USER)) > + return (("Add A Virtual Machine", ADD_DOMAIN), > + ("Create A Virtual Machine", CREATE_DOMAIN), > + ("Stop A Virtual Machine", STOP_DOMAIN), > + ("Remove A Virtual Machine", REMOVE_DOMAIN), > + ("List All Virtual Machines", LIST_DOMAINS), > + ("Create A User", CREATE_USER)) > > def handle_selection(self, item): > - if item is DEFINE_DOMAIN: DefineDomain() > - elif item is CREATE_DOMAIN: CreateDomain() > - elif item is DESTROY_DOMAIN: DestroyDomain() > - elif item is UNDEFINE_DOMAIN: UndefineDomain() > - elif item is LIST_DOMAINS: ListDomains() > - elif item is CREATE_USER: CreateUser() > + if item is ADD_DOMAIN: AddDomain() > + elif item is CREATE_DOMAIN: CreateDomain() > + elif item is STOP_DOMAIN: StopDomain() > + elif item is REMOVE_DOMAIN: RemoveDomain() > + elif item is LIST_DOMAINS: ListDomains() > + elif item is CREATE_USER: CreateUser() > > def NodeMenu(): > screen = NodeMenuScreen() > diff --git a/nodeadmin/removedomain.py b/nodeadmin/removedomain.py > new file mode 100755 > index 0000000..4e31428 > --- /dev/null > +++ b/nodeadmin/removedomain.py > @@ -0,0 +1,83 @@ > +#!/usr/bin/env python > +# > +# removedomain.py - Copyright (C) 2009 Red Hat, Inc. > +# Written by Darryl L. Pierce > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +from snack import * > +from configscreen import * > + > +class RemoveDomainConfigScreen(DomainListConfigScreen): > + LIST_PAGE = 1 > + CONFIRM_PAGE = 2 > + REMOVE_PAGE = 3 > + > + def __init__(self): > + DomainListConfigScreen.__init__(self, "Remove A Domain") > + > + def get_elements_for_page(self, screen, page): > + if page is self.LIST_PAGE: return self.get_domain_list_page(screen) > + elif page is self.CONFIRM_PAGE: return self.get_confirm_page(screen) > + elif page is self.REMOVE_PAGE: return self.get_remove_page(screen) > + > + def page_has_next(self, page): > + if page is self.LIST_PAGE: return self.has_selectable_domains() > + elif page is self.CONFIRM_PAGE: return True > + return False > + > + def page_has_back(self, page): > + if page is self.CONFIRM_PAGE: return True > + elif page is self.REMOVE_PAGE: return True > + return False > + > + def get_back_page(self, page): > + if page is self.CONFIRM_PAGE: return self.LIST_PAGE > + elif page is self.REMOVE_PAGE: return self.LIST_PAGE > + > + def validate_input(self, page, errors): > + if page is self.LIST_PAGE: > + if self.get_selected_domain() is not None: > + return True > + else: > + errors.append("You must first select a domain.") > + elif page is self.CONFIRM_PAGE: > + if self.__confirm_remove.value(): > + domain = self.get_selected_domain() > + try: > + self.get_libvirt().remove_domain(domain) > + return True > + except Exception, error: > + errors.append("Failed to remove %s." % domain) > + errors.append(str(error)) > + else: > + errors.append("You must confirm undefining the domain to proceed.") > + return False > + > + def get_confirm_page(self, screen): > + self.__confirm_remove = Checkbox("Check here to confirm undefining %s." % self.get_selected_domain(), 0) > + grid = Grid(1, 1) > + grid.setField(self.__confirm_remove, 0, 0) > + return [grid] > + > + def get_remove_page(self, screen): > + grid = Grid(1, 1) > + grid.setField(Label("%s has been removed." % self.get_selected_domain()), 0, 0) > + return [grid] > + > +def RemoveDomain(): > + screen = RemoveDomainConfigScreen() > + screen.start() > diff --git a/nodeadmin/setup.py.in b/nodeadmin/setup.py.in > index 3635810..1e6e028 100644 > --- a/nodeadmin/setup.py.in > +++ b/nodeadmin/setup.py.in > @@ -25,12 +25,12 @@ setup(name = "nodeadmin", > entry_points = { > 'console_scripts': [ > 'nodeadmin = nodeadmin.nodeadmin:NodeAdmin', > - 'definedom = nodeadmin.definedomain:DefineDomain', > - 'createdom = nodeadmin.createdomain:CreateDomain', > - 'destroydom = nodeadmin.destroydomain:DestroyDomain', > - 'undefinedom = nodeadmin.undefinedomain:UndefineDomain', > + 'addvm = nodeadmin.adddomain:AddDomain', > + 'startvm = nodeadmin.startdomain:StartDomain', > + 'stopvm = nodeadmin.stopdomain:StopDomain', > + 'rmvm = nodeadmin.removedomain:RemoveDomain', > 'createuser = nodeadmin.createuser:CreateUser', > - 'listdoms = nodeadmin.listdomains:ListDomains', > + 'listvms = nodeadmin.listdomains:ListDomains', > 'definenet = nodeadmin.definenet:DefineNetwork', > 'createnet = nodeadmin.createnetwork:CreateNetwork', > 'destroynet = nodeadmin.destroynetwork:DestroyNetwork', > diff --git a/nodeadmin/startdomain.py b/nodeadmin/startdomain.py > new file mode 100755 > index 0000000..082ac0d > --- /dev/null > +++ b/nodeadmin/startdomain.py > @@ -0,0 +1,65 @@ > +#!/usr/bin/env python > +# > +# startdomain.py - Copyright (C) 2009 Red Hat, Inc. > +# Written by Darryl L. Pierce > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +from snack import * > +from configscreen import * > + > +class StartDomainConfigScreen(DomainListConfigScreen): > + LIST_PAGE = 1 > + START_PAGE = 2 > + > + def __init__(self): > + DomainListConfigScreen.__init__(self, "Start A Domain") > + > + def get_elements_for_page(self, screen, page): > + if page is self.LIST_PAGE: > + return self.get_domain_list_page(screen, started = False) > + elif page is self.START_PAGE: > + return self.get_start_domain_page(screen) > + > + def page_has_next(self, page): > + if page is self.LIST_PAGE: return self.has_selectable_domains() > + return False > + > + def page_has_back(self, page): > + if page is self.START_PAGE: return True > + return False > + > + def validate_input(self, page, errors): > + if page is self.LIST_PAGE: > + if self.get_selected_domain() is not None: > + domain = self.get_selected_domain() > + try: > + self.get_libvirt().start_domain(domain) > + return True > + except Exception, error: > + errors.append("There was an error creating the domain: %s" % domain) > + errors.append(str(error)) > + else: > + errors.append("You must first select a domain to start.") > + > + def get_start_domain_page(self, screen): > + grid = Grid(1, 1) > + grid.setField(Label("%s was successfully started." % self.get_selected_domain()), 0, 0) > + return [grid] > + > +def StartDomain(): > + screen = StartDomainConfigScreen() > + screen.start() > diff --git a/nodeadmin/stopdomain.py b/nodeadmin/stopdomain.py > new file mode 100755 > index 0000000..3ddd681 > --- /dev/null > +++ b/nodeadmin/stopdomain.py > @@ -0,0 +1,66 @@ > +#!/usr/bin/env python > +# > +# stopdomain.py - Copyright (C) 2009 Red Hat, Inc. > +# Written by Darryl L. Pierce > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +from snack import * > +from configscreen import * > + > +class StopDomainConfigScreen(DomainListConfigScreen): > + LIST_PAGE = 1 > + STOP_PAGE = 2 > + > + def __init__(self): > + DomainListConfigScreen.__init__(self, "Stop A Domain") > + > + def get_elements_for_page(self, screen, page): > + if page is self.LIST_PAGE: > + return self.get_domain_list_page(screen, defined = False) > + elif page is self.STOP_PAGE: > + return self.get_stop_page(screen) > + > + def page_has_next(self, page): > + if page is self.LIST_PAGE: return self.has_selectable_domains() > + return False > + > + def page_has_back(self, page): > + if page is self.STOP_PAGE: return True > + return False > + > + def validate_input(self, page, errors): > + if page is self.LIST_PAGE: > + if self.get_selected_domain() is not None: > + domain = self.get_selected_domain() > + try: > + self.get_libvirt().stop_domain(domain) > + return True > + except Exception, error: > + errors.append("There was an error stop the domain: %s" % domain) > + errors.append(str(error)) > + else: > + errors.append("You must first select a domain to stop.") > + return False > + > + def get_stop_page(self, screen): > + grid = Grid(1, 1) > + grid.setField(Label("%s was successfully stoped." % self.get_selected_domain()), 0, 0) > + return [grid] > + > +def StopDomain(): > + screen = StopDomainConfigScreen() > + screen.start() > diff --git a/nodeadmin/undefinedomain.py b/nodeadmin/undefinedomain.py > deleted file mode 100755 > index 2620540..0000000 > --- a/nodeadmin/undefinedomain.py > +++ /dev/null > @@ -1,83 +0,0 @@ > -#!/usr/bin/env python > -# > -# undefinedomain.py - Copyright (C) 2009 Red Hat, Inc. > -# Written by Darryl L. Pierce > -# > -# This program is free software; you can redistribute it and/or modify > -# it under the terms of the GNU General Public License as published by > -# the Free Software Foundation; version 2 of the License. > -# > -# This program is distributed in the hope that it will be useful, > -# but WITHOUT ANY WARRANTY; without even the implied warranty of > -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > -# GNU General Public License for more details. > -# > -# You should have received a copy of the GNU General Public License > -# along with this program; if not, write to the Free Software > -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > -# MA 02110-1301, USA. A copy of the GNU General Public License is > -# also available at http://www.gnu.org/copyleft/gpl.html. > - > -from snack import * > -from configscreen import * > - > -class UndefineDomainConfigScreen(DomainListConfigScreen): > - LIST_PAGE = 1 > - CONFIRM_PAGE = 2 > - UNDEFINE_PAGE = 3 > - > - def __init__(self): > - DomainListConfigScreen.__init__(self, "Undefine A Domain") > - > - def get_elements_for_page(self, screen, page): > - if page is self.LIST_PAGE: return self.get_domain_list_page(screen) > - elif page is self.CONFIRM_PAGE: return self.get_confirm_page(screen) > - elif page is self.UNDEFINE_PAGE: return self.get_undefine_page(screen) > - > - def page_has_next(self, page): > - if page is self.LIST_PAGE: return self.has_selectable_domains() > - elif page is self.CONFIRM_PAGE: return True > - return False > - > - def page_has_back(self, page): > - if page is self.CONFIRM_PAGE: return True > - elif page is self.UNDEFINE_PAGE: return True > - return False > - > - def get_back_page(self, page): > - if page is self.CONFIRM_PAGE: return self.LIST_PAGE > - elif page is self.UNDEFINE_PAGE: return self.LIST_PAGE > - > - def validate_input(self, page, errors): > - if page is self.LIST_PAGE: > - if self.get_selected_domain() is not None: > - return True > - else: > - errors.append("You must first select a domain.") > - elif page is self.CONFIRM_PAGE: > - if self.__confirm_undefine.value(): > - domain = self.get_selected_domain() > - try: > - self.get_libvirt().undefine_domain(domain) > - return True > - except Exception, error: > - errors.append("Failed to undefine %s." % domain) > - errors.append(str(error)) > - else: > - errors.append("You must confirm undefining the domain to proceed.") > - return False > - > - def get_confirm_page(self, screen): > - self.__confirm_undefine = Checkbox("Check here to confirm undefining %s." % self.get_selected_domain(), 0) > - grid = Grid(1, 1) > - grid.setField(self.__confirm_undefine, 0, 0) > - return [grid] > - > - def get_undefine_page(self, screen): > - grid = Grid(1, 1) > - grid.setField(Label("%s has been undefined." % self.get_selected_domain()), 0, 0) > - return [grid] > - > -def UndefineDomain(): > - screen = UndefineDomainConfigScreen() > - screen.start() > diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in > index 2a6b7b6..49f41cb 100644 > --- a/ovirt-node.spec.in > +++ b/ovirt-node.spec.in > @@ -183,10 +183,10 @@ cd - > %{__install} -p -m0644 nodeadmin/mainmenu.py %{buildroot}%{python_sitelib}/nodeadmin > > %{__install} -p -m0644 nodeadmin/nodemenu.py %{buildroot}%{python_sitelib}/nodeadmin > -%{__install} -p -m0755 nodeadmin/definedomain.py %{buildroot}%{python_sitelib}/nodeadmin > -%{__install} -p -m0755 nodeadmin/createdomain.py %{buildroot}%{python_sitelib}/nodeadmin > -%{__install} -p -m0755 nodeadmin/destroydomain.py %{buildroot}%{python_sitelib}/nodeadmin > -%{__install} -p -m0755 nodeadmin/undefinedomain.py %{buildroot}%{python_sitelib}/nodeadmin > +%{__install} -p -m0755 nodeadmin/adddomain.py %{buildroot}%{python_sitelib}/nodeadmin > +%{__install} -p -m0755 nodeadmin/startdomain.py %{buildroot}%{python_sitelib}/nodeadmin > +%{__install} -p -m0755 nodeadmin/stopdomain.py %{buildroot}%{python_sitelib}/nodeadmin > +%{__install} -p -m0755 nodeadmin/removedomain.py %{buildroot}%{python_sitelib}/nodeadmin > %{__install} -p -m0755 nodeadmin/listdomains.py %{buildroot}%{python_sitelib}/nodeadmin > %{__install} -p -m0644 nodeadmin/domainconfig.py %{buildroot}%{python_sitelib}/nodeadmin > > @@ -369,11 +369,11 @@ fi > %{_initrddir}/ovirt-functions > %defattr(-,root,root,0644) > %{_bindir}/nodeadmin > -%{_bindir}/definedom > -%{_bindir}/createdom > -%{_bindir}/destroydom > -%{_bindir}/undefinedom > -%{_bindir}/listdoms > +%{_bindir}/addvm > +%{_bindir}/startvm > +%{_bindir}/stopvm > +%{_bindir}/rmvm > +%{_bindir}/listvms > %{_bindir}/definenet > %{_bindir}/createnet > %{_bindir}/destroynet > ACK, pending below changes CreateDomain -> StartDomain nodeadmin/nodemenu.py:from createdomain import CreateDomain nodeadmin/nodemenu.py: elif item is CREATE_DOMAIN: CreateDomain() From dpierce at redhat.com Tue Oct 27 16:59:58 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 27 Oct 2009 12:59:58 -0400 Subject: [Ovirt-devel] [PATCH node] Renamed files and menu items for node administration: In-Reply-To: <4AE701C1.7020207@redhat.com> References: <1256154482-9174-1-git-send-email-dpierce@redhat.com> <4AE701C1.7020207@redhat.com> Message-ID: <20091027165958.GA4438@mcpierce-laptop.rdu.redhat.com> On Tue, Oct 27, 2009 at 10:20:49AM -0400, Joey Boggs wrote: > ACK, pending below changes > > CreateDomain -> StartDomain > nodeadmin/nodemenu.py:from createdomain import CreateDomain > nodeadmin/nodemenu.py: elif item is CREATE_DOMAIN: > CreateDomain() Done and pushed upstream. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From dpierce at redhat.com Tue Oct 27 18:51:10 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 27 Oct 2009 14:51:10 -0400 Subject: [Ovirt-devel] Storage admin patches Message-ID: <1256669472-19845-1-git-send-email-dpierce@redhat.com> This set of patches supercedes the previous set, and has been rebased with changes from upstream. From dpierce at redhat.com Tue Oct 27 18:51:11 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 27 Oct 2009 14:51:11 -0400 Subject: [Ovirt-devel] [PATCH 1/2] Provides a new storage administration system to the managed node. In-Reply-To: <1256669472-19845-1-git-send-email-dpierce@redhat.com> References: <1256669472-19845-1-git-send-email-dpierce@redhat.com> Message-ID: <1256669472-19845-2-git-send-email-dpierce@redhat.com> Users can now: * Add a new storage pool. * Delete a storage pool. * Start and stop storage pools. * Add a new storage volume. * Delete a storage volume. * List existing storage pools, with details. Signed-off-by: Darryl L. Pierce --- Makefile.am | 28 +++++-- nodeadmin/adddomain.py | 15 +--- nodeadmin/addpool.py | 182 ++++++++++++++++++++++++++++++++++++++++++++ nodeadmin/addvolume.py | 160 ++++++++++++++++++++++++++++++++++++++ nodeadmin/configscreen.py | 52 +++++++++++++ nodeadmin/createmeter.py | 30 +++++++ nodeadmin/libvirtworker.py | 67 +++++++++++++++-- nodeadmin/listpools.py | 63 +++++++++++++++ nodeadmin/mainmenu.py | 24 ++++--- nodeadmin/poolconfig.py | 137 +++++++++++++++++++++++++++++++++ nodeadmin/removepool.py | 72 +++++++++++++++++ nodeadmin/removevolume.py | 76 ++++++++++++++++++ nodeadmin/setup.py.in | 9 ++- nodeadmin/startpool.py | 62 +++++++++++++++ nodeadmin/stoppool.py | 62 +++++++++++++++ nodeadmin/storagemenu.py | 63 +++++++++++++++ nodeadmin/utils.py | 10 +++ nodeadmin/volumeconfig.py | 76 ++++++++++++++++++ ovirt-node.spec.in | 7 ++ 19 files changed, 1155 insertions(+), 40 deletions(-) create mode 100644 nodeadmin/addpool.py create mode 100644 nodeadmin/addvolume.py create mode 100644 nodeadmin/createmeter.py create mode 100644 nodeadmin/listpools.py create mode 100644 nodeadmin/poolconfig.py create mode 100644 nodeadmin/removepool.py create mode 100644 nodeadmin/removevolume.py create mode 100644 nodeadmin/startpool.py create mode 100644 nodeadmin/stoppool.py create mode 100644 nodeadmin/storagemenu.py create mode 100644 nodeadmin/volumeconfig.py diff --git a/Makefile.am b/Makefile.am index 3ce24c1..55ef277 100644 --- a/Makefile.am +++ b/Makefile.am @@ -28,29 +28,39 @@ EXTRA_DIST = \ images/syslinux-vesa-splash.jpg \ nodeadmin/__init__.py \ nodeadmin/adddomain.py \ + nodeadmin/addpool.py \ + nodeadmin/addvolume.py \ nodeadmin/configscreen.py \ + nodeadmin/createmeter.py \ nodeadmin/createnetwork.py \ nodeadmin/createuser.py \ + nodeadmin/definenet.py \ nodeadmin/destroynetwork.py \ + nodeadmin/domainconfig.py \ nodeadmin/halworker.py \ nodeadmin/libvirtworker.py \ - nodeadmin/userworker.py \ + nodeadmin/listdomains.py \ + nodeadmin/listnetworks.py \ + nodeadmin/listpools.py \ nodeadmin/mainmenu.py \ nodeadmin/menuscreen.py \ + nodeadmin/networkconfig.py \ nodeadmin/netmenu.py \ + nodeadmin/nodeadmin.py \ nodeadmin/nodemenu.py \ nodeadmin/removedomain.py \ - nodeadmin/undefinenetwork.py \ + nodeadmin/removepool.py \ + nodeadmin/removevolume.py \ + nodeadmin/setup.py \ nodeadmin/startdomain.py \ + nodeadmin/startpool.py \ nodeadmin/stopdomain.py \ - nodeadmin/definenet.py \ - nodeadmin/domainconfig.py \ - nodeadmin/networkconfig.py \ - nodeadmin/listdomains.py \ - nodeadmin/listnetworks.py \ - nodeadmin/nodeadmin.py \ - nodeadmin/setup.py \ + nodeadmin/stoppool.py \ + nodeadmin/storagemenu.py \ + nodeadmin/undefinenetwork.py \ + nodeadmin/userworker.py \ nodeadmin/utils.py \ + nodeadmin/volumeconfig.py \ scripts/collectd.conf.in \ scripts/ovirt \ scripts/ovirt-awake \ diff --git a/nodeadmin/adddomain.py b/nodeadmin/adddomain.py index 70a2011..bb06a62 100755 --- a/nodeadmin/adddomain.py +++ b/nodeadmin/adddomain.py @@ -20,11 +20,10 @@ from snack import * import os +from createmeter import CreateMeter from domainconfig import DomainConfig from configscreen import ConfigScreen -import urlgrabber.progress as progress import utils -import logging from virtinst import * @@ -51,16 +50,6 @@ OS_VARIANT="os.variant" MEMORY="memory" CPUS="cpus" -class DummyMeter(progress.BaseMeter): - def _do_start(self, now = None): - logging.info("Starting...") - - def _do_end(self, amount_read, now = None): - logging.info("Ending: read=%d" % amount_read) - - def _do_update(self, amount_read, now = None): - logging.info("Update: read=%d" % amount_read) - class DomainConfigScreen(ConfigScreen): def __init__(self): ConfigScreen.__init__(self, "Create A New Virtual Machine") @@ -212,7 +201,7 @@ class DomainConfigScreen(ConfigScreen): self.__config.set_virt_type(self.__virt_types.getSelection()) self.__config.set_architecture(self.__architectures.getSelection()) elif page == CONFIRM_PAGE: - self.get_libvirt().define_domain(self.__config, DummyMeter()) + self.get_libvirt().define_domain(self.__config, CreateMeter()) self.set_finished() def get_back_page(self, page): diff --git a/nodeadmin/addpool.py b/nodeadmin/addpool.py new file mode 100644 index 0000000..389be52 --- /dev/null +++ b/nodeadmin/addpool.py @@ -0,0 +1,182 @@ +# addstorage.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +import traceback +import utils + +from configscreen import * +from poolconfig import PoolConfig +from virtinst import Storage + +POOL_NAME_PAGE = 1 +POOL_DETAILS_PAGE = 2 +CONFIRM_PAGE = 3 + +class AddStoragePoolConfigScreen(ConfigScreen): + def __init__(self): + ConfigScreen.__init__(self, "Add A Storage Pool") + self.__config = PoolConfig(self.get_libvirt()) + + def get_elements_for_page(self, screen, page): + if page is POOL_NAME_PAGE: return self.get_pool_name_page(screen) + elif page is POOL_DETAILS_PAGE: return self.get_pool_details_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + return page < CONFIRM_PAGE + + def page_has_back(self, page): + return page > POOL_NAME_PAGE + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def validate_input(self, page, errors): + if page is POOL_NAME_PAGE: + if utils.string_is_not_blank(self.__name.value()): + if self.get_libvirt().storage_pool_exists(self.__name.value()): + errors.append("Name '%s' already in use by another pool." % self.__name.value()) + else: + return True + else: + errors.append("Storage object name must be a string between 0 and 50 characters.") + elif page is POOL_DETAILS_PAGE: + result = True + if self.__config.needs_target_path(): + if utils.string_is_not_blank(self.__target_path.value()): + if self.__target_path.value()[0:1] is not '/': + errors.append("'%s' is not an absolute path." % self.__target_path.value()) + result = False + else: + errors.append("You must enter a target path.") + result = False + if self.__config.needs_format(): + if self.__formats.getSelection() is None: + errors.append("You must select a pool format.") + result = False + if self.__config.needs_hostname(): + if utils.string_is_not_blank(self.__hostname.value()): + errors.append("You must enter a hostname.") + result = False + if self.__config.needs_source_path(): + if utils.string_is_not_blank(self.__source_path.value()): + if self.__source_path.value()[0:1] is not '/': + errors.append("'%s' is not an absolute path." % self.__source_path.value()) + result = False + else: + errors.append("you must enter a source path.") + result = False + return result + elif page is CONFIRM_PAGE: return True + return False + + def process_input(self, page): + if page is POOL_NAME_PAGE: + self.__config.set_name(self.__name.value()) + self.__config.set_type(self.__type.getSelection()) + #self._reset_flags(self.__type.current()) + elif page is POOL_DETAILS_PAGE: + if self.__config.needs_target_path(): + self.__config.set_target_path(self.__target_path.value()) + if self.__config.needs_format(): + self.__config.set_format(self.__formats.getSelection()) + if self.__config.needs_hostname(): + self.__config.set_hostname(self.__hostname.value()) + if self.__config.needs_source_path(): + self.__config.set_source_path(self.__source_path.value()) + if self.__config.needs_build_pool(): + self.__config.set_build_pool(self.__build_pool.value()) + elif page is CONFIRM_PAGE: + self.get_libvirt().define_storage_pool(self.__config.get_name(), config = self.__config) + self.get_libvirt().create_storage_pool(self.__config.get_name()) + self.set_finished() + + def get_pool_name_page(self, screen): + self.__name = Entry(50, self.__config.get_name()) + pooltypes = [] + for pooltype in Storage.StoragePool.get_pool_types(): + pooltypes.append(["%s: %s" % (pooltype, Storage.StoragePool.get_pool_type_desc(pooltype)), + pooltype, + self.__config.get_type() is pooltype]) + self.__type = RadioBar(screen, pooltypes) + grid = Grid(2, 2) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(self.__name, 1, 0, anchorLeft = 1) + grid.setField(Label("Type:"), 0, 1, anchorRight = 1, anchorTop = 1) + grid.setField(self.__type, 1, 1, anchorLeft = 1) + return [Label("Add Storage Pool"), + grid] + + def get_pool_details_page(self, screen): + rows = 0 + if self.__config.needs_target_path(): + self.__target_path = Entry(50, self.__config.get_target_path()) + rows += 1 + if self.__config.needs_format(): + formats = [] + for format in self.__config.get_formats(): + formats.append([format, format, format is self.__config.get_format()]) + self.__formats = RadioBar(screen, formats) + rows += 1 + if self.__config.needs_hostname(): + self.__hostname = Entry(50, self.__config.get_hostname()) + rows += 1 + if self.__config.needs_source_path(): + self.__source_path = Entry(50, self.__config.get_source_path()) + rows += 1 + if self.__config.needs_build_pool(): + self.__build_pool = Checkbox("Build Pool", self.__config.get_build_pool()) + rows += 1 + grid = Grid(2, rows) + currentrow = 0 + if self.__config.needs_target_path(): + grid.setField(Label("Target Path:"), 0, currentrow, anchorRight = 1) + grid.setField(self.__target_path, 1, currentrow, anchorLeft = 1) + currentrow += 1 + if self.__config.needs_format(): + grid.setField(Label("Format:"), 0, currentrow, anchorRight = 1, anchorTop = 1) + grid.setField(self.__formats, 1, currentrow, anchorLeft = 1) + currentrow += 1 + if self.__config.needs_hostname(): + grid.setField(Label("Host Name:"), 0, currentrow, anchorRight = 1) + grid.setField(self.__hostname, 1, currentrow, anchorRight = 1) + currentrow += 1 + if self.__config.needs_source_path(): + grid.setField(Label("Source Path:"), 0, currentrow, anchorRight = 1) + grid.setField(self.__source_path, 1, currentrow, anchorLeft = 1) + currentrow += 1 + if self.__config.needs_build_pool(): + grid.setField(Label(" "), 0, currentrow, anchorRight = 1) + grid.setField(self.__build_pool, 1, currentrow, anchorLeft = 1) + currentrow += 1 + return [Label("Specify a storage location to be later split into virtual machine storage"), + grid] + + def get_confirm_page(self, screen): + grid = Grid(2, 2) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(Label(self.__config.get_name()), 1, 0, anchorLeft = 1) + grid.setField(Label("Target Path:"), 0, 1, anchorRight = 1) + grid.setField(Label(self.__config.get_target_path()), 1, 1, anchorLeft = 1) + return [Label("Confirm Pool Details"), + grid] + +def AddStoragePool(): + screen = AddStoragePoolConfigScreen() + screen.start() diff --git a/nodeadmin/addvolume.py b/nodeadmin/addvolume.py new file mode 100644 index 0000000..82c014c --- /dev/null +++ b/nodeadmin/addvolume.py @@ -0,0 +1,160 @@ +# addvolume.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +import traceback + +from createmeter import CreateMeter +from configscreen import * +from volumeconfig import StorageVolumeConfig +from utils import * + +SELECT_POOL_PAGE = 1 +VOLUME_NAME_PAGE = 2 +VOLUME_FORMAT_PAGE = 3 +MAX_CAPACITY_PAGE = 4 +CONFIRM_PAGE = 5 + +class AddVolumeConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Add A New Storage Volume") + self.__config = StorageVolumeConfig() + + def get_elements_for_page(self, screen, page): + if page is SELECT_POOL_PAGE: return self.get_storage_pool_list_page(screen) + elif page is VOLUME_NAME_PAGE: return self.get_volume_name_page(screen) + elif page is VOLUME_FORMAT_PAGE: return self.get_volume_format_page(screen) + elif page is MAX_CAPACITY_PAGE: return self.get_max_capacity_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + if page is SELECT_POOL_PAGE: + return self.has_selectable_pools() + else: + if page < CONFIRM_PAGE: return True + return False + + def page_has_back(self, page): + if page > SELECT_POOL_PAGE: return True + return False + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def validate_input(self, page, errors): + if page is SELECT_POOL_PAGE: + if self.get_selected_pool() is not None: + return True + else: + errors.append("You must select a storage pool.") + elif page is VOLUME_NAME_PAGE: + if string_is_not_blank(self.__name.value()): + return True + else: + errors.append("Storage object name can only contain alphanumeric, '_', '.', or '-' characters.") + elif page is VOLUME_FORMAT_PAGE: + if self.__formats.current() is not None: + return True + else: + errors.append("You must select a volume format.") + elif page is MAX_CAPACITY_PAGE: + if string_is_not_blank(self.__capacity.value()): + if string_is_not_blank(self.__allocation.value()): + capacity = int(self.__capacity.value()) + allocation = int(self.__allocation.value()) + if capacity > 0: + if capacity <= self.__config.get_pool().info()[3] / 1024**2: + if allocation >= 0: + if allocation <= capacity: + return True + else: + errors.append("Allocation cannot exceed the maximum capacity.") + else: + errors.append("The allocation must be greater than or equal to 0.") + else: + errors.append("The maximum capacity cannot exceed the storage pool size.") + else: + errors.append("The capacity must be greater than zero.") + else: + errors.append("An allocation value must be entered.") + else: + errors.append("A maximum volume capacity must be entered.") + elif page is CONFIRM_PAGE: return True + return False + + def process_input(self, page): + if page is SELECT_POOL_PAGE: + self.__config.set_pool(self.get_libvirt().get_storage_pool(self.get_selected_pool())) + elif page is VOLUME_NAME_PAGE: + self.__config.set_name(self.__name.value()) + elif page is VOLUME_FORMAT_PAGE: + self.__config.set_format(self.__formats.current()) + elif page is MAX_CAPACITY_PAGE: + self.__config.set_max_capacity(int(self.__capacity.value())) + self.__config.set_allocation(int(self.__allocation.value())) + elif page is CONFIRM_PAGE: + self.get_libvirt().define_storage_volume(self.__config, CreateMeter()) + self.set_finished() + + def get_volume_name_page(self, screen): + self.__name = Entry(50, self.__config.get_name()) + grid = Grid(2, 1) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(self.__name, 1, 0, anchorLeft = 1) + return [Label("New Storage Volume"), + grid, + Label("Name of the volume to create. File extension may be appended.")] + + def get_volume_format_page(self, screen): + self.__formats = Listbox(0) + for format in self.__config.get_formats_for_pool(): + self.__formats.append(format, format) + grid = Grid(1, 1) + grid.setField(self.__formats, 0, 0) + return [Label("Select The Volume Format"), + grid] + + def get_max_capacity_page(self, screen): + self.__capacity = Entry(6, str(self.__config.get_max_capacity())) + self.__allocation = Entry(6, str(self.__config.get_allocation())) + grid = Grid(2, 2) + grid.setField(Label("Max. Capacity (MB):"), 0, 0, anchorRight = 1) + grid.setField(self.__capacity, 1, 0, anchorLeft = 1) + grid.setField(Label("Allocation (MB):"), 0, 1, anchorRight = 1) + grid.setField(self.__allocation, 1, 1, anchorLeft = 1) + return [Label("Storage Volume Quota"), + Label("%s's available space: %0.2f GB" % (self.__config.get_pool().name(), + self.__config.get_pool().info()[3] / 1024.0**3)), + grid] + + def get_confirm_page(self, screen): + grid = Grid(2, 5) + grid.setField(Label("Volume Name:"), 0, 0, anchorRight = 1) + grid.setField(Label("%s (%s)" % (self.__config.get_name(), self.__config.get_pool().name())), 1, 0, anchorLeft = 1) + grid.setField(Label("Format:"), 0, 1, anchorRight = 1) + grid.setField(Label(self.__config.get_format()), 1, 1, anchorLeft = 1) + grid.setField(Label("Max. Capacity:"), 0, 2, anchorRight = 1) + grid.setField(Label("%0.2f GB" % (self.__config.get_max_capacity() / 1024.0)), 1, 2, anchorLeft = 1) + grid.setField(Label("Allocation:"), 0, 3, anchorRight = 1) + grid.setField(Label("%0.2f GB" % (self.__config.get_allocation() / 1024.0)), 1, 3, anchorLeft = 1) + return [Label("Ready To Allocation New Storage Volume"), + grid] + +def AddStorageVolume(): + screen = AddVolumeConfigScreen() + screen.start() diff --git a/nodeadmin/configscreen.py b/nodeadmin/configscreen.py index f214aea..7654697 100644 --- a/nodeadmin/configscreen.py +++ b/nodeadmin/configscreen.py @@ -179,3 +179,55 @@ class NetworkListConfigScreen(ConfigScreen): def has_selectable_networks(self): return self.__has_networks + +class StorageListConfigScreen(ConfigScreen): + '''Provides a base class for any configuration screen that deals with storage pool lists.''' + + def __init__(self, title): + ConfigScreen.__init__(self, title) + + def get_storage_pool_list_page(self, screen, defined=True, created=True): + pools = self.get_libvirt().list_storage_pools(defined=defined, created=created) + if len(pools) > 0: + self.__has_pools = True + self.__pools_list = Listbox(0) + for pool in pools: + self.__pools_list.append(pool, pool) + result = self.__pools_list + else: + self.__has_pools = False + result = Label("There are no storage pools available.") + grid = Grid(1, 1) + grid.setField(result, 0, 0) + return [Label("Storage Pool List"), + grid] + + def get_selected_pool(self): + return self.__pools_list.current() + + def has_selectable_pools(self): + return self.__has_pools + + def get_storage_volume_list_page(self, screen): + '''Requires that self.__pools_list have a selected element.''' + pool = self.get_libvirt().get_storage_pool(self.get_selected_pool()) + if len(pool.listVolumes()) > 0: + self.__has_volumes = True + self.__volumes_list = Listbox(0) + for volname in pool.listVolumes(): + volume = pool.storageVolLookupByName(volname) + self.__volumes_list.append("%s (%0.2f GB)" % (volume.name(), volume.info()[2] / 1024**3), volume.name()) + result = self.__volumes_list + else: + self.__has_volumes = False + result = Label("There are no storage volumes available.") + grid = Grid(1, 1) + grid.setField(result, 0, 0) + return [Label("Storage Volume List"), + grid] + + def get_selected_volume(self): + return self.__volumes_list.current() + + def has_selectable_volumes(self): + return self.__has_volumes diff --git a/nodeadmin/createmeter.py b/nodeadmin/createmeter.py new file mode 100644 index 0000000..521e7d8 --- /dev/null +++ b/nodeadmin/createmeter.py @@ -0,0 +1,30 @@ +# createmeter.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +import urlgrabber.progress as progress +import logging + +class CreateMeter(progress.BaseMeter): + def _do_start(self, now = None): + logging.info("Starting...") + + def _do_end(self, amount_read, now = None): + logging.info("Ending: read=%d" % amount_read) + + def _do_update(self, amount_read, now = None): + logging.info("Update: read=%d" % amount_read) diff --git a/nodeadmin/libvirtworker.py b/nodeadmin/libvirtworker.py index ba07605..b2acabe 100644 --- a/nodeadmin/libvirtworker.py +++ b/nodeadmin/libvirtworker.py @@ -35,6 +35,10 @@ class LibvirtWorker: self.__net.setup(self.__conn) (self.__new_guest, self.__new_domain) = virtinst.CapabilitiesParser.guest_lookup(conn = self.__conn) + def get_connection(self): + '''Returns the underlying connection.''' + return self.__conn + def list_domains(self, defined = True, started = True): '''Lists all domains.''' result = [] @@ -134,9 +138,12 @@ class LibvirtWorker: network = self.get_network(name) network.undefine() - def list_storage_pools(self): + def list_storage_pools(self, defined=True, created=True): '''Returns the list of all defined storage pools.''' - return self.__conn.listStoragePools() + pools = [] + if defined: pools.extend(self.__conn.listDefinedStoragePools()) + if created: pools.extend(self.__conn.listStoragePools()) + return pools def storage_pool_exists(self, name): '''Returns whether a storage pool exists.''' @@ -144,16 +151,62 @@ class LibvirtWorker: if name in pools: return True return False - def define_storage_pool(self, name): + def create_storage_pool(self, name): + '''Starts the named storage pool if it is not currently started.''' + if name not in self.list_storage_pools(defined = False): + pool = self.get_storage_pool(name) + pool.create(0) + + def destroy_storage_pool(self, name): + '''Stops the specified storage pool.''' + if name in self.list_storage_pools(defined = False): + pool = self.get_storage_pool(name) + pool.destroy() + + def define_storage_pool(self, name, config = None, meter = None): '''Defines a storage pool with the given name.''' - try: + if config is None: pool = virtinst.Storage.DirectoryPool(conn=self.__conn, name=name, target_path=DEFAULT_POOL_TARGET_PATH) - newpool = pool.install(build=True, create=True) + newpool = pool.install(build=True, create=True, meter=meter) newpool.setAutostart(True) - except Exception, error: - raise RuntimeError("Could not create pool: %s - %s", str(error)) + else: + pool = config.get_pool() + pool.target_path = config.get_target_path() + if config.needs_hostname(): + pool.host = config.get_hostname() + if config.needs_source_path(): + pool.source_path = config.get_source_path() + if config.needs_format(): + pool.format = config.get_format() + pool.conn = self.__conn + pool.get_xml_config() + newpool = pool.install(meter=meter, + build=True, # config.get_build_pool(), + create=True) + newpool.setAutostart(True) + + def undefine_storage_pool(self, name): + '''Undefines the specified storage pool.''' + pool = self.get_storage_pool(name) + pool.undefine() + + def get_storage_pool(self, name): + '''Returns the storage pool with the specified name.''' + return self.__conn.storagePoolLookupByName(name) + + def define_storage_volume(self, config, meter): + '''Defines a new storage volume.''' + self.create_storage_pool(config.get_pool().name()) + volume = config.create_volume() + volume.install(meter = meter) + + def remove_storage_volume(self, poolname, volumename): + '''Removes the specified storage volume.''' + pool = self.get_storage_pool(poolname) + volume = pool.storageVolLookupByName(volumename) + volume.delete(0) def list_bridges(self): '''Lists all defined and active bridges.''' diff --git a/nodeadmin/listpools.py b/nodeadmin/listpools.py new file mode 100644 index 0000000..686c42d --- /dev/null +++ b/nodeadmin/listpools.py @@ -0,0 +1,63 @@ +# listpools.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * + +from configscreen import * + +LIST_PAGE = 1 +DETAILS_PAGE = 2 + +class ListStoragePoolsConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "List Storage Pools") + + def get_elements_for_page(self, screen, page): + if page is LIST_PAGE: return self.get_storage_pool_list_page(screen) + elif page is DETAILS_PAGE: return self.get_pool_details_page(screen) + + def page_has_next(self, page): + if page is LIST_PAGE and self.has_selectable_pools(): + return True + return False + + def page_has_back(self, page): + if page is DETAILS_PAGE: return True + return False + + def get_pool_details_page(self, screen): + pool = self.get_libvirt().get_storage_pool(self.get_selected_pool()) + volumes = Listbox(0); + for name in pool.listVolumes(): + volume = pool.storageVolLookupByName(name) + volumes.append("%s (%0.1f G)" % (name, volume.info()[1] / 1024**3), name) + grid = Grid(2, 3) + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) + grid.setField(Label(pool.name()), 1, 0, anchorLeft = 1) + grid.setField(Label("Volumes:"), 0, 1, anchorRight = 1) + grid.setField(volumes, 1, 1, anchorLeft = 1) + grid.setField(Label("Autostart:"), 0, 2, anchorRight = 1) + label = "No" + if pool.autostart(): label = "Yes" + grid.setField(Label(label), 1, 2, anchorLeft = 1) + return [Label("Details For Storage Pool: %s" % self.get_selected_pool()), + grid] + +def ListStoragePools(): + screen = ListStoragePoolsConfigScreen() + screen.start() diff --git a/nodeadmin/mainmenu.py b/nodeadmin/mainmenu.py index 73501fa..52d9298 100755 --- a/nodeadmin/mainmenu.py +++ b/nodeadmin/mainmenu.py @@ -19,28 +19,32 @@ from snack import * import traceback -from menuscreen import MenuScreen -from nodemenu import NodeMenu -from netmenu import NetworkMenu +from menuscreen import MenuScreen +from nodemenu import NodeMenu +from netmenu import NetworkMenu +from storagemenu import StoragePoolMenu import utils import logging NODE_MENU = 1 NETWORK_MENU = 2 -EXIT_CONSOLE = 99 +STORAGE_MENU = 3 +EXIT_CONSOLE = 4 class MainMenuScreen(MenuScreen): def __init__(self): MenuScreen.__init__(self, "Main Menu") def get_menu_items(self): - return (("Node Administration", NODE_MENU), - ("Network Administration", NETWORK_MENU)) - - def handle_selection(self, page): - if page is NODE_MENU: NodeMenu() - elif page is NETWORK_MENU: NetworkMenu() + return (("Node Administration", NODE_MENU), + ("Network Administration", NETWORK_MENU), + ("Storage Pool Administration", STORAGE_MENU)) + + def handle_selection(self, item): + if item is NODE_MENU: NodeMenu() + elif item is NETWORK_MENU: NetworkMenu() + elif item is STORAGE_MENU: StoragePoolMenu() def MainMenu(): screen = MainMenuScreen() diff --git a/nodeadmin/poolconfig.py b/nodeadmin/poolconfig.py new file mode 100644 index 0000000..06af722 --- /dev/null +++ b/nodeadmin/poolconfig.py @@ -0,0 +1,137 @@ +# poolconfig.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from virtinst import Storage + +ROOT_TARGET_PATH="/var/lib/libvirt/images/%s" + +class PoolConfig: + def __init__(self, libvirt): + self.__libvirt = libvirt + self.__name = "" + self.set_type(None) + self.__format = None + self.__hostname = "" + self.__target_path = "" + self.__source_path = "" + self.__build_pool = False + + def get_pool(self): + return self.__pool + + def set_name(self, name): + self.__name = name + + def get_name(self): + return self.__name + + def set_type(self, pooltype): + self.__type = pooltype + self.__needs_target_path = False + self.__needs_format = False + self.__needs_hostname = False + self.__needs_source_path = False + self.__needs_build_pool = False + if pooltype is not None: + if pooltype is Storage.StoragePool.TYPE_DIR: + self.__needs_target_path = True + self.__target_path = ROOT_TARGET_PATH % self.__name + self.__build_pool = True + elif pooltype is Storage.StoragePool.TYPE_DISK: + self.__needs_target_path = True + self.__needs_format = True + self.__needs_source_path = True + self.__needs_build_pool = True + elif pooltype is Storage.StoragePool.TYPE_FS: + self.__needs_target_path = True + self.__needs_format = True + self.__needs_source_path = True + self.__build_pool = True + elif pooltype is Storage.StoragePool.TYPE_ISCSI: + self.__needs_target_path = True + self.__needs_hostname = True + self.__needs_source_path = True + self.__build_pool = False + elif pooltype is Storage.StoragePool.TYPE_LOGICAL: + self.__needs_target_path = True + self.__needs_source_path = True + self.__needs_build_pool = True + elif pooltype is Storage.StoragePool.TYPE_NETFS: + self.__needs_target_path = True + self.__needs_format = True + self.__needs_hostname = True + self.__needs_source_path = True + self.__build_pool = True + # create pool + pool_class = Storage.StoragePool.get_pool_class(self.__type) + self.__pool = pool_class(name = self.__name, + conn = self.__libvirt.get_connection()) + if self.__needs_format: + self.__format = self.__pool.formats[0] + else: + self.__type = Storage.StoragePool.get_pool_types()[0] + + def get_type(self): + return self.__type + + def needs_target_path(self): + return self.__needs_target_path + + def needs_format(self): + return self.__needs_format + + def needs_hostname(self): + return self.__needs_hostname + + def needs_source_path(self): + return self.__needs_source_path + + def needs_build_pool(self): + return self.__needs_build_pool + def set_target_path(self, path): + self.__target_path = path + + def get_target_path(self): + return self.__target_path + + def get_formats(self): + return self.__pool.formats + + def set_format(self, format): + self.__format = format + + def get_format(self): + return self.__format + + def set_hostname(self, hostname): + self.__hostname = hostname + + def get_hostname(self): + return self.__hostname + + def set_source_path(self, source_path): + self.__source_path = source_path + + def get_source_path(self): + return self.__source_path + + def set_build_pool(self, build_pool): + self.__build_pool = build_pool + + def get_build_pool(self): + return self.__build_pool diff --git a/nodeadmin/removepool.py b/nodeadmin/removepool.py new file mode 100644 index 0000000..7a7f46d --- /dev/null +++ b/nodeadmin/removepool.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# +# removepool.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +from configscreen import * + +LIST_POOLS_PAGE = 1 +CONFIRM_PAGE = 2 + +class RemoveStoragePoolConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Remove A Storage Pool") + + def get_elements_for_page(self, screen, page): + if page is LIST_POOLS_PAGE: return self.get_storage_pool_list_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + return page is LIST_POOLS_PAGE and self.has_selectable_pools() + + def page_has_back(self, page): + return False + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def validate_input(self, page, errors): + if page is LIST_POOLS_PAGE: + if self.get_selected_pool() is not None: + return True + else: + errors.append("Please select a storage pool to be removed.") + elif page is CONFIRM_PAGE: + if self.__confirm.value(): + return True + else: + errors.append("You must confirm removing a storage pool.") + return False + + def process_input(self, page): + if page is CONFIRM_PAGE: + self.get_libvirt().destroy_storage_pool(self.get_selected_pool()) + self.get_libvirt().undefine_storage_pool(self.get_selected_pool()) + self.set_finished() + + def get_confirm_page(self, screen): + self.__confirm = Checkbox("Check here to confirm deleting pool: %s" % self.get_selected_pool()) + grid = Grid(1, 1) + grid.setField(self.__confirm, 0, 0) + return [Label("Remove Selected Storage Pool"), + grid] + +def RemoveStoragePool(): + screen = RemoveStoragePoolConfigScreen() + screen.start() diff --git a/nodeadmin/removevolume.py b/nodeadmin/removevolume.py new file mode 100644 index 0000000..5ad3058 --- /dev/null +++ b/nodeadmin/removevolume.py @@ -0,0 +1,76 @@ +# removevolume.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +import traceback + +from createmeter import CreateMeter +from configscreen import * +from volumeconfig import StorageVolumeConfig +from utils import * + +SELECT_POOL_PAGE = 1 +SELECT_VOLUME_PAGE = 2 +CONFIRM_PAGE = 3 + +class RemoveVolumeConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Add A New Storage Volume") + self.__config = StorageVolumeConfig() + + def get_elements_for_page(self, screen, page): + if page is SELECT_POOL_PAGE: return self.get_storage_pool_list_page(screen) + elif page is SELECT_VOLUME_PAGE: return self.get_storage_volume_list_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + if page is SELECT_POOL_PAGE: return self.has_selectable_pools() + elif page is SELECT_VOLUME_PAGE: return self.has_selectable_volumes() + return False + + def validate_input(self, page, errors): + if page is SELECT_POOL_PAGE: return self.get_selected_pool() is not None + elif page is SELECT_VOLUME_PAGE: return self.get_selected_volume() is not None + elif page is CONFIRM_PAGE: + if self.__confirm.value(): + return True + else: + errors.append("You must confirm deleting a storage volume.") + return False + + def process_input(self, page): + if page is CONFIRM_PAGE: + self.get_libvirt().remove_storage_volume(self.get_selected_pool(), self.get_selected_volume()) + self.set_finished() + + def page_has_back(self, page): + return page > SELECT_POOL_PAGE + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def get_confirm_page(self, screen): + self.__confirm = Checkbox("Check here to confirm deleting volume: %s" % self.get_selected_volume()) + grid = Grid(1, 1) + grid.setField(self.__confirm, 0, 0) + return [Label("Remove Selected Storage Volume"), + grid] + +def RemoveStorageVolume(): + screen = RemoveVolumeConfigScreen() + screen.start() diff --git a/nodeadmin/setup.py.in b/nodeadmin/setup.py.in index 1e6e028..17bfe93 100644 --- a/nodeadmin/setup.py.in +++ b/nodeadmin/setup.py.in @@ -35,5 +35,12 @@ setup(name = "nodeadmin", 'createnet = nodeadmin.createnetwork:CreateNetwork', 'destroynet = nodeadmin.destroynetwork:DestroyNetwork', 'undefinenet = nodeadmin.undefinenetwork:UndefineNetwork', - 'listnets = nodeadmin.listnetworks:ListNetworks'] + 'listnets = nodeadmin.listnetworks:ListNetworks', + 'addpool = nodeadmin.addpool:AddStoragePool', + 'rmpool = nodeadmin.removepool:RemoveStoragePool', + 'startpool = nodeadmin.startpool:StartStoragePool', + 'stoppool = nodeadmin.stoppool:StopStoragePool', + 'addvolume = nodeadmin.addvolume:AddStorageVolume', + 'rmvolume = nodeadmin.removevolume:RemoveStorageVolume', + 'listpools = nodeadmin.listpools:ListPools'] }) diff --git a/nodeadmin/startpool.py b/nodeadmin/startpool.py new file mode 100644 index 0000000..8a84512 --- /dev/null +++ b/nodeadmin/startpool.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# +# startpool.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +from configscreen import * + +LIST_POOLS_PAGE = 1 +FINAL_PAGE = 2 + +class StartStoragePoolConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Start A Storage Pool") + + def get_elements_for_page(self, screen, page): + if page is LIST_POOLS_PAGE: return self.get_storage_pool_list_page(screen, created = False) + elif page is FINAL_PAGE: return self.get_final_page(screen) + + def page_has_next(self, page): + return page is LIST_POOLS_PAGE and self.has_selectable_pools() + + def page_has_back(self, page): + return False + + def page_has_finish(self, page): + return page is FINAL_PAGE + + def validate_input(self, page, errors): + if page is LIST_POOLS_PAGE: + if self.get_selected_pool() is not None: + return True + else: + errors.append("Please select a storage pool to be started.") + return False + + def process_input(self, page): + if page is LIST_POOLS_PAGE: + self.get_libvirt().create_storage_pool(self.get_selected_pool()) + self.set_finished() + + def get_final_page(self, screen): + return [Label("Storage pool started: %s" % self.get_selected_pool())] + +def StartStoragePool(): + screen = StartStoragePoolConfigScreen() + screen.start() diff --git a/nodeadmin/stoppool.py b/nodeadmin/stoppool.py new file mode 100644 index 0000000..0522b95 --- /dev/null +++ b/nodeadmin/stoppool.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# +# stoppool.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +from configscreen import * + +LIST_POOLS_PAGE = 1 +FINAL_PAGE = 2 + +class StopStoragePoolConfigScreen(StorageListConfigScreen): + def __init__(self): + StorageListConfigScreen.__init__(self, "Stop A Storage Pool") + + def get_elements_for_page(self, screen, page): + if page is LIST_POOLS_PAGE: return self.get_storage_pool_list_page(screen, defined = False) + elif page is FINAL_PAGE: return self.get_final_page(screen) + + def page_has_next(self, page): + return page is LIST_POOLS_PAGE and self.has_selectable_pools() + + def page_has_back(self, page): + return False + + def page_has_finish(self, page): + return page is FINAL_PAGE + + def validate_input(self, page, errors): + if page is LIST_POOLS_PAGE: + if self.get_selected_pool() is not None: + return True + else: + errors.append("Please select a storage pool to be stopped.") + return False + + def process_input(self, page): + if page is LIST_POOLS_PAGE: + self.get_libvirt().destroy_storage_pool(self.get_selected_pool()) + self.set_finished() + + def get_final_page(self, screen): + return [Label("Storage pool stopped: %s" % self.get_selected_pool())] + +def StopStoragePool(): + screen = StopStoragePoolConfigScreen() + screen.start() diff --git a/nodeadmin/storagemenu.py b/nodeadmin/storagemenu.py new file mode 100644 index 0000000..0b56dae --- /dev/null +++ b/nodeadmin/storagemenu.py @@ -0,0 +1,63 @@ +# storagemenu.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +import traceback + +from menuscreen import MenuScreen +from addpool import AddStoragePool +from startpool import StartStoragePool +from stoppool import StopStoragePool +from removepool import RemoveStoragePool +from addvolume import AddStorageVolume +from removevolume import RemoveStorageVolume +from listpools import ListStoragePools + +ADD_POOL = 1 +START_POOL = 2 +STOP_POOL = 3 +REMOVE_POOL = 4 +ADD_VOLUME = 5 +REMOVE_VOLUME = 6 +LIST_POOLS = 7 + +class StoragePoolMenuScreen(MenuScreen): + def __init__(self): + MenuScreen.__init__(self, "Storage Pool Administration") + + def get_menu_items(self): + return (("Add A Storage Pool", ADD_POOL), + ("Start A Storage Pool", START_POOL), + ("Stop A Storage Pool", STOP_POOL), + ("Remove A Storage Pool", REMOVE_POOL), + ("Add A Storage Volume", ADD_VOLUME), + ("Remove A Storage Volume", REMOVE_VOLUME), + ("List Storage Pools", LIST_POOLS)) + + def handle_selection(self, item): + if item is ADD_POOL: AddStoragePool() + elif item is START_POOL: StartStoragePool() + elif item is STOP_POOL: StopStoragePool() + elif item is REMOVE_POOL: RemoveStoragePool() + elif item is ADD_VOLUME: AddStorageVolume() + elif item is REMOVE_VOLUME: RemoveStorageVolume() + elif item is LIST_POOLS: ListStoragePools() + +def StoragePoolMenu(): + screen = StoragePoolMenuScreen() + screen.start() diff --git a/nodeadmin/utils.py b/nodeadmin/utils.py index 55a838c..28ccb8b 100644 --- a/nodeadmin/utils.py +++ b/nodeadmin/utils.py @@ -17,9 +17,19 @@ # also available at http://www.gnu.org/copyleft/gpl.html. import logging +import re logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', filename='/var/log/ovirt-nodeadmin.log', filemode='w') + +def string_is_not_blank(value): + if len(value) > 0: return True + return False + +def string_has_no_spaces(value): + if re.match("^[a-zA-Z0-9_]*$", value): + return True + return False diff --git a/nodeadmin/volumeconfig.py b/nodeadmin/volumeconfig.py new file mode 100644 index 0000000..7741391 --- /dev/null +++ b/nodeadmin/volumeconfig.py @@ -0,0 +1,76 @@ +# volumeconfig.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +import virtinst +from virtinst import Storage + +class StorageVolumeConfig: + def __init__(self): + self.__pool = None + self.__name = "" + self.__formats = None + self.__format = None + self.__max_capacity = 10000 + self.__allocation = 0 + + def set_pool(self, pool): + self.__pool = pool + self.__formats = None + self.__pool_type = virtinst.util.get_xml_path(self.__pool.XMLDesc(0), '/pool/@type') + self.__volume_class = Storage.StoragePool.get_volume_for_pool(self.__pool_type) + + def get_pool(self): + return self.__pool + + def create_volume(self): + volume = self.__volume_class(name = self.__name + ".img", + allocation = self.__allocation * 1024**2, + capacity = self.__max_capacity * 1024**2, + pool = self.__pool) + volume.pool = self.__pool + volume.format = self.__format + return volume + + def set_name(self, name): + self.__name = name + + def get_name(self): + return self.__name + + def get_formats_for_pool(self): + if self.__formats is None: + self.__formats = self.__volume_class.formats + return self.__formats + + def set_format(self, format): + self.__format = format + + def get_format(self): + return self.__format + + def set_max_capacity(self, capacity): + self.__max_capacity = capacity + + def get_max_capacity(self): + return self.__max_capacity + + def set_allocation(self, allocation): + self.__allocation = allocation + + def get_allocation(self): + return self.__allocation diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index d23a4ef..6509fa0 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -369,11 +369,18 @@ fi %{_initrddir}/ovirt-functions %defattr(-,root,root,0644) %{_bindir}/nodeadmin +%{_bindir}/addpool %{_bindir}/addvm +%{_bindir}/addvolume %{_bindir}/startvm %{_bindir}/stopvm %{_bindir}/rmvm +%{_bindir}/listpools %{_bindir}/listvms +%{_bindir}/rmpool +%{_bindir}/rmvolume +%{_bindir}/startpool +%{_bindir}/stoppool %{_bindir}/definenet %{_bindir}/createnet %{_bindir}/destroynet -- 1.6.2.5 From dpierce at redhat.com Tue Oct 27 18:51:12 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Tue, 27 Oct 2009 14:51:12 -0400 Subject: [Ovirt-devel] [PATCH 2/2] Refactor domain storage setup to use pool and volume selection screens. In-Reply-To: <1256669472-19845-2-git-send-email-dpierce@redhat.com> References: <1256669472-19845-1-git-send-email-dpierce@redhat.com> <1256669472-19845-2-git-send-email-dpierce@redhat.com> Message-ID: <1256669472-19845-3-git-send-email-dpierce@redhat.com> Now, when the user elects to use managed storage, they're show the list of available storage pools. Then, after selecting one, the user is shown the list of volumes on that pool. These are then used to create the domain. Signed-off-by: Darryl L. Pierce --- Makefile.am | 1 + nodeadmin/adddomain.py | 186 ++++++++++++++++++++++++++------------------ nodeadmin/domainconfig.py | 17 +++- nodeadmin/libvirtworker.py | 34 ++++---- 4 files changed, 142 insertions(+), 96 deletions(-) diff --git a/Makefile.am b/Makefile.am index 55ef277..e712d6a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -48,6 +48,7 @@ EXTRA_DIST = \ nodeadmin/netmenu.py \ nodeadmin/nodeadmin.py \ nodeadmin/nodemenu.py \ + nodeadmin/poolconfig.py \ nodeadmin/removedomain.py \ nodeadmin/removepool.py \ nodeadmin/removevolume.py \ diff --git a/nodeadmin/adddomain.py b/nodeadmin/adddomain.py index bb06a62..34aa59c 100755 --- a/nodeadmin/adddomain.py +++ b/nodeadmin/adddomain.py @@ -37,10 +37,11 @@ OS_VARIANT_PAGE = 12 RAM_CPU_PAGE = 13 ENABLE_STORAGE_PAGE = 14 LOCAL_STORAGE_PAGE = 15 -MANAGED_STORAGE_PAGE = 16 -BRIDGE_PAGE = 17 -VIRT_DETAILS_PAGE = 18 -CONFIRM_PAGE = 19 +SELECT_POOL_PAGE = 16 +SELECT_VOLUME_PAGE = 17 +BRIDGE_PAGE = 18 +VIRT_DETAILS_PAGE = 19 +CONFIRM_PAGE = 20 LOCATION="location" KICKSTART="kickstart" @@ -58,24 +59,25 @@ class DomainConfigScreen(ConfigScreen): self.__config.set_virt_type(self.get_libvirt().get_default_virt_type()) def get_elements_for_page(self, screen, page): - if page == VM_DETAILS_PAGE: return self.get_vm_details_page(screen) - elif page == LOCAL_INSTALL_PAGE: return self.get_local_install_page(screen) - elif page == SELECT_CDROM_PAGE: return self.get_select_cdrom_page(screen) - elif page == SELECT_ISO_PAGE: return self.get_select_iso_page(screen) - elif page == NETWORK_INSTALL_PAGE: return self.get_network_install_page(screen) - elif page == OS_TYPE_PAGE: return self.get_os_type_page(screen) - elif page == OS_VARIANT_PAGE: return self.get_os_variant_page(screen) - elif page == RAM_CPU_PAGE: return self.get_ram_and_cpu_page(screen) - elif page == ENABLE_STORAGE_PAGE: return self.get_enable_storage_page(screen) - elif page == LOCAL_STORAGE_PAGE: return self.get_local_storage_page(screen) - elif page == MANAGED_STORAGE_PAGE: return self.get_managed_storage_page(screen) - elif page == BRIDGE_PAGE: return self.get_bridge_page(screen) - elif page == VIRT_DETAILS_PAGE: return self.get_virt_details_page(screen) - elif page == CONFIRM_PAGE: return self.get_confirm_page(screen) + if page is VM_DETAILS_PAGE: return self.get_vm_details_page(screen) + elif page is LOCAL_INSTALL_PAGE: return self.get_local_install_page(screen) + elif page is SELECT_CDROM_PAGE: return self.get_select_cdrom_page(screen) + elif page is SELECT_ISO_PAGE: return self.get_select_iso_page(screen) + elif page is NETWORK_INSTALL_PAGE: return self.get_network_install_page(screen) + elif page is OS_TYPE_PAGE: return self.get_os_type_page(screen) + elif page is OS_VARIANT_PAGE: return self.get_os_variant_page(screen) + elif page is RAM_CPU_PAGE: return self.get_ram_and_cpu_page(screen) + elif page is ENABLE_STORAGE_PAGE: return self.get_enable_storage_page(screen) + elif page is LOCAL_STORAGE_PAGE: return self.get_local_storage_page(screen) + elif page is SELECT_POOL_PAGE: return self.get_select_pool_page(screen) + elif page is SELECT_VOLUME_PAGE: return self.get_select_volume_page(screen) + elif page is BRIDGE_PAGE: return self.get_bridge_page(screen) + elif page is VIRT_DETAILS_PAGE: return self.get_virt_details_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) return [] def validate_input(self, page, errors): - if page == VM_DETAILS_PAGE: + if page is VM_DETAILS_PAGE: if len(self.__guest_name.value()) > 0: if self.get_libvirt().domain_exists(self.__guest_name.value()): errors.append("Guest name '%s' is already in use." % self.__guest_name.value()) @@ -83,12 +85,12 @@ class DomainConfigScreen(ConfigScreen): return True else: errors.append("Guest name must be a string between 0 and 50 characters.") - elif page == LOCAL_INSTALL_PAGE: + elif page is LOCAL_INSTALL_PAGE: if self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM: return True elif self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_ISO: return True - elif page == SELECT_CDROM_PAGE: + elif page is SELECT_CDROM_PAGE: if self.__install_media.getSelection() != None: if len(self.get_hal().list_installable_volumes()) == 0: errors.append("No installable media is available.") @@ -96,7 +98,7 @@ class DomainConfigScreen(ConfigScreen): return True else: errors.append("You must select an install media.") - elif page == SELECT_ISO_PAGE: + elif page is SELECT_ISO_PAGE: if len(self.__iso_path.value()) > 0: if os.path.exists(self.__iso_path.value()): if os.path.isfile(self.__iso_path.value()): @@ -108,14 +110,14 @@ class DomainConfigScreen(ConfigScreen): errors.append(self.__iso_path.value()) else: errors.append("An install media selection is required.") - elif page == NETWORK_INSTALL_PAGE: + elif page is NETWORK_INSTALL_PAGE: if len(self.__install_url.value()) > 0: return True else: errors.append("An install tree is required.") - elif page == OS_TYPE_PAGE: return True - elif page == OS_VARIANT_PAGE: return True - elif page == RAM_CPU_PAGE: + elif page is OS_TYPE_PAGE: return True + elif page is OS_VARIANT_PAGE: return True + elif page is RAM_CPU_PAGE: if (len(self.__memory.value()) > 0 and len(self.__cpus.value()) > 0) \ and (int(self.__memory.value()) > 0 and int(self.__cpus.value()) > 0): return True @@ -128,8 +130,8 @@ class DomainConfigScreen(ConfigScreen): errors.append("A value must be entered for CPUs.") elif int(self.__cpus.value()) <= 0: errors.append("A positive integer value must be entered for memory.") - elif page == ENABLE_STORAGE_PAGE: return True - elif page == LOCAL_STORAGE_PAGE: + elif page is ENABLE_STORAGE_PAGE: return True + elif page is LOCAL_STORAGE_PAGE: if len(self.__storage_size.value()) > 0: if float(self.__storage_size.value()) > 0: return True @@ -137,12 +139,17 @@ class DomainConfigScreen(ConfigScreen): errors.append("A positive value must be entered for the storage size.") else: errors.append("A value must be entered for the storage size.") - elif page == MANAGED_STORAGE_PAGE: - if self.__existing_storage.getSelection() is not None: + elif page is SELECT_POOL_PAGE: + if self.__storage_pool.getSelection() is not None: + return True + else: + errors.append("Please select a storage pool.") + elif page is SELECT_VOLUME_PAGE: + if self.__storage_volume.getSelection() is not None: return True else: errors.append("Please select a storage volume.") - elif page == BRIDGE_PAGE: + elif page is BRIDGE_PAGE: if self.__network_bridges.getSelection() != None: if len(self.__mac_address.value()) > 0: # TODO: regex check the format @@ -151,62 +158,66 @@ class DomainConfigScreen(ConfigScreen): errors.append("MAC address must be supplied.") else: errors.append("A network bridge must be selected.") - elif page == VIRT_DETAILS_PAGE: + elif page is VIRT_DETAILS_PAGE: if self.__virt_types.getSelection() != None and self.__architectures.getSelection() != None: return True if self.__virt_types.getSelection() is None: errors.append("Please select a virtualization type.") if self.__architectures.getSelection() is None: errors.append("Please selection an architecture.") - elif page == CONFIRM_PAGE: return True + elif page is CONFIRM_PAGE: return True return False def process_input(self, page): - if page == VM_DETAILS_PAGE: + if page is VM_DETAILS_PAGE: self.__config.set_guest_name(self.__guest_name.value()) self.__config.set_install_type(self.__install_type.getSelection()) - elif page == LOCAL_INSTALL_PAGE: + elif page is LOCAL_INSTALL_PAGE: self.__config.set_use_cdrom_source(self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM) - elif page == SELECT_CDROM_PAGE: + elif page is SELECT_CDROM_PAGE: self.__config.set_install_media(self.__install_media.getSelection()) - elif page == SELECT_ISO_PAGE: + elif page is SELECT_ISO_PAGE: self.__config.set_iso_path(self.__iso_path.value()) - elif page == NETWORK_INSTALL_PAGE: + elif page is NETWORK_INSTALL_PAGE: self.__config.set_install_url(self.__install_url.value()) self.__config.set_kickstart_url(self.__kickstart_url.value()) self.__config.set_kernel_options(self.__kernel_options.value()) - elif page == OS_TYPE_PAGE: + elif page is OS_TYPE_PAGE: self.__config.set_os_type(self.__os_types.getSelection()) - elif page == OS_VARIANT_PAGE: + elif page is OS_VARIANT_PAGE: self.__config.set_os_variant(self.__os_variants.getSelection()) - elif page == RAM_CPU_PAGE: + elif page is RAM_CPU_PAGE: self.__config.set_memory(int(self.__memory.value())) self.__config.set_cpus(int(self.__cpus.value())) - elif page == ENABLE_STORAGE_PAGE: + elif page is ENABLE_STORAGE_PAGE: self.__config.set_enable_storage(self.__enable_storage.value()) if self.__storage_type.getSelection() == DomainConfig.NEW_STORAGE: self.__config.set_use_local_storage(True) elif self.__storage_type.getSelection() == DomainConfig.EXISTING_STORAGE: self.__config.set_use_local_storage(False) - elif page == LOCAL_STORAGE_PAGE: + elif page is LOCAL_STORAGE_PAGE: self.__config.set_storage_size(float(self.__storage_size.value())) self.__config.set_allocate_storage(self.__allocate_storage.value()) - elif page == MANAGED_STORAGE_PAGE: + elif page is SELECT_POOL_PAGE: self.__config.set_use_local_storage(False) - self.__config.set_existing_storage(self.__existing_storage.getSelection()) - self.__config.set_storage_size(self.get_libvirt().get_storage_size(self.__existing_storage.getSelection())) - elif page == BRIDGE_PAGE: + self.__config.set_storage_pool(self.__storage_pool.getSelection()) + elif page is SELECT_VOLUME_PAGE: + self.__config.set_storage_volume(self.__storage_volume.getSelection()) + volume = self.get_libvirt().get_storage_volume(self.__config.get_storage_pool(), + self.__config.get_storage_volume()) + self.__config.set_storage_size(volume.info()[1] / 1024.0 ** 3) + elif page is BRIDGE_PAGE: self.__config.set_network_bridge(self.__network_bridges.getSelection()) - elif page == VIRT_DETAILS_PAGE: + elif page is VIRT_DETAILS_PAGE: self.__config.set_virt_type(self.__virt_types.getSelection()) self.__config.set_architecture(self.__architectures.getSelection()) - elif page == CONFIRM_PAGE: + elif page is CONFIRM_PAGE: self.get_libvirt().define_domain(self.__config, CreateMeter()) self.set_finished() def get_back_page(self, page): result = page - if page == OS_TYPE_PAGE: + if page is OS_TYPE_PAGE: install_type = self.__config.get_install_type() if install_type == DomainConfig.LOCAL_INSTALL: if self.__config.get_use_cdrom_source(): @@ -217,24 +228,26 @@ class DomainConfigScreen(ConfigScreen): result = NETWORK_INSTALL_PAGE elif install_type == DomainConfig.PXE_INSTALL: result = VM_DETAILS_PAGE - elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: + elif page is LOCAL_STORAGE_PAGE or page is SELECT_VOLUME_PAGE: result = ENABLE_STORAGE_PAGE - elif page == NETWORK_INSTALL_PAGE: + elif page is SELECT_POOL_PAGE: + result = ENABLE_STORAGE_PAGE + elif page is NETWORK_INSTALL_PAGE: result = VM_DETAILS_PAGE - elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: + elif page is SELECT_CDROM_PAGE or page is SELECT_ISO_PAGE: result = LOCAL_INSTALL_PAGE - elif page == BRIDGE_PAGE: + elif page is BRIDGE_PAGE: if self.__config.get_use_local_storage(): result = LOCAL_STORAGE_PAGE else: - result = MANAGED_STORAGE_PAGE + result = SELECT_VOLUME_PAGE else: if page > 1: result = page - 1 return result def get_next_page(self, page): result = page - if page == VM_DETAILS_PAGE: + if page is VM_DETAILS_PAGE: install_type = self.__config.get_install_type() if install_type == DomainConfig.LOCAL_INSTALL: result = LOCAL_INSTALL_PAGE @@ -242,34 +255,36 @@ class DomainConfigScreen(ConfigScreen): result = NETWORK_INSTALL_PAGE elif install_type == DomainConfig.PXE_INSTALL: result = OS_TYPE_PAGE - elif page == LOCAL_INSTALL_PAGE: + elif page is LOCAL_INSTALL_PAGE: if self.__config.get_use_cdrom_source(): result = SELECT_CDROM_PAGE else: result = SELECT_ISO_PAGE - elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: + elif page is SELECT_CDROM_PAGE or page is SELECT_ISO_PAGE: result = OS_TYPE_PAGE - elif page == NETWORK_INSTALL_PAGE: + elif page is NETWORK_INSTALL_PAGE: result = OS_TYPE_PAGE - elif page == ENABLE_STORAGE_PAGE: + elif page is ENABLE_STORAGE_PAGE: result = BRIDGE_PAGE if self.__config.get_enable_storage(): if self.__config.get_use_local_storage(): result = LOCAL_STORAGE_PAGE else: - result = MANAGED_STORAGE_PAGE - elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: + result = SELECT_POOL_PAGE + elif page is LOCAL_STORAGE_PAGE: result = BRIDGE_PAGE else: result = page + 1 return result def page_has_finish(self, page): - if page == CONFIRM_PAGE: return True + if page is CONFIRM_PAGE: return True return False def page_has_next(self, page): - if page < CONFIRM_PAGE: + if page is SELECT_POOL_PAGE: return self.__has_pools + elif page is SELECT_VOLUME_PAGE: return self.__has_volumes + elif page < CONFIRM_PAGE: return True def get_vm_details_page(self, screen): @@ -393,17 +408,36 @@ class DomainConfigScreen(ConfigScreen): return [Label("Configure local storage"), grid] - def get_managed_storage_page(self, screen): + def get_select_pool_page(self, screen): + pools = [] + for pool in self.get_libvirt().list_storage_pools(): + pools.append([pool, pool, pool == self.__config.get_storage_pool()]) + if len(pools) > 0: + self.__storage_pool = RadioBar(screen, (pools)) + grid = Grid(2, 1) + grid.setField(Label("Storage pool:"), 0, 0, anchorTop = 1) + grid.setField(self.__storage_pool, 1, 0) + self.__has_pools = True + else: + grid = Label("There are no storage pools available.") + self.__has_pools = False + return [Label("Configure Managed Storage: Select A Pool"), + grid] + + def get_select_volume_page(self, screen): volumes = [] - for volume in self.get_libvirt().list_storage_volumes(): - volumes.append(["%s (%d GB)" % (volume.name(), volume.info()[1] / (1024 ** 3)), - volume.name(), - self.__config.is_existing_storage(volume.name())]) - self.__existing_storage = RadioBar(screen, (volumes)) - grid = Grid(2, 1) - grid.setField(Label("Existing storage:"), 0, 0) - grid.setField(self.__existing_storage, 1, 0) - return [Label("Configure managed storage"), + for volume in self.get_libvirt().list_storage_volumes(self.__config.get_storage_pool()): + volumes.append([volume, volume, volume == self.__config.get_storage_volume()]) + if len(volumes) > 0: + self.__storage_volume = RadioBar(screen, (volumes)) + grid = Grid(2, 1) + grid.setField(Label("Storage volumes:"), 0, 0, anchorTop = 1) + grid.setField(self.__storage_volume, 1, 0) + self.__has_volumes = True + else: + grid = Label("This storage pool has no defined volumes.") + self.__has_volumes = False + return [Label("Configure Managed Storage: Select A Volume"), grid] def get_bridge_page(self, screen): @@ -448,7 +482,9 @@ class DomainConfigScreen(ConfigScreen): grid.setField(Label("CPUs:"), 0, 3, anchorRight = 1) grid.setField(Label("%d" % self.__config.get_cpus()), 1, 3, anchorLeft = 1) grid.setField(Label("Storage:"), 0, 4, anchorRight = 1) - grid.setField(Label(self.__config.get_existing_storage()), 1, 4, anchorLeft = 1) + grid.setField(Label("%s (on %s)" % (self.__config.get_storage_volume(), + self.__config.get_storage_pool())), + 1, 4, anchorLeft = 1) grid.setField(Label("Network:"), 0, 5, anchorRight = 1) grid.setField(Label(self.__config.get_network_bridge()), 1, 5, anchorLeft = 1) return [Label("Ready to begin installation of %s" % self.__config.get_guest_name()), diff --git a/nodeadmin/domainconfig.py b/nodeadmin/domainconfig.py index ef39fe0..4466e67 100644 --- a/nodeadmin/domainconfig.py +++ b/nodeadmin/domainconfig.py @@ -50,7 +50,8 @@ class DomainConfig: self.__use_local_storage = True self.__storage_size = 8.0 self.__allocate_storage = True - self.__existing_storage = "" + self.__storage_pool = "" + self.__storage_volume = "" self.__network_bridge = None self.__mac_address = None self.__virt_type = None @@ -177,11 +178,17 @@ class DomainConfig: def get_allocate_storage(self): return self.__allocate_storage - def set_existing_storage(self, storage): - self.__existing_storage = storage + def set_storage_pool(self, pool): + self.__storage_pool = pool - def get_existing_storage(self): - return self.__existing_storage + def get_storage_pool(self): + return self.__storage_pool + + def set_storage_volume(self, volume): + self.__storage_volume = volume + + def get_storage_volume(self): + return self.__storage_volume def is_existing_storage(self, storage): return self.__existing_storage == storage diff --git a/nodeadmin/libvirtworker.py b/nodeadmin/libvirtworker.py index b2acabe..f31266c 100644 --- a/nodeadmin/libvirtworker.py +++ b/nodeadmin/libvirtworker.py @@ -196,6 +196,11 @@ class LibvirtWorker: '''Returns the storage pool with the specified name.''' return self.__conn.storagePoolLookupByName(name) + def list_storage_volumes(self, poolname): + '''Returns the list of all defined storage volumes for a given pool.''' + pool = self.get_storage_pool(poolname) + return pool.listVolumes() + def define_storage_volume(self, config, meter): '''Defines a new storage volume.''' self.create_storage_pool(config.get_pool().name()) @@ -204,10 +209,15 @@ class LibvirtWorker: def remove_storage_volume(self, poolname, volumename): '''Removes the specified storage volume.''' - pool = self.get_storage_pool(poolname) - volume = pool.storageVolLookupByName(volumename) + volume = self.get_storage_volume(poolname, volumename) volume.delete(0) + def get_storage_volume(self, poolname, volumename): + '''Returns a reference to the specified storage volume.''' + pool =self.get_storage_pool(poolname) + volume = pool.storageVolLookupByName(volumename) + return volume + def list_bridges(self): '''Lists all defined and active bridges.''' bridges = self.__conn.listNetworks() @@ -221,21 +231,9 @@ class LibvirtWorker: def generate_mac_address(self): return self.__net.macaddr - def list_storage_volumes(self): - '''Lists all defined storage volumes.''' - pools = self.__conn.listStoragePools() - pools.extend(self.__conn.listDefinedStoragePools()) - result = [] - for name in pools: - pool = self.__conn.storagePoolLookupByName(name) - for volname in pool.listVolumes(): - volume = self.__conn.storageVolLookupByPath("/var/lib/libvirt/images/%s" % volname) - result.append(volume) - return result - - def get_storage_size(self, name): + def get_storage_size(self, poolname, volumename): '''Returns the size of the specified storage volume.''' - volume = self.__conn.storageVolLookupByPath("/var/lib/libvirt/images/%s" % name) + volume = self.get_storage_volume(poolname, volumename) return volume.info()[1] / (1024.0 ** 3) def get_virt_types(self): @@ -381,6 +379,10 @@ class LibvirtWorker: pool_object = pool, suffix = ".img") path = os.path.join(DEFAULT_POOL_TARGET_PATH, path) + else: + volume = self.get_storage_volume(config.get_storage_pool(), + config.get_storage_volume()) + path = volume.path() if path is not None: storage= virtinst.VirtualDisk(conn = self.__conn, -- 1.6.2.5 From jboggs at redhat.com Wed Oct 28 01:13:42 2009 From: jboggs at redhat.com (Joey Boggs) Date: Tue, 27 Oct 2009 21:13:42 -0400 Subject: [Ovirt-devel] [PATCH 1/2] Provides a new storage administration system to the managed node. In-Reply-To: <1256669472-19845-2-git-send-email-dpierce@redhat.com> References: <1256669472-19845-1-git-send-email-dpierce@redhat.com> <1256669472-19845-2-git-send-email-dpierce@redhat.com> Message-ID: <4AE79AC6.8060705@redhat.com> Darryl L. Pierce wrote: > Users can now: > * Add a new storage pool. > * Delete a storage pool. > * Start and stop storage pools. > * Add a new storage volume. > * Delete a storage volume. > * List existing storage pools, with details. > > Signed-off-by: Darryl L. Pierce > --- > Makefile.am | 28 +++++-- > nodeadmin/adddomain.py | 15 +--- > nodeadmin/addpool.py | 182 ++++++++++++++++++++++++++++++++++++++++++++ > nodeadmin/addvolume.py | 160 ++++++++++++++++++++++++++++++++++++++ > nodeadmin/configscreen.py | 52 +++++++++++++ > nodeadmin/createmeter.py | 30 +++++++ > nodeadmin/libvirtworker.py | 67 +++++++++++++++-- > nodeadmin/listpools.py | 63 +++++++++++++++ > nodeadmin/mainmenu.py | 24 ++++--- > nodeadmin/poolconfig.py | 137 +++++++++++++++++++++++++++++++++ > nodeadmin/removepool.py | 72 +++++++++++++++++ > nodeadmin/removevolume.py | 76 ++++++++++++++++++ > nodeadmin/setup.py.in | 9 ++- > nodeadmin/startpool.py | 62 +++++++++++++++ > nodeadmin/stoppool.py | 62 +++++++++++++++ > nodeadmin/storagemenu.py | 63 +++++++++++++++ > nodeadmin/utils.py | 10 +++ > nodeadmin/volumeconfig.py | 76 ++++++++++++++++++ > ovirt-node.spec.in | 7 ++ > 19 files changed, 1155 insertions(+), 40 deletions(-) > create mode 100644 nodeadmin/addpool.py > create mode 100644 nodeadmin/addvolume.py > create mode 100644 nodeadmin/createmeter.py > create mode 100644 nodeadmin/listpools.py > create mode 100644 nodeadmin/poolconfig.py > create mode 100644 nodeadmin/removepool.py > create mode 100644 nodeadmin/removevolume.py > create mode 100644 nodeadmin/startpool.py > create mode 100644 nodeadmin/stoppool.py > create mode 100644 nodeadmin/storagemenu.py > create mode 100644 nodeadmin/volumeconfig.py > > diff --git a/Makefile.am b/Makefile.am > index 3ce24c1..55ef277 100644 > --- a/Makefile.am > +++ b/Makefile.am > @@ -28,29 +28,39 @@ EXTRA_DIST = \ > images/syslinux-vesa-splash.jpg \ > nodeadmin/__init__.py \ > nodeadmin/adddomain.py \ > + nodeadmin/addpool.py \ > + nodeadmin/addvolume.py \ > nodeadmin/configscreen.py \ > + nodeadmin/createmeter.py \ > nodeadmin/createnetwork.py \ > nodeadmin/createuser.py \ > + nodeadmin/definenet.py \ > nodeadmin/destroynetwork.py \ > + nodeadmin/domainconfig.py \ > nodeadmin/halworker.py \ > nodeadmin/libvirtworker.py \ > - nodeadmin/userworker.py \ > + nodeadmin/listdomains.py \ > + nodeadmin/listnetworks.py \ > + nodeadmin/listpools.py \ > nodeadmin/mainmenu.py \ > nodeadmin/menuscreen.py \ > + nodeadmin/networkconfig.py \ > nodeadmin/netmenu.py \ > + nodeadmin/nodeadmin.py \ > nodeadmin/nodemenu.py \ > nodeadmin/removedomain.py \ > - nodeadmin/undefinenetwork.py \ > + nodeadmin/removepool.py \ > + nodeadmin/removevolume.py \ > + nodeadmin/setup.py \ > nodeadmin/startdomain.py \ > + nodeadmin/startpool.py \ > nodeadmin/stopdomain.py \ > - nodeadmin/definenet.py \ > - nodeadmin/domainconfig.py \ > - nodeadmin/networkconfig.py \ > - nodeadmin/listdomains.py \ > - nodeadmin/listnetworks.py \ > - nodeadmin/nodeadmin.py \ > - nodeadmin/setup.py \ > + nodeadmin/stoppool.py \ > + nodeadmin/storagemenu.py \ > + nodeadmin/undefinenetwork.py \ > + nodeadmin/userworker.py \ > nodeadmin/utils.py \ > + nodeadmin/volumeconfig.py \ > scripts/collectd.conf.in \ > scripts/ovirt \ > scripts/ovirt-awake \ > diff --git a/nodeadmin/adddomain.py b/nodeadmin/adddomain.py > index 70a2011..bb06a62 100755 > --- a/nodeadmin/adddomain.py > +++ b/nodeadmin/adddomain.py > @@ -20,11 +20,10 @@ > > from snack import * > import os > +from createmeter import CreateMeter > from domainconfig import DomainConfig > from configscreen import ConfigScreen > -import urlgrabber.progress as progress > import utils > -import logging > > from virtinst import * > > @@ -51,16 +50,6 @@ OS_VARIANT="os.variant" > MEMORY="memory" > CPUS="cpus" > > -class DummyMeter(progress.BaseMeter): > - def _do_start(self, now = None): > - logging.info("Starting...") > - > - def _do_end(self, amount_read, now = None): > - logging.info("Ending: read=%d" % amount_read) > - > - def _do_update(self, amount_read, now = None): > - logging.info("Update: read=%d" % amount_read) > - > class DomainConfigScreen(ConfigScreen): > def __init__(self): > ConfigScreen.__init__(self, "Create A New Virtual Machine") > @@ -212,7 +201,7 @@ class DomainConfigScreen(ConfigScreen): > self.__config.set_virt_type(self.__virt_types.getSelection()) > self.__config.set_architecture(self.__architectures.getSelection()) > elif page == CONFIRM_PAGE: > - self.get_libvirt().define_domain(self.__config, DummyMeter()) > + self.get_libvirt().define_domain(self.__config, CreateMeter()) > self.set_finished() > > def get_back_page(self, page): > diff --git a/nodeadmin/addpool.py b/nodeadmin/addpool.py > new file mode 100644 > index 0000000..389be52 > --- /dev/null > +++ b/nodeadmin/addpool.py > @@ -0,0 +1,182 @@ > +# addstorage.py - Copyright (C) 2009 Red Hat, Inc. > +# Written by Darryl L. Pierce > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +from snack import * > +import traceback > +import utils > + > +from configscreen import * > +from poolconfig import PoolConfig > +from virtinst import Storage > + > +POOL_NAME_PAGE = 1 > +POOL_DETAILS_PAGE = 2 > +CONFIRM_PAGE = 3 > + > +class AddStoragePoolConfigScreen(ConfigScreen): > + def __init__(self): > + ConfigScreen.__init__(self, "Add A Storage Pool") > + self.__config = PoolConfig(self.get_libvirt()) > + > + def get_elements_for_page(self, screen, page): > + if page is POOL_NAME_PAGE: return self.get_pool_name_page(screen) > + elif page is POOL_DETAILS_PAGE: return self.get_pool_details_page(screen) > + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) > + > + def page_has_next(self, page): > + return page < CONFIRM_PAGE > + > + def page_has_back(self, page): > + return page > POOL_NAME_PAGE > + > + def page_has_finish(self, page): > + return page is CONFIRM_PAGE > + > + def validate_input(self, page, errors): > + if page is POOL_NAME_PAGE: > + if utils.string_is_not_blank(self.__name.value()): > + if self.get_libvirt().storage_pool_exists(self.__name.value()): > + errors.append("Name '%s' already in use by another pool." % self.__name.value()) > + else: > + return True > + else: > + errors.append("Storage object name must be a string between 0 and 50 characters.") > + elif page is POOL_DETAILS_PAGE: > + result = True > + if self.__config.needs_target_path(): > + if utils.string_is_not_blank(self.__target_path.value()): > + if self.__target_path.value()[0:1] is not '/': > + errors.append("'%s' is not an absolute path." % self.__target_path.value()) > + result = False > + else: > + errors.append("You must enter a target path.") > + result = False > + if self.__config.needs_format(): > + if self.__formats.getSelection() is None: > + errors.append("You must select a pool format.") > + result = False > + if self.__config.needs_hostname(): > + if utils.string_is_not_blank(self.__hostname.value()): > + errors.append("You must enter a hostname.") > + result = False > + if self.__config.needs_source_path(): > + if utils.string_is_not_blank(self.__source_path.value()): > + if self.__source_path.value()[0:1] is not '/': > + errors.append("'%s' is not an absolute path." % self.__source_path.value()) > + result = False > + else: > + errors.append("you must enter a source path.") > + result = False > + return result > + elif page is CONFIRM_PAGE: return True > + return False > + > + def process_input(self, page): > + if page is POOL_NAME_PAGE: > + self.__config.set_name(self.__name.value()) > + self.__config.set_type(self.__type.getSelection()) > + #self._reset_flags(self.__type.current()) > + elif page is POOL_DETAILS_PAGE: > + if self.__config.needs_target_path(): > + self.__config.set_target_path(self.__target_path.value()) > + if self.__config.needs_format(): > + self.__config.set_format(self.__formats.getSelection()) > + if self.__config.needs_hostname(): > + self.__config.set_hostname(self.__hostname.value()) > + if self.__config.needs_source_path(): > + self.__config.set_source_path(self.__source_path.value()) > + if self.__config.needs_build_pool(): > + self.__config.set_build_pool(self.__build_pool.value()) > + elif page is CONFIRM_PAGE: > + self.get_libvirt().define_storage_pool(self.__config.get_name(), config = self.__config) > + self.get_libvirt().create_storage_pool(self.__config.get_name()) > + self.set_finished() > + > + def get_pool_name_page(self, screen): > + self.__name = Entry(50, self.__config.get_name()) > + pooltypes = [] > + for pooltype in Storage.StoragePool.get_pool_types(): > + pooltypes.append(["%s: %s" % (pooltype, Storage.StoragePool.get_pool_type_desc(pooltype)), > + pooltype, > + self.__config.get_type() is pooltype]) > + self.__type = RadioBar(screen, pooltypes) > + grid = Grid(2, 2) > + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) > + grid.setField(self.__name, 1, 0, anchorLeft = 1) > + grid.setField(Label("Type:"), 0, 1, anchorRight = 1, anchorTop = 1) > + grid.setField(self.__type, 1, 1, anchorLeft = 1) > + return [Label("Add Storage Pool"), > + grid] > + > + def get_pool_details_page(self, screen): > + rows = 0 > + if self.__config.needs_target_path(): > + self.__target_path = Entry(50, self.__config.get_target_path()) > + rows += 1 > + if self.__config.needs_format(): > + formats = [] > + for format in self.__config.get_formats(): > + formats.append([format, format, format is self.__config.get_format()]) > + self.__formats = RadioBar(screen, formats) > + rows += 1 > + if self.__config.needs_hostname(): > + self.__hostname = Entry(50, self.__config.get_hostname()) > + rows += 1 > + if self.__config.needs_source_path(): > + self.__source_path = Entry(50, self.__config.get_source_path()) > + rows += 1 > + if self.__config.needs_build_pool(): > + self.__build_pool = Checkbox("Build Pool", self.__config.get_build_pool()) > + rows += 1 > + grid = Grid(2, rows) > + currentrow = 0 > + if self.__config.needs_target_path(): > + grid.setField(Label("Target Path:"), 0, currentrow, anchorRight = 1) > + grid.setField(self.__target_path, 1, currentrow, anchorLeft = 1) > + currentrow += 1 > + if self.__config.needs_format(): > + grid.setField(Label("Format:"), 0, currentrow, anchorRight = 1, anchorTop = 1) > + grid.setField(self.__formats, 1, currentrow, anchorLeft = 1) > + currentrow += 1 > + if self.__config.needs_hostname(): > + grid.setField(Label("Host Name:"), 0, currentrow, anchorRight = 1) > + grid.setField(self.__hostname, 1, currentrow, anchorRight = 1) > + currentrow += 1 > + if self.__config.needs_source_path(): > + grid.setField(Label("Source Path:"), 0, currentrow, anchorRight = 1) > + grid.setField(self.__source_path, 1, currentrow, anchorLeft = 1) > + currentrow += 1 > + if self.__config.needs_build_pool(): > + grid.setField(Label(" "), 0, currentrow, anchorRight = 1) > + grid.setField(self.__build_pool, 1, currentrow, anchorLeft = 1) > + currentrow += 1 > + return [Label("Specify a storage location to be later split into virtual machine storage"), > + grid] > + > + def get_confirm_page(self, screen): > + grid = Grid(2, 2) > + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) > + grid.setField(Label(self.__config.get_name()), 1, 0, anchorLeft = 1) > + grid.setField(Label("Target Path:"), 0, 1, anchorRight = 1) > + grid.setField(Label(self.__config.get_target_path()), 1, 1, anchorLeft = 1) > + return [Label("Confirm Pool Details"), > + grid] > + > +def AddStoragePool(): > + screen = AddStoragePoolConfigScreen() > + screen.start() > diff --git a/nodeadmin/addvolume.py b/nodeadmin/addvolume.py > new file mode 100644 > index 0000000..82c014c > --- /dev/null > +++ b/nodeadmin/addvolume.py > @@ -0,0 +1,160 @@ > +# addvolume.py - Copyright (C) 2009 Red Hat, Inc. > +# Written by Darryl L. Pierce > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +from snack import * > +import traceback > + > +from createmeter import CreateMeter > +from configscreen import * > +from volumeconfig import StorageVolumeConfig > +from utils import * > + > +SELECT_POOL_PAGE = 1 > +VOLUME_NAME_PAGE = 2 > +VOLUME_FORMAT_PAGE = 3 > +MAX_CAPACITY_PAGE = 4 > +CONFIRM_PAGE = 5 > + > +class AddVolumeConfigScreen(StorageListConfigScreen): > + def __init__(self): > + StorageListConfigScreen.__init__(self, "Add A New Storage Volume") > + self.__config = StorageVolumeConfig() > + > + def get_elements_for_page(self, screen, page): > + if page is SELECT_POOL_PAGE: return self.get_storage_pool_list_page(screen) > + elif page is VOLUME_NAME_PAGE: return self.get_volume_name_page(screen) > + elif page is VOLUME_FORMAT_PAGE: return self.get_volume_format_page(screen) > + elif page is MAX_CAPACITY_PAGE: return self.get_max_capacity_page(screen) > + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) > + > + def page_has_next(self, page): > + if page is SELECT_POOL_PAGE: > + return self.has_selectable_pools() > + else: > + if page < CONFIRM_PAGE: return True > + return False > + > + def page_has_back(self, page): > + if page > SELECT_POOL_PAGE: return True > + return False > + > + def page_has_finish(self, page): > + return page is CONFIRM_PAGE > + > + def validate_input(self, page, errors): > + if page is SELECT_POOL_PAGE: > + if self.get_selected_pool() is not None: > + return True > + else: > + errors.append("You must select a storage pool.") > + elif page is VOLUME_NAME_PAGE: > + if string_is_not_blank(self.__name.value()): > + return True > + else: > + errors.append("Storage object name can only contain alphanumeric, '_', '.', or '-' characters.") > + elif page is VOLUME_FORMAT_PAGE: > + if self.__formats.current() is not None: > + return True > + else: > + errors.append("You must select a volume format.") > + elif page is MAX_CAPACITY_PAGE: > + if string_is_not_blank(self.__capacity.value()): > + if string_is_not_blank(self.__allocation.value()): > + capacity = int(self.__capacity.value()) > + allocation = int(self.__allocation.value()) > + if capacity > 0: > + if capacity <= self.__config.get_pool().info()[3] / 1024**2: > + if allocation >= 0: > + if allocation <= capacity: > + return True > + else: > + errors.append("Allocation cannot exceed the maximum capacity.") > + else: > + errors.append("The allocation must be greater than or equal to 0.") > + else: > + errors.append("The maximum capacity cannot exceed the storage pool size.") > + else: > + errors.append("The capacity must be greater than zero.") > + else: > + errors.append("An allocation value must be entered.") > + else: > + errors.append("A maximum volume capacity must be entered.") > + elif page is CONFIRM_PAGE: return True > + return False > + > + def process_input(self, page): > + if page is SELECT_POOL_PAGE: > + self.__config.set_pool(self.get_libvirt().get_storage_pool(self.get_selected_pool())) > + elif page is VOLUME_NAME_PAGE: > + self.__config.set_name(self.__name.value()) > + elif page is VOLUME_FORMAT_PAGE: > + self.__config.set_format(self.__formats.current()) > + elif page is MAX_CAPACITY_PAGE: > + self.__config.set_max_capacity(int(self.__capacity.value())) > + self.__config.set_allocation(int(self.__allocation.value())) > + elif page is CONFIRM_PAGE: > + self.get_libvirt().define_storage_volume(self.__config, CreateMeter()) > + self.set_finished() > + > + def get_volume_name_page(self, screen): > + self.__name = Entry(50, self.__config.get_name()) > + grid = Grid(2, 1) > + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) > + grid.setField(self.__name, 1, 0, anchorLeft = 1) > + return [Label("New Storage Volume"), > + grid, > + Label("Name of the volume to create. File extension may be appended.")] > + > + def get_volume_format_page(self, screen): > + self.__formats = Listbox(0) > + for format in self.__config.get_formats_for_pool(): > + self.__formats.append(format, format) > + grid = Grid(1, 1) > + grid.setField(self.__formats, 0, 0) > + return [Label("Select The Volume Format"), > + grid] > + > + def get_max_capacity_page(self, screen): > + self.__capacity = Entry(6, str(self.__config.get_max_capacity())) > + self.__allocation = Entry(6, str(self.__config.get_allocation())) > + grid = Grid(2, 2) > + grid.setField(Label("Max. Capacity (MB):"), 0, 0, anchorRight = 1) > + grid.setField(self.__capacity, 1, 0, anchorLeft = 1) > + grid.setField(Label("Allocation (MB):"), 0, 1, anchorRight = 1) > + grid.setField(self.__allocation, 1, 1, anchorLeft = 1) > + return [Label("Storage Volume Quota"), > + Label("%s's available space: %0.2f GB" % (self.__config.get_pool().name(), > + self.__config.get_pool().info()[3] / 1024.0**3)), > + grid] > + > + def get_confirm_page(self, screen): > + grid = Grid(2, 5) > + grid.setField(Label("Volume Name:"), 0, 0, anchorRight = 1) > + grid.setField(Label("%s (%s)" % (self.__config.get_name(), self.__config.get_pool().name())), 1, 0, anchorLeft = 1) > + grid.setField(Label("Format:"), 0, 1, anchorRight = 1) > + grid.setField(Label(self.__config.get_format()), 1, 1, anchorLeft = 1) > + grid.setField(Label("Max. Capacity:"), 0, 2, anchorRight = 1) > + grid.setField(Label("%0.2f GB" % (self.__config.get_max_capacity() / 1024.0)), 1, 2, anchorLeft = 1) > + grid.setField(Label("Allocation:"), 0, 3, anchorRight = 1) > + grid.setField(Label("%0.2f GB" % (self.__config.get_allocation() / 1024.0)), 1, 3, anchorLeft = 1) > + return [Label("Ready To Allocation New Storage Volume"), > + grid] > + > +def AddStorageVolume(): > + screen = AddVolumeConfigScreen() > + screen.start() > diff --git a/nodeadmin/configscreen.py b/nodeadmin/configscreen.py > index f214aea..7654697 100644 > --- a/nodeadmin/configscreen.py > +++ b/nodeadmin/configscreen.py > @@ -179,3 +179,55 @@ class NetworkListConfigScreen(ConfigScreen): > > def has_selectable_networks(self): > return self.__has_networks > + > +class StorageListConfigScreen(ConfigScreen): > + '''Provides a base class for any configuration screen that deals with storage pool lists.''' > + > + def __init__(self, title): > + ConfigScreen.__init__(self, title) > + > + def get_storage_pool_list_page(self, screen, defined=True, created=True): > + pools = self.get_libvirt().list_storage_pools(defined=defined, created=created) > + if len(pools) > 0: > + self.__has_pools = True > + self.__pools_list = Listbox(0) > + for pool in pools: > + self.__pools_list.append(pool, pool) > + result = self.__pools_list > + else: > + self.__has_pools = False > + result = Label("There are no storage pools available.") > + grid = Grid(1, 1) > + grid.setField(result, 0, 0) > + return [Label("Storage Pool List"), > + grid] > + > + def get_selected_pool(self): > + return self.__pools_list.current() > + > + def has_selectable_pools(self): > + return self.__has_pools > + > + def get_storage_volume_list_page(self, screen): > + '''Requires that self.__pools_list have a selected element.''' > + pool = self.get_libvirt().get_storage_pool(self.get_selected_pool()) > + if len(pool.listVolumes()) > 0: > + self.__has_volumes = True > + self.__volumes_list = Listbox(0) > + for volname in pool.listVolumes(): > + volume = pool.storageVolLookupByName(volname) > + self.__volumes_list.append("%s (%0.2f GB)" % (volume.name(), volume.info()[2] / 1024**3), volume.name()) > + result = self.__volumes_list > + else: > + self.__has_volumes = False > + result = Label("There are no storage volumes available.") > + grid = Grid(1, 1) > + grid.setField(result, 0, 0) > + return [Label("Storage Volume List"), > + grid] > + > + def get_selected_volume(self): > + return self.__volumes_list.current() > + > + def has_selectable_volumes(self): > + return self.__has_volumes > diff --git a/nodeadmin/createmeter.py b/nodeadmin/createmeter.py > new file mode 100644 > index 0000000..521e7d8 > --- /dev/null > +++ b/nodeadmin/createmeter.py > @@ -0,0 +1,30 @@ > +# createmeter.py - Copyright (C) 2009 Red Hat, Inc. > +# Written by Darryl L. Pierce > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +import urlgrabber.progress as progress > +import logging > + > +class CreateMeter(progress.BaseMeter): > + def _do_start(self, now = None): > + logging.info("Starting...") > + > + def _do_end(self, amount_read, now = None): > + logging.info("Ending: read=%d" % amount_read) > + > + def _do_update(self, amount_read, now = None): > + logging.info("Update: read=%d" % amount_read) > diff --git a/nodeadmin/libvirtworker.py b/nodeadmin/libvirtworker.py > index ba07605..b2acabe 100644 > --- a/nodeadmin/libvirtworker.py > +++ b/nodeadmin/libvirtworker.py > @@ -35,6 +35,10 @@ class LibvirtWorker: > self.__net.setup(self.__conn) > (self.__new_guest, self.__new_domain) = virtinst.CapabilitiesParser.guest_lookup(conn = self.__conn) > > + def get_connection(self): > + '''Returns the underlying connection.''' > + return self.__conn > + > def list_domains(self, defined = True, started = True): > '''Lists all domains.''' > result = [] > @@ -134,9 +138,12 @@ class LibvirtWorker: > network = self.get_network(name) > network.undefine() > > - def list_storage_pools(self): > + def list_storage_pools(self, defined=True, created=True): > '''Returns the list of all defined storage pools.''' > - return self.__conn.listStoragePools() > + pools = [] > + if defined: pools.extend(self.__conn.listDefinedStoragePools()) > + if created: pools.extend(self.__conn.listStoragePools()) > + return pools > > def storage_pool_exists(self, name): > '''Returns whether a storage pool exists.''' > @@ -144,16 +151,62 @@ class LibvirtWorker: > if name in pools: return True > return False > > - def define_storage_pool(self, name): > + def create_storage_pool(self, name): > + '''Starts the named storage pool if it is not currently started.''' > + if name not in self.list_storage_pools(defined = False): > + pool = self.get_storage_pool(name) > + pool.create(0) > + > + def destroy_storage_pool(self, name): > + '''Stops the specified storage pool.''' > + if name in self.list_storage_pools(defined = False): > + pool = self.get_storage_pool(name) > + pool.destroy() > + > + def define_storage_pool(self, name, config = None, meter = None): > '''Defines a storage pool with the given name.''' > - try: > + if config is None: > pool = virtinst.Storage.DirectoryPool(conn=self.__conn, > name=name, > target_path=DEFAULT_POOL_TARGET_PATH) > - newpool = pool.install(build=True, create=True) > + newpool = pool.install(build=True, create=True, meter=meter) > newpool.setAutostart(True) > - except Exception, error: > - raise RuntimeError("Could not create pool: %s - %s", str(error)) > + else: > + pool = config.get_pool() > + pool.target_path = config.get_target_path() > + if config.needs_hostname(): > + pool.host = config.get_hostname() > + if config.needs_source_path(): > + pool.source_path = config.get_source_path() > + if config.needs_format(): > + pool.format = config.get_format() > + pool.conn = self.__conn > + pool.get_xml_config() > + newpool = pool.install(meter=meter, > + build=True, # config.get_build_pool(), > + create=True) > + newpool.setAutostart(True) > + > + def undefine_storage_pool(self, name): > + '''Undefines the specified storage pool.''' > + pool = self.get_storage_pool(name) > + pool.undefine() > + > + def get_storage_pool(self, name): > + '''Returns the storage pool with the specified name.''' > + return self.__conn.storagePoolLookupByName(name) > + > + def define_storage_volume(self, config, meter): > + '''Defines a new storage volume.''' > + self.create_storage_pool(config.get_pool().name()) > + volume = config.create_volume() > + volume.install(meter = meter) > + > + def remove_storage_volume(self, poolname, volumename): > + '''Removes the specified storage volume.''' > + pool = self.get_storage_pool(poolname) > + volume = pool.storageVolLookupByName(volumename) > + volume.delete(0) > > def list_bridges(self): > '''Lists all defined and active bridges.''' > diff --git a/nodeadmin/listpools.py b/nodeadmin/listpools.py > new file mode 100644 > index 0000000..686c42d > --- /dev/null > +++ b/nodeadmin/listpools.py > @@ -0,0 +1,63 @@ > +# listpools.py - Copyright (C) 2009 Red Hat, Inc. > +# Written by Darryl L. Pierce > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +from snack import * > + > +from configscreen import * > + > +LIST_PAGE = 1 > +DETAILS_PAGE = 2 > + > +class ListStoragePoolsConfigScreen(StorageListConfigScreen): > + def __init__(self): > + StorageListConfigScreen.__init__(self, "List Storage Pools") > + > + def get_elements_for_page(self, screen, page): > + if page is LIST_PAGE: return self.get_storage_pool_list_page(screen) > + elif page is DETAILS_PAGE: return self.get_pool_details_page(screen) > + > + def page_has_next(self, page): > + if page is LIST_PAGE and self.has_selectable_pools(): > + return True > + return False > + > + def page_has_back(self, page): > + if page is DETAILS_PAGE: return True > + return False > + > + def get_pool_details_page(self, screen): > + pool = self.get_libvirt().get_storage_pool(self.get_selected_pool()) > + volumes = Listbox(0); > + for name in pool.listVolumes(): > + volume = pool.storageVolLookupByName(name) > + volumes.append("%s (%0.1f G)" % (name, volume.info()[1] / 1024**3), name) > + grid = Grid(2, 3) > + grid.setField(Label("Name:"), 0, 0, anchorRight = 1) > + grid.setField(Label(pool.name()), 1, 0, anchorLeft = 1) > + grid.setField(Label("Volumes:"), 0, 1, anchorRight = 1) > + grid.setField(volumes, 1, 1, anchorLeft = 1) > + grid.setField(Label("Autostart:"), 0, 2, anchorRight = 1) > + label = "No" > + if pool.autostart(): label = "Yes" > + grid.setField(Label(label), 1, 2, anchorLeft = 1) > + return [Label("Details For Storage Pool: %s" % self.get_selected_pool()), > + grid] > + > +def ListStoragePools(): > + screen = ListStoragePoolsConfigScreen() > + screen.start() > diff --git a/nodeadmin/mainmenu.py b/nodeadmin/mainmenu.py > index 73501fa..52d9298 100755 > --- a/nodeadmin/mainmenu.py > +++ b/nodeadmin/mainmenu.py > @@ -19,28 +19,32 @@ > from snack import * > import traceback > > -from menuscreen import MenuScreen > -from nodemenu import NodeMenu > -from netmenu import NetworkMenu > +from menuscreen import MenuScreen > +from nodemenu import NodeMenu > +from netmenu import NetworkMenu > +from storagemenu import StoragePoolMenu > > import utils > import logging > > NODE_MENU = 1 > NETWORK_MENU = 2 > -EXIT_CONSOLE = 99 > +STORAGE_MENU = 3 > +EXIT_CONSOLE = 4 > > class MainMenuScreen(MenuScreen): > def __init__(self): > MenuScreen.__init__(self, "Main Menu") > > def get_menu_items(self): > - return (("Node Administration", NODE_MENU), > - ("Network Administration", NETWORK_MENU)) > - > - def handle_selection(self, page): > - if page is NODE_MENU: NodeMenu() > - elif page is NETWORK_MENU: NetworkMenu() > + return (("Node Administration", NODE_MENU), > + ("Network Administration", NETWORK_MENU), > + ("Storage Pool Administration", STORAGE_MENU)) > + > + def handle_selection(self, item): > + if item is NODE_MENU: NodeMenu() > + elif item is NETWORK_MENU: NetworkMenu() > + elif item is STORAGE_MENU: StoragePoolMenu() > > def MainMenu(): > screen = MainMenuScreen() > diff --git a/nodeadmin/poolconfig.py b/nodeadmin/poolconfig.py > new file mode 100644 > index 0000000..06af722 > --- /dev/null > +++ b/nodeadmin/poolconfig.py > @@ -0,0 +1,137 @@ > +# poolconfig.py - Copyright (C) 2009 Red Hat, Inc. > +# Written by Darryl L. Pierce > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +from virtinst import Storage > + > +ROOT_TARGET_PATH="/var/lib/libvirt/images/%s" > + > +class PoolConfig: > + def __init__(self, libvirt): > + self.__libvirt = libvirt > + self.__name = "" > + self.set_type(None) > + self.__format = None > + self.__hostname = "" > + self.__target_path = "" > + self.__source_path = "" > + self.__build_pool = False > + > + def get_pool(self): > + return self.__pool > + > + def set_name(self, name): > + self.__name = name > + > + def get_name(self): > + return self.__name > + > + def set_type(self, pooltype): > + self.__type = pooltype > + self.__needs_target_path = False > + self.__needs_format = False > + self.__needs_hostname = False > + self.__needs_source_path = False > + self.__needs_build_pool = False > + if pooltype is not None: > + if pooltype is Storage.StoragePool.TYPE_DIR: > + self.__needs_target_path = True > + self.__target_path = ROOT_TARGET_PATH % self.__name > + self.__build_pool = True > + elif pooltype is Storage.StoragePool.TYPE_DISK: > + self.__needs_target_path = True > + self.__needs_format = True > + self.__needs_source_path = True > + self.__needs_build_pool = True > + elif pooltype is Storage.StoragePool.TYPE_FS: > + self.__needs_target_path = True > + self.__needs_format = True > + self.__needs_source_path = True > + self.__build_pool = True > + elif pooltype is Storage.StoragePool.TYPE_ISCSI: > + self.__needs_target_path = True > + self.__needs_hostname = True > + self.__needs_source_path = True > + self.__build_pool = False > + elif pooltype is Storage.StoragePool.TYPE_LOGICAL: > + self.__needs_target_path = True > + self.__needs_source_path = True > + self.__needs_build_pool = True > + elif pooltype is Storage.StoragePool.TYPE_NETFS: > + self.__needs_target_path = True > + self.__needs_format = True > + self.__needs_hostname = True > + self.__needs_source_path = True > + self.__build_pool = True > + # create pool > + pool_class = Storage.StoragePool.get_pool_class(self.__type) > + self.__pool = pool_class(name = self.__name, > + conn = self.__libvirt.get_connection()) > + if self.__needs_format: > + self.__format = self.__pool.formats[0] > + else: > + self.__type = Storage.StoragePool.get_pool_types()[0] > + > + def get_type(self): > + return self.__type > + > + def needs_target_path(self): > + return self.__needs_target_path > + > + def needs_format(self): > + return self.__needs_format > + > + def needs_hostname(self): > + return self.__needs_hostname > + > + def needs_source_path(self): > + return self.__needs_source_path > + > + def needs_build_pool(self): > + return self.__needs_build_pool > + def set_target_path(self, path): > + self.__target_path = path > + > + def get_target_path(self): > + return self.__target_path > + > + def get_formats(self): > + return self.__pool.formats > + > + def set_format(self, format): > + self.__format = format > + > + def get_format(self): > + return self.__format > + > + def set_hostname(self, hostname): > + self.__hostname = hostname > + > + def get_hostname(self): > + return self.__hostname > + > + def set_source_path(self, source_path): > + self.__source_path = source_path > + > + def get_source_path(self): > + return self.__source_path > + > + def set_build_pool(self, build_pool): > + self.__build_pool = build_pool > + > + def get_build_pool(self): > + return self.__build_pool > diff --git a/nodeadmin/removepool.py b/nodeadmin/removepool.py > new file mode 100644 > index 0000000..7a7f46d > --- /dev/null > +++ b/nodeadmin/removepool.py > @@ -0,0 +1,72 @@ > +#!/usr/bin/env python > +# > +# removepool.py - Copyright (C) 2009 Red Hat, Inc. > +# Written by Darryl L. Pierce > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +from snack import * > +from configscreen import * > + > +LIST_POOLS_PAGE = 1 > +CONFIRM_PAGE = 2 > + > +class RemoveStoragePoolConfigScreen(StorageListConfigScreen): > + def __init__(self): > + StorageListConfigScreen.__init__(self, "Remove A Storage Pool") > + > + def get_elements_for_page(self, screen, page): > + if page is LIST_POOLS_PAGE: return self.get_storage_pool_list_page(screen) > + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) > + > + def page_has_next(self, page): > + return page is LIST_POOLS_PAGE and self.has_selectable_pools() > + > + def page_has_back(self, page): > + return False > + > + def page_has_finish(self, page): > + return page is CONFIRM_PAGE > + > + def validate_input(self, page, errors): > + if page is LIST_POOLS_PAGE: > + if self.get_selected_pool() is not None: > + return True > + else: > + errors.append("Please select a storage pool to be removed.") > + elif page is CONFIRM_PAGE: > + if self.__confirm.value(): > + return True > + else: > + errors.append("You must confirm removing a storage pool.") > + return False > + > + def process_input(self, page): > + if page is CONFIRM_PAGE: > + self.get_libvirt().destroy_storage_pool(self.get_selected_pool()) > + self.get_libvirt().undefine_storage_pool(self.get_selected_pool()) > + self.set_finished() > + > + def get_confirm_page(self, screen): > + self.__confirm = Checkbox("Check here to confirm deleting pool: %s" % self.get_selected_pool()) > + grid = Grid(1, 1) > + grid.setField(self.__confirm, 0, 0) > + return [Label("Remove Selected Storage Pool"), > + grid] > + > +def RemoveStoragePool(): > + screen = RemoveStoragePoolConfigScreen() > + screen.start() > diff --git a/nodeadmin/removevolume.py b/nodeadmin/removevolume.py > new file mode 100644 > index 0000000..5ad3058 > --- /dev/null > +++ b/nodeadmin/removevolume.py > @@ -0,0 +1,76 @@ > +# removevolume.py - Copyright (C) 2009 Red Hat, Inc. > +# Written by Darryl L. Pierce > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +from snack import * > +import traceback > + > +from createmeter import CreateMeter > +from configscreen import * > +from volumeconfig import StorageVolumeConfig > +from utils import * > + > +SELECT_POOL_PAGE = 1 > +SELECT_VOLUME_PAGE = 2 > +CONFIRM_PAGE = 3 > + > +class RemoveVolumeConfigScreen(StorageListConfigScreen): > + def __init__(self): > + StorageListConfigScreen.__init__(self, "Add A New Storage Volume") > + self.__config = StorageVolumeConfig() > + > + def get_elements_for_page(self, screen, page): > + if page is SELECT_POOL_PAGE: return self.get_storage_pool_list_page(screen) > + elif page is SELECT_VOLUME_PAGE: return self.get_storage_volume_list_page(screen) > + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) > + > + def page_has_next(self, page): > + if page is SELECT_POOL_PAGE: return self.has_selectable_pools() > + elif page is SELECT_VOLUME_PAGE: return self.has_selectable_volumes() > + return False > + > + def validate_input(self, page, errors): > + if page is SELECT_POOL_PAGE: return self.get_selected_pool() is not None > + elif page is SELECT_VOLUME_PAGE: return self.get_selected_volume() is not None > + elif page is CONFIRM_PAGE: > + if self.__confirm.value(): > + return True > + else: > + errors.append("You must confirm deleting a storage volume.") > + return False > + > + def process_input(self, page): > + if page is CONFIRM_PAGE: > + self.get_libvirt().remove_storage_volume(self.get_selected_pool(), self.get_selected_volume()) > + self.set_finished() > + > + def page_has_back(self, page): > + return page > SELECT_POOL_PAGE > + > + def page_has_finish(self, page): > + return page is CONFIRM_PAGE > + > + def get_confirm_page(self, screen): > + self.__confirm = Checkbox("Check here to confirm deleting volume: %s" % self.get_selected_volume()) > + grid = Grid(1, 1) > + grid.setField(self.__confirm, 0, 0) > + return [Label("Remove Selected Storage Volume"), > + grid] > + > +def RemoveStorageVolume(): > + screen = RemoveVolumeConfigScreen() > + screen.start() > diff --git a/nodeadmin/setup.py.in b/nodeadmin/setup.py.in > index 1e6e028..17bfe93 100644 > --- a/nodeadmin/setup.py.in > +++ b/nodeadmin/setup.py.in > @@ -35,5 +35,12 @@ setup(name = "nodeadmin", > 'createnet = nodeadmin.createnetwork:CreateNetwork', > 'destroynet = nodeadmin.destroynetwork:DestroyNetwork', > 'undefinenet = nodeadmin.undefinenetwork:UndefineNetwork', > - 'listnets = nodeadmin.listnetworks:ListNetworks'] > + 'listnets = nodeadmin.listnetworks:ListNetworks', > + 'addpool = nodeadmin.addpool:AddStoragePool', > + 'rmpool = nodeadmin.removepool:RemoveStoragePool', > + 'startpool = nodeadmin.startpool:StartStoragePool', > + 'stoppool = nodeadmin.stoppool:StopStoragePool', > + 'addvolume = nodeadmin.addvolume:AddStorageVolume', > + 'rmvolume = nodeadmin.removevolume:RemoveStorageVolume', > + 'listpools = nodeadmin.listpools:ListPools'] > }) > diff --git a/nodeadmin/startpool.py b/nodeadmin/startpool.py > new file mode 100644 > index 0000000..8a84512 > --- /dev/null > +++ b/nodeadmin/startpool.py > @@ -0,0 +1,62 @@ > +#!/usr/bin/env python > +# > +# startpool.py - Copyright (C) 2009 Red Hat, Inc. > +# Written by Darryl L. Pierce > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +from snack import * > +from configscreen import * > + > +LIST_POOLS_PAGE = 1 > +FINAL_PAGE = 2 > + > +class StartStoragePoolConfigScreen(StorageListConfigScreen): > + def __init__(self): > + StorageListConfigScreen.__init__(self, "Start A Storage Pool") > + > + def get_elements_for_page(self, screen, page): > + if page is LIST_POOLS_PAGE: return self.get_storage_pool_list_page(screen, created = False) > + elif page is FINAL_PAGE: return self.get_final_page(screen) > + > + def page_has_next(self, page): > + return page is LIST_POOLS_PAGE and self.has_selectable_pools() > + > + def page_has_back(self, page): > + return False > + > + def page_has_finish(self, page): > + return page is FINAL_PAGE > + > + def validate_input(self, page, errors): > + if page is LIST_POOLS_PAGE: > + if self.get_selected_pool() is not None: > + return True > + else: > + errors.append("Please select a storage pool to be started.") > + return False > + > + def process_input(self, page): > + if page is LIST_POOLS_PAGE: > + self.get_libvirt().create_storage_pool(self.get_selected_pool()) > + self.set_finished() > + > + def get_final_page(self, screen): > + return [Label("Storage pool started: %s" % self.get_selected_pool())] > + > +def StartStoragePool(): > + screen = StartStoragePoolConfigScreen() > + screen.start() > diff --git a/nodeadmin/stoppool.py b/nodeadmin/stoppool.py > new file mode 100644 > index 0000000..0522b95 > --- /dev/null > +++ b/nodeadmin/stoppool.py > @@ -0,0 +1,62 @@ > +#!/usr/bin/env python > +# > +# stoppool.py - Copyright (C) 2009 Red Hat, Inc. > +# Written by Darryl L. Pierce > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +from snack import * > +from configscreen import * > + > +LIST_POOLS_PAGE = 1 > +FINAL_PAGE = 2 > + > +class StopStoragePoolConfigScreen(StorageListConfigScreen): > + def __init__(self): > + StorageListConfigScreen.__init__(self, "Stop A Storage Pool") > + > + def get_elements_for_page(self, screen, page): > + if page is LIST_POOLS_PAGE: return self.get_storage_pool_list_page(screen, defined = False) > + elif page is FINAL_PAGE: return self.get_final_page(screen) > + > + def page_has_next(self, page): > + return page is LIST_POOLS_PAGE and self.has_selectable_pools() > + > + def page_has_back(self, page): > + return False > + > + def page_has_finish(self, page): > + return page is FINAL_PAGE > + > + def validate_input(self, page, errors): > + if page is LIST_POOLS_PAGE: > + if self.get_selected_pool() is not None: > + return True > + else: > + errors.append("Please select a storage pool to be stopped.") > + return False > + > + def process_input(self, page): > + if page is LIST_POOLS_PAGE: > + self.get_libvirt().destroy_storage_pool(self.get_selected_pool()) > + self.set_finished() > + > + def get_final_page(self, screen): > + return [Label("Storage pool stopped: %s" % self.get_selected_pool())] > + > +def StopStoragePool(): > + screen = StopStoragePoolConfigScreen() > + screen.start() > diff --git a/nodeadmin/storagemenu.py b/nodeadmin/storagemenu.py > new file mode 100644 > index 0000000..0b56dae > --- /dev/null > +++ b/nodeadmin/storagemenu.py > @@ -0,0 +1,63 @@ > +# storagemenu.py - Copyright (C) 2009 Red Hat, Inc. > +# Written by Darryl L. Pierce > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +from snack import * > +import traceback > + > +from menuscreen import MenuScreen > +from addpool import AddStoragePool > +from startpool import StartStoragePool > +from stoppool import StopStoragePool > +from removepool import RemoveStoragePool > +from addvolume import AddStorageVolume > +from removevolume import RemoveStorageVolume > +from listpools import ListStoragePools > + > +ADD_POOL = 1 > +START_POOL = 2 > +STOP_POOL = 3 > +REMOVE_POOL = 4 > +ADD_VOLUME = 5 > +REMOVE_VOLUME = 6 > +LIST_POOLS = 7 > + > +class StoragePoolMenuScreen(MenuScreen): > + def __init__(self): > + MenuScreen.__init__(self, "Storage Pool Administration") > + > + def get_menu_items(self): > + return (("Add A Storage Pool", ADD_POOL), > + ("Start A Storage Pool", START_POOL), > + ("Stop A Storage Pool", STOP_POOL), > + ("Remove A Storage Pool", REMOVE_POOL), > + ("Add A Storage Volume", ADD_VOLUME), > + ("Remove A Storage Volume", REMOVE_VOLUME), > + ("List Storage Pools", LIST_POOLS)) > + > + def handle_selection(self, item): > + if item is ADD_POOL: AddStoragePool() > + elif item is START_POOL: StartStoragePool() > + elif item is STOP_POOL: StopStoragePool() > + elif item is REMOVE_POOL: RemoveStoragePool() > + elif item is ADD_VOLUME: AddStorageVolume() > + elif item is REMOVE_VOLUME: RemoveStorageVolume() > + elif item is LIST_POOLS: ListStoragePools() > + > +def StoragePoolMenu(): > + screen = StoragePoolMenuScreen() > + screen.start() > diff --git a/nodeadmin/utils.py b/nodeadmin/utils.py > index 55a838c..28ccb8b 100644 > --- a/nodeadmin/utils.py > +++ b/nodeadmin/utils.py > @@ -17,9 +17,19 @@ > # also available at http://www.gnu.org/copyleft/gpl.html. > > import logging > +import re > > logging.basicConfig(level=logging.DEBUG, > format='%(asctime)s %(levelname)-8s %(message)s', > datefmt='%a, %d %b %Y %H:%M:%S', > filename='/var/log/ovirt-nodeadmin.log', > filemode='w') > + > +def string_is_not_blank(value): > + if len(value) > 0: return True > + return False > + > +def string_has_no_spaces(value): > + if re.match("^[a-zA-Z0-9_]*$", value): > + return True > + return False > diff --git a/nodeadmin/volumeconfig.py b/nodeadmin/volumeconfig.py > new file mode 100644 > index 0000000..7741391 > --- /dev/null > +++ b/nodeadmin/volumeconfig.py > @@ -0,0 +1,76 @@ > +# volumeconfig.py - Copyright (C) 2009 Red Hat, Inc. > +# Written by Darryl L. Pierce > +# > +# This program is free software; you can redistribute it and/or modify > +# it under the terms of the GNU General Public License as published by > +# the Free Software Foundation; version 2 of the License. > +# > +# This program is distributed in the hope that it will be useful, > +# but WITHOUT ANY WARRANTY; without even the implied warranty of > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +# GNU General Public License for more details. > +# > +# You should have received a copy of the GNU General Public License > +# along with this program; if not, write to the Free Software > +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, > +# MA 02110-1301, USA. A copy of the GNU General Public License is > +# also available at http://www.gnu.org/copyleft/gpl.html. > + > +import virtinst > +from virtinst import Storage > + > +class StorageVolumeConfig: > + def __init__(self): > + self.__pool = None > + self.__name = "" > + self.__formats = None > + self.__format = None > + self.__max_capacity = 10000 > + self.__allocation = 0 > + > + def set_pool(self, pool): > + self.__pool = pool > + self.__formats = None > + self.__pool_type = virtinst.util.get_xml_path(self.__pool.XMLDesc(0), '/pool/@type') > + self.__volume_class = Storage.StoragePool.get_volume_for_pool(self.__pool_type) > + > + def get_pool(self): > + return self.__pool > + > + def create_volume(self): > + volume = self.__volume_class(name = self.__name + ".img", > + allocation = self.__allocation * 1024**2, > + capacity = self.__max_capacity * 1024**2, > + pool = self.__pool) > + volume.pool = self.__pool > + volume.format = self.__format > + return volume > + > + def set_name(self, name): > + self.__name = name > + > + def get_name(self): > + return self.__name > + > + def get_formats_for_pool(self): > + if self.__formats is None: > + self.__formats = self.__volume_class.formats > + return self.__formats > + > + def set_format(self, format): > + self.__format = format > + > + def get_format(self): > + return self.__format > + > + def set_max_capacity(self, capacity): > + self.__max_capacity = capacity > + > + def get_max_capacity(self): > + return self.__max_capacity > + > + def set_allocation(self, allocation): > + self.__allocation = allocation > + > + def get_allocation(self): > + return self.__allocation > diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in > index d23a4ef..6509fa0 100644 > --- a/ovirt-node.spec.in > +++ b/ovirt-node.spec.in > @@ -369,11 +369,18 @@ fi > %{_initrddir}/ovirt-functions > %defattr(-,root,root,0644) > %{_bindir}/nodeadmin > +%{_bindir}/addpool > %{_bindir}/addvm > +%{_bindir}/addvolume > %{_bindir}/startvm > %{_bindir}/stopvm > %{_bindir}/rmvm > +%{_bindir}/listpools > %{_bindir}/listvms > +%{_bindir}/rmpool > +%{_bindir}/rmvolume > +%{_bindir}/startpool > +%{_bindir}/stoppool > %{_bindir}/definenet > %{_bindir}/createnet > %{_bindir}/destroynet > Patches applied fine this go around, heres my findings so far Works fine: fs Volume format options are blank for: lvm, disk - Other errors directory/iscsi/nfs - can't verify fqdn/ip may be best to not verify this and leave it up to the user, couldn't get past this point to verify volume creation For iscsi also get an error that source path isn't absolute - The virt-manager implementation has these as the input and I verified I was able to connect using virt-manager - targetpath /dev/disk/by-path "default" - hostname - accepts ip/fqdn - sourcepath == "targetname" The documentation surrounding the storage setup even for virt-manager is almost nonexistent, we may need to make sure we give examples at a minimum somewhere. From dpierce at redhat.com Wed Oct 28 14:05:36 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 28 Oct 2009 10:05:36 -0400 Subject: [Ovirt-devel] [PATCH 1/2] Provides a new storage administration system to the managed node. In-Reply-To: <4AE79AC6.8060705@redhat.com> References: <1256669472-19845-1-git-send-email-dpierce@redhat.com> <1256669472-19845-2-git-send-email-dpierce@redhat.com> <4AE79AC6.8060705@redhat.com> Message-ID: <20091028140536.GB3622@mcpierce-laptop.rdu.redhat.com> On Tue, Oct 27, 2009 at 09:13:42PM -0400, Joey Boggs wrote: > Patches applied fine this go around, heres my findings so far > > Works fine: fs > > Volume format options are blank for: lvm, disk Okay. I've added checks to make sure that, if the pool does not require a format, then the format page is skipped. Though in verifying the functionality, I can't get an LVM volume to create in either the admin tool or virt-manager. > - Other errors > directory/iscsi/nfs - can't verify fqdn/ip may be best to not verify > this and leave it up to the user, couldn't get past this point to verify > volume creation Okay. The problem was a missing "not" in the check for the hostname. > For iscsi also get an error that source path isn't absolute > - The virt-manager implementation has these as the input and I verified > I was able to connect using virt-manager > - targetpath /dev/disk/by-path "default" > - hostname - accepts ip/fqdn > - sourcepath == "targetname" > The documentation surrounding the storage setup even for virt-manager is > almost nonexistent, we may need to make sure we give examples at a > minimum somewhere. I'll work with Cole to clean things up some on both sides in future. -- Darryl L. Pierce, Sr. Software Engineer @ Red Hat, Inc. Virtual Machine Management - http://www.ovirt.org/ Is fearr Gaeilge bhriste n? B?arla cliste. -------------- next part -------------- A non-text attachment was scrubbed... Name: not available Type: application/pgp-signature Size: 197 bytes Desc: not available URL: From dpierce at redhat.com Wed Oct 28 18:25:51 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 28 Oct 2009 14:25:51 -0400 Subject: [Ovirt-devel] Refactoring of storage admin and node Message-ID: <1256754353-18515-1-git-send-email-dpierce@redhat.com> This patch set supercedes the previous set, and incorporates feedback from jboggs. There is a known issue in configuring volumes on an iSCSI pool that makes them unsupported at the moment. This code functions identical to virt-manager in that regard. From dpierce at redhat.com Wed Oct 28 18:25:52 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 28 Oct 2009 14:25:52 -0400 Subject: [Ovirt-devel] [PATCH 1/2] Provides a new storage administration system to the managed node. In-Reply-To: <1256754353-18515-1-git-send-email-dpierce@redhat.com> References: <1256754353-18515-1-git-send-email-dpierce@redhat.com> Message-ID: <1256754353-18515-2-git-send-email-dpierce@redhat.com> Users can now: * Add a new storage pool. * Delete a storage pool. * Start and stop storage pools. * Add a new storage volume. * Delete a storage volume. * List existing storage pools, with details. Signed-off-by: Darryl L. Pierce --- nodeadmin/output.log | 1 + nodeadmin/setup.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 0 deletions(-) create mode 100644 nodeadmin/output.log create mode 100644 nodeadmin/setup.py diff --git a/nodeadmin/output.log b/nodeadmin/output.log new file mode 100644 index 0000000..e8302cb --- /dev/null +++ b/nodeadmin/output.log @@ -0,0 +1 @@ +libvir: Storage error : Storage pool not found: no pool with matching name 'iscsipool' diff --git a/nodeadmin/setup.py b/nodeadmin/setup.py new file mode 100644 index 0000000..9af2752 --- /dev/null +++ b/nodeadmin/setup.py @@ -0,0 +1,46 @@ +# setup.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from setuptools import setup, find_packages + +setup(name = "nodeadmin", + version = "1.0.3", + package_dir = {'nodeadmin': 'nodeadmin'}, + packages = find_packages('.'), + entry_points = { + 'console_scripts': [ + 'nodeadmin = nodeadmin.nodeadmin:NodeAdmin', + 'addvm = nodeadmin.adddomain:AddDomain', + 'startvm = nodeadmin.startdomain:StartDomain', + 'stopvm = nodeadmin.stopdomain:StopDomain', + 'rmvm = nodeadmin.removedomain:RemoveDomain', + 'createuser = nodeadmin.createuser:CreateUser', + 'listvms = nodeadmin.listdomains:ListDomains', + 'definenet = nodeadmin.definenet:DefineNetwork', + 'createnet = nodeadmin.createnetwork:CreateNetwork', + 'destroynet = nodeadmin.destroynetwork:DestroyNetwork', + 'undefinenet = nodeadmin.undefinenetwork:UndefineNetwork', + 'listnets = nodeadmin.listnetworks:ListNetworks', + 'addpool = nodeadmin.addpool:AddStoragePool', + 'rmpool = nodeadmin.removepool:RemoveStoragePool', + 'startpool = nodeadmin.startpool:StartStoragePool', + 'stoppool = nodeadmin.stoppool:StopStoragePool', + 'addvolume = nodeadmin.addvolume:AddStorageVolume', + 'rmvolume = nodeadmin.removevolume:RemoveStorageVolume', + 'listpools = nodeadmin.listpools:ListPools'] + }) -- 1.6.2.5 From dpierce at redhat.com Wed Oct 28 18:25:53 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 28 Oct 2009 14:25:53 -0400 Subject: [Ovirt-devel] [PATCH 2/2] Refactor domain storage setup to use pool and volume selection screens. In-Reply-To: <1256754353-18515-2-git-send-email-dpierce@redhat.com> References: <1256754353-18515-1-git-send-email-dpierce@redhat.com> <1256754353-18515-2-git-send-email-dpierce@redhat.com> Message-ID: <1256754353-18515-3-git-send-email-dpierce@redhat.com> Now, when the user elects to use managed storage, they're show the list of available storage pools. Then, after selecting one, the user is shown the list of volumes on that pool. These are then used to create the domain. Signed-off-by: Darryl L. Pierce --- Makefile.am | 1 + nodeadmin/adddomain.py | 186 ++++++++++++++++++++++++++------------------ nodeadmin/domainconfig.py | 17 +++- nodeadmin/libvirtworker.py | 34 ++++---- 4 files changed, 142 insertions(+), 96 deletions(-) diff --git a/Makefile.am b/Makefile.am index 55ef277..e712d6a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -48,6 +48,7 @@ EXTRA_DIST = \ nodeadmin/netmenu.py \ nodeadmin/nodeadmin.py \ nodeadmin/nodemenu.py \ + nodeadmin/poolconfig.py \ nodeadmin/removedomain.py \ nodeadmin/removepool.py \ nodeadmin/removevolume.py \ diff --git a/nodeadmin/adddomain.py b/nodeadmin/adddomain.py index bb06a62..34aa59c 100755 --- a/nodeadmin/adddomain.py +++ b/nodeadmin/adddomain.py @@ -37,10 +37,11 @@ OS_VARIANT_PAGE = 12 RAM_CPU_PAGE = 13 ENABLE_STORAGE_PAGE = 14 LOCAL_STORAGE_PAGE = 15 -MANAGED_STORAGE_PAGE = 16 -BRIDGE_PAGE = 17 -VIRT_DETAILS_PAGE = 18 -CONFIRM_PAGE = 19 +SELECT_POOL_PAGE = 16 +SELECT_VOLUME_PAGE = 17 +BRIDGE_PAGE = 18 +VIRT_DETAILS_PAGE = 19 +CONFIRM_PAGE = 20 LOCATION="location" KICKSTART="kickstart" @@ -58,24 +59,25 @@ class DomainConfigScreen(ConfigScreen): self.__config.set_virt_type(self.get_libvirt().get_default_virt_type()) def get_elements_for_page(self, screen, page): - if page == VM_DETAILS_PAGE: return self.get_vm_details_page(screen) - elif page == LOCAL_INSTALL_PAGE: return self.get_local_install_page(screen) - elif page == SELECT_CDROM_PAGE: return self.get_select_cdrom_page(screen) - elif page == SELECT_ISO_PAGE: return self.get_select_iso_page(screen) - elif page == NETWORK_INSTALL_PAGE: return self.get_network_install_page(screen) - elif page == OS_TYPE_PAGE: return self.get_os_type_page(screen) - elif page == OS_VARIANT_PAGE: return self.get_os_variant_page(screen) - elif page == RAM_CPU_PAGE: return self.get_ram_and_cpu_page(screen) - elif page == ENABLE_STORAGE_PAGE: return self.get_enable_storage_page(screen) - elif page == LOCAL_STORAGE_PAGE: return self.get_local_storage_page(screen) - elif page == MANAGED_STORAGE_PAGE: return self.get_managed_storage_page(screen) - elif page == BRIDGE_PAGE: return self.get_bridge_page(screen) - elif page == VIRT_DETAILS_PAGE: return self.get_virt_details_page(screen) - elif page == CONFIRM_PAGE: return self.get_confirm_page(screen) + if page is VM_DETAILS_PAGE: return self.get_vm_details_page(screen) + elif page is LOCAL_INSTALL_PAGE: return self.get_local_install_page(screen) + elif page is SELECT_CDROM_PAGE: return self.get_select_cdrom_page(screen) + elif page is SELECT_ISO_PAGE: return self.get_select_iso_page(screen) + elif page is NETWORK_INSTALL_PAGE: return self.get_network_install_page(screen) + elif page is OS_TYPE_PAGE: return self.get_os_type_page(screen) + elif page is OS_VARIANT_PAGE: return self.get_os_variant_page(screen) + elif page is RAM_CPU_PAGE: return self.get_ram_and_cpu_page(screen) + elif page is ENABLE_STORAGE_PAGE: return self.get_enable_storage_page(screen) + elif page is LOCAL_STORAGE_PAGE: return self.get_local_storage_page(screen) + elif page is SELECT_POOL_PAGE: return self.get_select_pool_page(screen) + elif page is SELECT_VOLUME_PAGE: return self.get_select_volume_page(screen) + elif page is BRIDGE_PAGE: return self.get_bridge_page(screen) + elif page is VIRT_DETAILS_PAGE: return self.get_virt_details_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) return [] def validate_input(self, page, errors): - if page == VM_DETAILS_PAGE: + if page is VM_DETAILS_PAGE: if len(self.__guest_name.value()) > 0: if self.get_libvirt().domain_exists(self.__guest_name.value()): errors.append("Guest name '%s' is already in use." % self.__guest_name.value()) @@ -83,12 +85,12 @@ class DomainConfigScreen(ConfigScreen): return True else: errors.append("Guest name must be a string between 0 and 50 characters.") - elif page == LOCAL_INSTALL_PAGE: + elif page is LOCAL_INSTALL_PAGE: if self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM: return True elif self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_ISO: return True - elif page == SELECT_CDROM_PAGE: + elif page is SELECT_CDROM_PAGE: if self.__install_media.getSelection() != None: if len(self.get_hal().list_installable_volumes()) == 0: errors.append("No installable media is available.") @@ -96,7 +98,7 @@ class DomainConfigScreen(ConfigScreen): return True else: errors.append("You must select an install media.") - elif page == SELECT_ISO_PAGE: + elif page is SELECT_ISO_PAGE: if len(self.__iso_path.value()) > 0: if os.path.exists(self.__iso_path.value()): if os.path.isfile(self.__iso_path.value()): @@ -108,14 +110,14 @@ class DomainConfigScreen(ConfigScreen): errors.append(self.__iso_path.value()) else: errors.append("An install media selection is required.") - elif page == NETWORK_INSTALL_PAGE: + elif page is NETWORK_INSTALL_PAGE: if len(self.__install_url.value()) > 0: return True else: errors.append("An install tree is required.") - elif page == OS_TYPE_PAGE: return True - elif page == OS_VARIANT_PAGE: return True - elif page == RAM_CPU_PAGE: + elif page is OS_TYPE_PAGE: return True + elif page is OS_VARIANT_PAGE: return True + elif page is RAM_CPU_PAGE: if (len(self.__memory.value()) > 0 and len(self.__cpus.value()) > 0) \ and (int(self.__memory.value()) > 0 and int(self.__cpus.value()) > 0): return True @@ -128,8 +130,8 @@ class DomainConfigScreen(ConfigScreen): errors.append("A value must be entered for CPUs.") elif int(self.__cpus.value()) <= 0: errors.append("A positive integer value must be entered for memory.") - elif page == ENABLE_STORAGE_PAGE: return True - elif page == LOCAL_STORAGE_PAGE: + elif page is ENABLE_STORAGE_PAGE: return True + elif page is LOCAL_STORAGE_PAGE: if len(self.__storage_size.value()) > 0: if float(self.__storage_size.value()) > 0: return True @@ -137,12 +139,17 @@ class DomainConfigScreen(ConfigScreen): errors.append("A positive value must be entered for the storage size.") else: errors.append("A value must be entered for the storage size.") - elif page == MANAGED_STORAGE_PAGE: - if self.__existing_storage.getSelection() is not None: + elif page is SELECT_POOL_PAGE: + if self.__storage_pool.getSelection() is not None: + return True + else: + errors.append("Please select a storage pool.") + elif page is SELECT_VOLUME_PAGE: + if self.__storage_volume.getSelection() is not None: return True else: errors.append("Please select a storage volume.") - elif page == BRIDGE_PAGE: + elif page is BRIDGE_PAGE: if self.__network_bridges.getSelection() != None: if len(self.__mac_address.value()) > 0: # TODO: regex check the format @@ -151,62 +158,66 @@ class DomainConfigScreen(ConfigScreen): errors.append("MAC address must be supplied.") else: errors.append("A network bridge must be selected.") - elif page == VIRT_DETAILS_PAGE: + elif page is VIRT_DETAILS_PAGE: if self.__virt_types.getSelection() != None and self.__architectures.getSelection() != None: return True if self.__virt_types.getSelection() is None: errors.append("Please select a virtualization type.") if self.__architectures.getSelection() is None: errors.append("Please selection an architecture.") - elif page == CONFIRM_PAGE: return True + elif page is CONFIRM_PAGE: return True return False def process_input(self, page): - if page == VM_DETAILS_PAGE: + if page is VM_DETAILS_PAGE: self.__config.set_guest_name(self.__guest_name.value()) self.__config.set_install_type(self.__install_type.getSelection()) - elif page == LOCAL_INSTALL_PAGE: + elif page is LOCAL_INSTALL_PAGE: self.__config.set_use_cdrom_source(self.__install_source.getSelection() == DomainConfig.INSTALL_SOURCE_CDROM) - elif page == SELECT_CDROM_PAGE: + elif page is SELECT_CDROM_PAGE: self.__config.set_install_media(self.__install_media.getSelection()) - elif page == SELECT_ISO_PAGE: + elif page is SELECT_ISO_PAGE: self.__config.set_iso_path(self.__iso_path.value()) - elif page == NETWORK_INSTALL_PAGE: + elif page is NETWORK_INSTALL_PAGE: self.__config.set_install_url(self.__install_url.value()) self.__config.set_kickstart_url(self.__kickstart_url.value()) self.__config.set_kernel_options(self.__kernel_options.value()) - elif page == OS_TYPE_PAGE: + elif page is OS_TYPE_PAGE: self.__config.set_os_type(self.__os_types.getSelection()) - elif page == OS_VARIANT_PAGE: + elif page is OS_VARIANT_PAGE: self.__config.set_os_variant(self.__os_variants.getSelection()) - elif page == RAM_CPU_PAGE: + elif page is RAM_CPU_PAGE: self.__config.set_memory(int(self.__memory.value())) self.__config.set_cpus(int(self.__cpus.value())) - elif page == ENABLE_STORAGE_PAGE: + elif page is ENABLE_STORAGE_PAGE: self.__config.set_enable_storage(self.__enable_storage.value()) if self.__storage_type.getSelection() == DomainConfig.NEW_STORAGE: self.__config.set_use_local_storage(True) elif self.__storage_type.getSelection() == DomainConfig.EXISTING_STORAGE: self.__config.set_use_local_storage(False) - elif page == LOCAL_STORAGE_PAGE: + elif page is LOCAL_STORAGE_PAGE: self.__config.set_storage_size(float(self.__storage_size.value())) self.__config.set_allocate_storage(self.__allocate_storage.value()) - elif page == MANAGED_STORAGE_PAGE: + elif page is SELECT_POOL_PAGE: self.__config.set_use_local_storage(False) - self.__config.set_existing_storage(self.__existing_storage.getSelection()) - self.__config.set_storage_size(self.get_libvirt().get_storage_size(self.__existing_storage.getSelection())) - elif page == BRIDGE_PAGE: + self.__config.set_storage_pool(self.__storage_pool.getSelection()) + elif page is SELECT_VOLUME_PAGE: + self.__config.set_storage_volume(self.__storage_volume.getSelection()) + volume = self.get_libvirt().get_storage_volume(self.__config.get_storage_pool(), + self.__config.get_storage_volume()) + self.__config.set_storage_size(volume.info()[1] / 1024.0 ** 3) + elif page is BRIDGE_PAGE: self.__config.set_network_bridge(self.__network_bridges.getSelection()) - elif page == VIRT_DETAILS_PAGE: + elif page is VIRT_DETAILS_PAGE: self.__config.set_virt_type(self.__virt_types.getSelection()) self.__config.set_architecture(self.__architectures.getSelection()) - elif page == CONFIRM_PAGE: + elif page is CONFIRM_PAGE: self.get_libvirt().define_domain(self.__config, CreateMeter()) self.set_finished() def get_back_page(self, page): result = page - if page == OS_TYPE_PAGE: + if page is OS_TYPE_PAGE: install_type = self.__config.get_install_type() if install_type == DomainConfig.LOCAL_INSTALL: if self.__config.get_use_cdrom_source(): @@ -217,24 +228,26 @@ class DomainConfigScreen(ConfigScreen): result = NETWORK_INSTALL_PAGE elif install_type == DomainConfig.PXE_INSTALL: result = VM_DETAILS_PAGE - elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: + elif page is LOCAL_STORAGE_PAGE or page is SELECT_VOLUME_PAGE: result = ENABLE_STORAGE_PAGE - elif page == NETWORK_INSTALL_PAGE: + elif page is SELECT_POOL_PAGE: + result = ENABLE_STORAGE_PAGE + elif page is NETWORK_INSTALL_PAGE: result = VM_DETAILS_PAGE - elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: + elif page is SELECT_CDROM_PAGE or page is SELECT_ISO_PAGE: result = LOCAL_INSTALL_PAGE - elif page == BRIDGE_PAGE: + elif page is BRIDGE_PAGE: if self.__config.get_use_local_storage(): result = LOCAL_STORAGE_PAGE else: - result = MANAGED_STORAGE_PAGE + result = SELECT_VOLUME_PAGE else: if page > 1: result = page - 1 return result def get_next_page(self, page): result = page - if page == VM_DETAILS_PAGE: + if page is VM_DETAILS_PAGE: install_type = self.__config.get_install_type() if install_type == DomainConfig.LOCAL_INSTALL: result = LOCAL_INSTALL_PAGE @@ -242,34 +255,36 @@ class DomainConfigScreen(ConfigScreen): result = NETWORK_INSTALL_PAGE elif install_type == DomainConfig.PXE_INSTALL: result = OS_TYPE_PAGE - elif page == LOCAL_INSTALL_PAGE: + elif page is LOCAL_INSTALL_PAGE: if self.__config.get_use_cdrom_source(): result = SELECT_CDROM_PAGE else: result = SELECT_ISO_PAGE - elif page == SELECT_CDROM_PAGE or page == SELECT_ISO_PAGE: + elif page is SELECT_CDROM_PAGE or page is SELECT_ISO_PAGE: result = OS_TYPE_PAGE - elif page == NETWORK_INSTALL_PAGE: + elif page is NETWORK_INSTALL_PAGE: result = OS_TYPE_PAGE - elif page == ENABLE_STORAGE_PAGE: + elif page is ENABLE_STORAGE_PAGE: result = BRIDGE_PAGE if self.__config.get_enable_storage(): if self.__config.get_use_local_storage(): result = LOCAL_STORAGE_PAGE else: - result = MANAGED_STORAGE_PAGE - elif page == LOCAL_STORAGE_PAGE or page == MANAGED_STORAGE_PAGE: + result = SELECT_POOL_PAGE + elif page is LOCAL_STORAGE_PAGE: result = BRIDGE_PAGE else: result = page + 1 return result def page_has_finish(self, page): - if page == CONFIRM_PAGE: return True + if page is CONFIRM_PAGE: return True return False def page_has_next(self, page): - if page < CONFIRM_PAGE: + if page is SELECT_POOL_PAGE: return self.__has_pools + elif page is SELECT_VOLUME_PAGE: return self.__has_volumes + elif page < CONFIRM_PAGE: return True def get_vm_details_page(self, screen): @@ -393,17 +408,36 @@ class DomainConfigScreen(ConfigScreen): return [Label("Configure local storage"), grid] - def get_managed_storage_page(self, screen): + def get_select_pool_page(self, screen): + pools = [] + for pool in self.get_libvirt().list_storage_pools(): + pools.append([pool, pool, pool == self.__config.get_storage_pool()]) + if len(pools) > 0: + self.__storage_pool = RadioBar(screen, (pools)) + grid = Grid(2, 1) + grid.setField(Label("Storage pool:"), 0, 0, anchorTop = 1) + grid.setField(self.__storage_pool, 1, 0) + self.__has_pools = True + else: + grid = Label("There are no storage pools available.") + self.__has_pools = False + return [Label("Configure Managed Storage: Select A Pool"), + grid] + + def get_select_volume_page(self, screen): volumes = [] - for volume in self.get_libvirt().list_storage_volumes(): - volumes.append(["%s (%d GB)" % (volume.name(), volume.info()[1] / (1024 ** 3)), - volume.name(), - self.__config.is_existing_storage(volume.name())]) - self.__existing_storage = RadioBar(screen, (volumes)) - grid = Grid(2, 1) - grid.setField(Label("Existing storage:"), 0, 0) - grid.setField(self.__existing_storage, 1, 0) - return [Label("Configure managed storage"), + for volume in self.get_libvirt().list_storage_volumes(self.__config.get_storage_pool()): + volumes.append([volume, volume, volume == self.__config.get_storage_volume()]) + if len(volumes) > 0: + self.__storage_volume = RadioBar(screen, (volumes)) + grid = Grid(2, 1) + grid.setField(Label("Storage volumes:"), 0, 0, anchorTop = 1) + grid.setField(self.__storage_volume, 1, 0) + self.__has_volumes = True + else: + grid = Label("This storage pool has no defined volumes.") + self.__has_volumes = False + return [Label("Configure Managed Storage: Select A Volume"), grid] def get_bridge_page(self, screen): @@ -448,7 +482,9 @@ class DomainConfigScreen(ConfigScreen): grid.setField(Label("CPUs:"), 0, 3, anchorRight = 1) grid.setField(Label("%d" % self.__config.get_cpus()), 1, 3, anchorLeft = 1) grid.setField(Label("Storage:"), 0, 4, anchorRight = 1) - grid.setField(Label(self.__config.get_existing_storage()), 1, 4, anchorLeft = 1) + grid.setField(Label("%s (on %s)" % (self.__config.get_storage_volume(), + self.__config.get_storage_pool())), + 1, 4, anchorLeft = 1) grid.setField(Label("Network:"), 0, 5, anchorRight = 1) grid.setField(Label(self.__config.get_network_bridge()), 1, 5, anchorLeft = 1) return [Label("Ready to begin installation of %s" % self.__config.get_guest_name()), diff --git a/nodeadmin/domainconfig.py b/nodeadmin/domainconfig.py index ef39fe0..4466e67 100644 --- a/nodeadmin/domainconfig.py +++ b/nodeadmin/domainconfig.py @@ -50,7 +50,8 @@ class DomainConfig: self.__use_local_storage = True self.__storage_size = 8.0 self.__allocate_storage = True - self.__existing_storage = "" + self.__storage_pool = "" + self.__storage_volume = "" self.__network_bridge = None self.__mac_address = None self.__virt_type = None @@ -177,11 +178,17 @@ class DomainConfig: def get_allocate_storage(self): return self.__allocate_storage - def set_existing_storage(self, storage): - self.__existing_storage = storage + def set_storage_pool(self, pool): + self.__storage_pool = pool - def get_existing_storage(self): - return self.__existing_storage + def get_storage_pool(self): + return self.__storage_pool + + def set_storage_volume(self, volume): + self.__storage_volume = volume + + def get_storage_volume(self): + return self.__storage_volume def is_existing_storage(self, storage): return self.__existing_storage == storage diff --git a/nodeadmin/libvirtworker.py b/nodeadmin/libvirtworker.py index b2acabe..f31266c 100644 --- a/nodeadmin/libvirtworker.py +++ b/nodeadmin/libvirtworker.py @@ -196,6 +196,11 @@ class LibvirtWorker: '''Returns the storage pool with the specified name.''' return self.__conn.storagePoolLookupByName(name) + def list_storage_volumes(self, poolname): + '''Returns the list of all defined storage volumes for a given pool.''' + pool = self.get_storage_pool(poolname) + return pool.listVolumes() + def define_storage_volume(self, config, meter): '''Defines a new storage volume.''' self.create_storage_pool(config.get_pool().name()) @@ -204,10 +209,15 @@ class LibvirtWorker: def remove_storage_volume(self, poolname, volumename): '''Removes the specified storage volume.''' - pool = self.get_storage_pool(poolname) - volume = pool.storageVolLookupByName(volumename) + volume = self.get_storage_volume(poolname, volumename) volume.delete(0) + def get_storage_volume(self, poolname, volumename): + '''Returns a reference to the specified storage volume.''' + pool =self.get_storage_pool(poolname) + volume = pool.storageVolLookupByName(volumename) + return volume + def list_bridges(self): '''Lists all defined and active bridges.''' bridges = self.__conn.listNetworks() @@ -221,21 +231,9 @@ class LibvirtWorker: def generate_mac_address(self): return self.__net.macaddr - def list_storage_volumes(self): - '''Lists all defined storage volumes.''' - pools = self.__conn.listStoragePools() - pools.extend(self.__conn.listDefinedStoragePools()) - result = [] - for name in pools: - pool = self.__conn.storagePoolLookupByName(name) - for volname in pool.listVolumes(): - volume = self.__conn.storageVolLookupByPath("/var/lib/libvirt/images/%s" % volname) - result.append(volume) - return result - - def get_storage_size(self, name): + def get_storage_size(self, poolname, volumename): '''Returns the size of the specified storage volume.''' - volume = self.__conn.storageVolLookupByPath("/var/lib/libvirt/images/%s" % name) + volume = self.get_storage_volume(poolname, volumename) return volume.info()[1] / (1024.0 ** 3) def get_virt_types(self): @@ -381,6 +379,10 @@ class LibvirtWorker: pool_object = pool, suffix = ".img") path = os.path.join(DEFAULT_POOL_TARGET_PATH, path) + else: + volume = self.get_storage_volume(config.get_storage_pool(), + config.get_storage_volume()) + path = volume.path() if path is not None: storage= virtinst.VirtualDisk(conn = self.__conn, -- 1.6.2.5 From dpierce at redhat.com Wed Oct 28 20:29:53 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Wed, 28 Oct 2009 16:29:53 -0400 Subject: [Ovirt-devel] [PATCH] Users can now work with remote libvirt hosts. Message-ID: <1256761793-8344-1-git-send-email-dpierce@redhat.com> The user can: * select a remote machine * add a remote machine * remove a remote machine Signed-off-by: Darryl L. Pierce --- Makefile.am | 5 ++ nodeadmin/addhost.py | 129 ++++++++++++++++++++++++++++++++++++++++++++ nodeadmin/changehost.py | 58 ++++++++++++++++++++ nodeadmin/configscreen.py | 36 ++++++++++++- nodeadmin/definenet.py | 1 + nodeadmin/hostconnect.py | 29 ++++++++++ nodeadmin/hostmenu.py | 46 ++++++++++++++++ nodeadmin/libvirtworker.py | 53 +++++++++++++++++- nodeadmin/mainmenu.py | 14 +++-- nodeadmin/removehost.py | 66 ++++++++++++++++++++++ ovirt-node.spec.in | 5 ++ 11 files changed, 434 insertions(+), 8 deletions(-) create mode 100644 nodeadmin/addhost.py create mode 100644 nodeadmin/changehost.py create mode 100644 nodeadmin/hostconnect.py create mode 100644 nodeadmin/hostmenu.py create mode 100644 nodeadmin/removehost.py diff --git a/Makefile.am b/Makefile.am index 3ce24c1..995927d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -28,11 +28,15 @@ EXTRA_DIST = \ images/syslinux-vesa-splash.jpg \ nodeadmin/__init__.py \ nodeadmin/adddomain.py \ + nodeadmin/addhost.py \ + nodeadmin/changehost.py \ nodeadmin/configscreen.py \ nodeadmin/createnetwork.py \ nodeadmin/createuser.py \ nodeadmin/destroynetwork.py \ nodeadmin/halworker.py \ + nodeadmin/hostconnect.py \ + nodeadmin/hostmenu.py \ nodeadmin/libvirtworker.py \ nodeadmin/userworker.py \ nodeadmin/mainmenu.py \ @@ -40,6 +44,7 @@ EXTRA_DIST = \ nodeadmin/netmenu.py \ nodeadmin/nodemenu.py \ nodeadmin/removedomain.py \ + nodeadmin/removehost.py \ nodeadmin/undefinenetwork.py \ nodeadmin/startdomain.py \ nodeadmin/stopdomain.py \ diff --git a/nodeadmin/addhost.py b/nodeadmin/addhost.py new file mode 100644 index 0000000..ef35b7d --- /dev/null +++ b/nodeadmin/addhost.py @@ -0,0 +1,129 @@ +# addhost.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * + +from configscreen import * + +DETAILS_PAGE = 1 +CONFIRM_PAGE = 2 + +HYPERVISOR_XEN = "xen" +HYPERVISOR_KVM = "kvm" + +HYPERVISORS = {HYPERVISOR_XEN : "Xen", + HYPERVISOR_KVM : "QEMU/KVM"} + +CONNECTION_LOCAL = "local" +CONNECTION_KERBEROS = "kerberos" +CONNECTION_SSL = "ssl" +CONNECTION_SSH = "ssh" + +CONNECTIONS = {CONNECTION_LOCAL : "Local", + CONNECTION_KERBEROS : "Remote Password or Kerberos", + CONNECTION_SSL : "Remote SSL/TLS with x509 certificate", + CONNECTION_SSH : "Remote tunnel over SSH"} + +class AddHostConfigScreen(ConfigScreen): + def __init__(self): + ConfigScreen.__init__(self, "Add A Remote Host") + self.__configured = False + + def get_elements_for_page(self, screen, page): + if page is DETAILS_PAGE: return self.get_details_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + return page < CONFIRM_PAGE + + def page_has_back(self, page): + return page > DETAILS_PAGE + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def validate_input(self, page, errors): + if page is DETAILS_PAGE: + if len(self.__hostname.value()) > 0: + return True + else: + errors.append("You must enter a remote hostname.") + elif page is CONFIRM_PAGE: return True + return False + + def process_input(self, page): + if page is CONFIRM_PAGE: + hv = self.__hypervisor.getSelection() + conn = self.__connection.getSelection() + hostname = self.__hostname.value() + + if hv is HYPERVISOR_XEN: + if conn is CONNECTION_LOCAL: url = "xen:///" + elif conn is CONNECTION_KERBEROS: url = "xen+tcp:///" + hostname + "/" + elif conn is CONNECTION_SSL: url = "xen+tls:///" + hostname + "/" + elif conn is CONNECTION_SSH: url = "xen+ssh:///" + hostname + "/" + elif hv is HYPERVISOR_KVM: + if conn is CONNECTION_LOCAL: url = "qemu:///system" + elif conn is CONNECTION_KERBEROS: url = "qemu+tcp://" + hostname + "/system" + elif conn is CONNECTION_SSL: url = "qemu+tls://" + hostname + "/system" + elif conn is CONNECTION_SSH: url = "qemu+ssh://" + hostname + "/system" + + self.get_virt_manager_config().add_connection(url) + self.set_finished() + + def get_details_page(self, screen): + if not self.__configured: + self.__hypervisor = RadioBar(screen, ((HYPERVISORS[HYPERVISOR_XEN], HYPERVISOR_XEN, True), + (HYPERVISORS[HYPERVISOR_KVM], HYPERVISOR_KVM, False))) + self.__connection = RadioBar(screen, ((CONNECTIONS[CONNECTION_LOCAL], CONNECTION_LOCAL, True), + (CONNECTIONS[CONNECTION_KERBEROS], CONNECTION_KERBEROS, False), + (CONNECTIONS[CONNECTION_SSL], CONNECTION_SSL, False), + (CONNECTIONS[CONNECTION_SSH], CONNECTION_SSH, False))) + self.__hostname = Entry(50, "") + self.__autoconnect = Checkbox("Autoconnect on Startup") + self.__configured = True + grid = Grid(2, 4) + grid.setField(Label("Hypervisor:"), 0, 0, anchorRight = 1, anchorTop = 1) + grid.setField(self.__hypervisor, 1, 0, anchorLeft = 1) + grid.setField(Label("Connection:"), 0, 1, anchorRight = 1, anchorTop = 1) + grid.setField(self.__connection, 1, 1, anchorLeft = 1) + grid.setField(Label("Hostname:"), 0, 2, anchorRight = 1) + grid.setField(self.__hostname, 1, 2, anchorLeft = 1) + grid.setField(Label(""), 0, 3, anchorRight = 1) + grid.setField(self.__autoconnect, 1, 3, anchorLeft = 1) + return [Label("Add Connection"), + grid] + + def get_confirm_page(self, screen): + grid = Grid(2, 4) + grid.setField(Label("Hypervisor:"), 0, 0, anchorRight = 1) + grid.setField(Label(HYPERVISORS[self.__hypervisor.getSelection()]), 1, 0, anchorLeft = 1) + grid.setField(Label("Connection:"), 0, 1, anchorRight = 1) + grid.setField(Label(CONNECTIONS[self.__connection.getSelection()]), 1, 1, anchorLeft = 1) + grid.setField(Label("Hostname:"), 0, 2, anchorRight = 1) + grid.setField(Label(self.__hostname.value()), 1, 2, anchorLeft = 1) + grid.setField(Label("Autoconnect on Startup:"), 0, 3, anchorRight = 1) + label = "Yes" + if not self.__autoconnect.value(): label = "No" + grid.setField(Label(label), 1, 3, anchorLeft = 1) + return [Label("Confirm Connection"), + grid] + +def AddHost(): + screen = AddHostConfigScreen() + screen.start() diff --git a/nodeadmin/changehost.py b/nodeadmin/changehost.py new file mode 100644 index 0000000..23e6854 --- /dev/null +++ b/nodeadmin/changehost.py @@ -0,0 +1,58 @@ +# changehost.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * + +import logging +import libvirtworker +from configscreen import * + +CONNECTION_LIST_PAGE = 1 +CONNECTED_PAGE = 2 + +class ChangeHostConfigScreen(HostListConfigScreen): + def __init__(self): + HostListConfigScreen.__init__(self, "Change Host") + + def get_elements_for_page(self, screen, page): + if page is CONNECTION_LIST_PAGE: return self.get_connection_list_page(screen) + elif page is CONNECTED_PAGE: return self.get_connected_page(screen) + + def process_input(self, page): + if page is CONNECTION_LIST_PAGE: + logging.info("Changing libvirt connection to %s" % self.get_selected_connection()) + libvirtworker.set_default_url(self.get_selected_connection()) + self.get_libvirt().open_connection(self.get_selected_connection()) + elif page is CONNECTED_PAGE: self.set_finished() + + def page_has_next(self, page): + if page is CONNECTION_LIST_PAGE: return self.has_selectable_connections() + return False + + def page_has_back(self, page): + return page > CONNECTION_LIST_PAGE + + def page_has_finish(self, page): + return page is CONNECTED_PAGE + + def get_connected_page(self, screen): + return [Label("Connected to %s" % self.get_selected_connection())] + +def ChangeHost(): + screen = ChangeHostConfigScreen() + screen.start() diff --git a/nodeadmin/configscreen.py b/nodeadmin/configscreen.py index f214aea..98e0338 100644 --- a/nodeadmin/configscreen.py +++ b/nodeadmin/configscreen.py @@ -18,7 +18,7 @@ from snack import * from halworker import HALWorker -from libvirtworker import LibvirtWorker +from libvirtworker import * import traceback BACK_BUTTON = "back" @@ -35,6 +35,7 @@ class ConfigScreen: self.__finished = False self.__hal = HALWorker() self.__libvirt = LibvirtWorker() + self.__vm_config = VirtManagerConfig() def get_hal(self): return self.__hal @@ -42,6 +43,9 @@ class ConfigScreen: def get_libvirt(self): return self.__libvirt + def get_virt_manager_config(self): + return self.__vm_config + def set_finished(self): self.__finished = True @@ -179,3 +183,33 @@ class NetworkListConfigScreen(ConfigScreen): def has_selectable_networks(self): return self.__has_networks + +class HostListConfigScreen(ConfigScreen): + '''Provides a base class for working with lists of libvirt hosts.''' + + def __init__(self, title): + ConfigScreen.__init__(self, title) + + def get_connection_list_page(self, screen): + connections = self.get_virt_manager_config().get_connection_list() + result = None + + if len(connections) > 0: + self.__has_connections = True + self.__connection_list = Listbox(0) + for connection in connections: + self.__connection_list.append(connection, connection) + result = self.__connection_list + else: + self.__has_connections = False + result = Label("There are no defined connections.") + grid = Grid(1, 1) + grid.setField(result, 0, 0) + return [Label("Host List"), + grid] + + def get_selected_connection(self): + return self.__connection_list.current() + + def has_selectable_connections(self): + return self.__has_connections diff --git a/nodeadmin/definenet.py b/nodeadmin/definenet.py index 4aa37d5..6dff18f 100644 --- a/nodeadmin/definenet.py +++ b/nodeadmin/definenet.py @@ -20,6 +20,7 @@ from snack import * from IPy import IP import traceback import logging +import re from configscreen import ConfigScreen from networkconfig import NetworkConfig diff --git a/nodeadmin/hostconnect.py b/nodeadmin/hostconnect.py new file mode 100644 index 0000000..a1be569 --- /dev/null +++ b/nodeadmin/hostconnect.py @@ -0,0 +1,29 @@ +# hostconnect.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * + +from configscreen import * + +class HostConnectConfigScreen(ConfigScreen): + def __init__(self): + ConfigScree + +def HostConnect(): + screen = HostConnectConfigScreen() + screen.start() diff --git a/nodeadmin/hostmenu.py b/nodeadmin/hostmenu.py new file mode 100644 index 0000000..4054d6b --- /dev/null +++ b/nodeadmin/hostmenu.py @@ -0,0 +1,46 @@ +# hostmenu.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * + +from menuscreen import MenuScreen +from changehost import ChangeHost +from addhost import AddHost +from removehost import RemoveHost + +SELECT_HOST = 1 +ADD_HOST = 2 +REMOVE_HOST = 3 + +class HostMenuScreen(MenuScreen): + def __init__(self): + MenuScreen.__init__(self, "Host Menu Screen") + + def get_menu_items(self): + return (("Select A Host", SELECT_HOST), + ("Add A Host", ADD_HOST), + ("Remove A Host", REMOVE_HOST)) + + def handle_selection(self, item): + if item is SELECT_HOST: ChangeHost() + elif item is ADD_HOST: AddHost() + elif item is REMOVE_HOST: RemoveHost() + +def HostMenu(): + screen = HostMenuScreen() + screen.start() diff --git a/nodeadmin/libvirtworker.py b/nodeadmin/libvirtworker.py index ba07605..2998486 100644 --- a/nodeadmin/libvirtworker.py +++ b/nodeadmin/libvirtworker.py @@ -21,20 +21,69 @@ import libvirt import os import virtinst import utils +import logging from domainconfig import DomainConfig DEFAULT_POOL_TARGET_PATH="/var/lib/libvirt/images" +DEFAULT_URL="qemu:///system" + +default_url = DEFAULT_URL + +def set_default_url(url): + logging.info("Changing DEFAULT_URL to %s" % url) + global default_url + + default_url = url + +def get_default_url(): + logging.info("Returning default URL of %s" % default_url) + return default_url + +class VirtManagerConfig: + def __init__(self, filename = "/etc/remote-libvirt.conf"): + self.__filename = filename + + def get_connection_list(self): + result = [] + if os.path.exists(self.__filename): + input = file(self.__filename, "r") + for entry in input: result.append(entry[0:-1]) + return result + + def add_connection(self, connection): + connections = self.get_connection_list() + if connections.count(connection) is 0: + connections.append(connection) + self._save_connections(connections) + + def remove_connection(self, connection): + connections = self.get_connection_list() + if connections.count(connection) > 0: + connections.remove(connection) + self._save_connections(connections) + + def _save_connections(self, connections): + output = file(self.__filename, "w") + for entry in connections: + print >> output, entry + output.close class LibvirtWorker: '''Provides utilities for interfacing with libvirt.''' - def __init__(self, url = "qemu:///system"): - self.__conn = libvirt.open(url) + def __init__(self, url = None): + if url is None: url = get_default_url() + logging.info("Connecting to libvirt: %s" % url) + self.open_connection(url) self.__capabilities = virtinst.CapabilitiesParser.parse(self.__conn.getCapabilities()) self.__net = virtinst.VirtualNetworkInterface(conn = self.__conn) self.__net.setup(self.__conn) (self.__new_guest, self.__new_domain) = virtinst.CapabilitiesParser.guest_lookup(conn = self.__conn) + def open_connection(self, url): + '''Lets the user change the url for the connection.''' + self.__conn = libvirt.open(url) + def list_domains(self, defined = True, started = True): '''Lists all domains.''' result = [] diff --git a/nodeadmin/mainmenu.py b/nodeadmin/mainmenu.py index 73501fa..944ffeb 100755 --- a/nodeadmin/mainmenu.py +++ b/nodeadmin/mainmenu.py @@ -19,15 +19,17 @@ from snack import * import traceback -from menuscreen import MenuScreen -from nodemenu import NodeMenu -from netmenu import NetworkMenu +from menuscreen import MenuScreen +from nodemenu import NodeMenu +from netmenu import NetworkMenu +from hostmenu import HostMenu import utils import logging NODE_MENU = 1 NETWORK_MENU = 2 +HOST_MENU = 3 EXIT_CONSOLE = 99 class MainMenuScreen(MenuScreen): @@ -35,12 +37,14 @@ class MainMenuScreen(MenuScreen): MenuScreen.__init__(self, "Main Menu") def get_menu_items(self): - return (("Node Administration", NODE_MENU), - ("Network Administration", NETWORK_MENU)) + return (("Node Administration", NODE_MENU), + ("Network Administration", NETWORK_MENU), + ("Host Administration", HOST_MENU)) def handle_selection(self, page): if page is NODE_MENU: NodeMenu() elif page is NETWORK_MENU: NetworkMenu() + elif page is HOST_MENU: HostMenu() def MainMenu(): screen = MainMenuScreen() diff --git a/nodeadmin/removehost.py b/nodeadmin/removehost.py new file mode 100644 index 0000000..cf3c46c --- /dev/null +++ b/nodeadmin/removehost.py @@ -0,0 +1,66 @@ +# removehost.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * + +from configscreen import * + +SELECT_HOST_PAGE = 1 +CONFIRM_REMOVE_PAGE = 2 + +class RemoveHostConfigScreen(HostListConfigScreen): + def __init__(self): + HostListConfigScreen.__init__(self, "Remove Host Connection") + + def get_elements_for_page(self, screen, page): + if page is SELECT_HOST_PAGE: return self.get_connection_list_page(screen) + elif page is CONFIRM_REMOVE_PAGE: return self.get_confirm_remove_page(screen) + + def page_has_next(self, page): + return page is SELECT_HOST_PAGE and self.has_selectable_connections() + + def page_has_back(self, page): + return page is CONFIRM_REMOVE_PAGE + + def page_has_finish(self, page): + return page is CONFIRM_REMOVE_PAGE + + def validate_input(self, page, errors): + if page is SELECT_HOST_PAGE: return True + elif page is CONFIRM_REMOVE_PAGE: + if self.__confirm.value(): + return True + else: + errors.append("You must confirm removing the connection.") + return False + + def process_input(self, page): + if page is CONFIRM_REMOVE_PAGE: + self.get_virt_manager_config().remove_connection(self.get_selected_connection()) + self.set_finished() + + def get_confirm_remove_page(self, screen): + self.__confirm = Checkbox("Remove this connection: %s" % self.get_selected_connection(), 0) + grid = Grid(1, 1) + grid.setField(self.__confirm, 0, 0) + return [Label("Remove Host Connection"), + grid] + +def RemoveHost(): + screen = RemoveHostConfigScreen() + screen.start() diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in index d23a4ef..ae89273 100644 --- a/ovirt-node.spec.in +++ b/ovirt-node.spec.in @@ -197,6 +197,11 @@ cd - %{__install} -p -m0755 nodeadmin/destroynetwork.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0755 nodeadmin/undefinenetwork.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/addhost.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0644 nodeadmin/changehost.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/hostmenu.py %{buildroot}%{python_sitelib}/nodeadmin +%{__install} -p -m0755 nodeadmin/removehost.py %{buildroot}%{python_sitelib}/nodeadmin + %{__install} -p -m0755 nodeadmin/createuser.py %{buildroot}%{python_sitelib}/nodeadmin %{__install} -p -m0644 nodeadmin/halworker.py %{buildroot}%{python_sitelib}/nodeadmin -- 1.6.2.5 From dpierce at redhat.com Thu Oct 29 20:51:12 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Thu, 29 Oct 2009 16:51:12 -0400 Subject: [Ovirt-devel] [PATCH] Enables users to migrate virtual machines between hosts. Message-ID: <1256849472-31185-1-git-send-email-dpierce@redhat.com> Users select a virtual machine on their current libvirt host. They then select a target machine, which must have been previously configured as a connection. They confirm the migration and then it runs. Signed-off-by: Darryl L. Pierce --- nodeadmin/addhost.py | 10 ++++- nodeadmin/libvirtworker.py | 6 +++ nodeadmin/migratedomain.py | 81 ++++++++++++++++++++++++++++++++++++++++++++ nodeadmin/nodemenu.py | 28 +++++++++------ 4 files changed, 111 insertions(+), 14 deletions(-) create mode 100644 nodeadmin/migratedomain.py diff --git a/nodeadmin/addhost.py b/nodeadmin/addhost.py index ef35b7d..ebcb4ea 100644 --- a/nodeadmin/addhost.py +++ b/nodeadmin/addhost.py @@ -59,7 +59,9 @@ class AddHostConfigScreen(ConfigScreen): def validate_input(self, page, errors): if page is DETAILS_PAGE: - if len(self.__hostname.value()) > 0: + if self.__connection.getSelection() is CONNECTION_LOCAL: + return True + elif len(self.__hostname.value()) > 0: return True else: errors.append("You must enter a remote hostname.") @@ -115,8 +117,12 @@ class AddHostConfigScreen(ConfigScreen): grid.setField(Label(HYPERVISORS[self.__hypervisor.getSelection()]), 1, 0, anchorLeft = 1) grid.setField(Label("Connection:"), 0, 1, anchorRight = 1) grid.setField(Label(CONNECTIONS[self.__connection.getSelection()]), 1, 1, anchorLeft = 1) + if self.__connection.getSelection() is not CONNECTION_LOCAL: + hostname = self.__hostname.value() + else: + hostname = "local" grid.setField(Label("Hostname:"), 0, 2, anchorRight = 1) - grid.setField(Label(self.__hostname.value()), 1, 2, anchorLeft = 1) + grid.setField(Label(hostname), 1, 2, anchorLeft = 1) grid.setField(Label("Autoconnect on Startup:"), 0, 3, anchorRight = 1) label = "Yes" if not self.__autoconnect.value(): label = "No" diff --git a/nodeadmin/libvirtworker.py b/nodeadmin/libvirtworker.py index 2998486..878b01c 100644 --- a/nodeadmin/libvirtworker.py +++ b/nodeadmin/libvirtworker.py @@ -122,6 +122,12 @@ class LibvirtWorker: domain = self.get_domain(name) domain.undefine() + def migrate_domain(self, name, target): + '''Migrates the specified domain to the target machine.''' + target_conn = libvirt.open(target) + virtmachine = self.get_domain(name) + virtmachine.migrate(target_conn, libvirt.VIR_MIGRATE_LIVE, None, None, 0) + def list_networks(self, defined = True, started = True): '''Lists all networks.''' result = [] diff --git a/nodeadmin/migratedomain.py b/nodeadmin/migratedomain.py new file mode 100644 index 0000000..8c8c268 --- /dev/null +++ b/nodeadmin/migratedomain.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# +# migratedomain.py - Copyright (C) 2009 Red Hat, Inc. +# Written by Darryl L. Pierce +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. A copy of the GNU General Public License is +# also available at http://www.gnu.org/copyleft/gpl.html. + +from snack import * +from libvirtworker import LibvirtWorker +from configscreen import * + +LIST_DOMAINS = 1 +SELECT_TARGET = 2 +CONFIRM_PAGE = 3 + +class MigrateDomainConfigScreen(DomainListConfigScreen): + def __init__(self): + DomainListConfigScreen.__init__(self, "Migrate Virtual Machine") + self.__configured = False + + def get_elements_for_page(self, screen, page): + if page is LIST_DOMAINS: return self.get_domain_list_page(screen) + elif page is SELECT_TARGET: return self.get_target_page(screen) + elif page is CONFIRM_PAGE: return self.get_confirm_page(screen) + + def page_has_next(self, page): + if page is LIST_DOMAINS: return self.has_selectable_domains() + else: return page < CONFIRM_PAGE + + def page_has_back(self, page): + return page < CONFIRM_PAGE + + def page_has_finish(self, page): + return page is CONFIRM_PAGE + + def validate_input(self, page, errors): + if page is LIST_DOMAINS: return self.get_selected_domain() is not None + elif page is SELECT_TARGET: + if self.__targets.current() is None: + errors.append("Please enter a target hostname or IP address.") + return False + elif page is CONFIRM_PAGE: + if not self.__confirm.value(): + errors.append("You must confirm migrating this virtual machine to proceed.") + return False + return True + + def process_input(self, page): + if page is CONFIRM_PAGE: + self.get_libvirt().migrate_domain(self.get_selected_domain(), self.__targets.current()) + self.set_finished() + + def get_target_page(self, screen): + self.__targets = Listbox(0) + for connection in self.get_virt_manager_config().get_connection_list(): + self.__targets.append(connection, connection) + return [Label("Select A Target Host"), + self.__targets] + + def get_confirm_page(self, screen): + self.__confirm = Checkbox("Confirm migrating this virtual machine.") + grid = Grid(1, 1) + grid.setField(self.__confirm, 0, 0) + return [grid] + +def MigrateDomain(): + screen = MigrateDomainConfigScreen() + screen.start() diff --git a/nodeadmin/nodemenu.py b/nodeadmin/nodemenu.py index 16be89c..f213e09 100755 --- a/nodeadmin/nodemenu.py +++ b/nodeadmin/nodemenu.py @@ -26,17 +26,19 @@ from startdomain import StartDomain from stopdomain import StopDomain from removedomain import RemoveDomain from listdomains import ListDomains +from migratedomain import MigrateDomain from createuser import CreateUser import utils import logging -ADD_DOMAIN = 1 -START_DOMAIN = 2 -STOP_DOMAIN = 3 -REMOVE_DOMAIN = 4 -LIST_DOMAINS = 5 -CREATE_USER = 6 +ADD_DOMAIN = 1 +START_DOMAIN = 2 +STOP_DOMAIN = 3 +REMOVE_DOMAIN = 4 +LIST_DOMAINS = 5 +MIGRATE_DOMAIN = 6 +CREATE_USER = 7 class NodeMenuScreen(MenuScreen): def __init__(self): @@ -48,15 +50,17 @@ class NodeMenuScreen(MenuScreen): ("Stop A Virtual Machine", STOP_DOMAIN), ("Remove A Virtual Machine", REMOVE_DOMAIN), ("List All Virtual Machines", LIST_DOMAINS), + ("Migrate Virtual Machine", MIGRATE_DOMAIN), ("Create A User", CREATE_USER)) def handle_selection(self, item): - if item is ADD_DOMAIN: AddDomain() - elif item is START_DOMAIN: StartDomain() - elif item is STOP_DOMAIN: StopDomain() - elif item is REMOVE_DOMAIN: RemoveDomain() - elif item is LIST_DOMAINS: ListDomains() - elif item is CREATE_USER: CreateUser() + if item is ADD_DOMAIN: AddDomain() + elif item is START_DOMAIN: StartDomain() + elif item is STOP_DOMAIN: StopDomain() + elif item is REMOVE_DOMAIN: RemoveDomain() + elif item is LIST_DOMAINS: ListDomains() + elif item is MIGRATE_DOMAIN: MigrateDomain() + elif item is CREATE_USER: CreateUser() def NodeMenu(): screen = NodeMenuScreen() -- 1.6.2.5 From dpierce at redhat.com Fri Oct 30 19:39:45 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 30 Oct 2009 15:39:45 -0400 Subject: [Ovirt-devel] [PATCH] Added two libvirt directories to the initial node image that are not created by the RPM. Message-ID: <1256931585-9406-1-git-send-email-dpierce@redhat.com> Signed-off-by: Darryl L. Pierce --- common-post.ks | 3 +++ 1 files changed, 3 insertions(+), 0 deletions(-) diff --git a/common-post.ks b/common-post.ks index 3a23c02..d02c946 100644 --- a/common-post.ks +++ b/common-post.ks @@ -143,6 +143,9 @@ alias vi="mod_vi" alias ping='ping -c 3' EOF +mkdir /etc/libvirt/qemu +mkdir /etc/libvirt/storage + # Remove the default logrotate daily cron job # since we run it every 10 minutes instead. rm -f /etc/cron.daily/logrotate -- 1.6.2.5 From dpierce at redhat.com Fri Oct 30 19:41:10 2009 From: dpierce at redhat.com (Darryl L. Pierce) Date: Fri, 30 Oct 2009 15:41:10 -0400 Subject: [Ovirt-devel] [PATCH] Persists the libvirt directories storing qemu and storage details. Message-ID: <1256931670-9551-1-git-send-email-dpierce@redhat.com> Signed-off-by: Darryl L. Pierce --- scripts/ovirt-config-boot | 4 ++++ scripts/ovirt-config-storage | 2 +- 2 files changed, 5 insertions(+), 1 deletions(-) diff --git a/scripts/ovirt-config-boot b/scripts/ovirt-config-boot index 2961f76..2f1e81d 100755 --- a/scripts/ovirt-config-boot +++ b/scripts/ovirt-config-boot @@ -173,6 +173,10 @@ EOF echo "InitiatorName=$(iscsi-iname)" > /etc/iscsi/initiatorname.iscsi ovirt_store_config /etc/iscsi/initiatorname.iscsi + # persist the libvirt directories + ovirt_store_config /etc/libvirt/storage + ovirt_store_config /etc/libvirt/qemu + umount /liveos umount /boot # mark new Root ready to go, reboot() in ovirt-function switches it to active diff --git a/scripts/ovirt-config-storage b/scripts/ovirt-config-storage index eed126c..4930095 100755 --- a/scripts/ovirt-config-storage +++ b/scripts/ovirt-config-storage @@ -426,7 +426,7 @@ perform_partitioning() mke2fs -j /dev/HostVG/Data -L "DATA" tune2fs -c 0 -i 0 /dev/HostVG/Data echo "/dev/HostVG/Data /data ext3 defaults 0 0" >> /etc/fstab - echo "/data/images /var/lib/libvirt/images bind bind 0 0" >> /etc/fstab + echo "/data/images /var/lib/libvirt bind bind 0 0" >> /etc/fstab echo "/data/core /var/log/core bind bind 0 0" >> /etc/fstab fi -- 1.6.2.5 From apevec at gmail.com Fri Oct 30 22:08:26 2009 From: apevec at gmail.com (Alan Pevec) Date: Fri, 30 Oct 2009 23:08:26 +0100 Subject: [Ovirt-devel] [PATCH] Persists the libvirt directories storing qemu and storage details. In-Reply-To: <1256931670-9551-1-git-send-email-dpierce@redhat.com> References: <1256931670-9551-1-git-send-email-dpierce@redhat.com> Message-ID: <2be7262f0910301508g6fde3068kac14ef0ec0b644b@mail.gmail.com> On Fri, Oct 30, 2009 at 8:41 PM, Darryl L. Pierce wrote: > - ? ? ? ?echo "/data/images /var/lib/libvirt/images bind bind 0 0" >> /etc/fstab > + ? ? ? ?echo "/data/images /var/lib/libvirt bind bind 0 0" >> /etc/fstab I guess you wanted a minimal change, but it would be better to create new directory /data/libvirt/ and bind-mount it to /var/lib/libvirt /data/images/images will be confusing IMHO.