Created
March 12, 2013 04:25
-
-
Save jquast/5140347 to your computer and use it in GitHub Desktop.
Solaris 10 zone migration script -- transfers a virtual machine to another physical host by ssh.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/ksh | |
| # contact@jeffquast.com | |
| # | |
| # move a zone by replicating a zfs container to a remote host, | |
| # exporting and importing the zone configuration and attaching the container, | |
| # then destroying the previous zone after the destination zone is verified. | |
| # | |
| # default containers are 'tank' or 'rootpool' | |
| # | |
| # references: | |
| # http://docs.sun.com/app/docs/doc/817-1592/z.login.task-31?a=view | |
| # http://www.sun.com/software/solaris/howtoguides/moving_containers.jsp | |
| DESTROY_ZONE=`dirname $0`/destroy-zone.sh | |
| # first argument can be zone name | |
| if [ X"${1}" == X"-h" ]; then | |
| echo usage: | |
| echo | |
| echo $0 [zonename] [destination host] | |
| echo | |
| exit 1 | |
| fi | |
| if [ -z "${ZONENAME}" ]; then | |
| if [ ! -z "${1}" ]; then | |
| ZONENAME="$1" | |
| else | |
| read ZONENAME?"source zone name: " | |
| if [ -z "${ZONENAME}" ]; then | |
| echo "error: must specify ZONENAME" | |
| exit 1 | |
| fi | |
| fi | |
| fi | |
| if [ -z "${TANK}" ]; then | |
| zfs list tank >/dev/null 2>&1 | |
| if [ $? -eq 0 ]; then | |
| TANK=tank | |
| else | |
| zfs list rootpool >/dev/null 2>&1 | |
| if [ $? -eq 0 ]; then | |
| TANK=rootpool | |
| else | |
| read TANK?"Tank: " | |
| fi | |
| fi | |
| fi | |
| # second argument can be destination machine | |
| if [ -z "${DEST_MACHINE}" ]; then | |
| if [ ! -z "${2}" ]; then | |
| DEST_MACHINE="$2" | |
| else | |
| read DEST_MACHINE?"destination machine hostname? " | |
| fi | |
| if [ -z "${DEST_MACHINE}" ]; then | |
| echo "error: must specify DEST_MACHINE" | |
| fi | |
| fi | |
| if [ -z "${REMOTE_TANK}" ]; then | |
| ssh $DEST_MACHINE "zfs list tank" >/dev/null 2>&1 | |
| if [ $? -eq 0 ]; then | |
| REMOTE_TANK=tank | |
| else | |
| ssh $DEST_MACHINE "zfs list rootpool" >/dev/null 2>&1 | |
| if [ $? -eq 0 ]; then | |
| REMOTE_TANK=rootpool | |
| else | |
| read REMOTE_TANK?"Remote Tank: " | |
| fi | |
| fi | |
| fi | |
| ZFS_ZONEPATH=$TANK/zones/$ZONENAME | |
| ZONEPATH=/$ZFS_ZONEPATH | |
| REMOTE_ZFS_ZONEPATH=$REMOTE_TANK/zones/$ZONENAME | |
| REMOTE_ZONEPATH=/$REMOTE_ZFS_ZONEPATH | |
| IPADDRESS=$(zonecfg -z $ZONENAME info net | awk '/address:/{print $2}') | |
| # calculate related zfs filesystems | |
| # read filesystems in reverse order, | |
| # so that parent fs's are last | |
| #zfs list -t filesystem | egrep "zones/$ZONENAME" \ | |
| # | awk '{print $1}' | /usr/local/bin/tac $list | while read fs; do | |
| # zfs_filesystems="$zfs_filesystems $fs" | |
| #done | |
| # Trying out using zonecfg instead | |
| zfs_filesystems=`zfs list -o mountpoint,name -t filesystem | egrep "^$(zonecfg -z $ZONENAME info zonepath|awk -F':' '{print \$2}'|xargs echo) "|awk '{print $2}'` | |
| zfs_filesystems="$zfs_filesystems $(zonecfg -z $ZONENAME info dataset | /usr/local/bin/gawk -F':' '{if (match($1,"name")) print $2}')" | |
| if [ -z $zfs_filesystems ]; then | |
| echo "no filesystem found for $ZONENAME" | |
| exit 1 | |
| elif [ -z "${IPADDRESS}" ]; then | |
| echo "could not determine ip address of $ZONENAME" | |
| read yn?"Is this an Exclusive-IP zone? [y]" | |
| if [ X"${yn}" == X"y" ] || [ X"${yn}" == X"Y" ] || [ -z ${yn} ]; then | |
| read DEST_IF?"Remote interface for exclusive access to zone: " | |
| else | |
| exit 1 | |
| fi | |
| else | |
| DEST_IF=$(ssh $DEST_MACHINE "ifconfig -a" | grep 'UP,BROADCAST,RUNNING,MULTICAST,IPv4' | head -n 1 | awk -F':' '{print $1}') | |
| fi | |
| echo " local tank: $TANK" | |
| echo " remote tank: $REMOTE_TANK" | |
| echo " Zone name: $ZONENAME" | |
| echo " IP Address: $IPADDRESS" | |
| echo " Filesystems:" | |
| for fs in $zfs_filesystems; do echo " $fs"; done; echo | |
| echo " Moving zone to host: $DEST_MACHINE" | |
| ret=$? | |
| if [ $ret -ne 0 ]; then | |
| echo "you must be able to ssh to remote machine" | |
| exit $ret | |
| fi | |
| echo " remote interface: $DEST_IF" | |
| echo | |
| echo " Warning: Zone will recieve a HALT, and be" | |
| echo " unavailable during the transfer!" | |
| echo | |
| read none?"press return to proceed..." | |
| if ssh $DEST_MACHINE zoneadm -z $ZONENAME list 2>/dev/null; then | |
| read yn?"remove $ZONENAME on $DEST_MACHINE ? [n]" | |
| if [ X"${yn}" == X"y" ] || [ X"${yn}" == X"Y" ]; then | |
| printf "removing remote zone..." | |
| ssh $DEST_MACHINE "/usr/sbin/zoneadm -z $ZONENAME halt" 2>/dev/null | |
| ssh $DEST_MACHINE "/usr/sbin/zonecfg -z $ZONENAME delete -F" || exit 1 | |
| echo ok | |
| else | |
| echo "remote host has conflicting zone: $ZONENAME" | |
| exit 1 | |
| fi | |
| fi | |
| zone_state=$(zoneadm -z $ZONENAME list -p | awk -F':' '{print $3}') || exit 1 | |
| if [ $zone_state == "running" ] && [ $zone_state != "installed" ]; then | |
| printf "zone in state: $zone_state, halting..." | |
| zoneadm -z $ZONENAME halt || exit 1 | |
| echo ok | |
| else | |
| echo "zone in state: $zone_state, not halting" | |
| fi | |
| zone_state=$(zoneadm -z $ZONENAME list -p | awk -F':' '{print $3}') || exit 1 | |
| if [ $zone_state == "installed" ] && [ $zone_state != "configured" ]; then | |
| printf "zone in state: $zone_state, detaching..." | |
| zoneadm -z $ZONENAME detach || exit 1 | |
| echo ok | |
| else | |
| echo "zone in state: $zone_state, not detaching" | |
| fi | |
| for fs in $zfs_filesystems; do | |
| remote_fs=$(echo $fs | sed s/^$TANK/$REMOTE_TANK/g) | |
| echo "remote_fs=$remote_fs" | |
| snapname=${fs}@$(date +%Y%m%d%H%M)-migration | |
| remote_snapname=${remote_fs}@$(date +%Y%m%d%H%M)-migration | |
| if zfs list $snapname 2>/dev/null 1>&2; then | |
| printf "removing stale snapshot..." | |
| zfs destroy $snapname || exit 1 | |
| echo ok | |
| fi | |
| CONTINUE=1 | |
| if ssh $DEST_MACHINE "/usr/sbin/zfs list $remote_fs" \ | |
| 2>/dev/null 1>&2; then | |
| read yn?"remove $remote_fs on $DEST_MACHINE ? [n]" | |
| if [ X"${yn}" == X"y" ] || [ X"${yn}" == X"Y" ]; then | |
| printf "removing conflict zfs on remote host..." | |
| ssh $DEST_MACHINE "/usr/sbin/zfs destroy -r $remote_fs" || exit 1 | |
| echo ok | |
| else | |
| echo "error: remote host has conflicting zfs: $remote_fs" | |
| CONTINUE=0 | |
| fi | |
| fi | |
| if ! ssh $DEST_MACHINE "/usr/sbin/zfs list $REMOTE_TANK/zones" >/dev/null 2>&1; then | |
| printf "$REMOTE_TANK/zones does not exist, creating..." | |
| ssh $DEST_MACHINE "/usr/sbin/zfs create $REMOTE_TANK/zones" || exit 1 | |
| echo ok | |
| fi | |
| if [ $CONTINUE -eq 1 ]; then | |
| printf "snapshot ${snapname}..." | |
| zfs snapshot $snapname || exit 1 | |
| echo ok | |
| # sigh, I think i'd like to use -R, but its hit or miss | |
| size=$(zfs get -H referenced $snapname | awk '{print $3}') | |
| echo "transferring ($size) ..." | |
| snapsize=$(zfs list -H -o refer $snapname | /usr/local/bin/gawk '{ | |
| split("Z E P T G M K B", b, " ") | |
| p=match($0,"[ZEPTGMKB]"); | |
| d=substr($0, 0, p-1); | |
| t=substr($0, p, 1); | |
| for(i=1;i<length(b);i++) | |
| if (t==b[i]) | |
| { d*=1024; t=b[i+1] } | |
| printf ("%i", d);}') | |
| zfs send $snapname \ | |
| | /usr/local/bin/mbuffer -q -b 512 -s 65536 \ | |
| | /usr/local/bin/pv -prbe -B 1048576 -s $snapsize -N $snapname \ | |
| | ssh $DEST_MACHINE \ | |
| "/usr/local/bin/mbuffer -q -b 512 -s 65536 \ | |
| | /usr/sbin/zfs recv $remote_fs" | |
| ret=$? | |
| if [ $ret -ne 0 ]; then | |
| echo "error in transfer: $ret" | |
| exit $ret | |
| else echo ok; fi | |
| # this snapshot is no longer needed | |
| printf "zfs destroy $snapname..." | |
| zfs destroy $snapname | |
| ret=$? | |
| if [ $ret -ne 0 ]; then | |
| echo "error in destroy: $ret" | |
| exit $ret | |
| else echo ok; fi | |
| fi | |
| quota=$(zfs get -H -o value quota $fs) | |
| printf "set quota=$quota..." | |
| ssh $DEST_MACHINE /usr/sbin/zfs set quota=$quota $remote_fs | |
| ret=$? | |
| if [ $ret -ne 0 ]; then | |
| echo "error in zfs set $ret" | |
| exit $ret | |
| else echo ok; fi | |
| printf "set zoned=off..." | |
| ssh $DEST_MACHINE /usr/sbin/zfs set zoned=off $remote_fs | |
| ret=$? | |
| if [ $ret -ne 0 ]; then | |
| echo "error in zfs set $ret" | |
| exit $ret | |
| else echo ok; fi | |
| mountpoint=$(zfs get -H -o value mountpoint $fs) | |
| # what if mountpoint was /tank ($TANK), but is now /rootpool ($REMOTE_TANK) | |
| n_mountpoint=$(echo $mountpoint \ | |
| | /usr/local/bin/gawk -F '/' -vTANK=$TANK -vREMOTE_TANK=$REMOTE_TANK \ | |
| '{ if($2==TANK) { | |
| printf("/%s",REMOTE_TANK); | |
| for(n=3;n<=NF;n++) | |
| printf("/%s",$n); | |
| printf "\n" | |
| } else | |
| print $0 | |
| }') | |
| if [ ! -z $n_mountpoint ] && [ $mountpoint != $n_mountpoint ]; then | |
| echo "Changing mountpoint from $mountpoint to $n_mountpoint on remote machine" | |
| mountpoint=$n_mountpoint | |
| fi | |
| printf "set mountpoint=$mountpoint..." | |
| ssh $DEST_MACHINE /usr/sbin/zfs set mountpoint=$mountpoint $remote_fs | |
| ret=$? | |
| if [ $ret -ne 0 ]; then | |
| echo "error in zfs set $ret" | |
| exit $ret | |
| else echo ok; fi | |
| printf "set zoned=off..." | |
| ssh $DEST_MACHINE /usr/sbin/zfs set zoned=off $remote_fs | |
| ret=$? | |
| if [ $ret -ne 0 ]; then | |
| echo "error in zfs set $ret" | |
| exit $ret | |
| else echo ok; fi | |
| done | |
| printf "attaching zone at remote host..." | |
| ssh $DEST_MACHINE /usr/sbin/zonecfg -z $ZONENAME create -a $REMOTE_ZONEPATH || exit 1 | |
| echo ok | |
| if [ "${TANK}" != X"${REMOTETANK}" ]; then | |
| printf "modifying remote zonepath..." | |
| ssh $DEST_MACHINE "/usr/sbin/zonecfg -z $ZONENAME set zonepath=$REMOTE_ZONEPATH" || exit 1 | |
| echo ok | |
| fi | |
| printf "setting remote interface..." | |
| [ ! -z $IPADDRESS ] && SETIP="set address=$IPADDRESS" || SETIP= | |
| ssh $DEST_MACHINE "/usr/sbin/zonecfg -z $ZONENAME << EOF | |
| remove net | |
| add net | |
| set physical=$DEST_IF | |
| $SETIP | |
| end | |
| EOF" | |
| ret=$?; [ $ret -ne 0 ] && exit $ret | |
| echo ok | |
| printf "attaching zfs zone..." | |
| ssh $DEST_MACHINE "/usr/sbin/zoneadm -z $ZONENAME attach" | |
| ret=$? | |
| if [ $ret -ne 0 ]; then | |
| read yn?"force attach to remote machine? [y]" | |
| if [ X"${yn}" == X"n" ] || [ X"${yn}" == X"N" ]; then | |
| echo "zone move incomplete. You may complete by executing:" | |
| echo; echo " zoneadm -z $ZONEADM attach -F" | |
| echo; echo "on $DEST_MACHINE." | |
| exit 1 | |
| fi | |
| ssh $DEST_MACHINE "/usr/sbin/zoneadm -z $ZONENAME attach -F" | |
| else echo ok; fi | |
| printf "booting zone on $DEST_MACHINE..." | |
| ssh $DEST_MACHINE "/usr/sbin/zoneadm -z $ZONENAME boot" || exit 1 | |
| echo ok; sleep 1 | |
| zone_state=$(ssh $DEST_MACHINE zoneadm -z $ZONENAME list -p | awk -F':' '{print $3}') || exit 1 | |
| if [ X"$zone_state" == X"running" ]; then | |
| echo "zone state on $DEST_MACHINE is: $zone_state" | |
| zone_state=$(zoneadm -z $ZONENAME list -p | awk -F':' '{print $3}') || exit 1 | |
| echo "zone state on local machine is: $zone_state" | |
| read yn?"remove zone on local machine? [y]" | |
| if [ X"${yn}" == X"n" ] || [ X"${yn}" == X"N" ]; then | |
| echo "Take a byte out of i/o crime -- please remove unused zfs containers!" | |
| exit 1 | |
| fi | |
| $DESTROY_ZONE $ZONENAME || exit 1 | |
| else | |
| echo "Error: destination zone is in state: ${zone_state}" | |
| exit 1 | |
| fi | |
| echo "Move is complete!" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment