text: #!/usr/bin/env python
  # -*- coding: utf-8 -*-
  #
  # This file is part of Karesansui Core.
  #
  # Copyright (C) 2009-2012 HDE, Inc.
  #
  # Permission is hereby granted, free of charge, to any person obtaining a copy
  # of this software and associated documentation files (the "Software"), to deal
  # in the Software without restriction, including without limitation the rights
  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  # copies of the Software, and to permit persons to whom the Software is
  # furnished to do so, subject to the following conditions:
  #
  # The above copyright notice and this permission notice shall be included in
  # all copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  # THE SOFTWARE.
  #
  
  """ 
  <comment-ja>
  スナップショットの生成と適用、情報取得を行う
  </comment-ja>
  <comment-en>
  Take and revert snapshot of domain, or get stats of snapshot image.
  </comment-en>
  
  @file:   snapshot.py
  
  @author: Taizo ITO <taizo@karesansui-project.info>
  
  @copyright:    
  
  """
  
  import os
  import re
  import libvirt
  import libvirtmod
  import logging
  import glob
  
  from io import StringIO
  from xml.dom.minidom import DOMImplementation
  implementation = DOMImplementation()
  
  import karesansui
  import karesansui.lib.locale
  
  from karesansui.lib.utils import get_xml_parse        as XMLParse
  from karesansui.lib.utils import get_xml_xpath        as XMLXpath
  from karesansui.lib.utils import get_nums_xml_xpath   as XMLXpathNum
  from karesansui.lib.utils import get_inspect_stack, preprint_r, r_chgrp, r_chmod
  from karesansui.lib.const import VIRT_XML_CONFIG_DIR, VIRT_SNAPSHOT_DIR, KARESANSUI_GROUP
  from karesansui.lib.file.configfile import ConfigFile
  from karesansui.lib.virt.config import ConfigParam
  
  from karesansui.lib.virt.virt import KaresansuiVirtException, \
       KaresansuiVirtConnection
  
  class KaresansuiVirtSnapshotException(KaresansuiVirtException):
      pass
  
  class KaresansuiVirtSnapshot:
  
      def __init__(self,uri=None,readonly=True):
          self.__prep()
          self.logger.debug(get_inspect_stack())
          try:
              self.kvc = KaresansuiVirtConnection(uri=uri, readonly=readonly)
          except:
              raise KaresansuiVirtSnapshotException(_("Cannot open '%s'") % uri)
          try:
              from libvirtmod import virDomainRevertToSnapshot
              from libvirtmod import virDomainSnapshotCreateXML
              from libvirtmod import virDomainSnapshotCurrent
              from libvirtmod import virDomainSnapshotDelete
              from libvirtmod import virDomainSnapshotGetXMLDesc
              from libvirtmod import virDomainSnapshotLookupByName
          except:
              raise KaresansuiVirtSnapshotException(_("Snapshot is not supported by this version of libvirt"))
          self.error_msg = []
  
  
      def __del__(self):
          self.finish()
  
      def __prep(self):
          self.logger = logging.getLogger('karesansui.virt.snapshot')
  
      def finish(self):
          try:
              self.kvc.close()
          except:
              pass
  
      def append_error_msg(self,string):
          self.error_msg.append(string)
  
      def reset_error_msg(self):
          self.error_msg = []
  
      def generateXML(self, doc):
          out = StringIO()
          out.write(doc,toxml())
          return out.getvalue()
  
      def isSupportedDomain(self,domain=None):
          retval = True
  
          inactives = self.kvc.list_inactive_guest()
          actives   = self.kvc.list_active_guest()
  
          if domain is not None:
              if domain in inactives + actives:
                  guest = self.kvc.search_guests(domain)[0]
  
                  param = ConfigParam(domain)
                  param.load_xml_config(guest.XMLDesc(1))
  
                  for info in param.disks:
                      """
                      { 'bus'        :'ide',
                        'device'     :'disk',
                        'disk_type'  :'file',
                        'driver_name':'qemu',
                        'driver_type':'qcow2',
                        'path'       :'/path/to/disk_image.img',
                        'target'     :'hda'}
                      """
                      try:
                          if info['driver_type'] == "qcow2":
                              pass
                          elif info['device'] == "cdrom":
                              pass
                          else:
                              if info['disk_type'] != "block":
                                  self.append_error_msg(_("%s: unsupported image format %s") % (info['target'],info['driver_type']))
                                  retval = False
                                  #break
                      except:
                          retval = False
                          #break
              else:
                  retval = False
  
          return retval
  
      def _snapshotListNames(self,domain=None):
          retval = []
  
          if domain is not None:
              for xml_path in glob.glob("%s/%s/*.xml" % (VIRT_SNAPSHOT_DIR,domain,)):
                  retval.append(os.path.basename(xml_path)[0:-4])
  
          return retval
  
      def listNames(self,domain=None,all=False):
          retval = {}
  
          inactives = self.kvc.list_inactive_guest()
          actives   = self.kvc.list_active_guest()
  
          if domain is None:
              for domname in inactives + actives:
                  if all is True:
                      names = self._snapshotListNames(domname)
                  else:
                      guest = self.kvc.search_guests(domname)[0]
                      names = guest.snapshotListNames(0)
                  retval[domname] = names
          else:
              if domain in inactives + actives:
                  if all is True:
                      names = self._snapshotListNames(domain)
                  else:
                      guest = self.kvc.search_guests(domain)[0]
                      names = guest.snapshotListNames(0)
                  retval[domain] = names
              else:
                  pass
  
          return retval
  
      def listNum(self,domain=None,all=False):
          retval = 0
  
          inactives = self.kvc.list_inactive_guest()
          actives   = self.kvc.list_active_guest()
  
          if domain is not None:
              if domain in inactives + actives:
                  if all is True:
                      names = self._snapshotListNames(domain)
                      retval = len(names)
                  else:
                      guest = self.kvc.search_guests(domain)[0]
                      retval = guest.snapshotNum(0)
              else:
                  pass
  
          return retval
  
      def whichDomain(self,name):
          retval = []
  
          try:
              for domain,names in self.listNames().items():
                  if name in names:
                      retval.append(domain)
          except:
              pass
  
          return retval
  
      def lookupByName(self,name,domain=None):
          retval = False
  
          if domain is None:
              domains = self.whichDomain(name)
              if len(domains) != 0:
                  domain = domains[0]
  
          if domain is not None:
              guest = self.kvc.search_guests(domain)[0]
              retval = libvirtmod.virDomainSnapshotLookupByName(guest._o, name, 0)
  
          return retval
  
      """
      # 親スナップショットなし
      <domainsnapshot>
        <name>1271408851</name>
        <state>running</state>
        <creationTime>1271408851</creationTime>
        <domain>
          <uuid>7833e0a3-f45e-0528-3745-f5d60bf31bd5</uuid>
        </domain>
      </domainsnapshot>
  
      # 親スナップショットあり
      <domainsnapshot>
        <name>1271409890</name>
        <state>running</state>
        <parent>
          <name>1271408851</name>
        </parent>
        <creationTime>1271409890</creationTime>
        <domain>
          <uuid>7833e0a3-f45e-0528-3745-f5d60bf31bd5</uuid>
        </domain>
      </domainsnapshot>
      """
      def getXMLDesc(self,name,domain=None,flag=True):
          retval = None
  
          snapshot = self.lookupByName(name,domain=domain)
          if snapshot is not False:
  
              if flag is True:
                  xml_path = self.getSnapshotXMLPath(name,domain=domain)
                  if os.path.exists(xml_path):
                      retval = open(xml_path).read()
  
              if retval is None:
                  retval = libvirtmod.virDomainSnapshotGetXMLDesc(snapshot, 0)
                  if retval is False:
                      retval = None
  
          return retval
  
      def getSnapshotXMLPath(self,name,domain=None):
          retval = None
  
          try:
              if domain is None:
                  domains = self.whichDomain(name)
                  if len(domains) != 0:
                      domain = domains[0]
  
              xml_path = "%s/%s/%s.xml" % (VIRT_SNAPSHOT_DIR,domain,name,)
              if os.path.exists(xml_path):
                  retval = xml_path
          except:
              pass
  
          return retval
  
      def getSnapshotInfo(self,name,domain=None):
          retval = {}
  
          xml = self.getXMLDesc(name,domain=domain)
          if xml is not None:
  
              doc = XMLParse(xml)
              name         = XMLXpath(doc, '/domainsnapshot/name/text()')
              state        = XMLXpath(doc, '/domainsnapshot/state/text()')
              parent_name  = XMLXpath(doc, '/domainsnapshot/parent/name/text()')
              creationTime = XMLXpath(doc, '/domainsnapshot/creationTime/text()')
              domain_uuid  = XMLXpath(doc, '/domainsnapshot/domain/uuid/text()')
  
              retval['name']         = name
              retval['state']        = state
              retval['parent_name']  = parent_name
              retval['creationTime'] = creationTime
              retval['domain_uuid']  = domain_uuid
              if domain_uuid is not None:
                  for guests in self.kvc.search_guests():
                      if domain_uuid == guests.UUIDString():
                          retval['domain_name']  = guests.name()
              try:
                  retval['domain_name']
              except:
                  domains  = self.whichDomain(name)
                  if len(domains) != 0:
                      retval['domain_name']  = domains[0]
  
          return retval
  
      def getParentName(self,name,domain=None):
          retval = None
  
          info = self.getSnapshotInfo(name,domain=domain)
          if info is not False:
              try:
                 retval = info['parent_name']
              except:
                 pass
  
          return retval
  
      def getChildrenNames(self,name,domain=None):
          retval = []
  
          names = self.listNames()
          try:
              for _domain,_names in names.items():
                  for _name in _names:
                      if domain == _domain and _name != name and self.getParentName(_name,domain=_domain) == name:
                          retval.append(_name)
          except:
              pass
  
          return retval
  
      def hasCurrentSnapshot(self,domain=None):
          retval = False
  
          inactives = self.kvc.list_inactive_guest()
          actives   = self.kvc.list_active_guest()
  
          if domain is not None:
              if domain in inactives + actives:
                  guest = self.kvc.search_guests(domain)[0]
                  ret = guest.hasCurrentSnapshot(0)
                  if ret != 0:
                      retval = True
              else:
                  pass
  
          return retval
  
  
      def getCurrentSnapshotXMLDesc(self,domain=None):
          retval = False
  
          if domain is not None:
              has_current = self.hasCurrentSnapshot(domain)
              if has_current is True:
                  guest = self.kvc.search_guests(domain)[0]
                  snapshot = libvirtmod.virDomainSnapshotCurrent(guest._o,0)
                  if snapshot is not False:
                      retval = libvirtmod.virDomainSnapshotGetXMLDesc(snapshot, 0)
  
          return retval
  
      def getCurrentSnapshotName(self,domain=None,force=True):
          retval = False
  
          if domain is not None:
              xml = self.getCurrentSnapshotXMLDesc(domain)
              if xml is not False:
                  doc = XMLParse(xml)
                  retval = XMLXpath(doc, '/domainsnapshot/name/text()')
  
              # 取得できなければ、domainのXMLファイルから取得
              else:
                  if force is True:
                      xml_path = "%s/%s.xml" %(VIRT_XML_CONFIG_DIR,domain,)
                      if os.path.exists(xml_path):
                          try:
                              doc = XMLParse(xml_path)
                              retval = XMLXpath(doc, '/domain/currentSnapshot/text()')
                          except:
                              pass
  
          return retval
  
      def getCurrentSnapshotInfo(self,domain=None):
          retval = False
  
          if domain is not None:
              name = self.getCurrentSnapshotName(domain)
              if name is not False:
                  retval = self.getSnapshotInfo(name,domain=domain)
  
          return retval
  
      def createSnapshot(self, domain=None, xmlDesc=None):
          retval = False
  
          if domain is not None:
              parent_snapshot_name = self.getCurrentSnapshotName(domain)
  
              if xmlDesc is None:
                  xml = "<domainsnapshot/>"
  
              else: # validate xml file
                  try:
                      doc = XMLParse(xmlDesc)
                      name = XMLXpath(doc, '/domainsnapshot/name/text()')
                      if name is not None:
                          xml = xmlDesc
                  except:
                      pass
              try:
                  xml
                  guest = self.kvc.search_guests(domain)[0]
                  snapshot = libvirtmod.virDomainSnapshotCreateXML(guest._o,xml,0)
                  if snapshot is not False:
                      retval = libvirtmod.virDomainSnapshotGetXMLDesc(snapshot, 0)
              except:
                  pass
  
          if retval is not False:
              kvg_guest = self.kvc.search_kvg_guests(domain)[0]
              id = self.getCurrentSnapshotName(domain)
              kvg_guest.set_current_snapshot(id)
  
              # ここにsnapshotのxmlファイルに親のsnapshotの情報を書き込む処理
              try:
                  xml_path = self.getSnapshotXMLPath(id)
  
                  # <parent/>が設定されてない場合
                  # かつ、snapshot実行前に<currentSnapshot/>が設定されていた場合
                  if self.getParentName(id) is None and parent_snapshot_name is not None:
                      if os.path.exists(xml_path):
                          doc = XMLParse(xml_path)
                          parent = doc.createElement("parent")
                          name   = doc.createElement("name")
                          txt = doc.createTextNode(str(parent_snapshot_name))
                          name.appendChild(txt)
                          parent.appendChild(name)
                          doc.childNodes[0].appendChild(parent)
                          xmlDesc = self.generateXML(doc)
  
                          ConfigFile(xml_path).write(xmlDesc)
  
                  if os.path.exists(xml_path):
                      if os.getuid() == 0:
                          r_chgrp(xml_path,KARESANSUI_GROUP)
                          r_chmod(xml_path,"g+rw")
                          r_chmod(xml_path,"o-rwx")
              except:
                  pass
  
          return retval
  
      def deleteSnapshot(self, name, domain=None):
          retval = False
  
          snapshot = self.lookupByName(name,domain=domain)
          if snapshot is not False:
  
              if domain is None:
                  domains = self.whichDomain(name)
                  if len(domains) != 0:
                      domain = domains[0]
  
              retval = libvirtmod.virDomainSnapshotDelete(snapshot, 0)
  
          if retval is not False:
              kvg_guest = self.kvc.search_kvg_guests(domain)[0]
              id = self.getCurrentSnapshotName(domain)
              kvg_guest.set_current_snapshot(id)
  
          return retval
  
      def revertSnapshot(self, name, domain=None):
          retval = False
  
          snapshot = self.lookupByName(name,domain=domain)
          if snapshot is not False:
  
              if domain is None:
                  domains = self.whichDomain(name)
                  if len(domains) != 0:
                      domain = domains[0]
  
              kvg_guest = self.kvc.search_kvg_guests(domain)[0]
  
              guest = kvg_guest._conn.lookupByName(domain)
              retval = libvirtmod.virDomainRevertToSnapshot(guest,snapshot, 0)
  
          if retval is not False:
              id = self.getCurrentSnapshotName(domain)
              kvg_guest.set_current_snapshot(id)
  
          return retval
  
      def refreshSnapshot(self):
          from karesansui.lib.utils import execute_command
  
          inactives = self.kvc.list_inactive_guest()
          actives   = self.kvc.list_active_guest()
  
          do_refresh = False
          for domname in inactives + actives:
              all_num    = self.listNum(domain=domname,all=True)
              active_num = self.listNum(domain=domname,all=False)
              if all_num > 0 and active_num == 0:
                  do_refresh = True
  
          # 差分があれば実行
          if do_refresh is True:
              command = "/etc/init.d/libvirtd restart"
              command_args = command.split(" ")
              (rc,res) = execute_command(command_args)
              pass
  
          return True
  
  if __name__ == '__main__':
      """Testing
      """
  
      kvs = KaresansuiVirtSnapshot()
  
      """
      names = kvs.listNames()
  
      for _k,_v in names.iteritems():
          for _k2 in _v:
              print kvs.whichDomain(_k2)
              print kvs.lookupByName(_k2)
              print kvs.getXMLDesc(_k2)
              preprint_r(kvs.getSnapshotInfo(_k2))
  
          has_current = kvs.hasCurrentSnapshot(_k)
          if has_current is False:
              print "domain %s does not have current snapshot." % (_k,)
          else:
              print "domain %s has current snapshot." % (_k,)
              print kvs.getCurrentSnapshotXMLDesc(_k)
              preprint_r(kvs.getCurrentSnapshotInfo(_k))
  
      #print "createSnapshot"
      #print kvs.createSnapshot('guest1')
      #print kvs.revertSnapshot('1271409890')
      print kvs.isSupportedDomain('guest1')
      print kvs.isSupportedDomain('guest2')
  
      print kvs.getParentName('1271409890')
      print kvs.getChildrenNames('1271409890')
  
      print kvs.getCurrentSnapshotName("guest2")
      xml_path = kvs.getSnapshotXMLPath("1274174139")
      if os.path.exists(xml_path):
          dom = XMLParse(xml_path)
          parent = dom.createElement("parent")
          name   = dom.createElement("name")
          txt = dom.createTextNode("hoge")
          name.appendChild(txt)
          parent.appendChild(name)
          dom.childNodes[0].appendChild(parent)
          xmlDesc = kvs.generateXML(dom)
          print xmlDesc
  
      id = u'1274235845'
      print kvs.getSnapshotXMLPath(id)
      print kvs.getXMLDesc(id)
  
      id = '1274235848'
      id = '1274235841'
      for domain in kvs.whichDomain(id):
          print kvs.getSnapshotXMLPath(id,domain=domain)
          print kvs.getXMLDesc(id,domain=domain)
          print kvs.getChildrenNames(id,domain=domain)
      """
  
      names = kvs.listNames()
      preprint_r(names)
      names = kvs.listNames(all=True)
      preprint_r(names)
  
      kvs.refreshSnapshot()
  
      kvs.finish()
  