[PATCH 0/2] Fix attaching iscsi storage volume
by lvroyce@linux.vnet.ibm.com
From: Royce Lv <lvroyce(a)linux.vnet.ibm.com>
Iscsi volume fails to attach to vm because of its path is rejected
by previous cdrom validate logic,
and type 'unknown' forms wrong vm xml
This patchset fix the above bugs and form right vm xml in order to
start vm.
Royce Lv (2):
Guest storage: Fix attaching type judgement
Guest storage: fix volume format overwrite
src/kimchi/model/vmstorages.py | 16 +++++-----------
1 file changed, 5 insertions(+), 11 deletions(-)
--
1.8.3.2
10 years, 3 months
[PATCH] Fix sample plugin configuration
by Aline Manera
Since the authorization method changed, kimchiauth() does not accept any
parameter. The admin methods should be listed on Collection and
Resource elements.
Signed-off-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
---
plugins/sample/__init__.py | 2 ++
plugins/sample/sample.conf.in | 2 --
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/plugins/sample/__init__.py b/plugins/sample/__init__.py
index 4aca4e0..10c244c 100644
--- a/plugins/sample/__init__.py
+++ b/plugins/sample/__init__.py
@@ -64,12 +64,14 @@ class Circles(Collection):
def __init__(self, model):
super(Circles, self).__init__(model)
self.resource = Circle
+ self.admin_methods = ['POST', 'PUT']
class Rectangles(Collection):
def __init__(self, model):
super(Rectangles, self).__init__(model)
self.resource = Rectangle
+ self.admin_methods = ['POST', 'PUT']
class Circle(Resource):
diff --git a/plugins/sample/sample.conf.in b/plugins/sample/sample.conf.in
index 6e0908b..cf42467 100644
--- a/plugins/sample/sample.conf.in
+++ b/plugins/sample/sample.conf.in
@@ -17,8 +17,6 @@ tools.kimchiauth.on = True
[/rectangles]
tools.kimchiauth.on = True
-tools.kimchiauth.admin_methods = ['POST', 'PUT']
[/circles]
tools.kimchiauth.on = True
-tools.kimchiauth.admin_methods = ['POST', 'PUT']
--
1.9.3
10 years, 3 months
[PATCH 0/6 V2] Discover Kimchi peers using openSLP
by Aline Manera
v1 -> V2:
- Update docs/API.md
- Expose federation on /config/capabilities
Aline Manera (6):
Update kimchi.config values according to command line input
Delete http_port from /config API as it is not in use anymore
Add federation option to Kimchi config file
Discover Kimchi peers using openSLP
Add documentation on how to enable federation on Kimchi
Expose federation on /config/capabilities
docs/API.md | 11 ++++++++-
docs/README-federation.md | 27 +++++++++++++++++++++
src/kimchi.conf.in | 4 +++
src/kimchi/config.py.in | 1 +
src/kimchi/control/peers.py | 29 ++++++++++++++++++++++
src/kimchi/mockmodel.py | 14 ++++++++---
src/kimchi/model/config.py | 6 ++---
src/kimchi/model/peers.py | 59 +++++++++++++++++++++++++++++++++++++++++++++
src/kimchi/server.py | 1 -
src/kimchid.in | 36 +++++++++++++++++----------
tests/test_rest.py | 19 +++++++++------
11 files changed, 177 insertions(+), 30 deletions(-)
create mode 100644 docs/README-federation.md
create mode 100644 src/kimchi/control/peers.py
create mode 100644 src/kimchi/model/peers.py
--
1.9.3
10 years, 3 months
[PATCH] bug fix: Properly get the graphics expiration time
by Aline Manera
The data structure to graphics is:
{
graphics: {'passwd': '12345', 'passwdValidTo': 30}
}
Signed-off-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
---
src/kimchi/model/vms.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py
index fb4cf22..8045b1e 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -321,7 +321,7 @@ class VMModel(object):
if password is not None:
graphics.attrib['passwd'] = password
- expire = params.get("passwdValidTo")
+ expire = params['graphics'].get("passwdValidTo")
to = graphics.attrib.get('passwdValidTo')
if to is not None:
if (time.mktime(time.strptime(to, '%Y-%m-%dT%H:%M:%S'))
--
1.9.3
10 years, 3 months
[PATCH] model.host: changing listDevices() to listAllDevices()
by Daniel Henrique Barboza
The method listDevices() has unexpected behavior in certain
situations and architectures. According the libvirt API,
listAllDevices() delivers more control over the results and
it does not present the same problems.
For those reasons, this commit changes all the occurences of
listDevices() to use listAllDevices() instead.
Signed-off-by: Daniel Henrique Barboza <danielhb(a)linux.vnet.ibm.com>
---
src/kimchi/model/host.py | 33 ++++++++++++++++++++++++---------
1 file changed, 24 insertions(+), 9 deletions(-)
diff --git a/src/kimchi/model/host.py b/src/kimchi/model/host.py
index 39f45d8..553ad7c 100644
--- a/src/kimchi/model/host.py
+++ b/src/kimchi/model/host.py
@@ -18,6 +18,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import grp
+import libvirt
import os
import time
import platform
@@ -277,31 +278,45 @@ class PartitionModel(object):
class DevicesModel(object):
def __init__(self, **kargs):
self.conn = kargs['conn']
+ self.cap_map = \
+ {'fc_host': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_FC_HOST,
+ 'net': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_NET,
+ 'pci': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_PCI_DEV,
+ 'scsi': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_SCSI,
+ 'scsi_host': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_SCSI_HOST,
+ 'storage': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_STORAGE,
+ 'usb_device': libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_USB_DEV,
+ 'usb':
+ libvirt.VIR_CONNECT_LIST_NODE_DEVICES_CAP_USB_INTERFACE}
def get_list(self, _cap=None):
+ if _cap == 'fc_host':
+ return self._get_devices_fc_host()
+ return self._get_devices_with_capability(_cap)
+
+ def _get_devices_with_capability(self, cap):
conn = self.conn.get()
- if _cap is None:
- dev_names = [name.name() for name in conn.listAllDevices(0)]
- elif _cap == 'fc_host':
- dev_names = self._get_devices_fc_host()
+ if cap is None:
+ cap_flag = 0
else:
- # Get devices with required capability
- dev_names = conn.listDevices(_cap, 0)
- return dev_names
+ cap_flag = self.cap_map.get(cap)
+ if cap_flag is None:
+ return []
+ return [name.name() for name in conn.listAllDevices(cap_flag)]
def _get_devices_fc_host(self):
conn = self.conn.get()
# Libvirt < 1.0.5 does not support fc_host capability
if not CapabilitiesModel().fc_host_support:
ret = []
- scsi_hosts = conn.listDevices('scsi_host', 0)
+ scsi_hosts = self._get_devices_with_capability('scsi_host')
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):
ret.append(host)
return ret
- return conn.listDevices('fc_host', 0)
+ return self._get_devices_with_capability('fc_host')
class DeviceModel(object):
--
1.8.3.1
10 years, 3 months
[PATCH] bug fix: Auto-generate guest console password when the passed value is an empty string
by Aline Manera
As the password will be an empty string we need to check if its value is
not None, otherwise the password will not be generated.
Signed-off-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
---
src/kimchi/model/vms.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py
index e91c0ed..d8a02cd 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -312,7 +312,7 @@ class VMModel(object):
return xml
password = params['graphics'].get("passwd")
- if password and len(password.strip()) == 0:
+ if password is not None and len(password.strip()) == 0:
password = "".join(random.sample(string.ascii_letters +
string.digits, 8))
--
1.9.3
10 years, 3 months
[V3] UI: List iSCSI Servers & Targets
by huoyuxin@linux.vnet.ibm.com
From: Yu Xin Huo <huoyuxin(a)linux.vnet.ibm.com>
Signed-off-by: Yu Xin Huo <huoyuxin(a)linux.vnet.ibm.com>
---
ui/css/theme-default/storage.css | 31 ++++++++++-
ui/js/src/kimchi.api.js | 20 +++++++-
ui/js/src/kimchi.storagepool_add_main.js | 88 ++++++++++++++++++++++++++++++
ui/pages/storagepool-add.html.tmpl | 12 +++-
4 files changed, 146 insertions(+), 5 deletions(-)
diff --git a/ui/css/theme-default/storage.css b/ui/css/theme-default/storage.css
index 4f439e8..ab92de2 100644
--- a/ui/css/theme-default/storage.css
+++ b/ui/css/theme-default/storage.css
@@ -315,7 +315,7 @@
}
.hide-content {
- display: none;
+ display: none!important;
}
.volumeslist {
@@ -593,3 +593,32 @@
center no-repeat;
padding: 0 20px 0 26px;
}
+
+.storage-admin .filter-select {
+ display: inline-block;
+ position: relative;
+}
+
+#iscsiportId, .storage-admin .filter-select input {
+ border: 1px solid #CCCCCC;
+ border-radius: 1px;
+ font-size: 14px;
+ padding: 3px 3px 3px 10px;
+ height: 30px;
+}
+
+.storage-admin .filter-select input::-ms-clear {
+ display: none;
+}
+
+#iSCSIServer input {
+ width: 410px;
+}
+
+#iscsiportId {
+ width: 60px;
+}
+
+#iSCSITarget input {
+ width: 493px;
+}
diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js
index 4562992..b657aeb 100644
--- a/ui/js/src/kimchi.api.js
+++ b/ui/js/src/kimchi.api.js
@@ -774,7 +774,9 @@ var kimchi = {
contentType : 'application/json',
dataType : 'json',
success : suc,
- error : err
+ error : err ? err : function(data) {
+ kimchi.message.error(data.responseJSON.reason);
+ }
});
},
@@ -1098,5 +1100,21 @@ var kimchi = {
kimchi.message.error(data.responseJSON.reason);
}
});
+ },
+
+ getISCSITargets : function(server, port, suc, err) {
+ server = encodeURIComponent(server);
+ port = encodeURIComponent(port);
+ kimchi.requestJSON({
+ url : kimchi.url + 'storageservers/'+server+'/storagetargets?_target_type=iscsi&_server_port='+port,
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ resend : true,
+ success : suc,
+ error : err ? err : function(data) {
+ kimchi.message.error(data.responseJSON.reason);
+ }
+ });
}
};
diff --git a/ui/js/src/kimchi.storagepool_add_main.js b/ui/js/src/kimchi.storagepool_add_main.js
index ecbc682..dc1b1c6 100644
--- a/ui/js/src/kimchi.storagepool_add_main.js
+++ b/ui/js/src/kimchi.storagepool_add_main.js
@@ -34,6 +34,90 @@ kimchi.storagepool_add_main = function() {
});
};
+kimchi.storageFilterSelect = function(id, isUpdate) {
+ var input = $('input', '#'+id);
+ var options = $(".option", '#'+id);
+ var filter = function(container, key){
+ container.children().each(function(){
+ $(this).css("display", $(this).text().indexOf(key)==-1 ? "none" : "");
+ });
+ };
+ if(!isUpdate){
+ input.on("keyup", function(){
+ filter(options, input.val());
+ });
+ }
+ options.children().each(function(){
+ $(this).click(function(){
+ options.children().removeClass("active");
+ input.val($(this).text());
+ $(this).addClass("active");
+ filter(options, "");
+ });
+ });
+};
+
+kimchi.setupISCSI = function(){
+ var loadTargets = function(server, port, callback){
+ var isUpdate = $(".option", "#iSCSITarget").children().length > 0;
+ $(".option", "#iSCSITarget").empty();
+ $('input', "#iSCSITarget").attr("placeholder", "loading targets...");
+ kimchi.getISCSITargets(server, port, function(data){
+ if(data.length==0){
+ $('input', "#iSCSITarget").attr("placeholder", "no targets is got, please input one.");
+ }else{
+ for(var i=0; i<data.length; i++){
+ var itemNode = $.parseHTML("<li>"+data[i].target+"</li>");
+ $(".option", "#iSCSITarget").append(itemNode);
+ }
+ $('input', "#iSCSITarget").attr("placeholder", "");
+ $(".popover", "#iSCSITarget").css("display", "block");
+ }
+ kimchi.storageFilterSelect('iSCSITarget', isUpdate);
+ $('input', "#iSCSITarget").trigger("focus");
+ callback();
+ }, function(data){
+ $('input', "#iSCSITarget").attr("placeholder","failed to load targets.");
+ callback();
+ kimchi.message.error(data.responseJSON.reason);
+ });
+ };
+ var triggerLoadTarget = function(){
+ $('input', "#iSCSITarget").val("");
+ var server = $("#iscsiserverId").val().trim();
+ var port = $("#iscsiportId").val().trim();
+ if(server!="" && port!="" && !$("#iscsiserverId").hasClass("invalid-field") && !$("#iscsiportId").hasClass("invalid-field")){
+ $("#iscsiserverId").attr("disabled", true);
+ $("#iscsiportId").attr("disabled", true);
+ loadTargets(server, port, function(){
+ $("#iscsiserverId").attr("disabled", false);
+ $("#iscsiportId").attr("disabled", false);
+ });
+ }
+ };
+ $("#iscsiserverId").change(function(){
+ triggerLoadTarget();
+ });
+ $("#iscsiportId").change(function(){
+ triggerLoadTarget();
+ });
+ var initISCSIServers = function(){
+ kimchi.getStorageServers("iscsi", function(data){
+ for(var i=0;i<data.length;i++){
+ var itemNode = $.parseHTML("<li>"+data[i].host+"</li>");
+ $(".option", "#iSCSIServer").append(itemNode);
+ $(itemNode).click(function(){
+ $("#iscsiportId").val($(this).prop("port"));
+ $("#iscsiserverId").val($(this).text());
+ triggerLoadTarget();
+ }).prop("port", data[i].port);
+ }
+ kimchi.storageFilterSelect('iSCSIServer', false);
+ });
+ };
+ initISCSIServers();
+};
+
kimchi.initStorageAddPage = function() {
kimchi.listHostPartitions(function(data) {
if (data.length > 0) {
@@ -160,6 +244,10 @@ kimchi.initStorageAddPage = function() {
$('#iscsiportId').keyup(function(event) {
$(this).toggleClass("invalid-field",!/^[0-9]+$/.test($(this).val()));
});
+ $('#iscsiserverId').keyup(function(event) {
+ $(this).toggleClass("invalid-field",!kimchi.isServer($(this).val().trim()));
+ });
+ kimchi.setupISCSI();
};
/* Returns 'true' if all form fields were filled, 'false' if
diff --git a/ui/pages/storagepool-add.html.tmpl b/ui/pages/storagepool-add.html.tmpl
index 1eb2029..6f1861b 100644
--- a/ui/pages/storagepool-add.html.tmpl
+++ b/ui/pages/storagepool-add.html.tmpl
@@ -23,7 +23,7 @@
<!DOCTYPE html>
<html>
<body>
- <div class="window storage-window">
+ <div class="window storage-window storage-admin">
<header>
<h1 class="title">$_("Define a New Storage Pool")</h1>
<div class="close">X</div>
@@ -111,7 +111,10 @@
<div class="field">
<p class="text-help">
$_("iSCSI server IP or hostname. It should not be empty.")</p>
- <input id="iscsiserverId" placeholder="$_("Server")" type="text" class="text storage-base-input-width">
+ <span class="filter-select popable" id="iSCSIServer">
+ <input id="iscsiserverId" type="text" placeholder="$_("Server")">
+ <div class="popover"><ul class="option select-list"></ul></div>
+ </span>
<input id="iscsiportId" placeholder="$_("Port")" type="text" class="text storage-port-width" maxlength="4">
</div>
</section>
@@ -119,7 +122,10 @@
<h2>4. $_("Target")</h2>
<div class="field">
<p class="text-help">$_("The iSCSI target on iSCSI server")</p>
- <input id="iscsiTargetId" type="text" class="text storage-base-input-width">
+ <span class="filter-select popable" id="iSCSITarget">
+ <input id="iscsiTargetId" type="text">
+ <div class="popover"><ul class="option select-list"></ul></div>
+ </span>
</div>
</section>
<section class="form-section">
--
1.7.1
10 years, 3 months
[PATCH 0/9] i18n support: Addition of new languages.
by Paulo Vital
This patch set adds new languages to improve Kimchi's i18n support. The
new languages are: German (de_DE), Spanish (es_ES), Franch (fr_FR),
Italian (it_IT), Japanese (ja_JP), Korean (ko_KR), Russian (ru_RU) and
the Traditional Chinese (zh_TW).
Also, the patch set removed the symbolic link of plugins/sample/po/LINGUAS
to be a 'real' file containing only the three first languages in the
sample plugin.
Paulo Vital (9):
i18n support: Changed the file type of plugins/sample/po/LINGUAS
i18n support: Add German translation files.
i18n support: Add Spanish translation files.
i18n support: Add French translation files.
i18n support: Add Italian translation files.
i18n support: Add Japanese translation files.
18n support: Add Korean translation files.
i18n support: Add Russian translation files.
i18n support: Add Traditional Chinese translation files.
plugins/sample/po/LINGUAS | 4 +-
po/LINGUAS | 8 +
po/de_DE.po | 2256 ++++++++++++++++++++++++++++++++++++++++++++
po/es_ES.po | 2281 ++++++++++++++++++++++++++++++++++++++++++++
po/fr_FR.po | 2285 +++++++++++++++++++++++++++++++++++++++++++++
po/it_IT.po | 2248 ++++++++++++++++++++++++++++++++++++++++++++
po/ja_JP.po | 2229 +++++++++++++++++++++++++++++++++++++++++++
po/ko_KR.po | 2137 ++++++++++++++++++++++++++++++++++++++++++
po/ru_RU.po | 2140 ++++++++++++++++++++++++++++++++++++++++++
po/zh_TW.po | 2105 +++++++++++++++++++++++++++++++++++++++++
10 files changed, 17692 insertions(+), 1 deletion(-)
mode change 120000 => 100644 plugins/sample/po/LINGUAS
create mode 100644 po/de_DE.po
create mode 100644 po/es_ES.po
create mode 100644 po/fr_FR.po
create mode 100644 po/it_IT.po
create mode 100644 po/ja_JP.po
create mode 100644 po/ko_KR.po
create mode 100644 po/ru_RU.po
create mode 100644 po/zh_TW.po
--
1.9.3
10 years, 3 months
[PATCH] Centralize graphics information
by Aline Manera
Recently the ticket information was added to the vm to report the
graphics password and its expiration time.
That way, graphics information was found in 2 different data structuce:
some information under "graphics" and other ones under "ticket".
{
"graphics":{
"type":"vnc",
"port":null,
"listen":"127.0.0.1"
},
"ticket":{
"passwd":null,
"expire":null
},
"name":"Ubuntu14.04",
"uuid":"8522d82d-d51b-436f-88ff-aff1fed27613",
"access":"full",
"state":"shutoff",
"memory":1024
...
}
As "ticket" data structure only contains graphics information it makes
more sense to centralize it under "graphics" and also use the same
terminology used by libvirt (passwd and passwdValidTo)
"graphics":{
"type":"vnc",
"port":null,
"listen":"127.0.0.1"
"passwd":null,
"passwdValidTo":null
},
Signed-off-by: Aline Manera <alinefm(a)linux.vnet.ibm.com>
---
docs/API.md | 14 +++---
src/kimchi/API.json | 12 ++---
src/kimchi/control/vms.py | 2 +-
src/kimchi/i18n.py | 4 +-
src/kimchi/mockmodel.py | 38 +++++++++-------
src/kimchi/model/vms.py | 112 ++++++++++++++++++++++------------------------
tests/test_mockmodel.py | 2 +-
tests/test_model.py | 19 ++++----
tests/test_rest.py | 13 +++---
9 files changed, 111 insertions(+), 105 deletions(-)
diff --git a/docs/API.md b/docs/API.md
index d75c55f..7d5e804 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -94,9 +94,8 @@ the following general conventions:
* port: The real port number of the graphics, vnc or spice. Users
can use this port to connect to the vm with general vnc/spice
clients.
- * ticket: A ticket represents credentials to access VM.
- * password: the password of a ticket.
- * expire: lifetime of a ticket.
+ * passwd: console password
+ * passwdValidTo: lifetime for the console password.
* users: A list of system users who have permission to access the VM.
Default is: empty (i.e. only root-users may access).
* groups: A list of system groups whose users have permission to access
@@ -110,9 +109,12 @@ the following general conventions:
will take effect in next reboot)
* memory: New amount of memory (MB) for this VM (if VM is running, new
value will take effect in next reboot)
- * ticket: A ticket represents credentials to access VM.
- * password *(optional)*: the password of a ticket.
- * expire *(optional)*: lifetime of a ticket.
+ * graphics: A dict to show detail of VM graphics.
+ * passwd *(optional)*: console password. When omitted a random password
+ willbe generated.
+ * passwdValidTo *(optional)*: lifetime for the console password. When
+ omitted the password will be valid just
+ for 30 seconds.
* **POST**: *See Virtual Machine Actions*
diff --git a/src/kimchi/API.json b/src/kimchi/API.json
index c3fc5e3..67612f2 100644
--- a/src/kimchi/API.json
+++ b/src/kimchi/API.json
@@ -235,17 +235,17 @@
"error": "KCHVM0026E"
}
},
- "ticket": {
- "description": "A ticket represents credentials to access VM",
+ "graphics": {
+ "description": "Graphics information from guest",
"type": "object",
"properties": {
- "password": {
- "description": "the password of a ticket.",
+ "passwd": {
+ "description": "New graphics password.",
"type": "string",
"error": "KCHVM0031E"
},
- "size": {
- "description": "lifetime of a ticket.",
+ "passwdValidTo": {
+ "description": "Life time for the graphics password.",
"type": "number",
"error": "KCHVM0032E"
}
diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py
index 6632e52..88d8a81 100644
--- a/src/kimchi/control/vms.py
+++ b/src/kimchi/control/vms.py
@@ -36,7 +36,7 @@ class VM(Resource):
super(VM, self).__init__(model, ident)
self.role_key = 'guests'
self.update_params = ["name", "users", "groups", "cpus", "memory",
- "ticket"]
+ "graphics"]
self.screenshot = VMScreenShot(model, ident)
self.uri_fmt = '/vms/%s'
for ident, node in sub_nodes.items():
diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py
index 2eae7e8..c276b38 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -94,8 +94,8 @@ messages = {
"KCHVM0028E": _("Group(s) '%(groups)s' do not exist"),
"KCHVM0029E": _("Unable to shutdown virtual machine %(name)s. Details: %(err)s"),
"KCHVM0030E": _("Unable to get access metadata of virtual machine %(name)s. Details: %(err)s"),
- "KCHVM0031E": _("password of a vm ticket must be a string."),
- "KCHVM0032E": _("expire of a vm ticket must be a number."),
+ "KCHVM0031E": _("The guest console password must be a string."),
+ "KCHVM0032E": _("The life time for the guest console password must be a number."),
"KCHVMIF0001E": _("Interface %(iface)s does not exist in virtual machine %(name)s"),
"KCHVMIF0002E": _("Network %(network)s specified for virtual machine %(name)s does not exist"),
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py
index fc474de..1322def 100644
--- a/src/kimchi/mockmodel.py
+++ b/src/kimchi/mockmodel.py
@@ -91,15 +91,6 @@ class MockModel(object):
def _static_vm_update(self, dom, params):
state = dom.info['state']
- if 'ticket' in params:
- ticket = params.pop('ticket')
- passwd = ticket.get('passwd')
- dom.ticket["passwd"] = passwd if passwd is not None else "".join(
- random.sample(string.ascii_letters + string.digits, 8))
- expire = ticket.get('expire')
- dom.ticket["ValidTo"] = expire if expire is None else round(
- time.time()) + expire
-
for key, val in params.items():
if key == 'name':
if state == 'running' or params['name'] in self.vms_get_list():
@@ -126,7 +117,21 @@ class MockModel(object):
dom.info[key] = val
def _live_vm_update(self, dom, params):
- pass
+ if 'graphics' not in params:
+ return
+
+ graphics = params.pop('graphics')
+ passwd = graphics.get('passwd')
+ if passwd is None:
+ passwd = "".join(random.sample(string.ascii_letters +
+ string.digits, 8))
+
+ expire = graphics.get('passwdValidTo')
+ if expire is not None:
+ expire = round(time.time()) + expire
+
+ dom.info['graphics']["passwd"] = passwd
+ dom.info['graphics']["passwdValidTo"] = expire
def vm_update(self, name, params):
dom = self._get_vm(name)
@@ -141,10 +146,11 @@ class MockModel(object):
vm.info['screenshot'] = self.vmscreenshot_lookup(name)
else:
vm.info['screenshot'] = None
- vm.info['ticket'] = {"passwd": vm.ticket["passwd"]}
- validTo = vm.ticket["ValidTo"]
- vm.info['ticket']["expire"] = (validTo - round(time.time())
- if validTo is not None else None)
+
+ validTo = vm.info['graphics']['passwdValidTo']
+ validTo = (validTo - round(time.time()) if validTo is not None
+ else None)
+ vm.info['graphics']['passwdValidTo'] = validTo
return vm.info
def vm_delete(self, name):
@@ -1050,7 +1056,6 @@ class MockVM(object):
self.networks = template_info['networks']
ifaces = [MockVMIface(net) for net in self.networks]
self.storagedevices = {}
- self.ticket = {"passwd": "123456", "ValidTo": None}
self.ifaces = dict([(iface.info['mac'], iface) for iface in ifaces])
stats = {'cpu_utilization': 20,
@@ -1066,7 +1071,8 @@ class MockVM(object):
'cpus': self.cpus,
'icon': None,
'graphics': {'type': 'vnc', 'listen': '127.0.0.1',
- 'port': None},
+ 'port': None, 'passwd': '123456',
+ 'passwdValidTo': None},
'users': ['user1', 'user2', 'root'],
'groups': ['group1', 'group2', 'admin'],
'access': 'full'
diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py
index 476e4ac..0a05b76 100644
--- a/src/kimchi/model/vms.py
+++ b/src/kimchi/model/vms.py
@@ -251,12 +251,6 @@ class VMModel(object):
self.groups = import_class('kimchi.model.host.GroupsModel')(**kargs)
def update(self, name, params):
- if 'ticket' in params:
- password = params['ticket'].get("passwd")
- password = password if password is not None else "".join(
- random.sample(string.ascii_letters + string.digits, 8))
- params['ticket']['passwd'] = password
-
dom = self.get_vm(name, self.conn)
dom = self._static_vm_update(dom, params)
self._live_vm_update(dom, params)
@@ -315,14 +309,15 @@ class VMModel(object):
os_elem = E.os({"distro": distro, "version": version})
set_metadata_node(dom, os_elem)
- def _set_ticket(self, xml, params, flag=0):
+ def _set_graphics_passwd(self, xml, params, flag=0):
DEFAULT_VALID_TO = 30
password = params.get("passwd")
- expire = params.get("expire")
+ expire = params.get("passwdValidTo")
root = objectify.fromstring(xml)
graphic = root.devices.find("graphics")
if graphic is None:
return None
+
graphic.attrib['passwd'] = password
to = graphic.attrib.get('passwdValidTo')
if to is not None:
@@ -337,27 +332,27 @@ class VMModel(object):
return root if flag == 0 else graphic
- def _set_persistent_ticket(self, dom, xml, params):
- if 'ticket' not in params:
- # store the password for copy
- ticket = self._get_ticket(dom)
- if ticket['passwd'] is None:
- return xml
- else:
- params['ticket'] = ticket
- node = self._set_ticket(xml, params['ticket'])
- return xml if node is None else ET.tostring(node, encoding="utf-8")
+ def _update_graphics(self, dom, xml, params):
+ if 'graphics' not in params:
+ return xml
- def _set_live_ticket(self, dom, params):
- if 'ticket' not in params:
- return
- if dom.isActive():
- xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)
- flag = libvirt.VIR_DOMAIN_XML_SECURE
- node = self._set_ticket(xml, params['ticket'], flag)
- if node is not None:
- dom.updateDeviceFlags(etree.tostring(node),
- libvirt.VIR_DOMAIN_AFFECT_LIVE)
+ password = params['graphics'].get("passwd")
+ if password is None:
+ password = "".join(random.sample(string.ascii_letters +
+ string.digits, 8))
+ params['graphics']['passwd'] = password
+
+ if not dom.isActive():
+ node = self._set_graphics_passwd(xml, params['graphics'])
+ return xml if node is None else ET.tostring(node, encoding="utf-8")
+
+ xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)
+ flag = libvirt.VIR_DOMAIN_XML_SECURE
+ node = self._set_graphics_passwd(xml, params['graphics'], flag)
+ if node is not None:
+ dom.updateDeviceFlags(etree.tostring(node),
+ libvirt.VIR_DOMAIN_AFFECT_LIVE)
+ return xml
def _static_vm_update(self, dom, params):
state = DOM_STATE_MAP[dom.info()[0]]
@@ -374,7 +369,7 @@ class VMModel(object):
xpath = VM_STATIC_UPDATE_PARAMS[key]
new_xml = xmlutils.xml_item_update(new_xml, xpath, val)
- new_xml = self._set_persistent_ticket(dom, new_xml, params)
+ new_xml = self._update_graphics(dom, new_xml, params)
conn = self.conn.get()
try:
@@ -397,35 +392,19 @@ class VMModel(object):
def _live_vm_update(self, dom, params):
self._vm_update_access_metadata(dom, params)
- self._set_live_ticket(dom, params)
def _has_video(self, dom):
dom = ElementTree.fromstring(dom.XMLDesc(0))
return dom.find('devices/video') is not None
- def _get_ticket(self, dom):
- xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)
- root = objectify.fromstring(xml)
- graphic = root.devices.find("graphics")
- if graphic is None:
- return {"passwd": None, "expire": None}
- passwd = graphic.attrib.get('passwd')
- ticket = {"passwd": passwd}
- valid_to = graphic.attrib.get('passwdValidTo')
- ticket['expire'] = None
- if valid_to is not None:
- to = time.mktime(time.strptime(valid_to, '%Y-%m-%dT%H:%M:%S'))
- ticket['expire'] = to - time.mktime(time.gmtime())
-
- return ticket
-
def lookup(self, name):
dom = self.get_vm(name, self.conn)
info = dom.info()
state = DOM_STATE_MAP[info[0]]
screenshot = None
+ # (type, listen, port, passwd, passwdValidTo)
graphics = self._vm_get_graphics(name)
- graphics_type, graphics_listen, graphics_port = graphics
+ graphics_port = graphics[2]
graphics_port = graphics_port if state == 'running' else None
try:
if state == 'running' and self._has_video(dom):
@@ -465,10 +444,12 @@ class VMModel(object):
'cpus': info[3],
'screenshot': screenshot,
'icon': icon,
- 'graphics': {"type": graphics_type,
- "listen": graphics_listen,
- "port": graphics_port},
- 'ticket': self._get_ticket(dom),
+ # (type, listen, port, passwd, passwdValidTo)
+ 'graphics': {"type": graphics[0],
+ "listen": graphics[1],
+ "port": graphics_port,
+ "passwd": graphics[3],
+ "passwdValidTo": graphics[4]},
'users': users,
'groups': groups,
'access': 'full'
@@ -567,23 +548,38 @@ class VMModel(object):
def _vm_get_graphics(self, name):
dom = self.get_vm(name, self.conn)
- xml = dom.XMLDesc(0)
+ xml = dom.XMLDesc(libvirt.VIR_DOMAIN_XML_SECURE)
+
expr = "/domain/devices/graphics/@type"
res = xmlutils.xpath_get_text(xml, expr)
graphics_type = res[0] if res else None
+
expr = "/domain/devices/graphics/@listen"
res = xmlutils.xpath_get_text(xml, expr)
graphics_listen = res[0] if res else None
- graphics_port = None
+
+ graphics_port = graphics_passwd = graphics_passwdValidTo = None
if graphics_type:
- expr = "/domain/devices/graphics[@type='%s']/@port" % graphics_type
- res = xmlutils.xpath_get_text(xml, expr)
+ expr = "/domain/devices/graphics[@type='%s']/@port"
+ res = xmlutils.xpath_get_text(xml, expr % graphics_type)
graphics_port = int(res[0]) if res else None
- return graphics_type, graphics_listen, graphics_port
+
+ expr = "/domain/devices/graphics[@type='%s']/@passwd"
+ res = xmlutils.xpath_get_text(xml, expr % graphics_type)
+ graphics_passwd = res[0] if res else None
+
+ expr = "/domain/devices/graphics[@type='%s']/@passwdValidTo"
+ res = xmlutils.xpath_get_text(xml, expr % graphics_type)
+ if res:
+ to = time.mktime(time.strptime(res[0], '%Y-%m-%dT%H:%M:%S'))
+ graphics_passwdValidTo = to - time.mktime(time.gmtime())
+
+ return (graphics_type, graphics_listen, graphics_port,
+ graphics_passwd, graphics_passwdValidTo)
def connect(self, name):
- graphics = self._vm_get_graphics(name)
- graphics_type, graphics_listen, graphics_port = graphics
+ # (type, listen, port, passwd, passwdValidTo)
+ graphics_port = self._vm_get_graphics(name)[2]
if graphics_port is not None:
vnc.add_proxy_token(name, graphics_port)
else:
diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py
index b7e6a48..8de7ce9 100644
--- a/tests/test_mockmodel.py
+++ b/tests/test_mockmodel.py
@@ -143,7 +143,7 @@ class MockModelTests(unittest.TestCase):
keys = set(('name', 'state', 'stats', 'uuid', 'memory', 'cpus',
'screenshot', 'icon', 'graphics', 'users', 'groups',
- 'access', 'ticket'))
+ 'access'))
stats_keys = set(('cpu_utilization',
'net_throughput', 'net_throughput_peak',
diff --git a/tests/test_model.py b/tests/test_model.py
index f5b13d0..5090395 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -65,7 +65,7 @@ class ModelTests(unittest.TestCase):
keys = set(('name', 'state', 'stats', 'uuid', 'memory', 'cpus',
'screenshot', 'icon', 'graphics', 'users', 'groups',
- 'access', 'ticket'))
+ 'access'))
stats_keys = set(('cpu_utilization',
'net_throughput', 'net_throughput_peak',
@@ -764,24 +764,25 @@ class ModelTests(unittest.TestCase):
vms = inst.vms_get_list()
self.assertTrue('kimchi-vm1' in vms)
- # update vm ticket when vm is not running
+ # update vm graphics when vm is not running
inst.vm_update(u'kimchi-vm1',
- {"ticket": {"passwd": "123456"}})
+ {"graphics": {"passwd": "123456"}})
inst.vm_start('kimchi-vm1')
rollback.prependDefer(self._rollback_wrapper, inst.vm_poweroff,
'kimchi-vm1')
vm_info = inst.vm_lookup(u'kimchi-vm1')
- self.assertEquals('123456', vm_info['ticket']["passwd"])
- self.assertEquals(None, vm_info['ticket']["expire"])
+ self.assertEquals('123456', vm_info['graphics']["passwd"])
+ self.assertEquals(None, vm_info['graphics']["passwdValidTo"])
- # update vm ticket when vm is running
+ # update vm graphics when vm is running
inst.vm_update(u'kimchi-vm1',
- {"ticket": {"passwd": "abcdef", "expire": 20}})
+ {"graphics": {"passwd": "abcdef",
+ "passwdValidTo": 20}})
vm_info = inst.vm_lookup(u'kimchi-vm1')
- self.assertEquals('abcdef', vm_info['ticket']["passwd"])
- self.assertGreaterEqual(20, vm_info['ticket']['expire'])
+ self.assertEquals('abcdef', vm_info['graphics']["passwd"])
+ self.assertGreaterEqual(20, vm_info['graphics']['passwdValidTo'])
info = inst.vm_lookup('kimchi-vm1')
self.assertEquals('running', info['state'])
diff --git a/tests/test_rest.py b/tests/test_rest.py
index 2117399..0bf5997 100644
--- a/tests/test_rest.py
+++ b/tests/test_rest.py
@@ -221,20 +221,21 @@ class RestTests(unittest.TestCase):
resp = self.request('/vms/vm-1', req, 'PUT')
self.assertEquals(200, resp.status)
- req = json.dumps({"ticket": {'passwd': "abcdef"}})
+ req = json.dumps({"graphics": {'passwd': "abcdef"}})
resp = self.request('/vms/vm-1', req, 'PUT')
info = json.loads(resp.read())
- self.assertEquals('abcdef', info["ticket"]["passwd"])
- self.assertEquals(None, info["ticket"]["expire"])
+ self.assertEquals('abcdef', info["graphics"]["passwd"])
+ self.assertEquals(None, info["graphics"]["passwdValidTo"])
resp = self.request('/vms/vm-1/poweroff', '{}', 'POST')
self.assertEquals(200, resp.status)
- req = json.dumps({"ticket": {'passwd': "123456", "expire": 20}})
+ req = json.dumps({"graphics": {'passwd': "123456",
+ 'passwdValidTo': 20}})
resp = self.request('/vms/vm-1', req, 'PUT')
info = json.loads(resp.read())
- self.assertEquals('123456', info["ticket"]["passwd"])
- self.assertGreaterEqual(20, info["ticket"]["expire"])
+ self.assertEquals('123456', info["graphics"]["passwd"])
+ self.assertGreaterEqual(20, info["graphics"]["passwdValidTo"])
req = json.dumps({'name': 12})
resp = self.request('/vms/vm-1', req, 'PUT')
--
1.9.3
10 years, 3 months
Support async task query classification
by Royce Lv
Problems to solve:
When we generate the debug reports (or upload/download a storage
volume), only the request initiator gets task id, and is able to query
task status. We need other users from other browser to have same view as
the initiator, which is not possible now.
Solution:
1. We add async event information, events will also be classified
so that users can subscribe events interest them
2. We enable reverse mapping (task to resource which generate them)
query for users. So that user can find tasks related to a collection:
GET /tasks?init_collection=debugreports
after take a look at current implementation, we can also query
taget_uri
GET /tasks?target_uri=r(^/debugreports/*) (need to be encoded)
Since we currently out of support of async events, we may want to add
task query for this purpose.
Welcome comments!
10 years, 3 months