[PATCH v3] Keep disk ref_counts accurate
by Christy Perez
This patch is very different from v1. I sent a v2 to Aline to test, but
she found a problem with iSCSI, which Royce provided a fix for.
v1 : Change the way storage is represented (as volumes).
v1->v2:
- Royce pointed out that the concept of a volume was a new feature to
libvirt, and after some investigation I found that this would break
kimchi on RHEL 6.
- I changed from using the pool:vol label to identify storage to using
the path alone. This will make it easier to track disks not in pools.
v2->v3:
- Rebase and fixed conflicts
- Add ref_cnt checking on update. This problem was found by the model test
that ejects a CD ROM, leaving a blank 'path' element in the XML.
- Resolve issue with circular include of [s,g]et_ref_cnt functions in both
vmstorages.py and storagevolumes.py by creating a new diskutils module.
Christy Perez (1):
Prevent disks from being added twice
src/kimchi/model/diskutils.py | 75 ++++++++++++++++++++++++++++++++++++++
src/kimchi/model/storagevolumes.py | 51 +++++++++-----------------
src/kimchi/model/vmstorages.py | 70 +++++++++++++++++++++++++++++++----
3 files changed, 154 insertions(+), 42 deletions(-)
create mode 100644 src/kimchi/model/diskutils.py
--
1.9.3
10 years
[PATCH 0/6] New MockModel \o/
by Aline Manera
Yeap! Finally it is done!
The new MockModel uses Model with the 'test:///default' URI.
I also needed to update some tests cases and the run_tests.sh.in scripts as
the new MockModel overrides some Model functions, the test_model.py must be the
first test to be run.
But there are one test that is still failing:
======================================================================
FAIL: test_edit_vm (test_rest.RestTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_rest.py", line 248, in test_edit_vm
self.assertEquals('123456', info["graphics"]["passwd"])
AssertionError: '123456' != u'abcdef'
I could not identify the reason yet, as the same test runs fine when I only run
test_rest.py. I suspect it is a override issue.
While I am investigating it I decided to send the patches to collect feedbacks
soon.
Aline Manera (6):
Re-raise the original exception when creating a new Template
Use objectify to Remove metadata namespace
Check currentMemory exists prior to remote its XML node
Ensure the guest volume exists to remove it
Pass libvirt connection as CapabilitiesModel parameter
MockModel refactor: Create MockModel based on Model("test:///default")
src/kimchi/mockmodel.py | 1769 +++++++-----------------------------
src/kimchi/model/host.py | 3 +-
src/kimchi/model/storagepools.py | 2 +-
src/kimchi/model/storagetargets.py | 2 +-
src/kimchi/model/templates.py | 5 +-
src/kimchi/model/utils.py | 37 +-
src/kimchi/model/vmhostdevs.py | 3 +-
src/kimchi/model/vmifaces.py | 5 +-
src/kimchi/model/vms.py | 44 +-
src/kimchi/model/vmstorages.py | 16 +-
src/kimchi/vmtemplate.py | 9 +-
tests/run_tests.sh.in | 18 +-
tests/test_authorization.py | 25 +-
tests/test_mockmodel.py | 60 +-
tests/test_rest.py | 254 +++---
15 files changed, 565 insertions(+), 1687 deletions(-)
--
1.9.3
10 years
[v3 1/1] Virtual machine migration
by simonjin@linux.vnet.ibm.com
From: Simon Jin <simonjin(a)linux.vnet.ibm.com>
v3-v2: address commets from vianac(a)linux.vnet.ibm.com.
Signed-off-by: Simon Jin <simonjin(a)linux.vnet.ibm.com>
---
docs/API.md | 5 ++
src/kimchi/control/vms.py | 1 +
src/kimchi/i18n.py | 9 ++++
src/kimchi/model/vms.py | 116 ++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 131 insertions(+)
diff --git a/docs/API.md b/docs/API.md
index 9b866f3..17073c8 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -132,6 +132,11 @@ the following general conventions:
It emulates the power reset button on a machine. Note that there is a
risk of data loss caused by reset without the guest OS shutdown.
* connect: Prepare the connection for spice or vnc
+* migrate: Migrate a virtual machine to a remote server, only support live mode without block migration.
+ * remoteHost: IP address or hostname of the remote server.
+ * user: User of the remote server.
+ * passwd: Passwd of user on remote server.
+ * secure: Migrate in P2P secure tunel mode.
* clone: Create a new VM identical to this VM. The new VM's name, UUID and
network MAC addresses will be generated automatically. Each existing
diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py
index a1589ef..e6870e0 100644
--- a/src/kimchi/control/vms.py
+++ b/src/kimchi/control/vms.py
@@ -42,6 +42,7 @@ class VM(Resource):
for ident, node in sub_nodes.items():
setattr(self, ident, node(model, self.ident))
self.start = self.generate_action_handler('start')
+ self.migrate = self.generate_action_handler_task('migrate', ['remoteHost', 'user', 'passwd', 'secure'])
self.poweroff = self.generate_action_handler('poweroff')
self.shutdown = self.generate_action_handler('shutdown')
self.reset = self.generate_action_handler('reset')
diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py
index e823f2b..8d87671 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -105,6 +105,15 @@ messages = {
"KCHVM0033E": _("Virtual machine '%(name)s' must be stopped before cloning it."),
"KCHVM0034E": _("Insufficient disk space to clone virtual machine '%(name)s'"),
"KCHVM0035E": _("Unable to clone VM '%(name)s'. Details: %(err)s"),
+ "KCHVM0036E": _("Can't migrate to localhost."),
+ "KCHVM0037E": _("Can't migrate vm %(name)s on Hypervisor arch %(srcarch)s.to a different Hypervisor arch %(destarch)s."),
+ "KCHVM0038E": _("Can't migrate from %(srchyp)s to a different Hypervisor type %(desthyp)s."),
+ "KCHVM0039E": _("Can't migrate vm %(name)s, vm has passthrough device %(hostdevs)s."),
+ "KCHVM0040E": _("Failed to set migrateSetMaxDowntime when migrate vm %(name)s. Details: %(err)s "),
+ "KCHVM0041E": _("Can not migrate vm %(name)s when its in %(state)s state."),
+ "KCHVM0042E": _("Failed Migrate vm %(name)s due error: %(err)s"),
+ "KCHVM0043E": _("Failed destory vm %(name)s from migration recover. Details: %(err)s"),
+ "KCHVM0044E": _("Failed disconnect %(remoteuri)s when finish migrate vm %(name)s. Details: %(err)s"),
"KCHVMHDEV0001E": _("VM %(vmid)s does not contain directly assigned host device %(dev_name)s."),
"KCHVMHDEV0002E": _("The host device %(dev_name)s is not allowed to directly assign to VM."),
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py
index d194049..ed8f96f 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -33,6 +33,7 @@ from cherrypy.process.plugins import BackgroundTask
from kimchi import model, vnc
from kimchi.config import READONLY_POOL_TYPE
from kimchi.exception import InvalidOperation, InvalidParameter
+from kimchi.model.libvirtconnection import LibvirtConnection
from kimchi.exception import NotFoundError, OperationFailed
from kimchi.model.config import CapabilitiesModel
from kimchi.model.tasks import TaskModel
@@ -790,6 +791,121 @@ class VMModel(object):
except libvirt.libvirtError as e:
raise OperationFailed("KCHVM0019E",
{'name': name, 'err': e.get_error_message()})
+
+ def _preCheck(self, name, destConn, remoteHost):
+ kimchi_log.debug('precheck migrate %s' % name)
+
+ _destConn = destConn.get()
+ if remoteHost in ['localhost', '127.0.0.1']:
+ kimchi_log.debug('vm %s Can not migrate to localhost.' % name)
+ raise OperationFailed("KCHVM0036E", {'name': name,
+ 'remoteHost': remoteHost})
+ destarch = _destConn.getInfo()[0]
+ if self.conn.get().getInfo()[0] != destarch:
+ kimchi_log.debug('vm %s can not migrate to different arch server.' % (name, destarch))
+ raise OperationFailed("KCHVM0037E", {'name': name,
+ 'srcarch': self.conn.get().getInfo()[0],
+ 'destarch': destarch})
+ desthyp = _destConn.getType()
+ if self.conn.get().getType() != desthyp:
+ kimchi_log.debug('vm %s can not migrate to a different hypervisor' % name)
+ raise OperationFailed("KCHVM0038E", {'name': self.name,
+ 'srchyp':self.conn.get().getType(),
+ 'desthyp':desthyp})
+
+
+ #model.host import DOM_STATE_MAP, vmhostdevs import host,
+ #to void loop import DOM_STATE_MAP, move import VMHostDevsModel here
+ from kimchi.model.vmhostdevs import VMHostDevsModel
+
+ #check if there is any passthrough devices belong to this vm
+ vmhostdevs = VMHostDevsModel(conn = self.conn)
+ hostdevs = vmhostdevs.get_list(name)
+ if hostdevs:
+ raise OperationFailed("KCHVM0039E", {'name' : name,
+ 'hostdevs': hostdevs})
+ #open destination connect to migrate
+ @staticmethod
+ def _getRemoteConn(self, remoteHost, user, transport = 'ssh'):
+ duri = 'qemu+%s://%s@%s/system' % (transport,
+ user,remoteHost)
+ conn = LibvirtConnection(duri)
+ return conn
+
+ def _do_migrate(self, name, destConn, remoteHost, secure, cb):
+ _destConn = destConn.get()
+
+ cb('starting a migration')
+ kimchi_log.debug('migrate %s start' % name)
+ dom = self.get_vm(name, self.conn)
+ flag = 0
+ if secure:
+ flag |= (libvirt.VIR_MIGRATE_PEER2PEER |
+ libvirt.VIR_MIGRATE_TUNNELLED)
+
+ state = DOM_STATE_MAP[dom.info()[0]]
+ if state in ['running','paused']:
+ flag |= libvirt.VIR_MIGRATE_LIVE
+ try:
+ dom.migrateSetMaxDowntime(0)
+ except libvirt.libvirtError as e:
+ raise OperationFailed("KCHVM0040E",
+ {'name': name, 'err': e.get_error_message()})
+ elif state == 'shutoff':
+ flag |= libvirt.VIR_MIGRATE_OFFLINE
+ else:
+ kimchi_log.debug('Can not migrate vm %s when its in %s.' % (name, state))
+ raise OperationFailed("KCHVM0041E", {'name': self.name})
+ try:
+ dom.migrate(_destConn, flag, None, None, 0)
+ except libvirt.libvirtError as e:
+ kimchi_log.error('migrate %s to %s failed' % (name,
+ remoteHost))
+ self._recover(cb)
+ raise OperationFailed('KCHVM0042E', {'err': e.message,
+ 'name': name})
+ finally:
+ self._finish(name, destConn, cb)
+
+ def migrate(self, name, remoteHost, user, passwd = None, secure = None):
+ destconn = self._getRemoteConn(remoteHost, user, passwd)
+ self._preCheck(name, destconn, remoteHost, secure)
+
+ task_id = add_task('/vms/%s/migrate' % name, self._do_migrate, self.objstore,
+ {'name' : name, 'destconn': destconn,
+ 'remoteHost': remoteHost, 'secure': secure})
+
+ # Record vm migrate task for future querying
+ try:
+ with self.objstore as session:
+ session.store('migrate', name, task_id)
+ return task_id
+ except Exception as e:
+ raise OperationFailed('KCHPOOL0037E', {'err': e.message})
+
+ def _recover(self, name, destconn, cb):
+ destvm = self.get_vm(name, destconn)
+ #recover
+ cb('recover from a failed migration',False)
+ kimchi_log.debug('recover from a failed migrate %s' % name)
+ try:
+ destvm.destroy()
+ kimchi_log.error("destory migrate vm on destination server")
+ except libvirt.libvirtError as e:
+ raise OperationFailed('KCHVM0043E', {'name' : name,
+ 'err': e.message})
+
+ def _finish(self, name, destConn, cb):
+ _destConn = destConn.get()
+ kimchi_log.debug('finished migrate %s' % name)
+ try:
+ _destConn.close()
+ except libvirt.libvirtError as e:
+ raise OperationFailed('KCHVM0044E', {'name' : name,
+ 'remoteuri' : _destConn.getURI(),
+ 'err': e.message})
+ finally:
+ cb('Migrate finished', True)
def poweroff(self, name):
dom = self.get_vm(name, self.conn)
--
1.8.3.1
10 years
[PATCHv3 0/8] LDAP support
by lvroyce@linux.vnet.ibm.com
From: Royce Lv <lvroyce(a)linux.vnet.ibm.com>
Royce Lv (8):
Add configuration of LDAP
Split PAM and LDAP authentication
Add LDAP authentication
Fix test cases for authentication
Split users and groups for permission query
Move validation to authorizaiton
change vm permission tag
Update test model to fix user/group listing
contrib/DEBIAN/control.in | 1 +
contrib/kimchi.spec.fedora.in | 1 +
contrib/kimchi.spec.suse.in | 1 +
docs/README.md | 36 +++++-----
src/kimchi.conf.in | 17 +++++
src/kimchi/auth.py | 157 +++++++++++++++++++++++++++++++-----------
src/kimchi/config.py.in | 5 ++
src/kimchi/control/auth.py | 42 +++++++++++
src/kimchi/control/host.py | 14 ----
src/kimchi/i18n.py | 1 +
src/kimchi/model/auth.py | 136 ++++++++++++++++++++++++++++++++++++
src/kimchi/model/host.py | 19 -----
src/kimchi/model/vms.py | 42 ++++++-----
tests/test_model.py | 3 +-
tests/test_rest.py | 4 +-
tests/utils.py | 43 ++++++++----
16 files changed, 398 insertions(+), 124 deletions(-)
create mode 100644 src/kimchi/control/auth.py
create mode 100644 src/kimchi/model/auth.py
--
1.8.3.2
10 years
[v4 1/1] Virtual machine migration
by simonjin@linux.vnet.ibm.com
From: Simon Jin <simonjin(a)linux.vnet.ibm.com>
Signed-off-by: Simon Jin <simonjin(a)linux.vnet.ibm.com>
---
docs/API.md | 5 ++
src/kimchi/control/vms.py | 1 +
src/kimchi/i18n.py | 11 ++++
src/kimchi/model/vms.py | 130 ++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 147 insertions(+)
diff --git a/docs/API.md b/docs/API.md
index 9b866f3..17073c8 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -132,6 +132,11 @@ the following general conventions:
It emulates the power reset button on a machine. Note that there is a
risk of data loss caused by reset without the guest OS shutdown.
* connect: Prepare the connection for spice or vnc
+* migrate: Migrate a virtual machine to a remote server, only support live mode without block migration.
+ * remoteHost: IP address or hostname of the remote server.
+ * user: User of the remote server.
+ * passwd: Passwd of user on remote server.
+ * secure: Migrate in P2P secure tunel mode.
* clone: Create a new VM identical to this VM. The new VM's name, UUID and
network MAC addresses will be generated automatically. Each existing
diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py
index a1589ef..e6870e0 100644
--- a/src/kimchi/control/vms.py
+++ b/src/kimchi/control/vms.py
@@ -42,6 +42,7 @@ class VM(Resource):
for ident, node in sub_nodes.items():
setattr(self, ident, node(model, self.ident))
self.start = self.generate_action_handler('start')
+ self.migrate = self.generate_action_handler_task('migrate', ['remoteHost', 'user', 'passwd', 'secure'])
self.poweroff = self.generate_action_handler('poweroff')
self.shutdown = self.generate_action_handler('shutdown')
self.reset = self.generate_action_handler('reset')
diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py
index e823f2b..6da834a 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -105,6 +105,17 @@ messages = {
"KCHVM0033E": _("Virtual machine '%(name)s' must be stopped before cloning it."),
"KCHVM0034E": _("Insufficient disk space to clone virtual machine '%(name)s'"),
"KCHVM0035E": _("Unable to clone VM '%(name)s'. Details: %(err)s"),
+ "KCHVM0036E": _("Migrate %(name)s to localhost %(remoteHost)s is not allowed."),
+ "KCHVM0037E": _("vm %(name)s is already in migrating, can not perform migrate on it again."),
+ "KCHVM0038E": _("Can't migrate vm %(name)s on Hypervisor arch %(srcarch)s.to a different Hypervisor arch %(destarch)s."),
+ "KCHVM0039E": _("Can't migrate %(name)s from %(srchyp)s to a different Hypervisor type %(desthyp)s."),
+ "KCHVM0040E": _("Can't migrate vm %(name)s, vm has passthrough device %(hostdevs)s."),
+ "KCHVM0041E": _("Failed to set migrateSetMaxDowntime when migrate vm %(name)s. Details: %(err)s "),
+ "KCHVM0042E": _("Can not migrate vm %(name)s when its in %(state)s state."),
+ "KCHVM0043E": _("Failed Migrate vm %(name)s due error: %(err)s"),
+ "KCHVM0044E": _("Failed to store migrate %(name)s task ID %(task_id)s. error: %(err)s"),
+ "KCHVM0045E": _("Failed to destroy vm %(name)s from migration recover. Details: %(err)s"),
+ "KCHVM0046E": _("Failed disconnect %(remoteuri)s when finish migrate vm %(name)s. Details: %(err)s"),
"KCHVMHDEV0001E": _("VM %(vmid)s does not contain directly assigned host device %(dev_name)s."),
"KCHVMHDEV0002E": _("The host device %(dev_name)s is not allowed to directly assign to VM."),
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py
index d194049..3243f70 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -35,6 +35,7 @@ from kimchi.config import READONLY_POOL_TYPE
from kimchi.exception import InvalidOperation, InvalidParameter
from kimchi.exception import NotFoundError, OperationFailed
from kimchi.model.config import CapabilitiesModel
+from kimchi.model.libvirtconnection import LibvirtConnection
from kimchi.model.tasks import TaskModel
from kimchi.model.templates import TemplateModel
from kimchi.model.utils import get_vm_name
@@ -790,6 +791,135 @@ class VMModel(object):
except libvirt.libvirtError as e:
raise OperationFailed("KCHVM0019E",
{'name': name, 'err': e.get_error_message()})
+
+ def _preCheck(self, name, destConn, remoteHost):
+ kimchi_log.debug('precheck migrate %s' % name)
+ _destConn = destConn.get()
+ if remoteHost in ['localhost', '127.0.0.1']:
+ kimchi_log.debug('vm %s Can not migrate to localhost.' % name)
+ raise OperationFailed("KCHVM0036E", {'name': name,
+ 'remoteHost': remoteHost})
+ try:
+ with self.objstore as session:
+ task_id = session.get('migrate', name)
+ if task_id:
+ kimchi_log.debug('vm %s is already in migrating.' % name)
+ raise OperationFailed("KCHVM0037E", {'name': name})
+ except NotFoundError as e:
+ kimchi_log.debug('No migrate on vm %s.' % name)
+
+ srcarch = self.conn.get().getInfo()[0]
+ destarch = _destConn.getInfo()[0]
+ if srcarch != destarch:
+ kimchi_log.debug('vm %s can not migrate to different arch server.' % (name, destarch))
+ raise OperationFailed("KCHVM0038E", {'name': name,
+ 'srcarch': srcarch,
+ 'destarch': destarch})
+ srchyp = self.conn.get().getType()
+ desthyp = _destConn.getType()
+ if srchyp != desthyp:
+ kimchi_log.debug('vm %s can not migrate to a different hypervisor' % name)
+ raise OperationFailed("KCHVM0039E", {'name': name,
+ 'srchyp': srchyp,
+ 'desthyp':desthyp})
+
+ #model.host import DOM_STATE_MAP, vmhostdevs import host,
+ #to void loop import DOM_STATE_MAP, move import VMHostDevsModel here
+ from kimchi.model.vmhostdevs import VMHostDevsModel
+ #check if there is any passthrough devices belong to this vm
+ vmhostdevs = VMHostDevsModel(conn = self.conn)
+ hostdevs = vmhostdevs.get_list(name)
+ if hostdevs:
+ raise OperationFailed("KCHVM0040E", {'name' : name,
+ 'hostdevs': hostdevs})
+ #open destination connect to migrate
+ @staticmethod
+ def _getRemoteConn(remoteHost, user, passwd, transport = 'ssh'):
+ duri = 'qemu+%s://%s@%s/system' % (transport, user,remoteHost)
+ conn = LibvirtConnection(duri)
+ return conn
+
+ def _do_migrate(self, cb, params):
+ name = params['name']
+ destConn = params['destConn']
+ remoteHost = params['remoteHost']
+ secure = params['secure']
+ _destConn = destConn.get()
+
+ cb('starting a migration')
+ kimchi_log.debug('migrate %s start' % name)
+ dom = self.get_vm(name, self.conn)
+ flag = 0
+ if secure:
+ flag |= (libvirt.VIR_MIGRATE_PEER2PEER |
+ libvirt.VIR_MIGRATE_TUNNELLED)
+
+ state = DOM_STATE_MAP[dom.info()[0]]
+ if state in ['running','paused']:
+ flag |= libvirt.VIR_MIGRATE_LIVE
+ try:
+ dom.migrateSetMaxDowntime(0)
+ except libvirt.libvirtError as e:
+ raise OperationFailed("KCHVM0041E",
+ {'name': name, 'err': e.get_error_message()})
+ elif state == 'shutoff':
+ flag |= libvirt.VIR_MIGRATE_OFFLINE
+ else:
+ kimchi_log.debug('Can not migrate vm %s when its in %s.' % (name, state))
+ raise OperationFailed("KCHVM0042E", {'name': name,
+ 'state': state})
+ try:
+ dom.migrate(_destConn, flag, None, None, 0)
+ except libvirt.libvirtError as e:
+ kimchi_log.error('migrate %s to %s failed' % (name, remoteHost))
+ self._finish(name, destConn, False, cb)
+ self._recover(name, destConn, cb)
+ raise OperationFailed('KCHVM0043E', {'err': e.message,
+ 'name': name})
+ self._finish(name, destConn, True, cb)
+
+ def migrate(self, name, remoteHost, user, passwd = None, secure = None):
+ destConn = self._getRemoteConn(remoteHost, user, passwd)
+ self._preCheck(name, destconn, remoteHost, secure)
+
+ params = {'name': name,
+ 'destConn': destConn,
+ 'remoteHost': remoteHost,
+ 'secure': secure}
+ task_id = add_task('/vms/%s/migrate' % name, self._do_migrate,
+ self.objstore, params)
+
+ # Record vm migrate task for future querying
+ try:
+ with self.objstore as session:
+ session.store('migrate', name, task_id)
+ except Exception as e:
+ raise OperationFailed('KCHVM0044E', {'name': name,
+ 'task_id': task_id,
+ 'err': e.message})
+
+ def _recover(self, name, destconn, cb):
+ destvm = self.get_vm(name, destconn)
+ cb('recover from a failed migration',False)
+ kimchi_log.debug('recover from a failed migrate %s' % name)
+ try:
+ destvm.destroy()
+ kimchi_log.error("Failed to destory migrate vm %s on \
+ destination server" % name)
+ except libvirt.libvirtError as e:
+ raise OperationFailed('KCHVM0045E', {'name' : name,
+ 'err': e.message})
+
+ def _finish(self, name, destConn, success, cb):
+ _destConn = destConn.get()
+ kimchi_log.debug('finished migrate %s' % name)
+ cb('Migrate finished', success)
+ try:
+ _destConn.close()
+ except libvirt.libvirtError as e:
+ raise OperationFailed('KCHVM0046E', {'name' : name,
+ 'remoteuri' :_destConn.getURI(),
+ 'err': e.message})
def poweroff(self, name):
dom = self.get_vm(name, self.conn)
--
1.8.3.1
10 years
[PATCH V2] Edit Template redefined
by Wen Wang
From: Wen Wang <wenwang(a)linux.vnet.ibm.com>
V1 -> V2:
1) Enable "iSCSI" and "SCSI" for storage.
2) Changed the storage tab from "storage" to "Storage".
3) Fix the defect that when editing/adding "Storage" or "Interface" line
went down issue.
4) "Storage" and "Interface" content can display properly on Remote and
Image created template.
5) Fix the defect that "Image" didn't show properly when using a Image
created template.
This patch redesigned "Edit Template" diaguage in "Templates". New "Edit
Template" will display all the related information into tabs of
"General", "Storage" and "Interface". Due to unfinished back-end work,
functions are not fully supported, which will be finished in the future
work.
Temporary disabled functions:
1) Multiple disk operation with multiple storage pools edit in template
for which reason the add button in "Storage" tab is disabled.
2) iSCSI and SCSI storage pool add is removed since we are going to
allow this kind of operation in the process of creating a VM.
Signed-off-by: Wen Wang <wenwang(a)linux.vnet.ibm.com>
---
ui/css/theme-default/template-edit.css | 116 +++++++----
ui/js/src/kimchi.template_edit_main.js | 364 +++++++++++++++++++++++---------
ui/pages/template-edit.html.tmpl | 168 ++++++++-------
3 files changed, 432 insertions(+), 216 deletions(-)
diff --git a/ui/css/theme-default/template-edit.css b/ui/css/theme-default/template-edit.css
index 4975f1b..094e909 100644
--- a/ui/css/theme-default/template-edit.css
+++ b/ui/css/theme-default/template-edit.css
@@ -17,24 +17,33 @@
*/
#template-edit-window {
font-size: 13px;
- height: 600px;
- width: 1000px;
+ height: 500px;
+ width: 800px;
}
-.template-edit-fieldset {
- float: left;
- padding: 1em;
+#edit-template-tabs {
+ background: none repeat scroll 0 0 transparent;
+ border: medium none;
+ height: 100%;
+ padding: 0;
}
-.template-edit-wrapper-label, .template-edit-wrapper-controls {
+#edit-template-tabs .form-template-inline-wrapper {
+ display: inline-block;
vertical-align: top;
- width: 470px;
}
.template-edit-wrapper-label {
- height: 18px;
- line-height: 18px;
- margin-top: 8px;
+ vertical-align: top;
+ min-width: 100px;
+ height: 35px;
+ line-height: 35px;
+ margin: 7px 0 8px;
+}
+
+.template-edit-wrapper-controls {
+ vertical-align: top;
+ width: 400px;
}
.template-edit-wrapper-controls input[type="text"] {
@@ -56,7 +65,7 @@
.template-edit-wrapper-controls > .dropdown {
margin: 5px 0 0 1px;
- width: 440px;
+ width: 372px;
}
.template-edit-wrapper-controls input[type="text"][disabled] {
@@ -65,41 +74,72 @@
cursor: not-allowed;
}
-.hidden-area {
- display: none;
+#edit-template-tabs .template-tab-header {
+ margin-bottom: 8px;
+ padding-bottom: 2px;
+ font-weight: bold;
+ border-bottom: 1px solid #999999;
+ overflow: hidden;
}
-.template-edit-wrapper-controls .select-list-box {
- width: 464px;
- max-height: 168px;
- overflow: auto;
- margin-top: 5px;
- border: 1px solid #ccc;
+#edit-template-tabs .template-tab-header .action-area {
+ float: right;
+ height: 20px;
+ width: 20px;
}
-.template-edit-wrapper-controls .select-list-box>li>label {
- display: block;
+#edit-template-tabs .template-interface-cell {
+ display: inline-block;
+ width: 250px;
}
-.template-edit-wrapper-controls .select-list-box>li>label>
-input[type="checkbox"] {
- display: none;
+#edit-template-tabs .template-storage-cell{
+ display: inline-block;
+ width: 230px;
+}
+
+#edit-template-tabs .template-storage-cell label {
+ height: 25px;
+ padding: 2px;
+ border: 1px;
}
-.template-edit-wrapper-controls .select-list-box>li>label>.item {
- display: block;
- height: 41px;
- line-height: 41px;
- padding: 0 20px 0 40px;
- border-bottom: 1px solid #ccc;
- box-shadow: 0px 1px 1px #fff;
- text-shadow: -1px -1px 1px #ddd, 1px 1px 1px #fff;
- color: #222;
- font-size: 12px;
+#form-template-storage .template-tab-body select {
+ width: 140px;
}
-.template-edit-wrapper-controls .select-list-box>li>label>
-input[type="checkbox"]:CHECKED+.item {
- background: #f8f8f8 url(../images/theme-default/check-green.png) no-repeat
- 10px center;
+#form-template-storage .template-tab-body input {
+ width: 56px;
+ height: 17px;
}
+
+#form-template-storage .template-tab-body .template-storage-name {
+ width: 220px;
+}
+
+#edit-template-tabs .template-tab-body input[readonly] {
+ background: none repeat scroll 0 0 rgba(0, 0, 0, 0);
+ border-color: transparent;
+ text-overflow: ellipsis;
+}
+
+#edit-template-tabs .template-tab-body .item {
+ height: 25px;
+}
+
+#form-template-interface .template-tab-body select {
+ width: 180px;
+}
+
+#edit-template-tabs .template-tab-body .action-area {
+ float: right;
+}
+
+#edit-template-tabs .template-tab-body .action-area button {
+ width: 20px;
+ height: 20px;
+}
+
+#edit-template-tabs .hide {
+ display: none;
+}
\ No newline at end of file
diff --git a/ui/js/src/kimchi.template_edit_main.js b/ui/js/src/kimchi.template_edit_main.js
index 2f4cc9a..12a9f20 100644
--- a/ui/js/src/kimchi.template_edit_main.js
+++ b/ui/js/src/kimchi.template_edit_main.js
@@ -16,19 +16,23 @@
* limitations under the License.
*/
kimchi.template_edit_main = function() {
- var templateEditForm = $('#form-template-edit');
+ var templateEditMain = $('#edit-template-tabs');
var origDisks;
var origPool;
+ var origNetworks;
var templateDiskSize;
- $('#template-name', templateEditForm).val(kimchi.selectedTemplate);
- kimchi.retrieveTemplate(kimchi.selectedTemplate, function(template) {
+ $('#template-name', templateEditMain).val(kimchi.selectedTemplate);
+ templateEditMain.tabs();
+
+ var initTemplate = function(template) {
origDisks = template.disks;
origPool = template.storagepool;
+ origNetworks = template.networks;
for(var i=0;i<template.disks.length;i++){
if(template.disks[i].base){
template["vm-image"] = template.disks[i].base;
- $('#templ-edit-cdrom').addClass('hide-content');
- $('#templ-edit-vm-image').removeClass('hide-content');
+ $('.templ-edit-cdrom').addClass('hide');
+ $('.templ-edit-vm-image').removeClass('hide');
break;
}
}
@@ -37,18 +41,12 @@ kimchi.template_edit_main = function() {
if (prop == 'graphics') {
value = value["type"];
}
- $('input[name="' + prop + '"]', templateEditForm).val(value);
- }
- var disks = template.disks;
- $('input[name="disks"]').val(disks[0].size);
- templateDiskSize = $('input[name="disks"]').val();
- if (disks[0].volume) {
- var spool_value = $('#form-template-edit [name="storagepool"]').val();
- $('input[name="storagepool"]', templateEditForm).val(spool_value + '/' + disks[0].volume);
- $('input[name="disks"]', templateEditForm).attr('disabled','disabled');
+ $('input[name="' + prop + '"]', templateEditMain).val(value);
}
var vncOpt = [{label: 'VNC', value: 'vnc'}];
+ $('#template-edit-graphics').append('<option selected>VNC</option>');
+ $('#template-edit-graphics').append('<option>Spice</option>');
kimchi.select('template-edit-graphics-list', vncOpt);
var enableSpice = function() {
if (kimchi.capabilities == undefined) {
@@ -61,119 +59,281 @@ kimchi.template_edit_main = function() {
}
};
enableSpice();
-
- var scsipools = {};
- kimchi.listStoragePools(function(result) {
- var options = [];
- if (result && result.length) {
- $.each(result, function(index, storagePool) {
- if ((storagePool.state=="active") && (storagePool.type !== 'kimchi-iso')) {
- if ((storagePool.type == 'iscsi') || (storagePool.type == 'scsi')){
- scsipools[storagePool.name] = [];
- kimchi.listStorageVolumes(storagePool.name, function(result) {
- if (result && result.length) {
- $.each(result, function(index, storageVolume) {
- options.push({
- label: storagePool.name + '/' + storageVolume.name,
- value: '/storagepools/' + storagePool.name + '/' + storageVolume.name
- });
- scsipools[storagePool.name].push(storageVolume)
- });
- }
- kimchi.select('template-edit-storagePool-list', options);
- });
+ var initStorage = function(result) {
+ var scsipools = {};
+ var addStorageItem = function(storageData) {
+ var thisName;
+ var thisType;
+ var thisDisk;
+ var nodeStorage = $.parseHTML(kimchi.substitute($('#template-storage-pool-tmpl').html(), storageData));
+ $('.template-tab-body', '#form-template-storage').append(nodeStorage);
+ $('.edit', '#form-template-storage').button({
+ icons : {primary : 'ui-icon-pencil'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ var storageItem = $(this).parent().parent();
+ thisName = $('.template-storage-name', storageItem).val();
+ thisType = $('.template-storage-type', storageItem).val();
+ thisDisk = $('.template-storage-disk', storageItem).val();
+ $('.template-storage-name', storageItem).hide();
+ $('.template-storage-disk', storageItem).attr('readonly', false);
+ if (thisType === 'iscsi' || thisType === 'scsi') {
+ $('.template-storage-disk', storageItem).attr('readonly', true);
+ } else {
+ $('.template-storage-disk', storageItem).attr('readonly', false);
+ }
+ $('.save', storageItem).parent().show();
+ $('.delete', storageItem).parent().hide();
+ var selectedStorage = $('select', storageItem).val();
+ $('.template-storage-name', storageItem).val(selectedStorage).hide();
+ $('select', storageItem).val(thisName).show();
+ });
+ $('.delete', '#form-template-storage').button({
+ icons : {primary : 'ui-icon-trash'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ $(this).parent().parent().remove();
+ });
+ $('.cancel', '#form-template-storage').button({
+ icons : {primary : 'ui-icon-arrowreturnthick-1-w'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ var cancelEntity = $(this).parent().parent();
+ if ($('.template-storage-name', cancelEntity).val() === 'null') {
+ cancelEntity.remove();
+ } else {
+ $('select', cancelEntity).hide();
+ $('.template-storage-name', cancelEntity).val(thisName).attr('readonly', true).show();
+ $('.template-storage-type', cancelEntity).val(thisType).attr('readonly', true);
+ $('.template-storage-disk', cancelEntity).val(thisDisk).attr('readonly', true);
+ $('.save', cancelEntity).parent().hide();
+ $('.edit', cancelEntity).parent().show();
+ }
+ });
+ $('.save', '#form-template-storage').button({
+ icons : {primary : 'ui-icon-disk'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ var storageItem = $(this).parent().parent();
+ $('.save', storageItem).parent().hide();
+ $('.delete', storageItem).parent().show();
+ var selectedStorage = $('select', storageItem).val();
+ $('.template-storage-name', storageItem).val(selectedStorage).attr('readonly', true).show();
+ $('.template-storage-disk', storageItem).attr('readonly', true);
+ $('.template-storage-type', storageItem).attr('readonly', true);
+ $('select', storageItem).hide();
+ });
+ var storageOptions = '';
+ var scsiOptions = '';
+ $('select', '#form-template-storage').find('option').remove();
+ $.each(result, function(index, storageEntities) {
+ if((storageEntities.state === 'active') && (storageEntities.type != 'kimchi-iso')) {
+ if(storageEntities.type === 'iscsi' || storageEntities.type === 'scsi') {
+ kimchi.listStorageVolumes(storageEntities.name, function(currentVolume) {
+ $.each(currentVolume, function(indexSCSI, scsiEntities) {
+ scsiOptions += '<option>' + storageEntities.name + '/' + scsiEntities.name + '</option>';
+ });
+ $('select', '#form-template-storage').append(scsiOptions);
+ }, function() {});
+ } else {
+ var isSlected = storageEntities.name === 'default' ? ' selected' : '';
+ storageOptions += '<option' + isSlected + '>' + storageEntities.name + '</option>';
}
- else {
- options.push({
- label: storagePool.name,
- value: '/storagepools/' + storagePool.name
- });
+ }
+ });
+ $('select', '#form-template-storage').append(storageOptions);
+ $('select', '#form-template-storage').change(function() {
+ var selectedItem = $(this).parent().parent();
+ var tempStorageName = $(this).val();
+ tempStorageName =tempStorageName.split('/')[0];
+ var scsiCap;
+ $.each(result, function(index, storageEntities) {
+ if (tempStorageName === storageEntities.name) {
+ selectedItem.find('.template-storage-type').val(storageEntities.type);
+ scsiCap = storageEntities.capacity / Math.pow(1024, 3);
}
+ })
+ if (tempStorageName === 'iscsi' || tempStorageName === 'scsi') {
+ $('.template-storage-disk', selectedItem).attr('readonly', true).val(scsiCap);
+ } else {
+ $('.template-storage-disk', selectedItem).attr('readonly', false).val('10');
}
});
- }
- if ($.isEmptyObject(scsipools)) {
- kimchi.select('template-edit-storagePool-list', options);
- }
- });
- kimchi.listNetworks(function(result) {
- if(result && result.length > 0) {
- var html = '';
- var tmpl = $('#tmpl-network').html();
- $.each(result, function(index, network) {
- if (result[index].state === 'active')
- html += kimchi.substitute(tmpl, network);
+ };
+
+ if ((origDisks && origDisks.length) && (origPool && origPool.length)) {
+ splitPool = origPool.split('/');
+ var defaultPool;
+ var defaultType;
+ $.each(result, function(index, poolEntities) {
+ if (poolEntities.name === splitPool[splitPool.length-1]) {
+ defaultType = poolEntities.type;
+ defaultPool = splitPool[splitPool.length-1]
+ }
});
- $('#template-edit-network-list').html(html).show();
- if(template.networks && template.networks.length > 0) {
- $('input[name="networks"]', templateEditForm).each(function(index, element) {
- var value = $(element).val();
- if(template.networks.indexOf(value) >= 0) {
- $(element).prop('checked', true);
- }
- });
+ if (origDisks[0]['volume']) {
+ defaultPool = defaultPool + '/' + origDisks[0]['volume'];
}
- } else {
- $('#template-edit-network-list').hide();
+ $.each(origDisks, function(index, diskEntities) {
+ var storageNodeData = {
+ viewMode : '',
+ editMode : 'hide',
+ storageName : defaultPool,
+ storageType : defaultType,
+ storageDisk : diskEntities.size
+ }
+ addStorageItem(storageNodeData);
+ });
+ $('.template-storage-disk').attr('readonly', true);
}
- });
- });
- $('#template-edit-storagePool').change(function() {
- storagepool = $(this).val();
- var storageArray = storagepool.split("/");
- if (storageArray.length > 3) {
- volumeName = storageArray.pop();
- poolName = storageArray.pop();
- kimchi.getStoragePoolVolume(poolName, volumeName, function(result) {
- $('input[name="disks"]', templateEditForm).val(result.capacity / Math.pow(1024,3));
- $('input[name="disks"]', templateEditForm).attr('disabled','disabled');
- return false;
- }, function (err) {
- kimchi.message.error(err.responseJSON.reason);
+ $('#template-edit-storage-add-button').button({
+ icons: {
+ primary: "ui-icon-plusthick"
+ },
+ text: false,
+ disabled: true
+ }).click(function(event) {
+ event.preventDefault();
+ var storageNodeData = {
+ viewMode : 'hide',
+ editMode : '',
+ storageName : 'null',
+ storageType : 'dir',
+ storageDisk : '10'
+ }
+ addStorageItem(storageNodeData);
});
- } else {
- $('input[name="disks"]', templateEditForm).removeAttr('disabled');
- $('input[name="disks"]', templateEditForm).val(templateDiskSize);
- }
- });
- $('input[name="disks"]', templateEditForm).keyup(function() {
- templateDiskSize = $('input[name="disks"]', templateEditForm).val();
- });
+ };
+ var initInterface = function(result) {
+ var addInterfaceItem = function(networkData) {
+ var nodeInterface = $.parseHTML(kimchi.substitute($('#template-interface-tmpl').html(), networkData));
+ $('.template-tab-body', '#form-template-interface').append(nodeInterface);
+ $('.edit', '#form-template-interface').button({
+ icons : {primary : 'ui-icon-pencil'},
+ text : false,
+ disabled : true
+ });
+ $('.delete', '#form-template-interface').button({
+ icons : {primary : 'ui-icon-trash'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ $(this).parent().parent().remove();
+ });
+ $('.cancel', '#form-template-interface').button({
+ icons : {primary : 'ui-icon-arrowreturnthick-1-w'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ $(this).parent().parent().remove();
+ });
+ $('.save', '#form-template-interface').button({
+ icons : {primary : 'ui-icon-disk'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ var interItem = $(this).parent().parent();
+ $('.save', interItem).parent().hide();
+ $('.delete', interItem).parent().show();
+ var selectedInterface = $('select', interItem).val();
+ $('.template-interface-name', interItem).val(selectedInterface).show();
+ $('select', interItem).hide();
+ });
+ var networkOptions = '';
+ for(var i=0;i<result.length;i++){
+ if(result[i].state === "active") {
+ var isSlected = i==0 ? ' selected' : '';
+ networkOptions += '<option' + isSlected + '>' + result[i].name + '</option>';
+ }
+ }
+ $('select', '#form-template-interface').find('option').remove();
+ $('select', '#form-template-interface').append(networkOptions);
+ };
+ if(result && result.length > 0) {
+ $.each(result, function(index, data) {
+ if($.inArray(data.name, origNetworks) > -1) {
+ addInterfaceItem({
+ mac : '',
+ network : data.name,
+ type : 'network',
+ viewMode : '',
+ editMode : 'hide'
+ });
+ }
+ });
+ }
+ $('#template-edit-interface-add-button').button({
+ icons: {
+ primary: 'ui-icon-plusthick'
+ },
+ text: false
+ }).click(function(evt) {
+ evt.preventDefault();
+ addInterfaceItem({
+ mac : '',
+ network : '',
+ type : 'network',
+ viewMode : 'hide',
+ editMode : ''
+ });
+ });
+ };
+ kimchi.listNetworks(initInterface);
+ kimchi.listStoragePools(initStorage);
+ };
+ kimchi.retrieveTemplate(kimchi.selectedTemplate, initTemplate);
+
$('#tmpl-edit-button-save').on('click', function() {
- var editableFields = [ 'name', 'cpus', 'memory', 'storagepool', 'disks', 'graphics'];
+ var editableFields = [ 'name', 'cpus', 'memory', 'disks', 'graphics'];
var data = {};
+ //Fix me: Only support one storage pool now
+ var storages = $('.template-tab-body .item', '#form-template-storage');
+ var tempName = $('.template-storage-name', storages).val();
+ tempName = tempName.split('/');
+ var tempNameHead =tempName[0];
+ var tempNameTail = tempNameHead;
+ if(tempNameHead === 'iscsi' || tempNameHead =='scsi') {
+ tempNameTail = tempName[tempName.length-1];
+ }
+ tempName = '/storagepools/' + tempNameHead;
+ data['storagepool'] = tempName;
$.each(editableFields, function(i, field) {
/* Support only 1 disk at this moment */
if (field == 'disks') {
- origDisks[0].size = Number($('#form-template-edit [name="' + field + '"]').val());
+ var tmpItem = $('#form-template-storage .item');
+ origDisks[0].size = Number($('.template-storage-disk', tmpItem).val());
+ if($('.template-storage-type', tmpItem).val() === 'iscsi' || $('.template-storage-type', tmpItem).val() =='scsi') {
+ origDisks[0]['volume'] = tempNameTail;
+ } else {
+ origDisks[0]['volume'] && delete origDisks[0]['volume'];
+ }
data[field] = origDisks;
}
else if (field == 'graphics') {
- var type = $('#form-template-edit [name="' + field + '"]').val();
+ var type = $('#form-template-general [name="' + field + '"]').val();
data[field] = {'type': type};
}
else {
- data[field] = $('#form-template-edit [name="' + field + '"]').val();
+ data[field] = $('#form-template-general [name="' + field + '"]').val();
}
});
data['memory'] = Number(data['memory']);
data['cpus'] = Number(data['cpus']);
- storagepool = data['storagepool'];
- storageArray = storagepool.split("/");
- if (storageArray.length > 3){
- /* Support only 1 disk at this moment */
- data["disks"][0].volume = storageArray.pop();
- data['storagepool'] = storageArray.join("/");
- } else if (data["disks"][0].volume) {
- delete data["disks"][0].volume;
- }
- var networks = templateEditForm.serializeObject().networks;
- if (networks instanceof Array) {
- data.networks = networks;
- } else if (networks != null) {
- data.networks = [networks];
+ var networks = $('.template-tab-body .item', '#form-template-interface');
+ var networkForUpdate = new Array();
+ $.each(networks, function(index, networkEntities) {
+ networkForUpdate.push($('.template-interface-name', networkEntities).val());
+ });
+ if (networkForUpdate instanceof Array) {
+ data.networks = networkForUpdate;
+ } else if (networkForUpdate != null) {
+ data.networks = [networkForUpdate];
} else {
data.networks = [];
}
diff --git a/ui/pages/template-edit.html.tmpl b/ui/pages/template-edit.html.tmpl
index 5a71d91..018ac10 100644
--- a/ui/pages/template-edit.html.tmpl
+++ b/ui/pages/template-edit.html.tmpl
@@ -28,74 +28,67 @@
<div class="close">X</div>
</header>
<div class="content">
- <form id="form-template-edit">
+ <div id="edit-template-tabs">
<input type="hidden" id="template-name" name="templateName" />
- <fieldset class="template-edit-fieldset">
- <div>
+ <ul>
+ <li>
+ <a href="#form-template-general">$_("General")</a>
+ </li>
+ <li>
+ <a href="#form-template-storage">$_("Storage")</a>
+ </li>
+ <li>
+ <a href="#form-template-interface">$_("Interface")</a>
+ </li>
+ </ul>
+ <form id="form-template-general">
+ <div class="form-template-inline-wrapper">
<div class="template-edit-wrapper-label">
<label for="template-edit-id-textbox">$_("Name")</label>
</div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-id-textbox" name="name" type="text" />
- </div>
- </div>
- <div>
<div class="template-edit-wrapper-label">
<label for="template-edit-vendor-textbox">$_("Vendor")</label>
</div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-vendor-textbox" name="os_distro" type="text" disabled="disabled" />
- </div>
- </div>
- <div>
<div class="template-edit-wrapper-label">
<label for="template-edit-version-textbox">$_("Version")</label>
</div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-version-textbox" name="os_version" type="text" disabled="disabled" />
- </div>
- </div>
- <div>
<div class="template-edit-wrapper-label">
<label for="template-edit-cpu-textbox">$_("CPU Number")</label>
</div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-cpu-textbox" name="cpus" type="text" />
- </div>
- </div>
- <div>
<div class="template-edit-wrapper-label">
<label for="template-edit-memory-textbox">$_("Memory (MB)")</label>
</div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-memory-textbox" name="memory" type="text" />
+ <div class="template-edit-wrapper-label templ-edit-cdrom">
+ <label for="template-edit-cdrom-textbox">$_("CDROM")</label>
+ </div>
+ <div class="template-edit-wrapper-label templ-edit-vm-image hide">
+ <label for="template-edit-vmimage-textbox">$_("Image File")</label>
</div>
- </div>
- <div>
<div class="template-edit-wrapper-label">
- <label for="template-edit-disk-textbox">$_("Disk (GB)")</label>
+ <label>$_("Graphics")</label>
</div>
+ </div>
+ <div class="form-template-inline-wrapper">
<div class="template-edit-wrapper-controls">
- <input id="template-edit-disk-textbox" name="disks" type="text" />
+ <input id="template-edit-id-textbox" name="name" type="text" />
</div>
- </div>
- </fieldset>
- <fieldset class="template-edit-fieldset">
- <div id="templ-edit-cdrom">
- <div class="template-edit-wrapper-label">
- <label for="template-edit-cdrom-textbox">$_("CDROM")</label>
+ <div class="template-edit-wrapper-controls">
+ <input id="template-edit-vendor-textbox" name="os_distro" type="text" disabled="disabled" />
</div>
<div class="template-edit-wrapper-controls">
- <input id="template-edit-cdrom-textbox" name="cdrom" type="text" disabled="disabled"/>
+ <input id="template-edit-version-textbox" name="os_version" type="text" disabled="disabled" />
</div>
- </div>
- <div id="templ-edit-vm-image" class="hide-content">
- <div class="template-edit-wrapper-label">$_("Image File")</div>
- <div class="template-edit-wrapper-controls"><input name="vm-image" type="text" disabled/></div>
- </div>
- <div>
- <div class="template-edit-wrapper-label">
- <label>$_("Graphics")</label>
+ <div class="template-edit-wrapper-controls">
+ <input id="template-edit-cpu-textbox" name="cpus" type="text" />
+ </div>
+ <div class="template-edit-wrapper-controls">
+ <input id="template-edit-memory-textbox" name="memory" type="text" />
+ </div>
+ <div class="template-edit-wrapper-controls templ-edit-cdrom">
+ <input id="template-edit-cdrom-textbox" name="cdrom" type="text" disabled="disabled" />
+ </div>
+ <div class="template-edit-wrapper-controls templ-edit-vm-image hide">
+ <input id="template-edit-vmimage-textbox" name="vm-image" type="text" disabled="disabled" />
</div>
<div class="template-edit-wrapper-controls">
<div class="btn dropdown popable">
@@ -108,40 +101,26 @@
</div>
</div>
</div>
- <div>
- <div class="template-edit-wrapper-label">
- <label>$_("Storage Pool")</label>
- </div>
- <div class="template-edit-wrapper-controls">
- <div class="btn dropdown popable">
- <input id="template-edit-storagePool" name="storagepool" type="hidden" />
- <span class="text" id="template-edit-storage-label"></span><span class="arrow"></span>
- <div class="popover" style="width: 100%">
- <ul class="select-list" id="template-edit-storagePool-list" data-target="template-edit-storagePool" data-label="template-edit-storage-label">
- </ul>
- </div>
- </div>
- </div>
+ </form>
+ <form id="form-template-storage">
+ <div class="template-tab-header">
+ <span class="template-storage-cell">$_("Storage Pool")</span>
+ <span class="template-storage-cell">$_("Type")</span>
+ <span class="template-storage-cell">$_("Disk(GB)")</span>
+ <button type="button" id="template-edit-storage-add-button" class="action-area"></button>
</div>
- <div>
- <div class="template-edit-wrapper-label">
- <label>$_("Network")</label>
- </div>
- <div class="template-edit-wrapper-controls">
- <ul class="select-list-box" id="template-edit-network-list">
- </ul>
- <script id="tmpl-network" type="text/html">
- <li>
- <label>
- <input name="networks" type="checkbox" value="{name}" />
- <span class="item">{name}</span>
- </label>
- </li>
- </script>
- </div>
+ <div class="template-tab-body">
</div>
- </fieldset>
- </form>
+ </form>
+ <form id="form-template-interface">
+ <div class="template-tab-header">
+ <span class="template-interface-cell">$_("Network")</span>
+ <span class="template-interface-cell">$_("Type")</span>
+ <button type="button" id="template-edit-interface-add-button" class="action-area"></button>
+ </div>
+ <div class="template-tab-body"></div>
+ </form>
+ </div>
</div>
<footer>
<div class="btn-group">
@@ -152,3 +131,40 @@
<script>
kimchi.template_edit_main();
</script>
+<script id="template-storage-pool-tmpl" type="text/html">
+ <div class='item'>
+ <span class="template-storage-cell">
+ <input class="template-storage-name {viewMode}" value={storageName} readonly=true type="text"/>
+ <select class="{editMode}"></select>
+ </span>
+ <span class="template-storage-cell">
+ <input class="template-storage-type" value={storageType} readonly=true type="text" />
+ </span>
+ <span class="template-storage-cell">
+ <input class="template-storage-disk" value={storageDisk} type="text" />
+ </span>
+ <span class="action-area {editMode}">
+ <button class="save"></button><button class="cancel"></button>
+ </span>
+ <span class="action-area {viewMode}">
+ <button class="edit"></button><button class="delete"></button>
+ </span>
+ </div>
+</script>
+<script id="template-interface-tmpl" type="text/html">
+ <div class="item">
+ <span class="template-interface-cell">
+ <input class="template-interface-name {viewMode}" readonly="true" type="text" value={network} />
+ <select class="{editMode}"></select>
+ </span>
+ <span class="template-interface-cell">
+ <input value={type} readonly=true type="text" />
+ </span>
+ <span class="action-area {editMode}">
+ <button class="save"></button><button class="cancel"></button>
+ </span>
+ <span class="action-area {viewMode}">
+ <button class="edit"></button><button class="delete"></button>
+ </span>
+ </div>
+</script>
\ No newline at end of file
--
1.7.1
10 years
[v2 1/1] Virtual machine migration
by Simon Jin
Issue:
Migraton SSH authentication,
even 2 peers have ssh key exchanged, migration still failed due to failed authentication,
first of all, still need passwd input to get a remote libvirt connection
second, after that, migration still failed with error:
libvirt: QEMU Driver error : 操作失败: Failed to connect to remote libvirt URI qemu+ssh://root@9.115.122.75/system: Cannot recv data: Permission denied, please try again.
Permission denied, please try again.
Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).: Connection reset by peer
How to test:
Apply this patch(did some hack to easy debug)
and issue migrate action with:
sudo curl -k -u use:password -H "Content-Type: application/json" -H "Accept: application/json" https://localhost:8001/vms/RHEL-7/migrate -X POST -d '{"remoteHost": "9.115.122.75", "user": "root", "passwd": "passwd"}'
v2:
- use generate_action_handler_task
- Fix args in generate_action_handler_task
- Remove MigrationError
- Put import in alphabetic
- Support offline migration
Signed-off-by: Simon Jin <simonjin(a)linux.vnet.ibm.com>
---
docs/API.md | 4 ++
src/kimchi/control/vms.py | 1 +
src/kimchi/i18n.py | 10 ++++
src/kimchi/model/vms.py | 145 ++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 160 insertions(+)
diff --git a/docs/API.md b/docs/API.md
index 9c06f85..36e5ab6 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -132,6 +132,10 @@ the following general conventions:
It emulates the power reset button on a machine. Note that there is a
risk of data loss caused by reset without the guest OS shutdown.
* connect: Prepare the connection for spice or vnc
+* migrate: Migrate a virtual machine to a remote server, only support live mode without block migration.
+ * remoteHost: IP address or hostname of the remote server.
+ * user: user of the remote server.
+ * passwd: passwd of user on remote server.
### Sub-resource: Virtual Machine Screenshot
diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py
index 88d8a81..0ab239e 100644
--- a/src/kimchi/control/vms.py
+++ b/src/kimchi/control/vms.py
@@ -42,6 +42,7 @@ class VM(Resource):
for ident, node in sub_nodes.items():
setattr(self, ident, node(model, self.ident))
self.start = self.generate_action_handler('start')
+ self.migrate = self.generate_action_handler_task('migrate', ['remoteHost', 'user', 'passwd'])
self.poweroff = self.generate_action_handler('poweroff')
self.shutdown = self.generate_action_handler('shutdown')
self.reset = self.generate_action_handler('reset')
diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py
index f789259..6edc595 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -101,6 +101,16 @@ messages = {
"KCHVM0030E": _("Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"),
"KCHVM0031E": _("The guest console password must be a string."),
"KCHVM0032E": _("The life time for the guest console password must be a number."),
+ "KCHVM0033E": _("Can't migrate to localhost."),
+ "KCHVM0034E": _("Can't migrate vm %(name)s on Hypervisor arch %(srcarch)s.to a different Hypervisor arch %(destarch)s."),
+ "KCHVM0035E": _("Can't migrate from %(srchyp)s to a different Hypervisor type %(desthyp)s."),
+ "KCHVM0037E": _("Can't migrate vm %(name)s,which isn't in running state."),
+ "KCHVM0038E": _("Can't migrate vm %(name)s, vm has passthrough device %(hostdevs)s."),
+ "KCHVM0039E": _("Can't migrate vm %(name)s, vm already exist on destination server %(remoteHost)s."),
+ "KCHVM0040E": _("Failed Migrate vm %(name)s due error: %(err)s"),
+ "KCHVM0041E": _("Failed resume vm %(name)s from migration recover. Details: %(err)s"),
+ "KCHVM0042E": _("Failed destory vm %(name)s from migration recover. Details: %(err)s"),
+ "KCHVM0043E": _("Failed disconnect %(remoteuri)s when finish migrate vm %(name)s. Details: %(err)s"),
"KCHVMHDEV0001E": _("VM %(vmid)s does not contain directly assigned host device %(dev_name)s."),
"KCHVMHDEV0002E": _("The host device %(dev_name)s is not allowed to directly assign to VM."),
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py
index f2e4ae3..c6709ca 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -30,16 +30,20 @@ from xml.etree import ElementTree
import libvirt
from cherrypy.process.plugins import BackgroundTask
+from kimchi.utils import add_task
from kimchi import vnc
from kimchi.config import READONLY_POOL_TYPE
from kimchi.exception import InvalidOperation, InvalidParameter
+from kimchi.model.libvirtconnection import LibvirtConnection
from kimchi.exception import NotFoundError, OperationFailed
from kimchi.model.config import CapabilitiesModel
from kimchi.model.templates import TemplateModel
+from kimchi.model.templates import TemplateModel
from kimchi.model.utils import get_vm_name
from kimchi.model.utils import get_metadata_node
from kimchi.model.utils import set_metadata_node
from kimchi.screenshot import VMScreenshot
+#from kimchi.model.vmhostdevs import VMHostDevsModel
from kimchi.utils import import_class, kimchi_log, run_setfacl_set_attr
from kimchi.utils import template_name_from_uri
from kimchi.xmlutils.utils import xpath_get_text, xml_item_update
@@ -514,6 +518,147 @@ class VMModel(object):
except libvirt.libvirtError as e:
raise OperationFailed("KCHVM0019E",
{'name': name, 'err': e.get_error_message()})
+
+ def _preCheck(self, name, destConn, remoteHost):
+ kimchi_log.debug('precheck migrate %s' % name)
+
+ _destConn = destConn.get()
+ if remoteHost in ['localhost', '127.0.0.1']:
+ kimchi_log.debug('vm %s is not in running, only running vm can be migrated' % name)
+ raise OperationFailed("KCHVM00033E", {'name': name,
+ 'remoteHost': remoteHost})
+
+
+ if self.conn.get().getInfo()[0] != _destConn.getInfo()[0]:
+ kimchi_log.debug('vm %s can not migrate to different arch server.' % (name, _destConn.getInfo()[0]))
+ raise OperationFailed("KCHVM00034E", {'name': name,
+ 'srcarch': self.conn.get().getType(),
+ 'destarch': _destConn.getType()})
+
+ if self.conn.get().getType() != _destConn.getType():
+ kimchi_log.debug('vm %s can not migrate to a different hypervisor' % name)
+ raise OperationFailed("KCHVM00035E", {'name': self.name,
+ 'srchyp':self.conn.get().getType(),
+ 'desthyp': _destConn.getType()})
+
+
+ #model.host import DOM_STATE_MAP, vmhostdevs import host,
+ #to void loop import DOM_STATE_MAP, move import VMHostDevsModel here
+ from kimchi.model.vmhostdevs import VMHostDevsModel
+ #check if there is any passthrough devices belong to this vm
+ vmhostdevs = VMHostDevsModel(conn = self.conn)
+ hostdevs = vmhostdevs.get_list(name)
+ if hostdevs:
+ raise OperationFailed("KCHVM00038E", {'name' : name,
+ 'hostdevs': hostdevs})
+ #try: #FIXME
+ # self.get_vm(name, destConn)
+ #except NotFoundError as e:
+ # if e != NotFoundError:
+ # kimchi_log.debug('migrate vm %s already exist on %s' % (name, destConn.getURI()))
+ #raise OperationFailed("KCHVM00039E", {'name' : name,
+ # 'remoteHost': remoteHost})
+
+ def _getRemoteConn(self, remoteHost, user):
+ #open destination connect to migrate
+ transport = 'ssh'
+ duri = 'qemu+%s://%s@%s/system' % (transport,
+ user,remoteHost)
+
+ conn = LibvirtConnection(duri)
+ return conn
+
+ def _preMigrate(self, name):
+ #can't update VM while in Migration source/destination
+ kimchi_log.debug('prepare migrate %s' % name)
+ dom = self.get_vm(name, self.conn)
+ try:
+ dom.migrateSetMaxDowntime(0)
+ except libvirt.libvirtError as e:
+ print e.message
+
+ def _do_migrate(self, name, destConn, remoteHost, cb):
+ _destConn = destConn.get()
+ cb('starting a migration')
+ kimchi_log.debug('migrate %s start' % name)
+ #import pdb
+ #pdb.set_trace()
+ dom = self.get_vm(name, self.conn)
+ flag = 0
+ flag |= (libvirt.VIR_MIGRATE_PEER2PEER |
+ libvirt.VIR_MIGRATE_TUNNELLED)
+
+ state = DOM_STATE_MAP[dom.info()[0]]
+ if state == 'running':
+ flag |= libvirt.VIR_MIGRATE_LIVE
+ try:
+ dom.migrateSetMaxDowntime(0)
+ except libvirt.libvirtError as e:
+ print e.message #FIXME
+
+ elif state == 'shutoff':
+ flag |= libvirt.VIR_MIGRATE_OFFLINE
+ else: #FIXME any other state will prevend migration ?
+ kimchi_log.debug('Can not migrate vm %s when its in %s.' % (name, state))
+ raise OperationFailed("KCHVM00037E", {'name': self.name})
+
+ try:
+ dom.migrate(_destConn, flag, None, None, 0)
+ except libvirt.libvirtError as e:
+ kimchi_log.error('migrate %s to %s failed' % (name,
+ remoteHost))
+ self._recover(cb)
+ raise OperationFailed('KCHPOOL0040E', {'err': e.message,
+ 'name': name})
+ finally:
+ self._finish(name, destConn, cb)
+
+ def migrate(self, name, remoteHost, user, passwd ):
+ destconn = self._getRemoteConn(remoteHost, user)
+
+ flag = self._preCheck(name, destconn, remoteHost)
+ self._preMigrate(name)
+ def cb(message):
+ print message
+
+
+ self._do_migrate(name, destconn, remoteHost,cb)
+
+ task_id = add_task('/vms/%s/migrate' % name, self._do_migrate, self.objstore,
+ {'name' : name, 'destconn': destconn,
+ 'remoteHost': remoteHost})
+
+ # Record vm migrate task for future querying
+ try:
+ with self.objstore as session:
+ session.store('migrate', name, task_id)
+ return task_id
+ except Exception as e:
+ raise OperationFailed('KCHPOOL0037E', {'err': e.message})
+
+ def _recover(self, name, destconn, cb):
+ destvm = self.get_vm(name, destconn)
+ #recover
+ cb('recover from a failed migration',False)
+ kimchi_log.debug('recover from a failed migrate %s' % name)
+ try:
+ destvm.destroy()
+ kimchi_log.error("destory migrate vm on destination server")
+ except libvirt.libvirtError as e:
+ raise OperationFailed('KCHPOOL0042E', {'name' : name,
+ 'err': e.message})
+
+ def _finish(self, name, destConn, cb):
+ _destConn = destConn.get()
+ kimchi_log.debug('finished migrate %s' % name)
+ try:
+ _destConn.close()
+ except libvirt.libvirtError as e:
+ raise OperationFailed('KCHPOOL0043E', {'name' : name,
+ 'remoteuri' : _destConn.getURI(),
+ 'err': e.message})
+ finally:
+ cb('Migrate finished', True)
def poweroff(self, name):
dom = self.get_vm(name, self.conn)
--
1.8.3.1
10 years
[PATCH V3] Edit Template redefined
by Wen Wang
From: Wen Wang <wenwang(a)linux.vnet.ibm.com>
V2 -> V3:
Fix the bug that iscsi and scsi disk size won't change automatically
V1 -> V2:
1) Enable "iSCSI" and "SCSI" for storage.
2) Changed the storage tab from "storage" to "Storage".
3) Fix the defect that when editing/adding "Storage" or "Interface" line
went down issue.
4) "Storage" and "Interface" content can display properly on Remote and
Image created template.
5) Fix the defect that "Image" didn't show properly when using a Image
created template.
This patch redesigned "Edit Template" diaguage in "Templates". New "Edit
Template" will display all the related information into tabs of
"General", "Storage" and "Interface". Due to unfinished back-end work,
functions are not fully supported, which will be finished in the future
work.
Temporary disabled functions:
1) Multiple disk operation with multiple storage pools edit in template
for which reason the add button in "Storage" tab is disabled.
2) iSCSI and SCSI storage pool add is removed since we are going to
allow this kind of operation in the process of creating a VM.
Signed-off-by: Wen Wang <wenwang(a)linux.vnet.ibm.com>
---
ui/css/theme-default/template-edit.css | 116 +++++++----
ui/js/src/kimchi.template_edit_main.js | 366 +++++++++++++++++++++++---------
ui/pages/template-edit.html.tmpl | 168 ++++++++-------
3 files changed, 434 insertions(+), 216 deletions(-)
diff --git a/ui/css/theme-default/template-edit.css b/ui/css/theme-default/template-edit.css
index 4975f1b..094e909 100644
--- a/ui/css/theme-default/template-edit.css
+++ b/ui/css/theme-default/template-edit.css
@@ -17,24 +17,33 @@
*/
#template-edit-window {
font-size: 13px;
- height: 600px;
- width: 1000px;
+ height: 500px;
+ width: 800px;
}
-.template-edit-fieldset {
- float: left;
- padding: 1em;
+#edit-template-tabs {
+ background: none repeat scroll 0 0 transparent;
+ border: medium none;
+ height: 100%;
+ padding: 0;
}
-.template-edit-wrapper-label, .template-edit-wrapper-controls {
+#edit-template-tabs .form-template-inline-wrapper {
+ display: inline-block;
vertical-align: top;
- width: 470px;
}
.template-edit-wrapper-label {
- height: 18px;
- line-height: 18px;
- margin-top: 8px;
+ vertical-align: top;
+ min-width: 100px;
+ height: 35px;
+ line-height: 35px;
+ margin: 7px 0 8px;
+}
+
+.template-edit-wrapper-controls {
+ vertical-align: top;
+ width: 400px;
}
.template-edit-wrapper-controls input[type="text"] {
@@ -56,7 +65,7 @@
.template-edit-wrapper-controls > .dropdown {
margin: 5px 0 0 1px;
- width: 440px;
+ width: 372px;
}
.template-edit-wrapper-controls input[type="text"][disabled] {
@@ -65,41 +74,72 @@
cursor: not-allowed;
}
-.hidden-area {
- display: none;
+#edit-template-tabs .template-tab-header {
+ margin-bottom: 8px;
+ padding-bottom: 2px;
+ font-weight: bold;
+ border-bottom: 1px solid #999999;
+ overflow: hidden;
}
-.template-edit-wrapper-controls .select-list-box {
- width: 464px;
- max-height: 168px;
- overflow: auto;
- margin-top: 5px;
- border: 1px solid #ccc;
+#edit-template-tabs .template-tab-header .action-area {
+ float: right;
+ height: 20px;
+ width: 20px;
}
-.template-edit-wrapper-controls .select-list-box>li>label {
- display: block;
+#edit-template-tabs .template-interface-cell {
+ display: inline-block;
+ width: 250px;
}
-.template-edit-wrapper-controls .select-list-box>li>label>
-input[type="checkbox"] {
- display: none;
+#edit-template-tabs .template-storage-cell{
+ display: inline-block;
+ width: 230px;
+}
+
+#edit-template-tabs .template-storage-cell label {
+ height: 25px;
+ padding: 2px;
+ border: 1px;
}
-.template-edit-wrapper-controls .select-list-box>li>label>.item {
- display: block;
- height: 41px;
- line-height: 41px;
- padding: 0 20px 0 40px;
- border-bottom: 1px solid #ccc;
- box-shadow: 0px 1px 1px #fff;
- text-shadow: -1px -1px 1px #ddd, 1px 1px 1px #fff;
- color: #222;
- font-size: 12px;
+#form-template-storage .template-tab-body select {
+ width: 140px;
}
-.template-edit-wrapper-controls .select-list-box>li>label>
-input[type="checkbox"]:CHECKED+.item {
- background: #f8f8f8 url(../images/theme-default/check-green.png) no-repeat
- 10px center;
+#form-template-storage .template-tab-body input {
+ width: 56px;
+ height: 17px;
}
+
+#form-template-storage .template-tab-body .template-storage-name {
+ width: 220px;
+}
+
+#edit-template-tabs .template-tab-body input[readonly] {
+ background: none repeat scroll 0 0 rgba(0, 0, 0, 0);
+ border-color: transparent;
+ text-overflow: ellipsis;
+}
+
+#edit-template-tabs .template-tab-body .item {
+ height: 25px;
+}
+
+#form-template-interface .template-tab-body select {
+ width: 180px;
+}
+
+#edit-template-tabs .template-tab-body .action-area {
+ float: right;
+}
+
+#edit-template-tabs .template-tab-body .action-area button {
+ width: 20px;
+ height: 20px;
+}
+
+#edit-template-tabs .hide {
+ display: none;
+}
\ No newline at end of file
diff --git a/ui/js/src/kimchi.template_edit_main.js b/ui/js/src/kimchi.template_edit_main.js
index 2f4cc9a..5073c62 100644
--- a/ui/js/src/kimchi.template_edit_main.js
+++ b/ui/js/src/kimchi.template_edit_main.js
@@ -16,19 +16,23 @@
* limitations under the License.
*/
kimchi.template_edit_main = function() {
- var templateEditForm = $('#form-template-edit');
+ var templateEditMain = $('#edit-template-tabs');
var origDisks;
var origPool;
+ var origNetworks;
var templateDiskSize;
- $('#template-name', templateEditForm).val(kimchi.selectedTemplate);
- kimchi.retrieveTemplate(kimchi.selectedTemplate, function(template) {
+ $('#template-name', templateEditMain).val(kimchi.selectedTemplate);
+ templateEditMain.tabs();
+
+ var initTemplate = function(template) {
origDisks = template.disks;
origPool = template.storagepool;
+ origNetworks = template.networks;
for(var i=0;i<template.disks.length;i++){
if(template.disks[i].base){
template["vm-image"] = template.disks[i].base;
- $('#templ-edit-cdrom').addClass('hide-content');
- $('#templ-edit-vm-image').removeClass('hide-content');
+ $('.templ-edit-cdrom').addClass('hide');
+ $('.templ-edit-vm-image').removeClass('hide');
break;
}
}
@@ -37,18 +41,12 @@ kimchi.template_edit_main = function() {
if (prop == 'graphics') {
value = value["type"];
}
- $('input[name="' + prop + '"]', templateEditForm).val(value);
- }
- var disks = template.disks;
- $('input[name="disks"]').val(disks[0].size);
- templateDiskSize = $('input[name="disks"]').val();
- if (disks[0].volume) {
- var spool_value = $('#form-template-edit [name="storagepool"]').val();
- $('input[name="storagepool"]', templateEditForm).val(spool_value + '/' + disks[0].volume);
- $('input[name="disks"]', templateEditForm).attr('disabled','disabled');
+ $('input[name="' + prop + '"]', templateEditMain).val(value);
}
var vncOpt = [{label: 'VNC', value: 'vnc'}];
+ $('#template-edit-graphics').append('<option selected>VNC</option>');
+ $('#template-edit-graphics').append('<option>Spice</option>');
kimchi.select('template-edit-graphics-list', vncOpt);
var enableSpice = function() {
if (kimchi.capabilities == undefined) {
@@ -61,119 +59,283 @@ kimchi.template_edit_main = function() {
}
};
enableSpice();
-
- var scsipools = {};
- kimchi.listStoragePools(function(result) {
- var options = [];
- if (result && result.length) {
- $.each(result, function(index, storagePool) {
- if ((storagePool.state=="active") && (storagePool.type !== 'kimchi-iso')) {
- if ((storagePool.type == 'iscsi') || (storagePool.type == 'scsi')){
- scsipools[storagePool.name] = [];
- kimchi.listStorageVolumes(storagePool.name, function(result) {
- if (result && result.length) {
- $.each(result, function(index, storageVolume) {
- options.push({
- label: storagePool.name + '/' + storageVolume.name,
- value: '/storagepools/' + storagePool.name + '/' + storageVolume.name
- });
- scsipools[storagePool.name].push(storageVolume)
- });
- }
- kimchi.select('template-edit-storagePool-list', options);
- });
- }
- else {
- options.push({
- label: storagePool.name,
- value: '/storagepools/' + storagePool.name
- });
- }
+ var initStorage = function(result) {
+ var scsipools = {};
+ var addStorageItem = function(storageData) {
+ var thisName;
+ var thisType;
+ var thisDisk;
+ var nodeStorage = $.parseHTML(kimchi.substitute($('#template-storage-pool-tmpl').html(), storageData));
+ $('.template-tab-body', '#form-template-storage').append(nodeStorage);
+ $('.edit', '#form-template-storage').button({
+ icons : {primary : 'ui-icon-pencil'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ var storageItem = $(this).parent().parent();
+ thisName = $('.template-storage-name', storageItem).val();
+ thisType = $('.template-storage-type', storageItem).val();
+ thisDisk = $('.template-storage-disk', storageItem).val();
+ $('.template-storage-name', storageItem).hide();
+ $('.template-storage-disk', storageItem).attr('readonly', false);
+ if (thisType === 'iscsi' || thisType === 'scsi') {
+ $('.template-storage-disk', storageItem).attr('readonly', true);
+ } else {
+ $('.template-storage-disk', storageItem).attr('readonly', false);
}
+ $('.save', storageItem).parent().show();
+ $('.delete', storageItem).parent().hide();
+ var selectedStorage = $('select', storageItem).val();
+ $('.template-storage-name', storageItem).val(selectedStorage).hide();
+ $('select', storageItem).val(thisName).show();
});
- }
- if ($.isEmptyObject(scsipools)) {
- kimchi.select('template-edit-storagePool-list', options);
- }
- });
- kimchi.listNetworks(function(result) {
- if(result && result.length > 0) {
- var html = '';
- var tmpl = $('#tmpl-network').html();
- $.each(result, function(index, network) {
- if (result[index].state === 'active')
- html += kimchi.substitute(tmpl, network);
+ $('.delete', '#form-template-storage').button({
+ icons : {primary : 'ui-icon-trash'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ $(this).parent().parent().remove();
+ });
+ $('.cancel', '#form-template-storage').button({
+ icons : {primary : 'ui-icon-arrowreturnthick-1-w'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ var cancelEntity = $(this).parent().parent();
+ if ($('.template-storage-name', cancelEntity).val() === 'null') {
+ cancelEntity.remove();
+ } else {
+ $('select', cancelEntity).hide();
+ $('.template-storage-name', cancelEntity).val(thisName).attr('readonly', true).show();
+ $('.template-storage-type', cancelEntity).val(thisType).attr('readonly', true);
+ $('.template-storage-disk', cancelEntity).val(thisDisk).attr('readonly', true);
+ $('.save', cancelEntity).parent().hide();
+ $('.edit', cancelEntity).parent().show();
+ }
+ });
+ $('.save', '#form-template-storage').button({
+ icons : {primary : 'ui-icon-disk'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ var storageItem = $(this).parent().parent();
+ $('.save', storageItem).parent().hide();
+ $('.delete', storageItem).parent().show();
+ var selectedStorage = $('select', storageItem).val();
+ $('.template-storage-name', storageItem).val(selectedStorage).attr('readonly', true).show();
+ $('.template-storage-disk', storageItem).attr('readonly', true);
+ $('.template-storage-type', storageItem).attr('readonly', true);
+ $('select', storageItem).hide();
});
- $('#template-edit-network-list').html(html).show();
- if(template.networks && template.networks.length > 0) {
- $('input[name="networks"]', templateEditForm).each(function(index, element) {
- var value = $(element).val();
- if(template.networks.indexOf(value) >= 0) {
- $(element).prop('checked', true);
+ var storageOptions = '';
+ var scsiOptions = '';
+ $('select', '#form-template-storage').find('option').remove();
+ $.each(result, function(index, storageEntities) {
+ if((storageEntities.state === 'active') && (storageEntities.type != 'kimchi-iso')) {
+ if(storageEntities.type === 'iscsi' || storageEntities.type === 'scsi') {
+ kimchi.listStorageVolumes(storageEntities.name, function(currentVolume) {
+ $.each(currentVolume, function(indexSCSI, scsiEntities) {
+ scsiOptions += '<option>' + storageEntities.name + '/' + scsiEntities.name + '</option>';
+ });
+ $('select', '#form-template-storage').append(scsiOptions);
+ }, function() {});
+ } else {
+ var isSlected = storageEntities.name === 'default' ? ' selected' : '';
+ storageOptions += '<option' + isSlected + '>' + storageEntities.name + '</option>';
+ }
+ }
+ });
+ $('select', '#form-template-storage').append(storageOptions);
+ $('select', '#form-template-storage').change(function() {
+ var selectedItem = $(this).parent().parent();
+ var tempStorageName = $(this).val();
+ var tempType;
+ tempStorageName =tempStorageName.split('/')[0];
+ var scsiCap;
+ $.each(result, function(index, storageEntities) {
+ if (tempStorageName === storageEntities.name) {
+ selectedItem.find('.template-storage-type').val(storageEntities.type);
+ scsiCap = storageEntities.capacity / Math.pow(1024, 3);
+ tempType = storageEntities.type;
}
});
+ if (tempType === 'iscsi' || tempType === 'scsi') {
+ $('.template-storage-disk', selectedItem).attr('readonly', true).val(scsiCap);
+ } else {
+ $('.template-storage-disk', selectedItem).attr('readonly', false).val('10');
+ }
+ });
+ };
+
+ if ((origDisks && origDisks.length) && (origPool && origPool.length)) {
+ splitPool = origPool.split('/');
+ var defaultPool;
+ var defaultType;
+ $.each(result, function(index, poolEntities) {
+ if (poolEntities.name === splitPool[splitPool.length-1]) {
+ defaultType = poolEntities.type;
+ defaultPool = splitPool[splitPool.length-1]
+ }
+ });
+ if (origDisks[0]['volume']) {
+ defaultPool = defaultPool + '/' + origDisks[0]['volume'];
}
- } else {
- $('#template-edit-network-list').hide();
+ $.each(origDisks, function(index, diskEntities) {
+ var storageNodeData = {
+ viewMode : '',
+ editMode : 'hide',
+ storageName : defaultPool,
+ storageType : defaultType,
+ storageDisk : diskEntities.size
+ }
+ addStorageItem(storageNodeData);
+ });
+ $('.template-storage-disk').attr('readonly', true);
}
- });
- });
- $('#template-edit-storagePool').change(function() {
- storagepool = $(this).val();
- var storageArray = storagepool.split("/");
- if (storageArray.length > 3) {
- volumeName = storageArray.pop();
- poolName = storageArray.pop();
- kimchi.getStoragePoolVolume(poolName, volumeName, function(result) {
- $('input[name="disks"]', templateEditForm).val(result.capacity / Math.pow(1024,3));
- $('input[name="disks"]', templateEditForm).attr('disabled','disabled');
- return false;
- }, function (err) {
- kimchi.message.error(err.responseJSON.reason);
+ $('#template-edit-storage-add-button').button({
+ icons: {
+ primary: "ui-icon-plusthick"
+ },
+ text: false,
+ disabled: true
+ }).click(function(event) {
+ event.preventDefault();
+ var storageNodeData = {
+ viewMode : 'hide',
+ editMode : '',
+ storageName : 'null',
+ storageType : 'dir',
+ storageDisk : '10'
+ }
+ addStorageItem(storageNodeData);
});
- } else {
- $('input[name="disks"]', templateEditForm).removeAttr('disabled');
- $('input[name="disks"]', templateEditForm).val(templateDiskSize);
- }
- });
- $('input[name="disks"]', templateEditForm).keyup(function() {
- templateDiskSize = $('input[name="disks"]', templateEditForm).val();
- });
+ };
+ var initInterface = function(result) {
+ var addInterfaceItem = function(networkData) {
+ var nodeInterface = $.parseHTML(kimchi.substitute($('#template-interface-tmpl').html(), networkData));
+ $('.template-tab-body', '#form-template-interface').append(nodeInterface);
+ $('.edit', '#form-template-interface').button({
+ icons : {primary : 'ui-icon-pencil'},
+ text : false,
+ disabled : true
+ });
+ $('.delete', '#form-template-interface').button({
+ icons : {primary : 'ui-icon-trash'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ $(this).parent().parent().remove();
+ });
+ $('.cancel', '#form-template-interface').button({
+ icons : {primary : 'ui-icon-arrowreturnthick-1-w'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ $(this).parent().parent().remove();
+ });
+ $('.save', '#form-template-interface').button({
+ icons : {primary : 'ui-icon-disk'},
+ text : false
+ }).click(function(evt) {
+ evt.preventDefault();
+ var interItem = $(this).parent().parent();
+ $('.save', interItem).parent().hide();
+ $('.delete', interItem).parent().show();
+ var selectedInterface = $('select', interItem).val();
+ $('.template-interface-name', interItem).val(selectedInterface).show();
+ $('select', interItem).hide();
+ });
+ var networkOptions = '';
+ for(var i=0;i<result.length;i++){
+ if(result[i].state === "active") {
+ var isSlected = i==0 ? ' selected' : '';
+ networkOptions += '<option' + isSlected + '>' + result[i].name + '</option>';
+ }
+ }
+ $('select', '#form-template-interface').find('option').remove();
+ $('select', '#form-template-interface').append(networkOptions);
+ };
+ if(result && result.length > 0) {
+ $.each(result, function(index, data) {
+ if($.inArray(data.name, origNetworks) > -1) {
+ addInterfaceItem({
+ mac : '',
+ network : data.name,
+ type : 'network',
+ viewMode : '',
+ editMode : 'hide'
+ });
+ }
+ });
+ }
+ $('#template-edit-interface-add-button').button({
+ icons: {
+ primary: 'ui-icon-plusthick'
+ },
+ text: false
+ }).click(function(evt) {
+ evt.preventDefault();
+ addInterfaceItem({
+ mac : '',
+ network : '',
+ type : 'network',
+ viewMode : 'hide',
+ editMode : ''
+ });
+ });
+ };
+ kimchi.listNetworks(initInterface);
+ kimchi.listStoragePools(initStorage);
+ };
+ kimchi.retrieveTemplate(kimchi.selectedTemplate, initTemplate);
+
$('#tmpl-edit-button-save').on('click', function() {
- var editableFields = [ 'name', 'cpus', 'memory', 'storagepool', 'disks', 'graphics'];
+ var editableFields = [ 'name', 'cpus', 'memory', 'disks', 'graphics'];
var data = {};
+ //Fix me: Only support one storage pool now
+ var storages = $('.template-tab-body .item', '#form-template-storage');
+ var tempName = $('.template-storage-name', storages).val();
+ tempName = tempName.split('/');
+ var tempNameHead =tempName[0];
+ var tempNameTail = tempNameHead;
+ if(tempNameHead === 'iscsi' || tempNameHead =='scsi') {
+ tempNameTail = tempName[tempName.length-1];
+ }
+ tempName = '/storagepools/' + tempNameHead;
+ data['storagepool'] = tempName;
$.each(editableFields, function(i, field) {
/* Support only 1 disk at this moment */
if (field == 'disks') {
- origDisks[0].size = Number($('#form-template-edit [name="' + field + '"]').val());
+ var tmpItem = $('#form-template-storage .item');
+ origDisks[0].size = Number($('.template-storage-disk', tmpItem).val());
+ if($('.template-storage-type', tmpItem).val() === 'iscsi' || $('.template-storage-type', tmpItem).val() =='scsi') {
+ origDisks[0]['volume'] = tempNameTail;
+ } else {
+ origDisks[0]['volume'] && delete origDisks[0]['volume'];
+ }
data[field] = origDisks;
}
else if (field == 'graphics') {
- var type = $('#form-template-edit [name="' + field + '"]').val();
+ var type = $('#form-template-general [name="' + field + '"]').val();
data[field] = {'type': type};
}
else {
- data[field] = $('#form-template-edit [name="' + field + '"]').val();
+ data[field] = $('#form-template-general [name="' + field + '"]').val();
}
});
data['memory'] = Number(data['memory']);
data['cpus'] = Number(data['cpus']);
- storagepool = data['storagepool'];
- storageArray = storagepool.split("/");
- if (storageArray.length > 3){
- /* Support only 1 disk at this moment */
- data["disks"][0].volume = storageArray.pop();
- data['storagepool'] = storageArray.join("/");
- } else if (data["disks"][0].volume) {
- delete data["disks"][0].volume;
- }
- var networks = templateEditForm.serializeObject().networks;
- if (networks instanceof Array) {
- data.networks = networks;
- } else if (networks != null) {
- data.networks = [networks];
+ var networks = $('.template-tab-body .item', '#form-template-interface');
+ var networkForUpdate = new Array();
+ $.each(networks, function(index, networkEntities) {
+ networkForUpdate.push($('.template-interface-name', networkEntities).val());
+ });
+ if (networkForUpdate instanceof Array) {
+ data.networks = networkForUpdate;
+ } else if (networkForUpdate != null) {
+ data.networks = [networkForUpdate];
} else {
data.networks = [];
}
diff --git a/ui/pages/template-edit.html.tmpl b/ui/pages/template-edit.html.tmpl
index 5a71d91..018ac10 100644
--- a/ui/pages/template-edit.html.tmpl
+++ b/ui/pages/template-edit.html.tmpl
@@ -28,74 +28,67 @@
<div class="close">X</div>
</header>
<div class="content">
- <form id="form-template-edit">
+ <div id="edit-template-tabs">
<input type="hidden" id="template-name" name="templateName" />
- <fieldset class="template-edit-fieldset">
- <div>
+ <ul>
+ <li>
+ <a href="#form-template-general">$_("General")</a>
+ </li>
+ <li>
+ <a href="#form-template-storage">$_("Storage")</a>
+ </li>
+ <li>
+ <a href="#form-template-interface">$_("Interface")</a>
+ </li>
+ </ul>
+ <form id="form-template-general">
+ <div class="form-template-inline-wrapper">
<div class="template-edit-wrapper-label">
<label for="template-edit-id-textbox">$_("Name")</label>
</div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-id-textbox" name="name" type="text" />
- </div>
- </div>
- <div>
<div class="template-edit-wrapper-label">
<label for="template-edit-vendor-textbox">$_("Vendor")</label>
</div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-vendor-textbox" name="os_distro" type="text" disabled="disabled" />
- </div>
- </div>
- <div>
<div class="template-edit-wrapper-label">
<label for="template-edit-version-textbox">$_("Version")</label>
</div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-version-textbox" name="os_version" type="text" disabled="disabled" />
- </div>
- </div>
- <div>
<div class="template-edit-wrapper-label">
<label for="template-edit-cpu-textbox">$_("CPU Number")</label>
</div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-cpu-textbox" name="cpus" type="text" />
- </div>
- </div>
- <div>
<div class="template-edit-wrapper-label">
<label for="template-edit-memory-textbox">$_("Memory (MB)")</label>
</div>
- <div class="template-edit-wrapper-controls">
- <input id="template-edit-memory-textbox" name="memory" type="text" />
+ <div class="template-edit-wrapper-label templ-edit-cdrom">
+ <label for="template-edit-cdrom-textbox">$_("CDROM")</label>
+ </div>
+ <div class="template-edit-wrapper-label templ-edit-vm-image hide">
+ <label for="template-edit-vmimage-textbox">$_("Image File")</label>
</div>
- </div>
- <div>
<div class="template-edit-wrapper-label">
- <label for="template-edit-disk-textbox">$_("Disk (GB)")</label>
+ <label>$_("Graphics")</label>
</div>
+ </div>
+ <div class="form-template-inline-wrapper">
<div class="template-edit-wrapper-controls">
- <input id="template-edit-disk-textbox" name="disks" type="text" />
+ <input id="template-edit-id-textbox" name="name" type="text" />
</div>
- </div>
- </fieldset>
- <fieldset class="template-edit-fieldset">
- <div id="templ-edit-cdrom">
- <div class="template-edit-wrapper-label">
- <label for="template-edit-cdrom-textbox">$_("CDROM")</label>
+ <div class="template-edit-wrapper-controls">
+ <input id="template-edit-vendor-textbox" name="os_distro" type="text" disabled="disabled" />
</div>
<div class="template-edit-wrapper-controls">
- <input id="template-edit-cdrom-textbox" name="cdrom" type="text" disabled="disabled"/>
+ <input id="template-edit-version-textbox" name="os_version" type="text" disabled="disabled" />
</div>
- </div>
- <div id="templ-edit-vm-image" class="hide-content">
- <div class="template-edit-wrapper-label">$_("Image File")</div>
- <div class="template-edit-wrapper-controls"><input name="vm-image" type="text" disabled/></div>
- </div>
- <div>
- <div class="template-edit-wrapper-label">
- <label>$_("Graphics")</label>
+ <div class="template-edit-wrapper-controls">
+ <input id="template-edit-cpu-textbox" name="cpus" type="text" />
+ </div>
+ <div class="template-edit-wrapper-controls">
+ <input id="template-edit-memory-textbox" name="memory" type="text" />
+ </div>
+ <div class="template-edit-wrapper-controls templ-edit-cdrom">
+ <input id="template-edit-cdrom-textbox" name="cdrom" type="text" disabled="disabled" />
+ </div>
+ <div class="template-edit-wrapper-controls templ-edit-vm-image hide">
+ <input id="template-edit-vmimage-textbox" name="vm-image" type="text" disabled="disabled" />
</div>
<div class="template-edit-wrapper-controls">
<div class="btn dropdown popable">
@@ -108,40 +101,26 @@
</div>
</div>
</div>
- <div>
- <div class="template-edit-wrapper-label">
- <label>$_("Storage Pool")</label>
- </div>
- <div class="template-edit-wrapper-controls">
- <div class="btn dropdown popable">
- <input id="template-edit-storagePool" name="storagepool" type="hidden" />
- <span class="text" id="template-edit-storage-label"></span><span class="arrow"></span>
- <div class="popover" style="width: 100%">
- <ul class="select-list" id="template-edit-storagePool-list" data-target="template-edit-storagePool" data-label="template-edit-storage-label">
- </ul>
- </div>
- </div>
- </div>
+ </form>
+ <form id="form-template-storage">
+ <div class="template-tab-header">
+ <span class="template-storage-cell">$_("Storage Pool")</span>
+ <span class="template-storage-cell">$_("Type")</span>
+ <span class="template-storage-cell">$_("Disk(GB)")</span>
+ <button type="button" id="template-edit-storage-add-button" class="action-area"></button>
</div>
- <div>
- <div class="template-edit-wrapper-label">
- <label>$_("Network")</label>
- </div>
- <div class="template-edit-wrapper-controls">
- <ul class="select-list-box" id="template-edit-network-list">
- </ul>
- <script id="tmpl-network" type="text/html">
- <li>
- <label>
- <input name="networks" type="checkbox" value="{name}" />
- <span class="item">{name}</span>
- </label>
- </li>
- </script>
- </div>
+ <div class="template-tab-body">
</div>
- </fieldset>
- </form>
+ </form>
+ <form id="form-template-interface">
+ <div class="template-tab-header">
+ <span class="template-interface-cell">$_("Network")</span>
+ <span class="template-interface-cell">$_("Type")</span>
+ <button type="button" id="template-edit-interface-add-button" class="action-area"></button>
+ </div>
+ <div class="template-tab-body"></div>
+ </form>
+ </div>
</div>
<footer>
<div class="btn-group">
@@ -152,3 +131,40 @@
<script>
kimchi.template_edit_main();
</script>
+<script id="template-storage-pool-tmpl" type="text/html">
+ <div class='item'>
+ <span class="template-storage-cell">
+ <input class="template-storage-name {viewMode}" value={storageName} readonly=true type="text"/>
+ <select class="{editMode}"></select>
+ </span>
+ <span class="template-storage-cell">
+ <input class="template-storage-type" value={storageType} readonly=true type="text" />
+ </span>
+ <span class="template-storage-cell">
+ <input class="template-storage-disk" value={storageDisk} type="text" />
+ </span>
+ <span class="action-area {editMode}">
+ <button class="save"></button><button class="cancel"></button>
+ </span>
+ <span class="action-area {viewMode}">
+ <button class="edit"></button><button class="delete"></button>
+ </span>
+ </div>
+</script>
+<script id="template-interface-tmpl" type="text/html">
+ <div class="item">
+ <span class="template-interface-cell">
+ <input class="template-interface-name {viewMode}" readonly="true" type="text" value={network} />
+ <select class="{editMode}"></select>
+ </span>
+ <span class="template-interface-cell">
+ <input value={type} readonly=true type="text" />
+ </span>
+ <span class="action-area {editMode}">
+ <button class="save"></button><button class="cancel"></button>
+ </span>
+ <span class="action-area {viewMode}">
+ <button class="edit"></button><button class="delete"></button>
+ </span>
+ </div>
+</script>
\ No newline at end of file
--
1.7.1
10 years
[PATCH] Enable storage volume upload
by Aline Manera
This patch enables storage volume upload and close the upload dialog
right before send the POST request.
It is needed to improve the user usability as the POST request takes around
5 minutes to be sent for an ISO ~1G.
So before send the POST request, the upload file name is saved in a
cookie according to its pool to be used later to update the UI.
When listing the storage volumes, the cookie will be read to retrieve
those storage volumes and create a fake entry on UI (in a similar way we
did for on going tasks).
That way the POST request will be sent in background and do not block
user on the Storage tab.
Signed-off-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
---
ui/js/src/kimchi.storage_main.js | 40 +++++++++++++++++--------
ui/js/src/kimchi.storagepool_add_volume_main.js | 20 ++++++++++++-
ui/pages/storagepool-add-volume.html.tmpl | 4 +--
3 files changed, 48 insertions(+), 16 deletions(-)
diff --git a/ui/js/src/kimchi.storage_main.js b/ui/js/src/kimchi.storage_main.js
index 9a45b65..1659351 100644
--- a/ui/js/src/kimchi.storage_main.js
+++ b/ui/js/src/kimchi.storage_main.js
@@ -233,21 +233,29 @@ kimchi.doListVolumes = function(poolObj) {
var listHtml = '';
var ongoingVolumes = [];
var ongoingVolumesMap = getOngoingVolumes();
+ var volume = {
+ poolName: poolName,
+ ref_cnt: 0,
+ capacity: 0,
+ format: '',
+ bootable: null,
+ os_distro: '',
+ allocation: 0,
+ os_version: '',
+ path: '',
+ type: 'file'
+ };
+
+ onholdVolumes = JSON.parse(kimchi.cookie.get('uploadVolumes') || '{}')
+ onholdVolumes = onholdVolumes[poolName] || []
+ $.each(onholdVolumes, function(index) {
+ volume.name = onholdVolumes[index]
+ listHtml += kimchi._generateVolumeHTML(volume);
+ });
+
$.each(ongoingVolumesMap, function(volumeName, task) {
ongoingVolumes.push(volumeName)
- var volume = {
- poolName: poolName,
- ref_cnt: 0,
- capacity: 0,
- name: volumeName,
- format: '',
- bootable: null,
- os_distro: '',
- allocation: 0,
- os_version: '',
- path: '',
- type: 'file'
- };
+ volume.name = volumeName
listHtml += kimchi._generateVolumeHTML(volume);
});
@@ -264,6 +272,12 @@ kimchi.doListVolumes = function(poolObj) {
volumeDiv.html("<div class='pool-empty'>" + i18n['KCHPOOL6002M'] + "</div>");
}
+ $.each(onholdVolumes, function(index) {
+ volumeBox = $('#volume' + poolName + ' [data-volume-name="' + onholdVolumes[index] + '"]');
+ $('.volume-progress', volumeBox).removeClass('hidden');
+ $('.progress-status', volumeBox).text(i18n['KCHPOOL6014M']);
+ });
+
$.each(ongoingVolumesMap, function(volumeName, task) {
kimchi.topic('kimchi/volumeTransferProgress').publish(task);
});
diff --git a/ui/js/src/kimchi.storagepool_add_volume_main.js b/ui/js/src/kimchi.storagepool_add_volume_main.js
index 590ccde..73877a2 100644
--- a/ui/js/src/kimchi.storagepool_add_volume_main.js
+++ b/ui/js/src/kimchi.storagepool_add_volume_main.js
@@ -84,11 +84,29 @@ kimchi.sp_add_volume_main = function() {
var fd = new FormData();
fd.append('name', fileName);
fd.append('file', blobFile);
+
+
+ volName = $('#volume-input-file').val()
+ uploadVolumes = JSON.parse(kimchi.cookie.get('uploadVolumes') || '{}');
+ vols = uploadVolumes[kimchi.selectedSP] || [];
+ vols.push(volName);
+ uploadVolumes[kimchi.selectedSP] = vols;
+ kimchi.cookie.set('uploadVolumes', JSON.stringify(uploadVolumes));
+
+ kimchi.topic('kimchi/storageVolumeAdded').publish();
+ kimchi.window.close();
+
kimchi.uploadVolumeToSP({
sp: kimchi.selectedSP,
formData: fd
}, function(result) {
- kimchi.window.close();
+ uploadVolumes = JSON.parse(kimchi.cookie.get('uploadVolumes') || '{}');
+ vols = uploadVolumes[kimchi.selectedSP] || [];
+ vols = jQuery.grep(vols, function(name) {
+ return name !== volName;
+ });
+ uploadVolumes[kimchi.selectedSP] = vols;
+ kimchi.cookie.set('uploadVolumes', JSON.stringify(uploadVolumes));
kimchi.topic('kimchi/storageVolumeAdded').publish();
}, onError);
};
diff --git a/ui/pages/storagepool-add-volume.html.tmpl b/ui/pages/storagepool-add-volume.html.tmpl
index 573a764..0927948 100644
--- a/ui/pages/storagepool-add-volume.html.tmpl
+++ b/ui/pages/storagepool-add-volume.html.tmpl
@@ -47,14 +47,14 @@
</div>
<div class="form-section">
<h2>
- <input type="radio" id="volume-type-upload" class="volume-type" name="volumeType" value="upload" disabled/>
+ <input type="radio" id="volume-type-upload" class="volume-type" name="volumeType" value="upload"/>
<label for="volume-type-upload">
$_("Upload an file")
</label>
</h2>
<div class="field">
<p class="text-help">
- $_("Choose the ISO file (with .iso suffix) you want to upload.")
+ $_("Choose the file you want to upload.")
</p>
<div class="textbox-wrapper">
<input type="file" class="volume-input upload" id="volume-input-file" name="volumeLocalFile" disabled="disabled" />
--
1.9.3
10 years