comments below
On 02/05/2014 10:18 PM, Rodrigo Trujillo wrote:
This patch creates functions that allow kimchi users to create an
libvirt
SCSI storagepool using the rest API. This patch creates the feature test
to check fc_host capability in libvirt. This patch implements basic
routines to add a disk (scsi) to a new vm template, based on given
volumes (LUN name) from UI or API directly.
Signed-off-by: Rodrigo Trujillo <rodrigo.trujillo(a)linux.vnet.ibm.com>
---
docs/API.md | 5 +++-
src/kimchi/API.json | 14 ++++++++--
src/kimchi/featuretests.py | 27 +++++++++++++++++++
src/kimchi/model/config.py | 5 +++-
src/kimchi/model/host.py | 4 +--
src/kimchi/model/libvirtstoragepool.py | 48 ++++++++++++++++++++++++++++++++--
src/kimchi/model/storagepools.py | 22 +++++++++++++---
src/kimchi/model/templates.py | 5 ++++
src/kimchi/model/vms.py | 25 ++++++++++++++++--
src/kimchi/vmtemplate.py | 31 +++++++++++++++++++++-
10 files changed, 172 insertions(+), 14 deletions(-)
diff --git a/docs/API.md b/docs/API.md
index 580728c..7f0628d 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -55,6 +55,8 @@ the following general conventions:
Independent Computing Environments
* null: Graphics is disabled or type not supported
* listen: The network which the vnc/spice server listens on.
+ * volumes *(optional)*: List of Fibre channel LUN names to be assigned as
+ disk to VM. Required if pool is type SCSI.
### Resource: Virtual Machine
@@ -269,7 +271,7 @@ A interface represents available network interface on VM.
* **POST**: Create a new Storage Pool
* name: The name of the Storage Pool.
* type: The type of the defined Storage Pool.
- Supported types: 'dir', 'kimchi-iso', 'netfs',
'logical', 'iscsi'
+ Supported types: 'dir', 'kimchi-iso', 'netfs',
'logical', 'iscsi, scsi'
'iscsi, scsi' ->
'iscsi', 'scsi' ?
* path: The path of the defined Storage Pool.
For 'kimchi-iso' pool refers to targeted deep scan path.
Pool types: 'dir', 'kimchi-iso'.
@@ -288,6 +290,7 @@ A interface represents available network interface on VM.
Pool types: 'iscsi'.
* username: Login username of the iSCSI target.
* password: Login password of the iSCSI target.
+ * adapter_name: *(optional) Scsi host name.
* adapter_name *(optional)*:
Scsi host name.
### Resource: Storage Pool
diff --git a/src/kimchi/API.json b/src/kimchi/API.json
index 08c77c5..842fb11 100644
--- a/src/kimchi/API.json
+++ b/src/kimchi/API.json
@@ -37,7 +37,7 @@
"type": {
"description": "The type of the defined Storage
Pool",
"type": "string",
- "pattern": "^dir|netfs|logical|kimchi-iso$",
+ "pattern":
"^dir|netfs|logical|kimchi-iso|scsi$",
where is iscsi?
as you say above:
+ Supported types: 'dir', 'kimchi-iso', 'netfs',
'logical', 'iscsi, scsi'
"required": true
},
"path": {
@@ -76,6 +76,10 @@
"minimum": 1,
"maximum": 65535
},
+ "adapter_name": {
+ "description": "SCSI host name",
+ "type": "string"
+ },
"auth": {
"description": "Storage back-end
authentication information",
"type": "object",
@@ -112,7 +116,13 @@
"type": "string",
"pattern": "^/storagepools/[^/]+/?$"
},
- "graphics": { "$ref":
"#/kimchitype/graphics" }
+ "graphics": { "$ref":
"#/kimchitype/graphics" },
+ "volumes": {
+ "description": "list of scsi volumes to be assigned
to the new VM.",
+ "type": "array",
+ "items": { "type": "string" },
+ "uniqueItems": true
+ }
}
},
"vm_update": {
diff --git a/src/kimchi/featuretests.py b/src/kimchi/featuretests.py
index d924050..f391eb6 100644
--- a/src/kimchi/featuretests.py
+++ b/src/kimchi/featuretests.py
@@ -57,6 +57,18 @@ ISO_STREAM_XML = """
</devices>
</domain>"""
+SCSI_FC_XML = """
+<pool type='scsi'>
+ <name>TEST_SCSI_FC_POOL</name>
+ <source>
+ <adapter type='fc_host' wwnn='1234567890abcdef'
wwpn='abcdef1234567890'/>
+ </source>
+ <target>
+ <path>/dev/disk/by-path</path>
+ </target>
+</pool>
+"""
+
class FeatureTests(object):
@@ -150,3 +162,18 @@ class FeatureTests(object):
return False
return True
+
+ @staticmethod
+ def libvirt_support_fc_host():
+ try:
+ conn = libvirt.open('qemu:///system')
+ pool = None
+ pool = conn.storagePoolDefineXML(SCSI_FC_XML, 0)
+ except libvirt.libvirtError as e:
+ if e.get_error_code() == 27:
+ # Libvirt requires adapter name, not needed when supports to FC
+ return False
+ finally:
+ pool is None or pool.undefine()
+ conn is None or conn.close()
+ return True
diff --git a/src/kimchi/model/config.py b/src/kimchi/model/config.py
index 0e66e02..6eb0e10 100644
--- a/src/kimchi/model/config.py
+++ b/src/kimchi/model/config.py
@@ -49,6 +49,7 @@ class CapabilitiesModel(object):
self.qemu_stream = False
self.qemu_stream_dns = False
self.libvirt_stream_protocols = []
+ self.fc_host_support = False
# Subscribe function to set host capabilities to be run when cherrypy
# server is up
@@ -60,6 +61,7 @@ class CapabilitiesModel(object):
self.qemu_stream = FeatureTests.qemu_supports_iso_stream()
self.qemu_stream_dns = FeatureTests.qemu_iso_stream_dns()
self.nfs_target_probe = FeatureTests.libvirt_support_nfs_probe()
+ self.fc_host_support = FeatureTests.libvirt_support_fc_host()
self.libvirt_stream_protocols = []
for p in ['http', 'https', 'ftp', 'ftps',
'tftp']:
@@ -75,7 +77,8 @@ class CapabilitiesModel(object):
return {'libvirt_stream_protocols': self.libvirt_stream_protocols,
'qemu_stream': self.qemu_stream,
'screenshot': VMScreenshot.get_stream_test_result(),
- 'system_report_tool': bool(report_tool)}
+ 'system_report_tool': bool(report_tool),
+ 'fc_host_support': self.fc_host_support}
oh, seems in your
1/5 patch, you have call fc_host_support
class DistrosModel(object):
diff --git a/src/kimchi/model/host.py b/src/kimchi/model/host.py
index 0545a88..816e2e8 100644
--- a/src/kimchi/model/host.py
+++ b/src/kimchi/model/host.py
@@ -218,7 +218,7 @@ class DevicesModel(object):
return dev_names
def _get_devices_fc_host(self):
- conn = self.conn.get()
+ conn = self.conn.get()
# Libvirt < 1.0.5 does not support fc_host capability
if not self.fc_host_support:
ret = []
@@ -226,7 +226,7 @@ class DevicesModel(object):
for host in scsi_hosts:
xml = conn.nodeDeviceLookupByName(host).XMLDesc(0)
path = '/device/capability/capability/@type'
- if 'fc_host' in xmlutils.xpath_get_text(xml, path):
+ if 'fc_host' in xmlutils.xpath_get_text(xml, path):
ret.append(host)
return ret
return conn.listDevices('fc_host',0)
diff --git a/src/kimchi/model/libvirtstoragepool.py
b/src/kimchi/model/libvirtstoragepool.py
index f4dbf2e..ceedbde 100644
--- a/src/kimchi/model/libvirtstoragepool.py
+++ b/src/kimchi/model/libvirtstoragepool.py
@@ -29,8 +29,7 @@ import libvirt
from kimchi.exception import InvalidParameter, OperationFailed, TimeoutExpired
from kimchi.iscsi import TargetClient
from kimchi.rollbackcontext import RollbackContext
-from kimchi.utils import parse_cmd_output, run_command
-
+from kimchi.utils import kimchi_log, parse_cmd_output, run_command
class StoragePoolDef(object):
@classmethod
@@ -175,6 +174,51 @@ class LogicalPoolDef(StoragePoolDef):
return xml
+class ScsiPoolDef(StoragePoolDef):
+ poolType = 'scsi'
+
+ def prepare(self, conn=None):
+ tmp_name = self.poolArgs['source']['name']
+ self.poolArgs['source']['name'] =
tmp_name.replace('scsi_','')
+ # fc_host adapters type are only available in libvirt >= 1.0.5
+ if not self.poolArgs['fc_host_support']:
+ self.poolArgs['source']['adapter_type'] =
'scsi_host'
+ msg = "Libvirt version <= 1.0.5. Setting SCSI host name as
'%s'; "\
+ "setting SCSI adapter type as 'scsi_host'; "\
+ "ignoring wwnn and wwpn." %tmp_name
+ kimchi_log.info(msg)
+ # Path for Fibre Channel scsi hosts
+ self.poolArgs['path'] = '/dev/disk/by-path'
+ if not self.poolArgs['source']['adapter_type']:
+ self.poolArgs['source']['adapter_type'] =
'scsi_host'
+
+ @property
+ def xml(self):
+ # Required parameters
+ # name:
+ # source[adapter_type]:
+ # source[name]:
+ # source[wwnn]:
+ # source[wwpn]:
+ # path:
+
+ xml = """
+ <pool type='scsi'>
+ <name>{name}</name>
+ <source>
+ <adapter type='{source[adapter_type]}'\
+ name='{source[name]}'\
+ wwnn='{source[wwnn]}'\
+ wwpn='{source[wwpn]}'/>
+ </source>
+ <target>
+ <path>{path}</path>
+ </target>
+ </pool>
+ """.format(**self.poolArgs)
+ return xml
+
+
class IscsiPoolDef(StoragePoolDef):
poolType = 'iscsi'
diff --git a/src/kimchi/model/storagepools.py b/src/kimchi/model/storagepools.py
index 233a8a7..9be7dad 100644
--- a/src/kimchi/model/storagepools.py
+++ b/src/kimchi/model/storagepools.py
@@ -26,6 +26,8 @@ from kimchi import xmlutils
from kimchi.scan import Scanner
from kimchi.exception import InvalidOperation, MissingParameter
from kimchi.exception import NotFoundError, OperationFailed
+from kimchi.model.config import CapabilitiesModel
+from kimchi.model.host import DeviceModel
from kimchi.model.libvirtstoragepool import StoragePoolDef
from kimchi.utils import add_task, kimchi_log
@@ -38,7 +40,11 @@ POOL_STATE_MAP = {0: 'inactive',
4: 'inaccessible'}
STORAGE_SOURCES = {'netfs': {'addr':
'/pool/source/host/@name',
- 'path': '/pool/source/dir/@path'}}
+ 'path': '/pool/source/dir/@path'},
+ 'scsi': {'adapter_type':
'/pool/source/adapter/@type',
+ 'adapter_name':
'/pool/source/adapter/@name',
+ 'wwnn': '/pool/source/adapter/@wwnn',
+ 'wwpn': '/pool/source/adapter/@wwpn'}}
class StoragePoolsModel(object):
@@ -47,6 +53,8 @@ class StoragePoolsModel(object):
self.objstore = kargs['objstore']
self.scanner = Scanner(self._clean_scan)
self.scanner.delete()
+ self.caps = CapabilitiesModel()
+ self.device = DeviceModel(**kargs)
def get_list(self):
try:
@@ -67,6 +75,13 @@ class StoragePoolsModel(object):
if params['type'] == 'kimchi-iso':
task_id = self._do_deep_scan(params)
+
+ if params['type'] == 'scsi':
+ extra_params = self.device.lookup(
+ params['source']['adapter_name'])
+ params['source'].update(extra_params)
+ params['fc_host_support'] = self.caps.fc_host_support
+
poolDef = StoragePoolDef.create(params)
poolDef.prepare(conn)
xml = poolDef.xml
@@ -84,9 +99,10 @@ class StoragePoolsModel(object):
return name
pool = conn.storagePoolDefineXML(xml, 0)
- if params['type'] in ['logical', 'dir',
'netfs']:
+ if params['type'] in ['logical', 'dir',
'netfs', 'scsi']:
pool.build(libvirt.VIR_STORAGE_POOL_BUILD_NEW)
- # autostart dir and logical storage pool created from kimchi
+ # autostart dir, logical, netfs and scsi storage pools created
+ # from kimchi
pool.setAutostart(1)
else:
# disable autostart for others
diff --git a/src/kimchi/model/templates.py b/src/kimchi/model/templates.py
index 03632a6..b004578 100644
--- a/src/kimchi/model/templates.py
+++ b/src/kimchi/model/templates.py
@@ -161,6 +161,11 @@ class LibvirtVMTemplate(VMTemplate):
xml = pool.XMLDesc(0)
return xmlutils.xpath_get_text(xml, "/pool/target/path")[0]
+ def _get_storage_type(self):
+ pool = self._storage_validate()
+ xml = pool.XMLDesc(0)
+ return xmlutils.xpath_get_text(xml, "/pool/@type")[0]
+
def fork_vm_storage(self, vm_uuid):
# Provision storage:
# TODO: Rebase on the storage API once upstream
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py
index d4384a1..4623e28 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -155,6 +155,11 @@ class VMsModel(object):
'diskRdKB': diskRdKB,
'diskWrKB': diskWrKB})
+ def _get_volume_path(self, pool, vol):
+ conn = self.conn.get()
+ pool = conn.storagePoolLookupByName(pool)
+ return pool.storageVolLookupByName(vol).path()
+
def create(self, params):
conn = self.conn.get()
t_name = template_name_from_uri(params['template'])
@@ -169,6 +174,7 @@ class VMsModel(object):
pool_uri = params.get('storagepool')
if pool_uri:
vm_overrides['storagepool'] = pool_uri
+ vm_overrides['fc_host_support'] = self.caps.fc_host_support
t = TemplateModel.get_template(t_name, self.objstore, self.conn,
vm_overrides)
@@ -177,7 +183,21 @@ class VMsModel(object):
raise InvalidOperation(err)
t.validate()
- vol_list = t.fork_vm_storage(vm_uuid)
+
+ # If storagepool is SCSI, volumes will be LUNs and must be passed by
+ # the user from UI or manually.
+ vol_list = []
+ if t._get_storage_type() == 'scsi':
+ if not params.get('volumes'):
+ raise InvalidOperation("Volume list (LUNs names) not given.")
+ else:
+ # Get system path of the LUNs
+ pool = t.info['storagepool'].split('/')[-1]
+ for vol in params.get('volumes'):
+ path = self._get_volume_path(pool, vol)
+ vol_list.append((vol, path))
+ else:
+ vol_list = t.fork_vm_storage(vm_uuid)
# Store the icon for displaying later
icon = t.info.get('icon')
@@ -193,7 +213,8 @@ class VMsModel(object):
xml = t.to_vm_xml(name, vm_uuid,
libvirt_stream=libvirt_stream,
qemu_stream_dns=self.caps.qemu_stream_dns,
- graphics=graphics)
+ graphics=graphics,
+ volumes=vol_list)
try:
conn.defineXML(xml.encode('utf-8'))
diff --git a/src/kimchi/vmtemplate.py b/src/kimchi/vmtemplate.py
index 58147e3..368d0b4 100644
--- a/src/kimchi/vmtemplate.py
+++ b/src/kimchi/vmtemplate.py
@@ -49,6 +49,7 @@ class VMTemplate(object):
"""
self.name = args['name']
self.info = {}
+ self.fc_host_support = args.get('fc_host_support')
# Identify the cdrom if present
iso_distro = iso_version = 'unknown'
@@ -180,6 +181,25 @@ class VMTemplate(object):
graphics_xml = graphics_xml + spicevmc_xml
return graphics_xml
+ def _get_scsi_disks_xml(self, luns):
+ ret = ""
+ # Passthrough configuration
+ disk_xml = """
+ <disk type='volume' device='lun'>
+ <driver name='qemu' type='raw'/>
+ <source dev='%(src)s'/>
+ <target dev='%(dev)s' bus='scsi'/>
+ </disk>"""
+ if not self.fc_host_support:
+ disk_xml = disk_xml.replace('volume','block')
+
+ # Creating disk xml for each lun passed
+ for index,(lun, path) in enumerate(luns):
+ dev = "sd%s" % string.lowercase[index]
+ params = {'src': path, 'dev': dev}
+ ret = ret + disk_xml % params
+ return ret
+
def to_volume_list(self, vm_uuid):
storage_path = self._get_storage_path()
ret = []
@@ -225,7 +245,6 @@ class VMTemplate(object):
params = dict(self.info)
params['name'] = vm_name
params['uuid'] = vm_uuid
- params['disks'] = self._get_disks_xml(vm_uuid)
params['networks'] = self._get_networks_xml()
params['qemu-namespace'] = ''
params['cdroms'] = ''
@@ -233,6 +252,13 @@ class VMTemplate(object):
graphics = kwargs.get('graphics')
params['graphics'] = self._get_graphics_xml(graphics)
+ # Current implementation just allows to create disk in one single
+ # storage pool, so we cannot mix the types (scsi volumes vs img file)
+ if self._get_storage_type() == 'scsi':
+ params['disks'] =
self._get_scsi_disks_xml(kwargs.get('volumes'))
+ else:
+ params['disks'] = self._get_disks_xml(vm_uuid)
+
qemu_stream_dns = kwargs.get('qemu_stream_dns', False)
libvirt_stream = kwargs.get('libvirt_stream', False)
cdrom_xml = self._get_cdrom_xml(libvirt_stream, qemu_stream_dns)
@@ -292,3 +318,6 @@ class VMTemplate(object):
def _get_storage_path(self):
return ''
+
+ def _get_storage_type(self):
+ return ''
--
Thanks and best regards!
Sheldon Feng(冯少合)<shaohef(a)linux.vnet.ibm.com>
IBM Linux Technology Center