Adding some disk image utils
Copy, clone, rebase images
git-svn-id: https://svn.apache.org/repos/asf/incubator/tashi/trunk@1455862 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/tashi/client/tashi-client.py b/src/tashi/client/tashi-client.py
index 640342e..1cc0526 100755
--- a/src/tashi/client/tashi-client.py
+++ b/src/tashi/client/tashi-client.py
@@ -261,6 +261,8 @@
'getSlots': (getSlots, None),
'getImages': (None, ['id', 'imageName', 'imageSize']),
'copyImage': (None, None),
+'cloneImage': (None, None),
+'rebaseImage': (None, None),
'createVm': (None, ['id', 'hostId', 'name', 'user', 'state', 'disk', 'memory', 'cores']),
'createMany': (createMany, ['id', 'hostId', 'name', 'user', 'state', 'disk', 'memory', 'cores']),
'shutdownMany': (shutdownMany, None),
@@ -286,6 +288,8 @@
'getSlots': [('cores', int, lambda: 1, False), ('memory', int, lambda: 128, False)],
'getImages': [],
'copyImage': [('src', str, lambda: requiredArg('src'),True), ('dst', str, lambda: requiredArg('dst'), True)],
+'cloneImage': [('src', str, lambda: requiredArg('src'),True), ('dst', str, lambda: requiredArg('dst'), True)],
+'rebaseImage': [('src', str, lambda: requiredArg('src'),True), ('dst', str, lambda: requiredArg('dst'), True)],
'getHosts': [],
'getUsers': [],
'getNetworks': [],
@@ -311,6 +315,8 @@
'vmmSpecificCall': '[instance, arg]',
'getSlots' : '[cores, memory]',
'copyImage' : '[src, dst]',
+'cloneImage' : '[src, dst]',
+'rebaseImage' : '[src, dst]',
}
# Descriptions
@@ -336,6 +342,8 @@
'vmmSpecificCall': 'Direct access to VM manager specific functionality',
'getImages' : 'Gets a list of available VM images',
'copyImage' : 'Copies a VM image',
+'cloneImage' : 'Creates a clone based on an existing image',
+'rebaseImage' : 'Merge changes from a cloned image to another image',
}
# Example use strings
@@ -360,6 +368,8 @@
'getVmLayout': [''],
'getImages': [''],
'copyImage': ['--src src.qcow2 --dst dst.qcow2'],
+'cloneImage': ['--src src.qcow2 --dst dst-clone.qcow2'],
+'rebaseImage': ['--src src-clone.qcow2 --dst dst-newimage.qcow2'],
'vmmSpecificCall': ['--instance 12345 --arg startVnc', '--instance vmname --arg stopVnc'],
}
diff --git a/src/tashi/clustermanager/clustermanagerservice.py b/src/tashi/clustermanager/clustermanagerservice.py
index e5b14b7..73fcf52 100644
--- a/src/tashi/clustermanager/clustermanagerservice.py
+++ b/src/tashi/clustermanager/clustermanagerservice.py
@@ -13,7 +13,7 @@
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
-# under the License.
+# under the License.
import logging
import threading
@@ -21,6 +21,7 @@
from tashi.rpycservices.rpyctypes import Errors, InstanceState, Instance, HostState, TashiException
from tashi import boolean, ConnectionManager, vmStates, hostStates, version, scrubString
+from tashi.dfs.diskimage import QemuImage
class ClusterManagerService(object):
"""RPC service for the ClusterManager"""
@@ -66,13 +67,15 @@
threading.Thread(name="monitorCluster", target=self.__monitorCluster).start()
+ self.qemuImage = QemuImage(self.config)
+
def __initAccounting(self):
self.accountBuffer = []
self.accountLines = 0
self.accountingClient = None
try:
if (self.accountingHost is not None) and \
- (self.accountingPort is not None):
+ (self.accountingPort is not None):
self.accountingClient = ConnectionManager(self.username, self.password, self.accountingPort)[self.accountingHost]
except:
self.log.exception("Could not init accounting")
@@ -696,6 +699,26 @@
except Exception, e:
self.log.exception('DFS image copy failed: %s (%s->%s)' % (e, imageSrc, imageDst))
+ # extern
+ def cloneImage(self, src, dst):
+ imageSrc = self.dfs.getLocalHandle("images/" + src)
+ imageDst = self.dfs.getLocalHandle("images/" + dst)
+ self.log.info('DFS image clone: %s->%s' % (imageSrc, imageDst))
+ try:
+ self.qemuImage.cloneImage(imageSrc, imageDst)
+ except Exception, e:
+ self.log.info('DFS image clone error: %s' % (e))
+
+ # extern
+ def rebaseImage(self, src, dst):
+ imageSrc = self.dfs.getLocalHandle("images/" + src)
+ imageDst = self.dfs.getLocalHandle("images/" + dst)
+ self.log.info('DFS image rebase: %s->%s' % (imageSrc, imageDst))
+ try:
+ self.qemuImage.rebaseImage(imageSrc, imageDst)
+ except Exception, e:
+ self.log.info('DFS image rebase error: %s' % (e))
+
# extern
def vmmSpecificCall(self, instanceId, arg):
instance = self.data.getInstance(instanceId)
diff --git a/src/tashi/dfs/diskimage.py b/src/tashi/dfs/diskimage.py
new file mode 100644
index 0000000..929156b
--- /dev/null
+++ b/src/tashi/dfs/diskimage.py
@@ -0,0 +1,46 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+import logging
+import subprocess
+
+from tashi.dfs.diskimageinterface import DiskImageInterface
+
+class QemuImage(DiskImageInterface):
+ ''' This class implements the image creation for qemu/kvm '''
+
+ def __init__(self, config):
+ DiskImageInterface.__init__(self, config)
+ self.log = logging.getLogger(__name__)
+ self.prefix = self.config.get("Vfs", "prefix")
+
+
+ def cloneImage(self, srcImage, dstImage):
+ self.log.info("Cloning image %s -> %s" % (srcImage, dstImage))
+ cmd = "/usr/bin/qemu-img create -b %s -f qcow2 %s" % (srcImage, dstImage)
+ p = subprocess.Popen(cmd.split(), executable=cmd.split()[0], stdout=subprocess.PIPE)
+ self.log.info("Finished cloning image %s" % (p.stdout.readlines()))
+
+ def rebaseImage(self, srcImage, dstImage):
+ self.log.info("Rebasing image %s -> %s" % (srcImage, dstImage))
+ cmd = "/usr/bin/qemu-img convert %s -O qcow2 %s" % (srcImage, dstImage)
+ p = subprocess.Popen(cmd.split(), executable=cmd.split()[0], stdout=subprocess.PIPE)
+ self.log.info("Finished rebasing image %s" % (p.stdout.readlines()))
+
+ def getImageInfo(self, srcImage):
+ cmd = "/usr/bin/qemu-img info %s" % srcImage
+ subprocess.Popen(cmd.split(), executable=cmd.split()[0], stdout=subprocess.PIPE)
diff --git a/src/tashi/dfs/diskimageinterface.py b/src/tashi/dfs/diskimageinterface.py
new file mode 100644
index 0000000..2c4bce2
--- /dev/null
+++ b/src/tashi/dfs/diskimageinterface.py
@@ -0,0 +1,31 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+class DiskImageInterface():
+ def __init__(self, config):
+ if self.__class__ is DiskImageInterface:
+ raise NotImplementedError
+ self.config = config
+
+ def cloneImage(self, srcImage, dstImage):
+ raise NotImplementedError
+
+ def rebaseImage(self, srcImage, dstImage):
+ raise NotImplementedError
+
+ def getImageInfo(self, srcImage, dstImage):
+ raise NotImplementedError
diff --git a/src/tashi/rpycservices/rpycservices.py b/src/tashi/rpycservices/rpycservices.py
index 0f760fc..5f47de4 100644
--- a/src/tashi/rpycservices/rpycservices.py
+++ b/src/tashi/rpycservices/rpycservices.py
@@ -19,7 +19,7 @@
from tashi.rpycservices.rpyctypes import Instance, Host, User
import cPickle
-clusterManagerRPCs = ['createVm', 'shutdownVm', 'destroyVm', 'suspendVm', 'resumeVm', 'migrateVm', 'pauseVm', 'unpauseVm', 'getHosts', 'getNetworks', 'getUsers', 'getInstances', 'vmmSpecificCall', 'registerNodeManager', 'vmUpdate', 'activateVm', 'registerHost', 'unregisterHost', 'getImages', 'copyImage', 'setHostState', 'setHostNotes', 'addReservation', 'delReservation', 'getReservation']
+clusterManagerRPCs = ['createVm', 'shutdownVm', 'destroyVm', 'suspendVm', 'resumeVm', 'migrateVm', 'pauseVm', 'unpauseVm', 'getHosts', 'getNetworks', 'getUsers', 'getInstances', 'vmmSpecificCall', 'registerNodeManager', 'vmUpdate', 'activateVm', 'registerHost', 'unregisterHost', 'getImages', 'copyImage', 'cloneImage', 'rebaseImage', 'setHostState', 'setHostNotes', 'addReservation', 'delReservation', 'getReservation']
nodeManagerRPCs = ['instantiateVm', 'shutdownVm', 'destroyVm', 'suspendVm', 'resumeVm', 'prepReceiveVm', 'prepSourceVm', 'migrateVm', 'receiveVm', 'pauseVm', 'unpauseVm', 'getVmInfo', 'listVms', 'vmmSpecificCall', 'getHostInfo', 'liveCheck']
accountingRPCs = ['record']