Skip to content

Instantly share code, notes, and snippets.

@jquast
Created March 12, 2013 04:25
Show Gist options
  • Select an option

  • Save jquast/5140347 to your computer and use it in GitHub Desktop.

Select an option

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.
#!/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