harry’s memorandum

おれおれメモ

XenServer5.5でsnapshotをrollback(revert to snapshot)してみる

XenでもXenServerの話。
XenServerは体感ではVMwareよりずっと軽く感じる*1ので、最近はとても愛用しているのですが、XenServer5.5のXenCenterにはスナップショットから元に戻す機能が付いていないので少し不満でした。
調べてみると、どうやらXenServer5.5ではcheckpoint(revert to snapshot)の機能は消されているようです。

http://community.citrix.com/display/ocb/2009/07/02/XenServer+-+LVM-based+Snapshots
"Revert"(旧checkpoint)は5.5では提供されていません

でも、スナップショットの中身がLVM-BaseならCLIの機能でいけるんじゃないか、と検索してみました。
http://docs.vmd.citrix.com/XenServer/5.5.0/1.0/en_gb/reference.html#id2584877
ちょっと面倒ですが、いちおういけそう。*2

  1. 取得したスナップショット名をメモ
  2. 元に戻すVMのMACアドレスをメモ
  3. VMの情報を取得
  4. domain0でUUIDを調べる (xe vm-list)
  5. VMをシャットダウン(xe vm-shutdown uuid=)
  6. VMを削除(xe vm-destroy uuid=)
  7. スナップショットからVMをあたらしく作成(xe vm-install new-name-label= template=)
  8. VMを起動(xe vm-start name-label=)
  9. MACアドレスを元に戻す

こんなの手でやってられないのでスクリプト化。やっつけでまともにテストしてないので保障なし。

require 'optparse'
require 'pp'

def xelist(sub_command, label)
  return nil unless ["vm-list", "template-list"].index(sub_command)
  data = IO.popen("xe #{sub_command} 2> /dev/null", "r").map
  vms = []
  while 0 < data.size
    hash = {}
    if data[1].split(":").size == 3
    else
      3.times {|n|
        elems = data[n].split(":")
        key, value = elems[0].split[0].strip.chomp, elems[-1].strip.chomp
        hash[key] = value
      }
      vms << hash
    end
    5.times {|n| data.shift}
  end
  key = label.is_uuid? ? "uuid" : "name-label"
  vms.each {|vm|
    if vm[key] == label
      if sub_command == "template-list"
        return vm["uuid"]
      else
        return vm["uuid"] ,vm["name-label"] ,vm["power-state"]
      end
    end
  }
  return nil
end

def xevmvif(label)
  data = IO.popen("xe vm-vif-list 2> /dev/null", "r").map
  vms = []
  while 0 < data.size
    hash = {}
    6.times {|n|
      elems = data[n].split(":")
      key, value = elems[0].split[0].strip.chomp, elems[1..-1].join(":").strip.chomp
      hash[key] = value
    }
    vms << hash
    8.times {|n| data.shift}
  end
  vms.each {|vm|
    return vm["MAC"] if vm["vm-name-label"] == label
  }
  return nil
end

class String
  def is_uuid?
    uuid = self.split("-")
    return nil unless uuid.size == 5
    [8,4,4,4,12] == [uuid[0].size, uuid[1].size, uuid[2].size, uuid[3].size, uuid[4].size]
  end
end

def system_e(command_line)
  puts " $ " + command_line
  system(command_line)
end

# main
o = {}
opt = OptionParser.new
opt.on("--vm", "--vm-list {uuid|label-name}") {|v| o[:vm] = v}
opt.on("--template", "--templatet {uuid|label-name}") {|v| o[:template] = v}

begin
  opt.parse!
rescue OptionParser::ParseError => e
  puts opt.help
  exit 2
else
  [:vm, :template].each {|x|
    unless o[x]
      puts opt.help
      exit 2
    end
  }
end

result = {}
# source vm exist?
result[:vm_uuid], result[:vm_label], result[:vm_state] =
   xelist("vm-list", o[:vm])
# template exist?
result[:tvm_uuid] = xelist("template-list", o[:template])

if result[:vm_uuid].nil?
  puts "#{result[:vm_uuid]} source vm not found."; exit
end
if result[:tvm_uuid].nil?
  puts "#{result[:tvm_uuid]} template vm not found."; exit
end

result[:mac] = xevmvif(result[:vm_label])

system_e("xe vm-shutdown uuid=#{result[:vm_uuid]}") if result[:vm_state] == "running"
system_e("xe vm-destroy uuid=#{result[:vm_uuid]}")
system_e("xe vm-install new-name-label=#{result[:vm_label]} template=#{result[:tvm_uuid]} > /dev/null 2>&1")
system_e("xe vm-start name-label=#{result[:vm_label]}")

puts "Please change your mac address - #{result[:mac]}"

exit

こんな感じで。ruby revert.rb --vm [対象のvm] --template [戻すスナップショット]

# ruby revert.rb --template test0001_snapshot --vm testvmname
$ xe vm-shutdown uuid=4f3ba61b-1253-4d23-eabf-ac82399a659c
$ xe vm-destroy uuid=4f3ba61b-1253-4d23-eabf-ac82399a659c
$ xe vm-install new-name-label=testvmname template=64610fff-80ff-e294-323a-7297aa5491f9 > /dev/null 2>&1
$ xe vm-start name-label=testvmname
Please change your mac address - 3e:da:82:89:cc:7d

*1:vSphereClientよりXenCenterが軽いからかな

*2:稼動中のメモリダンプをしてスナップショットするわけじゃないので、VMwareのような使い勝手にはいかないですね