Commit bb7144d5 authored by Łukasz Nowak's avatar Łukasz Nowak

XXX kvm: Implement external-disk

Support absolute and relative paths. Paths starting with "rbd:" are special,
and they point to the rbd endpoint, served by Ceph.

cache parameter is optional, in case if present, will be used as-is, even with
empty cache.

XXX:
 * improve
 * support cluster
 * support resilient?
 * find a way to test the rbd: magic
 * provide backward compatibility
parent f278bf5c
Pipeline #21624 failed with stage
in 0 seconds
......@@ -19,11 +19,11 @@ md5sum = 7e90da1f6dac4233e1aa3248f48e357c
[template-kvm]
filename = instance-kvm.cfg.jinja2
md5sum = 9fa3ca00545e2c742f4472506b4e43dc
md5sum = a55550d975d6550c55077f4a2f41614d
[template-kvm-cluster]
filename = instance-kvm-cluster.cfg.jinja2.in
md5sum = 406bf455afe1c71cb0e8cc04dd877f1d
md5sum = c00608f94ff6cfd8d37a280267c7227f
[template-kvm-resilient]
filename = instance-kvm-resilient.cfg.jinja2
......@@ -55,7 +55,7 @@ md5sum = a8cf453d20f01c707f02c4b4014580d8
[template-kvm-run]
filename = template/template-kvm-run.in
md5sum = 6c100eec00de5e53f64b075dd69a9865
md5sum = ea5b8a588869f791b36c17345d016c62
[template-kvm-controller]
filename = template/kvm-controller-run.in
......
......@@ -545,7 +545,60 @@
"description": "List of whitelisted domain names to be accessed from the VM. They will be resolved to IPs depending on where the VM end up. IPs can be used too.",
"type": "string",
"textarea": true
},
"external-disk": {
"title": "External disks",
"description": "Allows to set paths and types of manually provided external disks. Use index to order disks. Changing this parameter leads to kvm process restart.",
"type": "object",
"default": {},
"patternProperties": {
".*": {
"properties": {
"path": {
"title": "Path to the provided image",
"description": "Absolute or relative to the instance root path to the provided image. Image has to be provided, and being fully accessible, read and write, by the instance user for the kvm to start. Please note, that if the name starts with 'rbd:' it's considered special and won't be used as relative path.",
"type": "string"
},
"format": {
"title": "Format of a disk image",
"description": "Format of a disk image.",
"type": "string",
"default": "autodetect",
"enum": [
"autodetect",
"qcow2",
"rbd",
"raw",
"vdi",
"vmdk",
"cloop",
"qed"
]
},
"cache": {
"title": "Cache value for the disk",
"description": "Cache value directly passed to the kvm -drive invocation as cache=value.",
"type": "string",
"default": "writeback",
"enum": [
"writeback",
"none",
"unsafe",
"directsync",
"writethrough"
]
},
"index": {
"title": "Index of a disk",
"description": "An index value used to order disks for the VM, if not specified, disk order will be random on each run.",
"type": "integer",
"default": 0
}
}
}
}
}
},
"type": "object"
}
......
......@@ -139,7 +139,7 @@ config-wipe-disk-iterations = {{ dumps(kvm_parameter_dict.get('wipe-disk-iterati
config-document-host = ${apache-conf:ip}
config-document-port = ${apache-conf:port}
config-document-path = ${hash-code:passwd}
{%- for k in ['boot-image-url-list', 'boot-image-url-select', 'whitelist-domains'] %}
{%- for k in ['boot-image-url-list', 'boot-image-url-select', 'whitelist-domains', 'external-disk'] %}
{#- play nice - use parameter only if present #}
{%- if k in kvm_parameter_dict %}
{#- play safe - dumps value #}
......
......@@ -402,6 +402,58 @@
"description": "List of whitelisted domain names to be accessed from the VM. They will be resolved to IPs depending on where the VM end up. IPs can be used too.",
"type": "string",
"textarea": true
},
"external-disk": {
"title": "External disks",
"description": "Allows to set paths and types of manually provided external disks. Use index to order disks. Changing this parameter leads to kvm process restart.",
"type": "object",
"default": {},
"patternProperties": {
".*": {
"properties": {
"path": {
"title": "Path to the provided image",
"description": "Absolute or relative to the instance root path to the provided image. Image has to be provided, and being fully accessible, read and write, by the instance user for the kvm to start. Please note, that if the name starts with 'rbd:' it's considered special and won't be used as relative path.",
"type": "string"
},
"format": {
"title": "Format of a disk image",
"description": "Format of a disk image.",
"type": "string",
"default": "autodetect",
"enum": [
"autodetect",
"qcow2",
"rbd",
"raw",
"vdi",
"vmdk",
"cloop",
"qed"
]
},
"cache": {
"title": "Cache value for the disk",
"description": "Cache value directly passed to the kvm -drive invocation as cache=value.",
"type": "string",
"default": "writeback",
"enum": [
"writeback",
"none",
"unsafe",
"directsync",
"writethrough"
]
},
"index": {
"title": "Index of a disk",
"description": "An index value used to order disks for the VM, if not specified, disk order will be random on each run.",
"type": "integer",
"default": 0
}
}
}
}
}
}
}
......@@ -471,6 +471,9 @@ disk-storage-list =
external-disk-number = ${slap-parameter:external-disk-number}
external-disk-size = ${slap-parameter:external-disk-size}
external-disk-format = ${slap-parameter:external-disk-format}
# new external-disk parameter
external-disk = ${slap-parameter:external-disk}
instance-root = ${buildout:directory}
{% if enable_http -%}
httpd-port = ${slap-parameter:httpd-port}
......@@ -1093,6 +1096,7 @@ hard-drive-url-check-certificate = True
external-disk-number = 0
external-disk-size = 20
external-disk-format = qcow2
external-disk = {}
# Help to get some configuration files into the vm from http
enable-http-server = False
......
......@@ -12,6 +12,7 @@ from random import shuffle
import glob
import re
import json
import operator
# XXX: give all of this through parameter, don't use this as template, but as module
qemu_img_path = {{ repr(parameter_dict["qemu-img-path"]) }}
......@@ -46,6 +47,8 @@ pid_file_path = '{{ parameter_dict.get("pid-file-path") }}'
external_disk_number = {{ parameter_dict.get("external-disk-number") }}
external_disk_size = {{ parameter_dict.get("external-disk-size") }}
external_disk_format = {{ repr(parameter_dict["external-disk-format"]) }}
external_disk = {{ parameter_dict['external-disk'] }}
instance_root = '{{ parameter_dict['instance-root'] }}'
disk_storage_dict = {}
disk_storage_list = """{{ parameter_dict.get("disk-storage-list") }}""".split('\n')
map_storage_list = []
......@@ -305,6 +308,31 @@ for disk in additional_disk_list:
kvm_argument_list.extend([
'-drive', 'file=%s,if=%s' % (disk, disk_type)])
# allow empty index if one disk is provided
for entry in external_disk.keys():
if 'index' not in external_disk[entry]:
external_disk[entry]['index'] = 0
# support external-disk parameter
for disk_info in sorted(external_disk.values(), key=operator.itemgetter('index')):
if disk_info['path'].startswith('rbd:') or disk_info['path'].startswith('/'):
path = disk_info['path']
else:
path = os.path.join(instance_root, disk_info['path'])
drive_argument_list = [
"file=%s" %( path,),
"if=%s" % (disk_type,),
"cache=%s" % (disk_info.get('cache', 'writeback'),)
]
if disk_info.get('format', 'autodetect') != 'autodetect':
drive_argument_list.append(
"format=%s" % (disk_info['format'],)
)
kvm_argument_list += (
'-drive',
",".join(drive_argument_list)
)
if auto_ballooning:
kvm_argument_list.extend(['-device', 'virtio-balloon-pci,id=balloon0'])
......
......@@ -1868,8 +1868,25 @@ class TestParameterCluster(TestParameterDefault):
return 'kvm-cluster'
class ExternalDiskMixin(KvmMixin):
def getRunningDriveList(self, kvm_instance_partition):
_match_drive = re.compile('file.*if=virtio.*').match
with self.slap.instance_supervisor_rpc as instance_supervisor:
kvm_pid = next(q for q in instance_supervisor.getAllProcessInfo()
if 'kvm-' in q['name'])['pid']
drive_list = []
for entry in psutil.Process(kvm_pid).cmdline():
m = _match_drive(entry)
if m:
path = m.group(0)
drive_list.append(
path.replace(kvm_instance_partition, '${partition}')
)
return drive_list
@skipUnlessKvm
class TestExternalDisk(InstanceTestCase, KvmMixin):
class TestExternalDisk(InstanceTestCase, ExternalDiskMixin):
__partition_reference__ = 'ed'
kvm_instance_partition_reference = 'ed0'
......@@ -1958,21 +1975,6 @@ class TestExternalDisk(InstanceTestCase, KvmMixin):
super(TestExternalDisk, cls).tearDownClass()
shutil.rmtree(cls.working_directory)
def getRunningDriveList(self, kvm_instance_partition):
_match_drive = re.compile('file=(.+),if=virtio').match
with self.slap.instance_supervisor_rpc as instance_supervisor:
kvm_pid = next(q for q in instance_supervisor.getAllProcessInfo()
if 'kvm-' in q['name'])['pid']
dirve_list = []
for entry in psutil.Process(kvm_pid).cmdline():
m = _match_drive(entry)
if m:
path = m.group(1)
dirve_list.append(
path.replace(kvm_instance_partition, '${partition}')
)
return dirve_list
def test(self):
kvm_instance_partition = os.path.join(
self.slap.instance_directory, self.kvm_instance_partition_reference)
......@@ -2005,3 +2007,74 @@ class TestExternalDisk(InstanceTestCase, KvmMixin):
class TestExternalDiskJson(
KvmMixinJson, TestExternalDisk):
pass
@skipUnlessKvm
class TestExternalDiskModern(InstanceTestCase, ExternalDiskMixin):
__partition_reference__ = 'edm'
kvm_instance_partition_reference = 'edm0'
@classmethod
def getInstanceSoftwareType(cls):
return 'default'
@classmethod
def setUpClass(cls):
super(TestExternalDiskModern, cls).setUpClass()
def test(self):
self.working_directory = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, self.working_directory)
kvm_instance_partition = os.path.join(
self.slap.instance_directory, self.kvm_instance_partition_reference)
self.first_disk = os.path.join(self.working_directory, 'first_disk')
# find qemu_img from the tested SR via it's partition parameter, as
# otherwise qemu-kvm would be dependency of test suite
with open(
os.path.join(self.computer_partition_root_path, 'buildout.cfg')) as fh:
qemu_img = [
q for q in fh.readlines()
if 'raw qemu_img_executable_location' in q][0].split()[-1]
subprocess.check_call([
qemu_img, "create", "-f", "qcow", self.first_disk, "1M"])
second_disk = 'second_disk'
self.second_disk = os.path.join(kvm_instance_partition, second_disk)
subprocess.check_call([
qemu_img, "create", "-f", "qcow2", os.path.join(
kvm_instance_partition, self.second_disk), "1M"])
self.third_disk = os.path.join(self.working_directory, 'third_disk')
subprocess.check_call([
qemu_img, "create", "-f", "qcow2", self.third_disk, "1M"])
self.rerequestInstance({'_': json.dumps({
"external-disk": {
"second disk": {
"path": second_disk,
"index": 2,
},
"third disk": {
"path": self.third_disk,
"index": 3,
"cache": "none"
},
"first disk": {
"path": self.first_disk,
"index": 1,
"format": "qcow"
},
}
})})
self.waitForInstance()
drive_list = self.getRunningDriveList(kvm_instance_partition)
self.assertEqual(
drive_list,
[
'file=${partition}/srv/virtual.qcow2,if=virtio,discard=on,'
'format=qcow2',
'file=%s/first_disk,if=virtio,cache=writeback,format=qcow' % (
self.working_directory,),
'file=${partition}/second_disk,if=virtio,cache=writeback',
'file=%s/third_disk,if=virtio,cache=none' % (
self.working_directory,)
]
)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment