On 12/21/2012 11:28 PM, Jannis Pohlmann wrote:
This command has the following syntax:
morph deploy remote-virtualbox HOST:PATH VMNAME REPO REF SYSTEM
It resolves the REPO REF SYSTEM triplet into its artifacts, copies
the rootfs artifact to another machine using rsync and then creates
or updates a VirtualBox VM with the name VMNAME to boot from this
rootfs. It makes sure that the VM is powered off before replacing
its disk. It also starts the VM again once disks have been replaced.
This way the process can be used to repeatedly test a built system.
---
morphlib/plugins/deployment_plugin.py | 106 ++++++++++++++++++++++++++++++----
1 file changed, 94 insertions(+), 12 deletions(-)
diff --git a/morphlib/plugins/deployment_plugin.py
b/morphlib/plugins/deployment_plugin.py
index 9418d0c..4ad88b9 100644
--- a/morphlib/plugins/deployment_plugin.py
+++ b/morphlib/plugins/deployment_plugin.py
@@ -124,15 +124,97 @@ class DeploymentPlugin(cliapp.Plugin):
rsync_args += filenames
rsync_args.append(args[0]) # the destination
- self.run_cmd(['rsync', '-vP'] + rsync_args)
-
- def run_cmd(self, args):
- process = subprocess.Popen(args,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- stdout = process.stdout
- while True:
- line = stdout.readline()
- if not line:
- break
- self.app.output.write(line)
+ self.app.runcmd(['rsync', '-vP'] + rsync_args)
OK, ignore my comment from the previous patch :)
+ def deploy_remote_virtualbox(self, args, system, artifacts):
+ usage = 'remote-virtualbox HOST:DIR VM_NAME'
+
+ if len(args) < 2 or not ':' in args[0]:
+ raise UsageError(usage)
+
+ # Parse deployment method arguments.
+ destination = args[0]
+ host, dirname = destination.split(':')
+ vm = args[1]
+
+ def run_ssh(*args, **kwargs):
+ args = ['ssh', host] + list(args)
+ return self.app.runcmd(args, **kwargs)
What happens if ssh prompts for a password here?
+ def run_vboxmanage(*args, **kwargs):
+ args = ['ssh', host, 'VBoxManage'] + list(args)
+ return self.app.runcmd(args, **kwargs)
+
+ # Make sure the VM directory exists on the host.
+ self.app.status(msg='%(host)s: Ensuring directory "%(path)s"
exists',
+ host=host, path=dirname)
+ run_ssh('test -d "%s" || mkdir -p "%s"' % (dirname,
dirname))
+
+ # Obtain the rootfs artifact from the list.
+ rootfs_artifact = [x for x in artifacts if 'rootfs' in x.name][0]
Doing x.name.endswith('rootfs') would be more robust.
+ # Generate source and destination paths.
+ rootfs_source = self.lac.artifact_filename(rootfs_artifact)
+ rootfs_target = os.path.join(dirname, rootfs_artifact.basename())
+
+ # Determine image size.
+ size = self.app.runcmd(['stat', '-c', '%s',
rootfs_source]).strip()
+
+ # Copy the rootfs artifact over to the host.
+ self.app.status(msg='Copying %(system)s image to '
+ '"%(host)s:%(path)s" (%(size)iMB)',
+ system=system, host=host, path=dirname,
+ size=int(size)/1024/1024)
+ self.app.runcmd(['rsync', '-av', rootfs_source,
+ '%s:%s' % (host, rootfs_target)])
+
+ # Extract the rootfs image and convert it to a VDI disk.
+ self.app.status(msg='%(host)s: Checking if VDI %(system)s image '
+ 'exists',
+ host=host, system=system)
+ try:
+ run_ssh(['stat', rootfs_target])
+ except cliapp.AppException:
+ self.app.status(msg='%(host): Converting %(system)s image to VDI',
+ host=host, system=system)
+ run_ssh('gzip -c -d "%s" | '
+ 'VBoxManage convertfromraw stdin "%s.vdi" %s; ' %
+ (rootfs_target, rootfs_target, rootfs_target, size))
+ # Create the virtual machine on the host, if it doesn't already exist.
+ self.app.status(msg='%(host)s: Checking whether VM %(vm)s exists',
+ host=host, vm=vm)
+ try:
+ state = run_ssh('VBoxManage showvminfo "%s" | '
+ 'grep -i state' % vm).strip()
+ vm_exists = True
+ except:
+ self.app.status(msg='%(host)s: Creating VM %(vm)s',
+ host=host, vm=vm)
+ run_ssh('VBoxManage', 'createvm', '--name',
'"%s"' % vm,
+ '--ostype', 'Linux26_64', '--register')
+ vm_exists = False
+ state = None
+ if vm_exists and not 'powered off' in state:
+ self.app.status(msg='%(host)s: Shutting down VM
"%(vm)s"',
+ host=host, vm=vm)
+ run_ssh('VBoxManage', 'controlvm', '"%s"'
% vm, 'poweroff')
You only need to catch cliapp.AppException there, surely?
Does VBoxManage block until the VM has powered off fully? If so, a
comment to that effect would be good. If not ... :(
+ # Attaching the generated VDI disk to the VM as the main
disk.
+ self.app.status(msg='%(host)s: Attaching %(system)s VDI disk '
+ 'to VM %(vm)s',
+ host=host, system=system, vm=vm)
+ try:
+ run_ssh('VBoxManage', 'storagectl', '"%s"'
% vm,
+ '--name', '"SATA Controller"',
'--add', 'sata',
+ '--bootable', 'on', '--sataportcount',
'1', '>/dev/null')
+ except cliapp.AppException:
+ pass
+ run_ssh('VBoxManage', 'storageattach', '"%s"'
% vm,
+ '--storagectl', '"SATA Controller"',
'--port', '0',
+ '--device', '0', '--type', 'hdd',
'--medium',
+ '"%s.vdi"' % rootfs_target)
Does this replace the old disk? Again, a comment explaining how
VBoxManage behaves would be good.
+ # Start the VM on the host.
+ self.app.status(msg='%(host)s: Starting VM %(vm)s', host=host, vm=vm)
+ run_ssh('VBoxManage', 'startvm', '"%s"' % vm)
Do you think it's worth adding a warning to the 'deploy' command's help
that this doesn't migrate any of your configuration? Speaking as a
person who easily confused, I think it would be helpful to prevent
anyone having false expectations of this (already quite cool) feature.
Sam