core: split VM images handling to separate class
This will ease handling different types of VMM (which can require different image types, location etc).
This commit is contained in:
parent
d5cb05fdc6
commit
0a1f3d0a44
@ -347,6 +347,12 @@ class QubesVm(object):
|
|||||||
else:
|
else:
|
||||||
assert self.root_img is not None, "Missing root_img for standalone VM!"
|
assert self.root_img is not None, "Missing root_img for standalone VM!"
|
||||||
|
|
||||||
|
self.storage = defaults["storage_class"](self)
|
||||||
|
if hasattr(self, 'kernels_dir'):
|
||||||
|
self.storage.modules_img = os.path.join(self.kernels_dir,
|
||||||
|
"modules.img")
|
||||||
|
self.storage.modules_img_rw = self.kernel is None
|
||||||
|
|
||||||
# fire hooks
|
# fire hooks
|
||||||
for hook in self.hooks_init:
|
for hook in self.hooks_init:
|
||||||
hook(self)
|
hook(self)
|
||||||
@ -854,33 +860,17 @@ class QubesVm(object):
|
|||||||
return qubes.qubesutils.get_disk_usage(self.private_img)
|
return qubes.qubesutils.get_disk_usage(self.private_img)
|
||||||
|
|
||||||
def get_private_img_sz(self):
|
def get_private_img_sz(self):
|
||||||
if not os.path.exists(self.private_img):
|
return self.storage.get_private_img_sz()
|
||||||
return 0
|
|
||||||
|
|
||||||
return os.path.getsize(self.private_img)
|
|
||||||
|
|
||||||
def resize_private_img(self, size):
|
def resize_private_img(self, size):
|
||||||
assert size >= self.get_private_img_sz(), "Cannot shrink private.img"
|
assert size >= self.get_private_img_sz(), "Cannot shrink private.img"
|
||||||
|
|
||||||
f_private = open (self.private_img, "a+b")
|
# resize the image
|
||||||
f_private.truncate (size)
|
self.storage.resize_private_img(size)
|
||||||
f_private.close ()
|
|
||||||
|
|
||||||
|
# and then the filesystem
|
||||||
retcode = 0
|
retcode = 0
|
||||||
if self.is_running():
|
if self.is_running():
|
||||||
# find loop device
|
|
||||||
p = subprocess.Popen (["sudo", "losetup", "--associated", self.private_img],
|
|
||||||
stdout=subprocess.PIPE)
|
|
||||||
result = p.communicate()
|
|
||||||
m = re.match(r"^(/dev/loop\d+):\s", result[0])
|
|
||||||
if m is None:
|
|
||||||
raise QubesException("ERROR: Cannot find loop device!")
|
|
||||||
|
|
||||||
loop_dev = m.group(1)
|
|
||||||
|
|
||||||
# resize loop device
|
|
||||||
subprocess.check_call(["sudo", "losetup", "--set-capacity", loop_dev])
|
|
||||||
|
|
||||||
retcode = self.run("while [ \"`blockdev --getsize64 /dev/xvdb`\" -lt {0} ]; do ".format(size) +
|
retcode = self.run("while [ \"`blockdev --getsize64 /dev/xvdb`\" -lt {0} ]; do ".format(size) +
|
||||||
"head /dev/xvdb > /dev/null; sleep 0.2; done; resize2fs /dev/xvdb", user="root", wait=True)
|
"head /dev/xvdb > /dev/null; sleep 0.2; done; resize2fs /dev/xvdb", user="root", wait=True)
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
@ -983,23 +973,6 @@ class QubesVm(object):
|
|||||||
for hook in self.hooks_create_xenstore_entries:
|
for hook in self.hooks_create_xenstore_entries:
|
||||||
hook(self, xid=xid)
|
hook(self, xid=xid)
|
||||||
|
|
||||||
def _format_disk_dev(self, path, script, vdev, rw=True, type="disk", domain=None):
|
|
||||||
template = " <disk type='block' device='{type}'>\n" \
|
|
||||||
" <driver name='phy'/>\n" \
|
|
||||||
" <source dev='{path}'/>\n" \
|
|
||||||
" <target dev='{vdev}' bus='xen'/>\n" \
|
|
||||||
"{params}" \
|
|
||||||
" </disk>\n"
|
|
||||||
params = ""
|
|
||||||
if not rw:
|
|
||||||
params += " <readonly/>\n"
|
|
||||||
if domain:
|
|
||||||
params += " <domain name='%s'/>\n" % domain
|
|
||||||
if script:
|
|
||||||
params += " <script path='%s'/>\n" % script
|
|
||||||
return template.format(path=path, vdev=vdev, type=type,
|
|
||||||
params=params)
|
|
||||||
|
|
||||||
def _format_net_dev(self, ip, mac, backend):
|
def _format_net_dev(self, ip, mac, backend):
|
||||||
template = " <interface type='ethernet'>\n" \
|
template = " <interface type='ethernet'>\n" \
|
||||||
" <mac address='{mac}'/>\n" \
|
" <mac address='{mac}'/>\n" \
|
||||||
@ -1023,17 +996,6 @@ class QubesVm(object):
|
|||||||
slot=dev_match.group(2),
|
slot=dev_match.group(2),
|
||||||
fun=dev_match.group(3))
|
fun=dev_match.group(3))
|
||||||
|
|
||||||
def get_rootdev(self):
|
|
||||||
if self.template:
|
|
||||||
return self._format_disk_dev(
|
|
||||||
"{dir}/root.img:{dir}/root-cow.img".format(
|
|
||||||
dir=self.template.dir_path),
|
|
||||||
"block-snapshot", "xvda", False)
|
|
||||||
else:
|
|
||||||
return self._format_disk_dev(
|
|
||||||
"{dir}/root.img".format(dir=self.dir_path),
|
|
||||||
None, "xvda", True)
|
|
||||||
|
|
||||||
def get_config_params(self):
|
def get_config_params(self):
|
||||||
args = {}
|
args = {}
|
||||||
args['name'] = self.name
|
args['name'] = self.name
|
||||||
@ -1070,17 +1032,7 @@ class QubesVm(object):
|
|||||||
args['netdev'] = ''
|
args['netdev'] = ''
|
||||||
args['disable_network1'] = '<!--';
|
args['disable_network1'] = '<!--';
|
||||||
args['disable_network2'] = '-->';
|
args['disable_network2'] = '-->';
|
||||||
args['rootdev'] = self.get_rootdev()
|
args.update(self.storage.get_config_params())
|
||||||
args['privatedev'] = \
|
|
||||||
self._format_disk_dev("{dir}/private.img".format(dir=self.dir_path),
|
|
||||||
None, "xvdb", True)
|
|
||||||
args['volatiledev'] = \
|
|
||||||
self._format_disk_dev("{dir}/volatile.img".format(dir=self.dir_path),
|
|
||||||
None, "xvdc", True)
|
|
||||||
if hasattr(self, 'kernel'):
|
|
||||||
args['otherdevs'] = \
|
|
||||||
self._format_disk_dev("{dir}/modules.img".format(dir=self.kernels_dir),
|
|
||||||
None, "xvdd", self.kernel is None)
|
|
||||||
if hasattr(self, 'kernelopts'):
|
if hasattr(self, 'kernelopts'):
|
||||||
args['kernelopts'] = self.kernelopts
|
args['kernelopts'] = self.kernelopts
|
||||||
if self.debug:
|
if self.debug:
|
||||||
@ -1139,37 +1091,9 @@ class QubesVm(object):
|
|||||||
if dry_run:
|
if dry_run:
|
||||||
return
|
return
|
||||||
|
|
||||||
old_umask = os.umask(002)
|
self.storage.create_on_disk(verbose, source_template)
|
||||||
if verbose:
|
|
||||||
print >> sys.stderr, "--> Creating directory: {0}".format(self.dir_path)
|
|
||||||
os.mkdir (self.dir_path)
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print >> sys.stderr, "--> Creating the VM config file: {0}".format(self.conf_file)
|
|
||||||
|
|
||||||
template_priv = source_template.private_img
|
|
||||||
if verbose:
|
|
||||||
print >> sys.stderr, "--> Copying the template's private image: {0}".\
|
|
||||||
format(template_priv)
|
|
||||||
|
|
||||||
# We prefer to use Linux's cp, because it nicely handles sparse files
|
|
||||||
retcode = subprocess.call (["cp", template_priv, self.private_img])
|
|
||||||
if retcode != 0:
|
|
||||||
raise IOError ("Error while copying {0} to {1}".\
|
|
||||||
format(template_priv, self.private_img))
|
|
||||||
|
|
||||||
if self.updateable:
|
if self.updateable:
|
||||||
template_root = source_template.root_img
|
|
||||||
if verbose:
|
|
||||||
print >> sys.stderr, "--> Copying the template's root image: {0}".\
|
|
||||||
format(template_root)
|
|
||||||
|
|
||||||
# We prefer to use Linux's cp, because it nicely handles sparse files
|
|
||||||
retcode = subprocess.call (["cp", template_root, self.root_img])
|
|
||||||
if retcode != 0:
|
|
||||||
raise IOError ("Error while copying {0} to {1}".\
|
|
||||||
format(template_root, self.root_img))
|
|
||||||
|
|
||||||
kernels_dir = source_template.kernels_dir
|
kernels_dir = source_template.kernels_dir
|
||||||
if verbose:
|
if verbose:
|
||||||
print >> sys.stderr, "--> Copying the kernel (set kernel \"none\" to use it): {0}".\
|
print >> sys.stderr, "--> Copying the kernel (set kernel \"none\" to use it): {0}".\
|
||||||
@ -1180,15 +1104,10 @@ class QubesVm(object):
|
|||||||
shutil.copy(os.path.join(kernels_dir, f),
|
shutil.copy(os.path.join(kernels_dir, f),
|
||||||
os.path.join(self.dir_path, vm_files["kernels_subdir"], f))
|
os.path.join(self.dir_path, vm_files["kernels_subdir"], f))
|
||||||
|
|
||||||
# Create volatile.img
|
|
||||||
self.reset_volatile_storage(source_template = source_template, verbose=verbose)
|
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
print >> sys.stderr, "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, self.label.icon_path)
|
print >> sys.stderr, "--> Creating icon symlink: {0} -> {1}".format(self.icon_path, self.label.icon_path)
|
||||||
os.symlink (self.label.icon_path, self.icon_path)
|
os.symlink (self.label.icon_path, self.icon_path)
|
||||||
|
|
||||||
os.umask(old_umask)
|
|
||||||
|
|
||||||
# fire hooks
|
# fire hooks
|
||||||
for hook in self.hooks_create_on_disk:
|
for hook in self.hooks_create_on_disk:
|
||||||
hook(self, verbose, source_template=source_template)
|
hook(self, verbose, source_template=source_template)
|
||||||
@ -1224,29 +1143,7 @@ class QubesVm(object):
|
|||||||
if src_vm.is_running():
|
if src_vm.is_running():
|
||||||
raise QubesException("Attempt to clone a running VM!")
|
raise QubesException("Attempt to clone a running VM!")
|
||||||
|
|
||||||
if verbose:
|
self.storage.clone_disk_files(src_vm, verbose)
|
||||||
print >> sys.stderr, "--> Creating directory: {0}".format(self.dir_path)
|
|
||||||
os.mkdir (self.dir_path)
|
|
||||||
|
|
||||||
if src_vm.private_img is not None and self.private_img is not None:
|
|
||||||
if verbose:
|
|
||||||
print >> sys.stderr, "--> Copying the private image:\n{0} ==>\n{1}".\
|
|
||||||
format(src_vm.private_img, self.private_img)
|
|
||||||
# We prefer to use Linux's cp, because it nicely handles sparse files
|
|
||||||
retcode = subprocess.call (["cp", src_vm.private_img, self.private_img])
|
|
||||||
if retcode != 0:
|
|
||||||
raise IOError ("Error while copying {0} to {1}".\
|
|
||||||
format(src_vm.private_img, self.private_img))
|
|
||||||
|
|
||||||
if src_vm.updateable and src_vm.root_img is not None and self.root_img is not None:
|
|
||||||
if verbose:
|
|
||||||
print >> sys.stderr, "--> Copying the root image:\n{0} ==>\n{1}".\
|
|
||||||
format(src_vm.root_img, self.root_img)
|
|
||||||
# We prefer to use Linux's cp, because it nicely handles sparse files
|
|
||||||
retcode = subprocess.call (["cp", src_vm.root_img, self.root_img])
|
|
||||||
if retcode != 0:
|
|
||||||
raise IOError ("Error while copying {0} to {1}".\
|
|
||||||
format(src_vm.root_img, self.root_img))
|
|
||||||
|
|
||||||
if src_vm.icon_path is not None and self.icon_path is not None:
|
if src_vm.icon_path is not None and self.icon_path is not None:
|
||||||
if os.path.exists (src_vm.dir_path):
|
if os.path.exists (src_vm.dir_path):
|
||||||
@ -1268,20 +1165,7 @@ class QubesVm(object):
|
|||||||
if dry_run:
|
if dry_run:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not os.path.exists (self.dir_path):
|
self.storage.verify_files()
|
||||||
raise QubesException (
|
|
||||||
"VM directory doesn't exist: {0}".\
|
|
||||||
format(self.dir_path))
|
|
||||||
|
|
||||||
if self.updateable and not os.path.exists (self.root_img):
|
|
||||||
raise QubesException (
|
|
||||||
"VM root image file doesn't exist: {0}".\
|
|
||||||
format(self.root_img))
|
|
||||||
|
|
||||||
if not os.path.exists (self.private_img):
|
|
||||||
raise QubesException (
|
|
||||||
"VM private image file doesn't exist: {0}".\
|
|
||||||
format(self.private_img))
|
|
||||||
|
|
||||||
if not os.path.exists (os.path.join(self.kernels_dir, 'vmlinuz')):
|
if not os.path.exists (os.path.join(self.kernels_dir, 'vmlinuz')):
|
||||||
raise QubesException (
|
raise QubesException (
|
||||||
@ -1293,49 +1177,12 @@ class QubesVm(object):
|
|||||||
"VM initramfs does not exists: {0}".\
|
"VM initramfs does not exists: {0}".\
|
||||||
format(os.path.join(self.kernels_dir, 'initramfs')))
|
format(os.path.join(self.kernels_dir, 'initramfs')))
|
||||||
|
|
||||||
if not os.path.exists (os.path.join(self.kernels_dir, 'modules.img')):
|
|
||||||
raise QubesException (
|
|
||||||
"VM kernel modules image does not exists: {0}".\
|
|
||||||
format(os.path.join(self.kernels_dir, 'modules.img')))
|
|
||||||
|
|
||||||
# fire hooks
|
# fire hooks
|
||||||
for hook in self.hooks_verify_files:
|
for hook in self.hooks_verify_files:
|
||||||
hook(self)
|
hook(self)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def reset_volatile_storage(self, source_template = None, verbose = False):
|
|
||||||
assert not self.is_running(), "Attempt to clean volatile image of running VM!"
|
|
||||||
|
|
||||||
if source_template is None:
|
|
||||||
source_template = self.template
|
|
||||||
|
|
||||||
# Only makes sense on template based VM
|
|
||||||
if source_template is None:
|
|
||||||
# For StandaloneVM create it only if not already exists (eg after backup-restore)
|
|
||||||
if not os.path.exists(self.volatile_img):
|
|
||||||
if verbose:
|
|
||||||
print >> sys.stderr, "--> Creating volatile image: {0}...".format (self.volatile_img)
|
|
||||||
f_root = open (self.root_img, "r")
|
|
||||||
f_root.seek(0, os.SEEK_END)
|
|
||||||
root_size = f_root.tell()
|
|
||||||
f_root.close()
|
|
||||||
subprocess.check_call([system_path["prepare_volatile_img_cmd"], self.volatile_img, str(root_size / 1024 / 1024)])
|
|
||||||
return
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print >> sys.stderr, "--> Cleaning volatile image: {0}...".format (self.volatile_img)
|
|
||||||
if dry_run:
|
|
||||||
return
|
|
||||||
if os.path.exists (self.volatile_img):
|
|
||||||
os.remove (self.volatile_img)
|
|
||||||
|
|
||||||
if hasattr(source_template, 'clean_volatile_img'):
|
|
||||||
retcode = subprocess.call (["tar", "xf", source_template.clean_volatile_img, "-C", self.dir_path])
|
|
||||||
if retcode != 0:
|
|
||||||
raise IOError ("Error while unpacking {0} to {1}".\
|
|
||||||
format(source_template.clean_volatile_img, self.volatile_img))
|
|
||||||
|
|
||||||
def remove_from_disk(self):
|
def remove_from_disk(self):
|
||||||
if dry_run:
|
if dry_run:
|
||||||
return
|
return
|
||||||
@ -1344,7 +1191,7 @@ class QubesVm(object):
|
|||||||
for hook in self.hooks_remove_from_disk:
|
for hook in self.hooks_remove_from_disk:
|
||||||
hook(self)
|
hook(self)
|
||||||
|
|
||||||
shutil.rmtree (self.dir_path)
|
self.storage.remove_from_disk()
|
||||||
|
|
||||||
def write_firewall_conf(self, conf):
|
def write_firewall_conf(self, conf):
|
||||||
defaults = self.get_firewall_conf()
|
defaults = self.get_firewall_conf()
|
||||||
@ -1727,7 +1574,7 @@ class QubesVm(object):
|
|||||||
print >> sys.stderr, "--> Starting NetVM {0}...".format(self.netvm.name)
|
print >> sys.stderr, "--> Starting NetVM {0}...".format(self.netvm.name)
|
||||||
self.netvm.start(verbose = verbose, start_guid = start_guid, notify_function = notify_function)
|
self.netvm.start(verbose = verbose, start_guid = start_guid, notify_function = notify_function)
|
||||||
|
|
||||||
self.reset_volatile_storage(verbose=verbose)
|
self.storage.prepare_for_vm_startup(verbose=verbose)
|
||||||
if verbose:
|
if verbose:
|
||||||
print >> sys.stderr, "--> Loading the VM (type = {0})...".format(self.type)
|
print >> sys.stderr, "--> Loading the VM (type = {0})...".format(self.type)
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ class QubesTemplateVm(QubesVm):
|
|||||||
attrs_config['rootcow_img'] = {
|
attrs_config['rootcow_img'] = {
|
||||||
'func': lambda x: os.path.join(self.dir_path, vm_files["rootcow_img"]) }
|
'func': lambda x: os.path.join(self.dir_path, vm_files["rootcow_img"]) }
|
||||||
# Clean image for root-cow and swap (AppVM side)
|
# Clean image for root-cow and swap (AppVM side)
|
||||||
|
# TODO: not used anymore - clean up when all references removed
|
||||||
attrs_config['clean_volatile_img'] = {
|
attrs_config['clean_volatile_img'] = {
|
||||||
'func': lambda x: os.path.join(self.dir_path, vm_files["clean_volatile_img"]) }
|
'func': lambda x: os.path.join(self.dir_path, vm_files["clean_volatile_img"]) }
|
||||||
|
|
||||||
@ -76,35 +77,12 @@ class QubesTemplateVm(QubesVm):
|
|||||||
def get_firewall_defaults(self):
|
def get_firewall_defaults(self):
|
||||||
return { "rules": list(), "allow": False, "allowDns": False, "allowIcmp": False, "allowYumProxy": True }
|
return { "rules": list(), "allow": False, "allowDns": False, "allowIcmp": False, "allowYumProxy": True }
|
||||||
|
|
||||||
def get_rootdev(self):
|
|
||||||
return self._format_disk_dev(
|
|
||||||
"{dir}/root.img:{dir}/root-cow.img".format(
|
|
||||||
dir=self.dir_path),
|
|
||||||
"block-origin", "xvda", True)
|
|
||||||
|
|
||||||
def clone_disk_files(self, src_vm, verbose):
|
def clone_disk_files(self, src_vm, verbose):
|
||||||
if dry_run:
|
if dry_run:
|
||||||
return
|
return
|
||||||
|
|
||||||
super(QubesTemplateVm, self).clone_disk_files(src_vm=src_vm, verbose=verbose)
|
super(QubesTemplateVm, self).clone_disk_files(src_vm=src_vm, verbose=verbose)
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print >> sys.stderr, "--> Copying the template's clean volatile image:\n{0} ==>\n{1}".\
|
|
||||||
format(src_vm.clean_volatile_img, self.clean_volatile_img)
|
|
||||||
# We prefer to use Linux's cp, because it nicely handles sparse files
|
|
||||||
retcode = subprocess.call (["cp", src_vm.clean_volatile_img, self.clean_volatile_img])
|
|
||||||
if retcode != 0:
|
|
||||||
raise IOError ("Error while copying {0} to {1}".\
|
|
||||||
format(src_vm.clean_volatile_img, self.clean_volatile_img))
|
|
||||||
if verbose:
|
|
||||||
print >> sys.stderr, "--> Copying the template's volatile image:\n{0} ==>\n{1}".\
|
|
||||||
format(self.clean_volatile_img, self.volatile_img)
|
|
||||||
# We prefer to use Linux's cp, because it nicely handles sparse files
|
|
||||||
retcode = subprocess.call (["cp", self.clean_volatile_img, self.volatile_img])
|
|
||||||
if retcode != 0:
|
|
||||||
raise IOError ("Error while copying {0} to {1}".\
|
|
||||||
format(self.clean_img, self.volatile_img))
|
|
||||||
|
|
||||||
# Create root-cow.img
|
# Create root-cow.img
|
||||||
self.commit_changes(verbose=verbose)
|
self.commit_changes(verbose=verbose)
|
||||||
|
|
||||||
@ -112,42 +90,10 @@ class QubesTemplateVm(QubesVm):
|
|||||||
super(QubesTemplateVm, self).post_rename(old_name)
|
super(QubesTemplateVm, self).post_rename(old_name)
|
||||||
|
|
||||||
old_dirpath = os.path.join(os.path.dirname(self.dir_path), old_name)
|
old_dirpath = os.path.join(os.path.dirname(self.dir_path), old_name)
|
||||||
|
# TODO: clean_volatile_img not used anymore
|
||||||
self.clean_volatile_img = self.clean_volatile_img.replace(old_dirpath, self.dir_path)
|
self.clean_volatile_img = self.clean_volatile_img.replace(old_dirpath, self.dir_path)
|
||||||
self.rootcow_img = self.rootcow_img.replace(old_dirpath, self.dir_path)
|
self.rootcow_img = self.rootcow_img.replace(old_dirpath, self.dir_path)
|
||||||
|
|
||||||
def verify_files(self):
|
|
||||||
if dry_run:
|
|
||||||
return
|
|
||||||
|
|
||||||
super(QubesTemplateVm, self).verify_files()
|
|
||||||
|
|
||||||
if not os.path.exists (self.volatile_img):
|
|
||||||
raise QubesException (
|
|
||||||
"VM volatile image file doesn't exist: {0}".\
|
|
||||||
format(self.volatile_img))
|
|
||||||
|
|
||||||
if not os.path.exists (self.clean_volatile_img):
|
|
||||||
raise QubesException (
|
|
||||||
"Clean VM volatile image file doesn't exist: {0}".\
|
|
||||||
format(self.clean_volatile_img))
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def reset_volatile_storage(self, verbose = False):
|
|
||||||
assert not self.is_running(), "Attempt to clean volatile image of running Template VM!"
|
|
||||||
|
|
||||||
if verbose:
|
|
||||||
print >> sys.stderr, "--> Cleaning volatile image: {0}...".format (self.volatile_img)
|
|
||||||
if dry_run:
|
|
||||||
return
|
|
||||||
if os.path.exists (self.volatile_img):
|
|
||||||
os.remove (self.volatile_img)
|
|
||||||
|
|
||||||
retcode = subprocess.call (["tar", "xf", self.clean_volatile_img, "-C", self.dir_path])
|
|
||||||
if retcode != 0:
|
|
||||||
raise IOError ("Error while unpacking {0} to {1}".\
|
|
||||||
format(self.template.clean_volatile_img, self.volatile_img))
|
|
||||||
|
|
||||||
def commit_changes (self, verbose = False):
|
def commit_changes (self, verbose = False):
|
||||||
|
|
||||||
if not vmm.offline_mode:
|
if not vmm.offline_mode:
|
||||||
@ -158,16 +104,7 @@ class QubesTemplateVm(QubesVm):
|
|||||||
|
|
||||||
if dry_run:
|
if dry_run:
|
||||||
return
|
return
|
||||||
if os.path.exists (self.rootcow_img):
|
|
||||||
os.rename (self.rootcow_img, self.rootcow_img + '.old')
|
|
||||||
|
|
||||||
old_umask = os.umask(002)
|
self.storage.commit_template_changes()
|
||||||
f_cow = open (self.rootcow_img, "w")
|
|
||||||
f_root = open (self.root_img, "r")
|
|
||||||
f_root.seek(0, os.SEEK_END)
|
|
||||||
f_cow.truncate (f_root.tell()) # make empty sparse file of the same size as root.img
|
|
||||||
f_cow.close ()
|
|
||||||
f_root.close()
|
|
||||||
os.umask(old_umask)
|
|
||||||
|
|
||||||
register_qubes_vm_class(QubesTemplateVm)
|
register_qubes_vm_class(QubesTemplateVm)
|
||||||
|
@ -6,6 +6,7 @@ SETTINGS_SUFFIX = $(BACKEND_VMM)-$(OS)
|
|||||||
all:
|
all:
|
||||||
python -m compileall .
|
python -m compileall .
|
||||||
python -O -m compileall .
|
python -O -m compileall .
|
||||||
|
make -C storage all
|
||||||
|
|
||||||
install:
|
install:
|
||||||
ifndef PYTHON_SITEPATH
|
ifndef PYTHON_SITEPATH
|
||||||
@ -30,3 +31,4 @@ ifneq ($(BACKEND_VMM),)
|
|||||||
test -r settings-$(SETTINGS_SUFFIX).pyo && \
|
test -r settings-$(SETTINGS_SUFFIX).pyo && \
|
||||||
cp settings-$(SETTINGS_SUFFIX).pyo $(DESTDIR)$(PYTHON_QUBESPATH)/settings.pyo
|
cp settings-$(SETTINGS_SUFFIX).pyo $(DESTDIR)$(PYTHON_QUBESPATH)/settings.pyo
|
||||||
endif
|
endif
|
||||||
|
make -C storage install
|
||||||
|
@ -94,6 +94,11 @@ defaults = {
|
|||||||
|
|
||||||
'dom0_update_check_interval': 6*3600,
|
'dom0_update_check_interval': 6*3600,
|
||||||
|
|
||||||
|
'private_img_size': 2*1024*1024*1024,
|
||||||
|
'root_img_size': 10*1024*1024*1024,
|
||||||
|
|
||||||
|
'storage_class': None,
|
||||||
|
|
||||||
# how long (in sec) to wait for VMs to shutdown,
|
# how long (in sec) to wait for VMs to shutdown,
|
||||||
# before killing them (when used qvm-run with --wait option),
|
# before killing them (when used qvm-run with --wait option),
|
||||||
'shutdown_counter_max': 60,
|
'shutdown_counter_max': 60,
|
||||||
|
21
core/storage/Makefile
Normal file
21
core/storage/Makefile
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
OS ?= Linux
|
||||||
|
|
||||||
|
PYTHON_QUBESPATH = $(PYTHON_SITEPATH)/qubes
|
||||||
|
|
||||||
|
all:
|
||||||
|
python -m compileall .
|
||||||
|
python -O -m compileall .
|
||||||
|
|
||||||
|
install:
|
||||||
|
ifndef PYTHON_SITEPATH
|
||||||
|
$(error PYTHON_SITEPATH not defined)
|
||||||
|
endif
|
||||||
|
mkdir -p $(DESTDIR)$(PYTHON_QUBESPATH)/storage
|
||||||
|
cp __init__.py $(DESTDIR)$(PYTHON_QUBESPATH)/storage
|
||||||
|
cp __init__.py[co] $(DESTDIR)$(PYTHON_QUBESPATH)/storage
|
||||||
|
ifneq ($(BACKEND_VMM),)
|
||||||
|
if [ -r $(BACKEND_VMM).py ]; then \
|
||||||
|
cp $(BACKEND_VMM).py $(DESTDIR)$(PYTHON_QUBESPATH)/storage && \
|
||||||
|
cp $(BACKEND_VMM).py[co] $(DESTDIR)$(PYTHON_QUBESPATH)/storage; \
|
||||||
|
fi
|
||||||
|
endif
|
182
core/storage/__init__.py
Normal file
182
core/storage/__init__.py
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
#!/usr/bin/python2
|
||||||
|
#
|
||||||
|
# The Qubes OS Project, http://www.qubes-os.org
|
||||||
|
#
|
||||||
|
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from qubes.qubes import vm_files,system_path,defaults
|
||||||
|
from qubes.qubes import QubesException
|
||||||
|
import qubes.qubesutils
|
||||||
|
|
||||||
|
class QubesVmStorage(object):
|
||||||
|
"""
|
||||||
|
Class for handling VM virtual disks. This is base class for all other
|
||||||
|
implementations, mostly with Xen on Linux in mind.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, vm,
|
||||||
|
private_img_size = None,
|
||||||
|
root_img_size = None,
|
||||||
|
modules_img = None,
|
||||||
|
modules_img_rw = False):
|
||||||
|
self.vm = vm
|
||||||
|
self.vmdir = vm.dir_path
|
||||||
|
if private_img_size:
|
||||||
|
self.private_img_size = private_img_size
|
||||||
|
else:
|
||||||
|
self.private_img_size = defaults['private_img_size']
|
||||||
|
if root_img_size:
|
||||||
|
self.root_img_size = root_img_size
|
||||||
|
else:
|
||||||
|
self.root_img_size = defaults['root_img_size']
|
||||||
|
|
||||||
|
self.private_img = os.path.join(self.vmdir, vm_files["private_img"])
|
||||||
|
if self.vm.template:
|
||||||
|
self.root_img = self.vm.template.root_img
|
||||||
|
else:
|
||||||
|
self.root_img = os.path.join(self.vmdir, vm_files["root_img"])
|
||||||
|
self.volatile_img = os.path.join(self.vmdir, vm_files["volatile_img"])
|
||||||
|
|
||||||
|
# For now compute this path still in QubesVm
|
||||||
|
self.modules_img = modules_img
|
||||||
|
self.modules_img_rw = modules_img_rw
|
||||||
|
|
||||||
|
def get_config_params(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _copy_file(self, source, destination):
|
||||||
|
"""
|
||||||
|
Effective file copy, preserving sparse files etc.
|
||||||
|
"""
|
||||||
|
# TODO: Windows support
|
||||||
|
|
||||||
|
# We prefer to use Linux's cp, because it nicely handles sparse files
|
||||||
|
retcode = subprocess.call (["cp", source, destination])
|
||||||
|
if retcode != 0:
|
||||||
|
raise IOError ("Error while copying {0} to {1}".\
|
||||||
|
format(source, destination))
|
||||||
|
|
||||||
|
def get_disk_utilization(self):
|
||||||
|
return qubes.qubesutils.get_disk_usage(self.vmdir)
|
||||||
|
|
||||||
|
def get_disk_utilization_private_img(self):
|
||||||
|
return qubes.qubesutils.get_disk_usage(self.private_img)
|
||||||
|
|
||||||
|
def get_private_img_sz(self):
|
||||||
|
if not os.path.exists(self.private_img):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
return os.path.getsize(self.private_img)
|
||||||
|
|
||||||
|
def resize_private_img(self, size):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def create_on_disk_private_img(self, verbose, source_template = None):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def create_on_disk_root_img(self, verbose, source_template = None):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def create_on_disk(self, verbose, source_template = None):
|
||||||
|
if source_template is None:
|
||||||
|
source_template = self.vm.template
|
||||||
|
|
||||||
|
old_umask = os.umask(002)
|
||||||
|
if verbose:
|
||||||
|
print >> sys.stderr, "--> Creating directory: {0}".format(self.vmdir)
|
||||||
|
os.mkdir (self.vmdir)
|
||||||
|
|
||||||
|
self.create_on_disk_private_img(verbose, source_template)
|
||||||
|
self.create_on_disk_root_img(verbose, source_template)
|
||||||
|
self.reset_volatile_storage(verbose, source_template)
|
||||||
|
|
||||||
|
os.umask(old_umask)
|
||||||
|
|
||||||
|
def clone_disk_files(self, src_vm, verbose):
|
||||||
|
if verbose:
|
||||||
|
print >> sys.stderr, "--> Creating directory: {0}".format(self.vmdir)
|
||||||
|
os.mkdir (self.vmdir)
|
||||||
|
|
||||||
|
if src_vm.private_img is not None and self.private_img is not None:
|
||||||
|
if verbose:
|
||||||
|
print >> sys.stderr, "--> Copying the private image:\n{0} ==>\n{1}".\
|
||||||
|
format(src_vm.private_img, self.private_img)
|
||||||
|
self._copy_file(src_vm.private_img, self.private_img)
|
||||||
|
|
||||||
|
if src_vm.updateable and src_vm.root_img is not None and self.root_img is not None:
|
||||||
|
if verbose:
|
||||||
|
print >> sys.stderr, "--> Copying the root image:\n{0} ==>\n{1}".\
|
||||||
|
format(src_vm.root_img, self.root_img)
|
||||||
|
self._copy_file(src_vm.root_img, self.root_img)
|
||||||
|
|
||||||
|
# TODO: modules?
|
||||||
|
|
||||||
|
def verify_files(self):
|
||||||
|
if not os.path.exists (self.vmdir):
|
||||||
|
raise QubesException (
|
||||||
|
"VM directory doesn't exist: {0}".\
|
||||||
|
format(self.vmdir))
|
||||||
|
|
||||||
|
if self.vm.updateable and not os.path.exists (self.root_img):
|
||||||
|
raise QubesException (
|
||||||
|
"VM root image file doesn't exist: {0}".\
|
||||||
|
format(self.root_img))
|
||||||
|
|
||||||
|
if not os.path.exists (self.private_img):
|
||||||
|
raise QubesException (
|
||||||
|
"VM private image file doesn't exist: {0}".\
|
||||||
|
format(self.private_img))
|
||||||
|
if self.modules_img is not None:
|
||||||
|
if not os.path.exists(self.modules_img):
|
||||||
|
raise QubesException (
|
||||||
|
"VM kernel modules image does not exists: {0}".\
|
||||||
|
format(self.modules_img))
|
||||||
|
|
||||||
|
def remove_from_disk(self):
|
||||||
|
shutil.rmtree (self.vmdir)
|
||||||
|
|
||||||
|
def reset_volatile_storage(self, verbose = False, source_template = None):
|
||||||
|
if source_template is None:
|
||||||
|
source_template = self.vm.template
|
||||||
|
|
||||||
|
# Re-create only for template based VMs
|
||||||
|
if source_template is not None:
|
||||||
|
if os.path.exists(self.volatile_img):
|
||||||
|
os.remove(self.volatile_img)
|
||||||
|
|
||||||
|
# For StandaloneVM create it only if not already exists (eg after backup-restore)
|
||||||
|
if not os.path.exists(self.volatile_img):
|
||||||
|
if verbose:
|
||||||
|
print >> sys.stderr, "--> Creating volatile image: {0}...".\
|
||||||
|
format(self.volatile_img)
|
||||||
|
subprocess.check_call([system_path["prepare_volatile_img_cmd"],
|
||||||
|
self.volatile_img, str(self.root_img_size / 1024 / 1024)])
|
||||||
|
|
||||||
|
def prepare_for_vm_startup(self, verbose):
|
||||||
|
self.reset_volatile_storage(verbose=verbose)
|
||||||
|
pass
|
154
core/storage/xen.py
Normal file
154
core/storage/xen.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
#!/usr/bin/python2
|
||||||
|
#
|
||||||
|
# The Qubes OS Project, http://www.qubes-os.org
|
||||||
|
#
|
||||||
|
# Copyright (C) 2013 Marek Marczykowski <marmarek@invisiblethingslab.com>
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or
|
||||||
|
# modify it under the terms of the GNU General Public License
|
||||||
|
# as published by the Free Software Foundation; either version 2
|
||||||
|
# of the License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from qubes.storage import QubesVmStorage
|
||||||
|
from qubes.qubes import QubesException
|
||||||
|
|
||||||
|
class QubesXenVmStorage(QubesVmStorage):
|
||||||
|
"""
|
||||||
|
Class for VM storage of Xen VMs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, vm, **kwargs):
|
||||||
|
super(QubesXenVmStorage, self).__init__(vm, **kwargs)
|
||||||
|
|
||||||
|
self.root_dev = "xvda"
|
||||||
|
self.private_dev = "xvdb"
|
||||||
|
self.volatile_dev = "xvdc"
|
||||||
|
self.modules_dev = "xvdd"
|
||||||
|
|
||||||
|
def _format_disk_dev(self, path, script, vdev, rw=True, type="disk", domain=None):
|
||||||
|
template = " <disk type='block' device='{type}'>\n" \
|
||||||
|
" <driver name='phy'/>\n" \
|
||||||
|
" <source dev='{path}'/>\n" \
|
||||||
|
" <target dev='{vdev}' bus='xen'/>\n" \
|
||||||
|
"{params}" \
|
||||||
|
" </disk>\n"
|
||||||
|
params = ""
|
||||||
|
if not rw:
|
||||||
|
params += " <readonly/>\n"
|
||||||
|
if domain:
|
||||||
|
params += " <domain name='%s'/>\n" % domain
|
||||||
|
if script:
|
||||||
|
params += " <script path='%s'/>\n" % script
|
||||||
|
return template.format(path=path, vdev=vdev, type=type,
|
||||||
|
params=params)
|
||||||
|
|
||||||
|
def _get_rootdev(self):
|
||||||
|
if self.vm.is_template():
|
||||||
|
return self._format_disk_dev(
|
||||||
|
"{dir}/root.img:{dir}/root-cow.img".format(
|
||||||
|
dir=self.vmdir),
|
||||||
|
"block-origin", self.root_dev, True)
|
||||||
|
elif self.vm.template:
|
||||||
|
return self._format_disk_dev(
|
||||||
|
"{dir}/root.img:{dir}/root-cow.img".format(
|
||||||
|
dir=self.vm.template.vmdir),
|
||||||
|
"block-snapshot", self.root_dev, False)
|
||||||
|
else:
|
||||||
|
return self._format_disk_dev(
|
||||||
|
"{dir}/root.img".format(dir=self.vmdir),
|
||||||
|
None, self.root_dev, True)
|
||||||
|
|
||||||
|
def get_config_params(self):
|
||||||
|
args = {}
|
||||||
|
args['rootdev'] = self._get_rootdev()
|
||||||
|
args['privatedev'] = \
|
||||||
|
self._format_disk_dev(self.private_img,
|
||||||
|
None, self.private_dev, True)
|
||||||
|
args['volatiledev'] = \
|
||||||
|
self._format_disk_dev(self.volatile_img,
|
||||||
|
None, self.volatile_dev, True)
|
||||||
|
if self.modules_img is not None:
|
||||||
|
args['otherdevs'] = \
|
||||||
|
self._format_disk_dev(self.modules_img,
|
||||||
|
None, self.modules_dev, self.modules_img_rw)
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
def create_on_disk_private_img(self, verbose, source_template = None):
|
||||||
|
if source_template:
|
||||||
|
template_priv = source_template.private_img
|
||||||
|
if verbose:
|
||||||
|
print >> sys.stderr, "--> Copying the template's private image: {0}".\
|
||||||
|
format(template_priv)
|
||||||
|
self._copy_file(template_priv, self.private_img)
|
||||||
|
else:
|
||||||
|
f_private = open (self.private_img, "a+b")
|
||||||
|
f_private.truncate (self.private_img_size)
|
||||||
|
f_private.close ()
|
||||||
|
|
||||||
|
def create_on_disk_root_img(self, verbose, source_template = None):
|
||||||
|
if source_template:
|
||||||
|
if not self.vm.updateable:
|
||||||
|
# just use template's disk
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
template_root = source_template.root_img
|
||||||
|
if verbose:
|
||||||
|
print >> sys.stderr, "--> Copying the template's root image: {0}".\
|
||||||
|
format(template_root)
|
||||||
|
|
||||||
|
self._copy_file(template_root, self.root_img)
|
||||||
|
else:
|
||||||
|
f_root = open (self.root_img, "a+b")
|
||||||
|
f_root.truncate (self.root_img_size)
|
||||||
|
f_root.close ()
|
||||||
|
|
||||||
|
def resize_private_img(self, size):
|
||||||
|
f_private = open (self.private_img, "a+b")
|
||||||
|
f_private.truncate (size)
|
||||||
|
f_private.close ()
|
||||||
|
|
||||||
|
# find loop device if any
|
||||||
|
p = subprocess.Popen (["sudo", "losetup", "--associated", self.private_img],
|
||||||
|
stdout=subprocess.PIPE)
|
||||||
|
result = p.communicate()
|
||||||
|
m = re.match(r"^(/dev/loop\d+):\s", result[0])
|
||||||
|
if m is not None:
|
||||||
|
loop_dev = m.group(1)
|
||||||
|
|
||||||
|
# resize loop device
|
||||||
|
subprocess.check_call(["sudo", "losetup", "--set-capacity", loop_dev])
|
||||||
|
|
||||||
|
def commit_template_changes(self):
|
||||||
|
assert self.vm.is_template()
|
||||||
|
# TODO: move rootcow_img to this class; the same for vm.is_outdated()
|
||||||
|
if os.path.exists (self.vm.rootcow_img):
|
||||||
|
os.rename (self.vm.rootcow_img, self.vm.rootcow_img + '.old')
|
||||||
|
|
||||||
|
old_umask = os.umask(002)
|
||||||
|
f_cow = open (self.vm.rootcow_img, "w")
|
||||||
|
f_root = open (self.root_img, "r")
|
||||||
|
f_root.seek(0, os.SEEK_END)
|
||||||
|
f_cow.truncate (f_root.tell()) # make empty sparse file of the same size as root.img
|
||||||
|
f_cow.close ()
|
||||||
|
f_root.close()
|
||||||
|
os.umask(old_umask)
|
||||||
|
|
@ -197,6 +197,9 @@ fi
|
|||||||
%{python_sitearch}/qubes/backup.py
|
%{python_sitearch}/qubes/backup.py
|
||||||
%{python_sitearch}/qubes/backup.pyc
|
%{python_sitearch}/qubes/backup.pyc
|
||||||
%{python_sitearch}/qubes/backup.pyo
|
%{python_sitearch}/qubes/backup.pyo
|
||||||
|
%{python_sitearch}/qubes/storage/*.py
|
||||||
|
%{python_sitearch}/qubes/storage/*.pyc
|
||||||
|
%{python_sitearch}/qubes/storage/*.pyo
|
||||||
%{python_sitearch}/qubes/qmemman*.py*
|
%{python_sitearch}/qubes/qmemman*.py*
|
||||||
%{python_sitearch}/qubes/modules/0*.py*
|
%{python_sitearch}/qubes/modules/0*.py*
|
||||||
%{python_sitearch}/qubes/modules/__init__.py*
|
%{python_sitearch}/qubes/modules/__init__.py*
|
||||||
|
Loading…
Reference in New Issue
Block a user