[PATCH V13] Host device passthrough (Front-end): Add PCI Devices to VM
by Wen Wang
From: Yu Xin Huo <huoyuxin(a)linux.vnet.ibm.com>
Signed-off-by: Yu Xin Huo <huoyuxin(a)linux.vnet.ibm.com>
Signed-off-by: Wen Wang <wenwang(a)linux.vnet.ibm.com>
---
ui/css/theme-default/guest-edit.css | 90 ++++++++++++++++++++++++++++++++++-
ui/js/src/kimchi.api.js | 55 +++++++++++++++++++++
ui/js/src/kimchi.guest_edit_main.js | 81 +++++++++++++++++++++++++++++++
ui/pages/guest-edit.html.tmpl | 30 ++++++++++++
4 files changed, 254 insertions(+), 2 deletions(-)
diff --git a/ui/css/theme-default/guest-edit.css b/ui/css/theme-default/guest-edit.css
index 76fbaf2..b1d3931 100644
--- a/ui/css/theme-default/guest-edit.css
+++ b/ui/css/theme-default/guest-edit.css
@@ -17,8 +17,8 @@
*/
#guest-edit-window {
font-size: 13px;
- height: 400px;
- width: 610px;
+ height: 420px;
+ width: 820px;
}
#guest-edit-tabs {
@@ -261,3 +261,89 @@
width: 46%;
float: right;
}
+
+.guest-edit-pci {
+ height: 79%;
+ overflow: auto;
+ font-size: 12px;
+}
+
+.guest-edit-pci .guest-scroll-indent {
+ width: 783px;
+}
+
+.guest-edit-pci .filter {
+ height: 35px;
+ margin-right: 5px;
+ overflow: hidden;
+}
+
+.guest-edit-pci .group {
+ float: right;
+}
+
+.guest-edit-pci .filter .control {
+ border: 1px solid #AAAAAA;
+ font-size: 12px;
+ background-color: white;
+}
+
+.guest-edit-pci .filter select {
+ border-right: 0px!important;
+ border-radius: 7px 0px 0px 7px;
+ padding: 2px 2px 2px 7px;
+ width: 100px;
+}
+
+.guest-edit-pci .filter select option {
+ padding-left: 7px;
+}
+
+.guest-edit-pci .filter input {
+ border-radius: 0px 7px 7px 0px;
+ padding: 3px 3px 3px 10px;
+ width: 200px;
+ font-style: italic;
+}
+
+.guest-edit-pci .header {
+ margin-bottom: 8px;
+ padding-bottom: 2px;
+ font-weight: bold;
+ border-bottom: 1px solid #999999;
+}
+
+.guest-edit-pci .item {
+ margin-bottom: 4px;
+ overflow: hidden;
+}
+
+.guest-edit-pci .cell {
+ display: inline-block;
+ vertical-align: middle;
+ margin-right: 10px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.guest-edit-pci .item button {
+ width: 20px;
+ height: 20px;
+ float: right;
+}
+
+.guest-edit-pci .name {
+ width: 18%;
+ max-width: 18%;
+}
+
+.guest-edit-pci .product {
+ width: 45%;
+ max-width: 45%;
+}
+
+.guest-edit-pci .vendor {
+ width: 25%;
+ max-width: 25%;
+}
diff --git a/ui/js/src/kimchi.api.js b/ui/js/src/kimchi.api.js
index f0506fa..5895a07 100644
--- a/ui/js/src/kimchi.api.js
+++ b/ui/js/src/kimchi.api.js
@@ -1115,6 +1115,20 @@ var kimchi = {
});
},
+ getHostPCIDevices : function(suc, err) {
+ kimchi.requestJSON({
+ url : kimchi.url + 'host/devices?_passthrough=true&_cap=pci',
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ resend : true,
+ success : suc,
+ error : err ? err : function(data) {
+ kimchi.message.error(data.responseJSON.reason);
+ }
+ });
+ },
+
getISCSITargets : function(server, port, suc, err) {
server = encodeURIComponent(server);
port = port ? '&_server_port='+encodeURIComponent(port) : '';
@@ -1145,6 +1159,47 @@ var kimchi = {
});
},
+ getVMPCIDevices : function(id, suc, err) {
+ kimchi.requestJSON({
+ url : kimchi.url + 'vms/'+encodeURIComponent(id)+'/hostdevs',
+ type : 'GET',
+ contentType : 'application/json',
+ dataType : 'json',
+ resend : true,
+ success : suc,
+ error : err ? err : function(data) {
+ kimchi.message.error(data.responseJSON.reason);
+ }
+ });
+ },
+
+ addVMPCIDevice : function(vm, device, suc, err) {
+ kimchi.requestJSON({
+ url : kimchi.url + 'vms/'+ encodeURIComponent(vm) +'/hostdevs',
+ type : 'POST',
+ contentType : 'application/json',
+ dataType : 'json',
+ data : JSON.stringify(device),
+ success : suc,
+ error : err ? err : function(data) {
+ kimchi.message.error(data.responseJSON.reason);
+ }
+ });
+ },
+
+ removeVMPCIDevice : function(vm, device, suc, err) {
+ kimchi.requestJSON({
+ url : kimchi.url + 'vms/'+ encodeURIComponent(vm) +'/hostdevs/' + encodeURIComponent(device),
+ type : 'DELETE',
+ contentType : 'application/json',
+ dataType : 'json',
+ success : suc,
+ error : err ? err : function(data) {
+ kimchi.message.error(data.responseJSON.reason);
+ }
+ });
+ },
+
/**
* Add a volume to a given storage pool.
*/
diff --git a/ui/js/src/kimchi.guest_edit_main.js b/ui/js/src/kimchi.guest_edit_main.js
index c281289..030e112 100644
--- a/ui/js/src/kimchi.guest_edit_main.js
+++ b/ui/js/src/kimchi.guest_edit_main.js
@@ -359,6 +359,86 @@ kimchi.guest_edit_main = function() {
});
};
+ var setupPCIDevice = function(){
+ kimchi.getHostPCIDevices(function(hostPCIs){
+ kimchi.getVMPCIDevices(kimchi.selectedGuest, function(vmPCIs){
+ kimchi.getCapabilities(function(result) {
+ var pciEnabled = result.kernel_vfio;
+ for(var i=0; i<hostPCIs.length; i++){
+ var itemNode = $.parseHTML(kimchi.substitute($('#pci-tmpl').html(),{
+ name: hostPCIs[i].name,
+ product: hostPCIs[i].product.description,
+ vendor: hostPCIs[i].vendor.description
+ }));
+ $(".body", "#form-guest-edit-pci").append(itemNode);
+ var iconClass = "ui-icon-plus";
+ for(var j=0; j<vmPCIs.length; j++){
+ if(hostPCIs[i].name==vmPCIs[j].name){
+ iconClass = "ui-icon-minus";
+ break;
+ }
+ }
+ pciEnabled || $("button", itemNode).remove();
+ $("button", itemNode).button({
+ icons: { primary: iconClass },
+ text: false
+ }).click(function(){
+ var obj = $(this);
+ if(obj.button("option", "icons").primary == "ui-icon-minus"){
+ kimchi.removeVMPCIDevice(kimchi.selectedGuest, obj.parent().prop("id"), function(){
+ kimchi.getVMPCIDevices(kimchi.selectedGuest, function(vmPCIs1){
+ for(var k=0; k<hostPCIs.length; k++) {
+ $("button", "#" + hostPCIs[k].name).button("option", "icons", {primary: "ui-icon-plus"});
+ }
+ for(var k=0; k<vmPCIs1.length; k++) {
+ $("button", "#" + vmPCIs1[k].name).button("option", "icons", {primary: "ui-icon-minus"});
+ }
+ });
+ filterNodes($("select", "#form-guest-edit-pci").val(), $("input", "#form-guest-edit-pci").val());
+ });
+ }else{
+ kimchi.addVMPCIDevice(kimchi.selectedGuest, { name: obj.parent().prop("id") }, function(){
+ kimchi.getVMPCIDevices(kimchi.selectedGuest, function(vmPCIs1){
+ for(var k=0; k<vmPCIs1.length; k++) {
+ $("button", "#" + vmPCIs1[k].name).button("option", "icons", {primary: "ui-icon-minus"});
+ }
+ });
+ filterNodes($("select", "#form-guest-edit-pci").val(), $("input", "#form-guest-edit-pci").val());
+ });
+ }
+ });
+ }
+ });
+ });
+ });
+ var filterNodes = function(group, text){
+ text = text.toLowerCase();
+ $(".body", "#form-guest-edit-pci").children().each(function(){
+ var textFilter = $(".name", this).text().toLowerCase().indexOf(text)!=-1;
+ textFilter = textFilter || $(".product", this).text().toLowerCase().indexOf(text)!=-1;
+ textFilter = textFilter || $(".vendor", this).text().toLowerCase().indexOf(text)!=-1;
+ var display = "none";
+ var itemGroup = $("button", this).button("option", "icons").primary;
+ if(textFilter){
+ if(group == "all"){
+ display = "";
+ }else if(group=="toAdd" && itemGroup=="ui-icon-plus"){
+ display = ""
+ }else if(group == "added" && itemGroup=="ui-icon-minus"){
+ display = ""
+ }
+ }
+ $(this).css("display", display);
+ });
+ };
+ $("select", "#form-guest-edit-pci").change(function(){
+ filterNodes($(this).val(), $("input", "#form-guest-edit-pci").val());
+ });
+ $("input", "#form-guest-edit-pci").on("keyup", function() {
+ filterNodes($("select", "#form-guest-edit-pci").val(), $(this).val());
+ });
+ };
+
var initContent = function(guest) {
guest['icon'] = guest['icon'] || 'images/icon-vm.png';
$('#form-guest-edit-general').fillWithObject(guest);
@@ -395,6 +475,7 @@ kimchi.guest_edit_main = function() {
initStorageListeners();
setupInterface();
setupPermission();
+ setupPCIDevice();
kimchi.topic('kimchi/vmCDROMAttached').subscribe(onAttached);
kimchi.topic('kimchi/vmCDROMReplaced').subscribe(onReplaced);
diff --git a/ui/pages/guest-edit.html.tmpl b/ui/pages/guest-edit.html.tmpl
index 917b2e8..512909a 100644
--- a/ui/pages/guest-edit.html.tmpl
+++ b/ui/pages/guest-edit.html.tmpl
@@ -41,6 +41,9 @@
<li>
<a href="#form-guest-edit-permission">$_("Permission")</a>
</li>
+ <li>
+ <a href="#form-guest-edit-pci">$_("Host PCI Device")</a>
+ </li>
</ul>
<form id="form-guest-edit-general">
<fieldset class="guest-edit-fieldset">
@@ -138,6 +141,25 @@
</div>
</div>
</form>
+ <form id="form-guest-edit-pci" class="guest-edit-pci">
+ <div class="guest-scroll-indent">
+ <div class="filter">
+ <span class="group">
+ <select class="control">
+ <option value="all">$_("All")</option>
+ <option value="toAdd">$_("To Add")</option>
+ <option value="added">$_("Added")</option>
+ </select><input type="text" class="control" placeholder="$_("filter")">
+ </span>
+ </div>
+ <div class="header">
+ <span class="cell name">$_("Name")</span>
+ <span class="cell product">$_("Product")</span>
+ <span class="cell vendor">$_("Vendor")</span>
+ </div>
+ <div class="body"></div>
+ </div>
+ </form>
</div>
</div>
<footer>
@@ -221,6 +243,14 @@
<label>{val}</label>
</div>
</script>
+<script id="pci-tmpl" type="text/html">
+<div class="item" id="{name}">
+ <span class="cell name" title="{name}">{name}</span>
+ <span class="cell product" title="{product}">{product}</span>
+ <span class="cell vendor" title="{vendor}">{vendor}</span>
+ <button></button>
+</div>
+</script>
<script type="text/javascript">
kimchi.guest_edit_main();
</script>
--
1.7.1
10 years, 2 months
[PATCH 0/6] Use installed spice-html5 package when possible
by Aline Manera
spice-html5 is present in almost all supported Linux distributions
(RHEL6.5, Fedora20 and Ubuntu 14.04). So in those cases we should not package
the spice-html5 code into Kimchi and instead of that uses the spice-html5 package
as dependency.
This patch set change the build process to check the spice-html5 package
availability in order to decide to package or not the spice-html5 code.
It also gets the latest spice-html5 code into Kimchi and update the COPYING
content to point to the imported codes.
spice-html5 package is not available for RHEL7 and openSUSE 13.1. So
for those distributions, you must run the following command to Kimchi
includes the spice-html5 into its package.
./autogen.sh --with-spice-html5
Aline Manera (6):
Import the latest spice-html5 code into Kimchi
Modify spice_auto.html for Kimchi proposals
Add new spice-html5 code to Kimchi build process
Update Kimchi to use the installed spice-html5
Delete former imported spice code
Update COPYING content to expose the imported code
COPYING | 10 +-
autogen.sh | 2 +
configure.ac | 15 +-
contrib/DEBIAN/control.in | 3 +-
contrib/kimchi.spec.fedora.in | 30 +-
contrib/kimchi.spec.suse.in | 24 +-
docs/README.md | 10 +-
src/kimchi/Makefile.am | 11 +-
src/kimchi/config.py.in | 33 +-
tests/test_config.py.in | 15 +-
ui/Makefile.am | 2 +-
ui/css/Makefile.am | 2 -
ui/css/spice/Makefile.am | 20 -
ui/css/spice/spice.css | 115 ---
ui/js/Makefile.am | 2 -
ui/js/spice/Makefile.am | 22 -
ui/js/spice/atKeynames.js | 183 -----
ui/js/spice/bitmap.js | 51 --
ui/js/spice/cursor.js | 92 ---
ui/js/spice/display.js | 806 --------------------
ui/js/spice/enums.js | 282 -------
ui/js/spice/inputs.js | 251 -------
ui/js/spice/jsbn.js | 589 ---------------
ui/js/spice/lz.js | 166 ----
ui/js/spice/main.js | 176 -----
ui/js/spice/png.js | 256 -------
ui/js/spice/prng4.js | 79 --
ui/js/spice/quic.js | 1335 ---------------------------------
ui/js/spice/rng.js | 102 ---
ui/js/spice/rsa.js | 146 ----
ui/js/spice/sha1.js | 346 ---------
ui/js/spice/spiceconn.js | 447 -----------
ui/js/spice/spicedataview.js | 96 ---
ui/js/spice/spicemsg.js | 883 ----------------------
ui/js/spice/spicetype.js | 480 ------------
ui/js/spice/ticket.js | 250 ------
ui/js/spice/utils.js | 261 -------
ui/js/spice/wire.js | 123 ---
ui/js/src/kimchi.api.js | 2 +-
ui/pages/spice.html.tmpl | 140 ----
ui/spice-html5/Makefile.am | 25 +
ui/spice-html5/atKeynames.js | 183 +++++
ui/spice-html5/bitmap.js | 51 ++
ui/spice-html5/css/Makefile.am | 20 +
ui/spice-html5/css/spice.css | 118 +++
ui/spice-html5/cursor.js | 110 +++
ui/spice-html5/display.js | 823 ++++++++++++++++++++
ui/spice-html5/enums.js | 324 ++++++++
ui/spice-html5/inputs.js | 280 +++++++
ui/spice-html5/lz.js | 166 ++++
ui/spice-html5/main.js | 231 ++++++
ui/spice-html5/pages/Makefile.am | 20 +
ui/spice-html5/pages/spice_auto.html | 200 +++++
ui/spice-html5/playback.js | 278 +++++++
ui/spice-html5/png.js | 256 +++++++
ui/spice-html5/quic.js | 1335 +++++++++++++++++++++++++++++++++
ui/spice-html5/resize.js | 70 ++
ui/spice-html5/simulatecursor.js | 202 +++++
ui/spice-html5/spicearraybuffer.js | 58 ++
ui/spice-html5/spiceconn.js | 460 ++++++++++++
ui/spice-html5/spicedataview.js | 120 +++
ui/spice-html5/spicemsg.js | 1047 ++++++++++++++++++++++++++
ui/spice-html5/spicetype.js | 473 ++++++++++++
ui/spice-html5/thirdparty/Makefile.am | 20 +
ui/spice-html5/thirdparty/jsbn.js | 589 +++++++++++++++
ui/spice-html5/thirdparty/prng4.js | 79 ++
ui/spice-html5/thirdparty/rng.js | 102 +++
ui/spice-html5/thirdparty/rsa.js | 146 ++++
ui/spice-html5/thirdparty/sha1.js | 346 +++++++++
ui/spice-html5/ticket.js | 250 ++++++
ui/spice-html5/utils.js | 265 +++++++
ui/spice-html5/webm.js | 553 ++++++++++++++
ui/spice-html5/wire.js | 123 +++
73 files changed, 9419 insertions(+), 7762 deletions(-)
delete mode 100644 ui/css/spice/Makefile.am
delete mode 100644 ui/css/spice/spice.css
delete mode 100644 ui/js/spice/Makefile.am
delete mode 100644 ui/js/spice/atKeynames.js
delete mode 100644 ui/js/spice/bitmap.js
delete mode 100644 ui/js/spice/cursor.js
delete mode 100644 ui/js/spice/display.js
delete mode 100644 ui/js/spice/enums.js
delete mode 100644 ui/js/spice/inputs.js
delete mode 100644 ui/js/spice/jsbn.js
delete mode 100644 ui/js/spice/lz.js
delete mode 100644 ui/js/spice/main.js
delete mode 100644 ui/js/spice/png.js
delete mode 100644 ui/js/spice/prng4.js
delete mode 100644 ui/js/spice/quic.js
delete mode 100644 ui/js/spice/rng.js
delete mode 100644 ui/js/spice/rsa.js
delete mode 100644 ui/js/spice/sha1.js
delete mode 100644 ui/js/spice/spiceconn.js
delete mode 100644 ui/js/spice/spicedataview.js
delete mode 100644 ui/js/spice/spicemsg.js
delete mode 100644 ui/js/spice/spicetype.js
delete mode 100644 ui/js/spice/ticket.js
delete mode 100644 ui/js/spice/utils.js
delete mode 100644 ui/js/spice/wire.js
delete mode 100644 ui/pages/spice.html.tmpl
create mode 100644 ui/spice-html5/Makefile.am
create mode 100644 ui/spice-html5/atKeynames.js
create mode 100644 ui/spice-html5/bitmap.js
create mode 100644 ui/spice-html5/css/Makefile.am
create mode 100644 ui/spice-html5/css/spice.css
create mode 100644 ui/spice-html5/cursor.js
create mode 100644 ui/spice-html5/display.js
create mode 100644 ui/spice-html5/enums.js
create mode 100644 ui/spice-html5/inputs.js
create mode 100644 ui/spice-html5/lz.js
create mode 100644 ui/spice-html5/main.js
create mode 100644 ui/spice-html5/pages/Makefile.am
create mode 100644 ui/spice-html5/pages/spice_auto.html
create mode 100644 ui/spice-html5/playback.js
create mode 100644 ui/spice-html5/png.js
create mode 100644 ui/spice-html5/quic.js
create mode 100644 ui/spice-html5/resize.js
create mode 100644 ui/spice-html5/simulatecursor.js
create mode 100644 ui/spice-html5/spicearraybuffer.js
create mode 100644 ui/spice-html5/spiceconn.js
create mode 100644 ui/spice-html5/spicedataview.js
create mode 100644 ui/spice-html5/spicemsg.js
create mode 100644 ui/spice-html5/spicetype.js
create mode 100644 ui/spice-html5/thirdparty/Makefile.am
create mode 100644 ui/spice-html5/thirdparty/jsbn.js
create mode 100644 ui/spice-html5/thirdparty/prng4.js
create mode 100644 ui/spice-html5/thirdparty/rng.js
create mode 100644 ui/spice-html5/thirdparty/rsa.js
create mode 100644 ui/spice-html5/thirdparty/sha1.js
create mode 100644 ui/spice-html5/ticket.js
create mode 100644 ui/spice-html5/utils.js
create mode 100644 ui/spice-html5/webm.js
create mode 100644 ui/spice-html5/wire.js
--
1.9.3
10 years, 2 months
[RFC] Guest cloning
by Crístian Viana
Hi everyone,
I'm presenting here my proposal for the feature "Guest cloning" which
is expected to be implemented for Kimchi 1.4.
Description
Cloning a guest means creating a new guest with a copy of the settings
and data of the original guest. All data described by its XML will be
copied completely, with the following exceptions:
* name: the new guest will have an automatically generated name.
We can append "-clone<n>" to the original guest's name, where
<n> is related to the number of clones created from that guest.
For example, cloning a guest named "myfedora" will create a new
guest named "myfedora-clone1"; if another clone for that same
guest is requested, it will be named "myfedora-clone2".
* uuid: the new guest will have an automatically generated UUID.
We can create a random UUID for every cloned guest.
* devices/interface/mac: the new guest will have an automatically
generated MAC address for every network interface. We can create
random MAC addresses for every cloned guest.
* devices/disk: the new guest will have copies of the original
guest's disks. Depending on the storage pool type of each disk,
a different procedure may be used to copy that disk:
* DIR, NFS, Logical: the disk file will be copied to a new
file with a modified name (e.g. "disk.img" ->
"disk-clone1.img") on the same storage pool.
* SCSI, iSCSI: the volume data will be copied as a new
disk file on the storage pool "default".
REST API
Only one new REST command will be added.
Syntax
POST /vms/<vm-name>/clone
Parameters:
None.
Return:
An asynchronous Task with "target_uri" containing "/vms/<new-vm-name>".
As expected with any Task, the cloning process can be tracked by
checking the corresponding task's status.
Discussion
I think the most challenging part of this feature is how to deal with
different types of disks while not prompting the user with any input.
There are a lot of possibilities and a lot of things that can go wrong
during the disks copy but we still need to do whatever is easier for
the user. For example, do we really have to create the new disks in
the same storage pool as the original disk's? If that's not possible
(e.g. not available space), should we create them in another pool with
available space? Should we ask any input from the user (e.g. "Would you
like to create the new disk on the same storage pool or on a different
one?")? What about the *SCSI pool types, is it OK to copy the volume
data to a different storage pool (i.e. "default") like I'm proposing
here? I couldn't think of a way to add a new volume in an existing pool
of those types. How about making the *SCSI volumes shareable between
the original and the new VMs? I don't like that approach because then
both VMs will use the same disk, whatever is changed in one VM is also
changed in the other one, and that's not a clone for me, that's a
"hardlink".
Any feedback is welcome!
Best regards,
Crístian.
10 years, 2 months
[PATCH v12 0/6] Host Device Passthrough
by Zhou Zheng Sheng
Hi all,
Host device passthrough is to directly assign host device to guest
exclusively, so there is no virtualization overhead, and it improves
guest performance greatly. It's useful if you have a server with lots
of PCIe slots and cards.
Changelog:
This v12 patch improves the coding style. For example, using reflection
instead of maintaining method map, using lxml.builder to generate XML
strings.
The v11 patch series improve disto compatibility and device filtering.
Firstly, the patches adapt to Ubuntu 14.04, RHEL 6.5, RHEL 7, Fedora 20
and Fedora 19. It just relies on libvirt node-device API, kernel vfio
framework and /sys/kernel/iommu_group. So any distribution providing
these features should work. On old distributions like RHEL 6.5, they
are shipped with 2.6.X kernel which do not support vfio and sysfs iommu
group. We also try to be compatible, but as far as we tested, the PCI
passthrough feature using the traditional pci-stub + kvm approach is
buggy and not mature. So in this patch series, the back-end provides a
capability in /config/capabilities for the front-end to check, and then
freezes PCI passthrough web UI in this case. Didn't test on SuSe, just
because the author could not find a physical SuSe server. The patches
should work on SuSe as long as it provides the dependencies.
Secondly, there is 2 changes in PCI device filtering. Previously, we
only allowed to assign the "leaf" devices to guest. For example,
instead of assigning a USB controller, we assign the connected USB
device. We also made a PCI device whitelist according to the class
code. After some tests, we find that it's hardly useful if we only
allow "leaf" device, because in many cases the user wants to assign a
parent device. For example, the user may want to assign an HBA card to
guest, while there are many children LUNs under this card. The PCI
device code is also not a good way for checking if a device is suitable
to be passed through. There are too many vendors and devices, you'll
always find some "good" devices are out of the whitelist, and if we
grant the relared class code, it'll introduce "bad" devices.
So in this patch we just filter out video cards and PCI bridges. They
are not for passthrough absolutely. We also allow to passthrough a
parent device. The back-end provides API to check the affected devices
if you passthrough a particular one. The affected devices are the
devices in the same iommu group and their children devices.
As regard to the front-end, we only implemented PCI device passthrough,
PCI devices are the mostly useful and interesting devices to passthrough.
The back-end actually supports passing through LUNs and USB devices.
To test the patches, firstly reboot your host os, enable vt-d in BIOS.
Before loading Linux kernel, append "intel_iommu=on" to kernel
arguments. If you want to make it persistent, edit "grub.conf" and
append it. Then just apply the patch, start Kimchi daemon, edit a
shutdown guest, you'll see "Host PCI Device" tab. In the listing,
select "To Add", then click "+" besides one of the device. You'll find
PCI devices in the same group are also added. Then close the dialogue
and start the guest. In guest OS, "lspci" can show you the passthrough
devices.
In future, we plan to add more helpful information to assist user. For
example, when the user selects an NIC card, the front-end shows the
configured IP address. When the user selects an HBA card, the front-end
shows the related block devices such as sda sdb ... So the user can
avoid assigning a device in use by the host.
Yu Xin Huo (1):
Host device passthrough (Front-end): Add PCI Devices to VM
Zhou Zheng Sheng (5):
Host device passthrough: List all types of host devices
Host device passthrough: List eligible device to passthrough
Host device passthrough: Directly assign and dissmis host device from
VM
Host device passthrough: List VMs that are holding a host device
Host device passthrough: Add unit tests and documents
docs/API.md | 66 ++++++-
src/kimchi/API.json | 38 ++++
src/kimchi/control/host.py | 7 +
src/kimchi/control/vm/hostdevs.py | 43 +++++
src/kimchi/featuretests.py | 10 +-
src/kimchi/i18n.py | 13 ++
src/kimchi/mockmodel.py | 94 +++++++++-
src/kimchi/model/config.py | 6 +-
src/kimchi/model/host.py | 48 +++--
src/kimchi/model/hostdev.py | 323 +++++++++++++++++++++++++++++++++
src/kimchi/model/libvirtstoragepool.py | 18 +-
src/kimchi/model/vmhostdevs.py | 314 ++++++++++++++++++++++++++++++++
src/kimchi/rollbackcontext.py | 3 +
src/kimchi/xmlutils.py | 24 +++
tests/test_model.py | 31 ++++
tests/test_rest.py | 12 +-
tests/test_storagepool.py | 7 +-
ui/css/theme-default/guest-edit.css | 86 ++++++++-
ui/js/src/kimchi.api.js | 55 ++++++
ui/js/src/kimchi.guest_edit_main.js | 81 +++++++++
ui/pages/guest-edit.html.tmpl | 28 +++
21 files changed, 1251 insertions(+), 56 deletions(-)
create mode 100644 src/kimchi/control/vm/hostdevs.py
create mode 100644 src/kimchi/model/hostdev.py
create mode 100644 src/kimchi/model/vmhostdevs.py
--
1.9.3
10 years, 2 months
[PATCH] Backend support for templates with sockets, cores, and threads
by Christy Perez
In order to allow a guest to use SMT/hyperthreading, we should
enable passing in of the sockets, cores, and threads values when
creating a template.
All three values must be specified, as per the topology descr at
http://libvirt.org/formatdomain.html#elementsCPU
v3->v4:
- Remove the unused cpu_ elements from common_spec
- Pass new_t into validate function to reduce complexity
- Rearrange code to decrese indents in _get_cpu_xml
v2->v3:
- Set vcpus based on topology, if specified.
- Move the update cpu+topology validation out to a function
for redability
- Add a minimum value of 1 for topology values
- Leave new English error msg as empty string
- Update the API documentation on cpu defaults
v1->v2:
- Added a check to make sure that vcpus = sockets*cores*threads
- Set individual topoology params to required in API.json
- Change the topology object types from string to integer
- Always return cpu_info from templates lookup()
- Removed check for cpu_info in to_vm_xml
- Build cpu_info xml using lxml.builder instead of string
- CPU and topology verification on template update
Signed-off-by: Christy Perez <christy(a)linux.vnet.ibm.com>
---
docs/API.md | 13 ++++++++++++-
po/en_US.po | 3 +++
po/pt_BR.po | 3 +++
po/zh_CN.po | 3 +++
src/kimchi/API.json | 33 +++++++++++++++++++++++++++++++--
src/kimchi/control/templates.py | 13 ++++++++++---
src/kimchi/i18n.py | 1 +
src/kimchi/model/templates.py | 32 ++++++++++++++++++++++++++++++++
src/kimchi/osinfo.py | 5 ++---
src/kimchi/vmtemplate.py | 16 ++++++++++++++++
10 files changed, 113 insertions(+), 9 deletions(-)
diff --git a/docs/API.md b/docs/API.md
index 92fbbd5..6984649 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -194,7 +194,9 @@ Represents a snapshot of the Virtual Machine's primary monitor.
* name: The name of the Template. Used to identify the Template in this API
* os_distro *(optional)*: The operating system distribution
* os_version *(optional)*: The version of the operating system distribution
- * cpus *(optional)*: The number of CPUs assigned to the VM. Default is 1.
+ * cpus *(optional)*: The number of CPUs assigned to the VM.
+ Default is 1, unlees specifying a cpu topology. In that case, cpus
+ will default to a product of the topology values (see cpu_info).
* memory *(optional)*: The amount of memory assigned to the VM.
Default is 1024M.
* cdrom *(optional)*: A volume name or URI to an ISO image.
@@ -216,6 +218,15 @@ Represents a snapshot of the Virtual Machine's primary monitor.
Independent Computing Environments
* null: Graphics is disabled or type not supported
* listen: The network which the vnc/spice server listens on.
+ * cpu_info *(optional)*: CPU-specific information.
+ * topology: Specify sockets, threads, and cores to run the virtual CPU
+ threads on.
+ All three are required in order to specify cpu topology.
+ * sockets - The number of sockets to use.
+ * cores - The number of cores per socket.
+ * threads - The number of threads per core.
+ If specifying both cpus and CPU topology, make sure cpus is
+ equal to the product of sockets, cores, and threads.
### Sub-Collection: Virtual Machine Network Interfaces
diff --git a/po/en_US.po b/po/en_US.po
index eb571ca..a88675c 100644
--- a/po/en_US.po
+++ b/po/en_US.po
@@ -371,6 +371,9 @@ msgstr ""
msgid "Cannot identify base image %(path)s format"
msgstr ""
+msgid "When specifying CPU topology, VCPUs must be a product of sockets, cores, and threads."
+msgstr ""
+
#, python-format
msgid "Storage pool %(name)s already exists"
msgstr ""
diff --git a/po/pt_BR.po b/po/pt_BR.po
index f2fba64..1bebe30 100644
--- a/po/pt_BR.po
+++ b/po/pt_BR.po
@@ -424,6 +424,9 @@ msgstr "Imagem base do modelo deve ser um arquivo de imagem local válido"
msgid "Cannot identify base image %(path)s format"
msgstr "Não foi possível identificar o formato da imagem base %(path)s"
+msgid "When specifying CPU topology, VCPUs must be a product of sockets, cores, and threads."
+msgstr ""
+
#, python-format
msgid "Storage pool %(name)s already exists"
msgstr "Storage pool %(name)s já existe"
diff --git a/po/zh_CN.po b/po/zh_CN.po
index f77e405..f944b8d 100644
--- a/po/zh_CN.po
+++ b/po/zh_CN.po
@@ -392,6 +392,9 @@ msgstr "模板基础镜像必须为一个有效的本地镜像文件"
msgid "Cannot identify base image %(path)s format"
msgstr "未能识别基础镜像%(path)s格式"
+msgid "When specifying CPU topology, VCPUs must be a product of sockets, cores, and threads."
+msgstr ""
+
#, python-format
msgid "Storage pool %(name)s already exists"
msgstr "存储池%(name)s已经存在"
diff --git a/src/kimchi/API.json b/src/kimchi/API.json
index d9e13f0..d3edf27 100644
--- a/src/kimchi/API.json
+++ b/src/kimchi/API.json
@@ -26,6 +26,33 @@
]
}
}
+ },
+ "cpu_info": {
+ "description": "Configure CPU specifics for a VM.",
+ "type": "object",
+ "properties": {
+ "topology": {
+ "description": "Configure the guest CPU topology.",
+ "type": "object",
+ "properties": {
+ "sockets": {
+ "type": "integer",
+ "required": true,
+ "minimum": 1
+ },
+ "cores": {
+ "type": "integer",
+ "required": true,
+ "minimum": 1
+ },
+ "threads": {
+ "type": "integer",
+ "required": true,
+ "minimum": 1
+ }
+ }
+ }
+ }
}
},
"properties": {
@@ -448,7 +475,8 @@
"type": "array",
"items": { "type": "string" }
},
- "graphics": { "$ref": "#/kimchitype/graphics" }
+ "graphics": { "$ref": "#/kimchitype/graphics" },
+ "cpu_info": { "$ref": "#/kimchitype/cpu_info" }
},
"additionalProperties": false,
"error": "KCHAPI0001E"
@@ -612,7 +640,8 @@
"type": "array",
"items": { "type": "string" }
},
- "graphics": { "$ref": "#/kimchitype/graphics" }
+ "graphics": { "$ref": "#/kimchitype/graphics" },
+ "cpu_info": { "$ref": "#/kimchitype/cpu_info" }
},
"additionalProperties": false,
"error": "KCHAPI0001E"
diff --git a/src/kimchi/control/templates.py b/src/kimchi/control/templates.py
index e17fa54..54caa92 100644
--- a/src/kimchi/control/templates.py
+++ b/src/kimchi/control/templates.py
@@ -38,13 +38,14 @@ def __init__(self, model, ident):
self.update_params = ["name", "folder", "icon", "os_distro",
"storagepool", "os_version", "cpus",
"memory", "cdrom", "disks", "networks",
- "graphics"]
+ "graphics", "cpu_info"]
self.uri_fmt = "/templates/%s"
self.clone = self.generate_action_handler('clone')
@property
def data(self):
- return {'name': self.ident,
+ return_data = {
+ 'name': self.ident,
'icon': self.info['icon'],
'invalid': self.info['invalid'],
'os_distro': self.info['os_distro'],
@@ -56,4 +57,10 @@ def data(self):
'storagepool': self.info['storagepool'],
'networks': self.info['networks'],
'folder': self.info.get('folder', []),
- 'graphics': self.info['graphics']}
+ 'graphics': self.info['graphics']
+ }
+ if (self.info.get('cpu_info')):
+ return_data['cpu_info'] = self.info['cpu_info']
+ else:
+ return_data['cpu_info'] = ''
+ return return_data
diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py
index 75fb076..611316c 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -143,6 +143,7 @@
"KCHTMPL0022E": _("Disk size must be an integer greater than 1GB."),
"KCHTMPL0023E": _("Template base image must be a valid local image file"),
"KCHTMPL0024E": _("Cannot identify base image %(path)s format"),
+ "KCHTMPL0025E": _("When specifying CPU topology, VCPUs must be a product of sockets, cores, and threads."),
"KCHPOOL0001E": _("Storage pool %(name)s already exists"),
"KCHPOOL0002E": _("Storage pool %(name)s does not exist"),
diff --git a/src/kimchi/model/templates.py b/src/kimchi/model/templates.py
index 9278cdc..2954148 100644
--- a/src/kimchi/model/templates.py
+++ b/src/kimchi/model/templates.py
@@ -48,6 +48,22 @@ def create(self, params):
{'filename': iso, 'user': user,
'err': excp})
+ cpu_info = params.get('cpu_info')
+ if cpu_info:
+ cpu_info = dict(params['cpu_info'])
+ topology = cpu_info.get('topology')
+ # Check, even though currently only topology
+ # is supported.
+ if topology:
+ sockets = topology['sockets']
+ cores = topology['cores']
+ threads = topology['threads']
+ vcpus = params.get('cpus')
+ if vcpus is None:
+ params['cpus'] = sockets * cores * threads
+ elif vcpus != sockets * cores * threads:
+ raise InvalidParameter("KCHTMPL0025E")
+
conn = self.conn.get()
pool_uri = params.get(u'storagepool', '')
if pool_uri:
@@ -156,6 +172,10 @@ def update(self, name, params):
old_t = self.lookup(name)
new_t = copy.copy(old_t)
new_t.update(params)
+
+ if not self._validate_updated_cpu_params(new_t):
+ raise InvalidParameter('KCHTMPL0025E')
+
ident = name
conn = self.conn.get()
@@ -187,6 +207,18 @@ def update(self, name, params):
raise
return ident
+ def _validate_updated_cpu_params(self, info):
+ # Note: cpu_info is the parent of topology. cpus is vcpus
+ vcpus = info['cpus']
+ cpu_info = info.get('cpu_info')
+ if cpu_info is None:
+ return True
+ topology = cpu_info.get('topology')
+ if topology is None:
+ return True
+ return vcpus == topology['sockets'] * topology['cores'] * \
+ topology['threads']
+
class LibvirtVMTemplate(VMTemplate):
def __init__(self, args, scan=False, conn=None):
diff --git a/src/kimchi/osinfo.py b/src/kimchi/osinfo.py
index 6ee5e48..0e16b50 100644
--- a/src/kimchi/osinfo.py
+++ b/src/kimchi/osinfo.py
@@ -32,9 +32,8 @@
'power': ('ppc', 'ppc64')}
-common_spec = {'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1, 'memory': 1024,
- 'disks': [{'index': 0, 'size': 10}], 'cdrom_bus': 'ide',
- 'cdrom_index': 2, 'mouse_bus': 'ps2'}
+common_spec = {'cpus': 1, 'memory': 1024, 'disks': [{'index': 0, 'size': 10}],
+ 'cdrom_bus': 'ide', 'cdrom_index': 2, 'mouse_bus': 'ps2'}
modern_spec = dict(common_spec, disk_bus='virtio', nic_model='virtio')
diff --git a/src/kimchi/vmtemplate.py b/src/kimchi/vmtemplate.py
index 5f22db9..cfb7704 100644
--- a/src/kimchi/vmtemplate.py
+++ b/src/kimchi/vmtemplate.py
@@ -358,6 +358,20 @@ def _get_input_output_xml(self):
input_output += sound % self.info
return input_output
+ def _get_cpu_xml(self):
+
+ cpu_info = self.info.get('cpu_info')
+ if cpu_info is None:
+ return ""
+ cpu_topo = cpu_info.get('topology')
+ if cpu_topo is None:
+ return ""
+ return etree.tostring(E.cpu(E.topology(
+ sockets=str(cpu_topo['sockets']),
+ cores=str(cpu_topo['cores']),
+ threads=str(cpu_topo['threads']))))
+
+
def to_vm_xml(self, vm_name, vm_uuid, **kwargs):
params = dict(self.info)
params['name'] = vm_name
@@ -369,6 +383,7 @@ def to_vm_xml(self, vm_name, vm_uuid, **kwargs):
params['qemu-stream-cmdline'] = ''
graphics = kwargs.get('graphics')
params['graphics'] = self._get_graphics_xml(graphics)
+ params['cpu_info'] = self._get_cpu_xml()
# Current implementation just allows to create disk in one single
# storage pool, so we cannot mix the types (scsi volumes vs img file)
@@ -400,6 +415,7 @@ def to_vm_xml(self, vm_name, vm_uuid, **kwargs):
<uuid>%(uuid)s</uuid>
<memory unit='MiB'>%(memory)s</memory>
<vcpu>%(cpus)s</vcpu>
+ %(cpu_info)s
<os>
<type arch='%(arch)s'>hvm</type>
<boot dev='hd'/>
--
1.9.3
10 years, 2 months
[PATCH v3] Backend support for templates with sockets, cores, and threads
by Christy Perez
In order to allow a guest to use SMT/hyperthreading, we should
enable passing in of the sockets, cores, and threads values when
creating a template.
All three values must be specified, as per the topology descr at
http://libvirt.org/formatdomain.html#elementsCPU
v2->v3:
- Set vcpus based on topology, if specified.
- Move the update cpu+topology validation out to a function
for redability
- Add a minimum value of 1 for topology values
- Leave new English error msg as empty string
- Update the API documentation on cpu defaults
v1->v2:
- Added a check to make sure that vcpus = sockets*cores*threads
- Set individual topoology params to required in API.json
- Change the topology object types from string to integer
- Always return cpu_info from templates lookup()
- Removed check for cpu_info in to_vm_xml
- Build cpu_info xml using lxml.builder instead of string
- CPU and topology verification on template update
Signed-off-by: Christy Perez <christy(a)linux.vnet.ibm.com>
---
docs/API.md | 13 ++++++++++-
po/en_US.po | 3 +++
po/pt_BR.po | 3 +++
po/zh_CN.po | 3 +++
src/kimchi/API.json | 33 ++++++++++++++++++++++++--
src/kimchi/control/templates.py | 13 ++++++++---
src/kimchi/i18n.py | 1 +
src/kimchi/model/templates.py | 52 +++++++++++++++++++++++++++++++++++++++++
src/kimchi/osinfo.py | 2 +-
src/kimchi/vmtemplate.py | 15 ++++++++++++
10 files changed, 131 insertions(+), 7 deletions(-)
diff --git a/docs/API.md b/docs/API.md
index cc438cc..7f1a8c2 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -179,7 +179,9 @@ Represents a snapshot of the Virtual Machine's primary monitor.
* name: The name of the Template. Used to identify the Template in this API
* os_distro *(optional)*: The operating system distribution
* os_version *(optional)*: The version of the operating system distribution
- * cpus *(optional)*: The number of CPUs assigned to the VM. Default is 1.
+ * cpus *(optional)*: The number of CPUs assigned to the VM.
+ Default is 1, unlees specifying a cpu topology. In that case, cpus
+ will default to a product of the topology values (see cpu_info).
* memory *(optional)*: The amount of memory assigned to the VM.
Default is 1024M.
* cdrom *(optional)*: A volume name or URI to an ISO image.
@@ -201,6 +203,15 @@ Represents a snapshot of the Virtual Machine's primary monitor.
Independent Computing Environments
* null: Graphics is disabled or type not supported
* listen: The network which the vnc/spice server listens on.
+ * cpu_info *(optional)*: CPU-specific information.
+ * topology: Specify sockets, threads, and cores to run the virtual CPU
+ threads on.
+ All three are required in order to specify cpu topology.
+ * sockets - The number of sockets to use.
+ * cores - The number of cores per socket.
+ * threads - The number of threads per core.
+ If specifying both cpus and CPU topology, make sure cpus is
+ equal to the product of sockets, cores, and threads.
### Sub-Collection: Virtual Machine Network Interfaces
diff --git a/po/en_US.po b/po/en_US.po
index eb571ca..a88675c 100644
--- a/po/en_US.po
+++ b/po/en_US.po
@@ -371,6 +371,9 @@ msgstr ""
msgid "Cannot identify base image %(path)s format"
msgstr ""
+msgid "When specifying CPU topology, VCPUs must be a product of sockets, cores, and threads."
+msgstr ""
+
#, python-format
msgid "Storage pool %(name)s already exists"
msgstr ""
diff --git a/po/pt_BR.po b/po/pt_BR.po
index f2fba64..1bebe30 100644
--- a/po/pt_BR.po
+++ b/po/pt_BR.po
@@ -424,6 +424,9 @@ msgstr "Imagem base do modelo deve ser um arquivo de imagem local válido"
msgid "Cannot identify base image %(path)s format"
msgstr "Não foi possível identificar o formato da imagem base %(path)s"
+msgid "When specifying CPU topology, VCPUs must be a product of sockets, cores, and threads."
+msgstr ""
+
#, python-format
msgid "Storage pool %(name)s already exists"
msgstr "Storage pool %(name)s já existe"
diff --git a/po/zh_CN.po b/po/zh_CN.po
index f77e405..f944b8d 100644
--- a/po/zh_CN.po
+++ b/po/zh_CN.po
@@ -392,6 +392,9 @@ msgstr "模板基础镜像必须为一个有效的本地镜像文件"
msgid "Cannot identify base image %(path)s format"
msgstr "未能识别基础镜像%(path)s格式"
+msgid "When specifying CPU topology, VCPUs must be a product of sockets, cores, and threads."
+msgstr ""
+
#, python-format
msgid "Storage pool %(name)s already exists"
msgstr "存储池%(name)s已经存在"
diff --git a/src/kimchi/API.json b/src/kimchi/API.json
index 5b752dc..fe3df72 100644
--- a/src/kimchi/API.json
+++ b/src/kimchi/API.json
@@ -26,6 +26,33 @@
]
}
}
+ },
+ "cpu_info": {
+ "description": "Configure CPU specifics for a VM.",
+ "type": "object",
+ "properties": {
+ "topology": {
+ "description": "Configure the guest CPU topology.",
+ "type": "object",
+ "properties": {
+ "sockets": {
+ "type": "integer",
+ "required": true,
+ "minimum": 1
+ },
+ "cores": {
+ "type": "integer",
+ "required": true,
+ "minimum": 1
+ },
+ "threads": {
+ "type": "integer",
+ "required": true,
+ "minimum": 1
+ }
+ }
+ }
+ }
}
},
"properties": {
@@ -448,7 +475,8 @@
"type": "array",
"items": { "type": "string" }
},
- "graphics": { "$ref": "#/kimchitype/graphics" }
+ "graphics": { "$ref": "#/kimchitype/graphics" },
+ "cpu_info": { "$ref": "#/kimchitype/cpu_info" }
},
"additionalProperties": false,
"error": "KCHAPI0001E"
@@ -612,7 +640,8 @@
"type": "array",
"items": { "type": "string" }
},
- "graphics": { "$ref": "#/kimchitype/graphics" }
+ "graphics": { "$ref": "#/kimchitype/graphics" },
+ "cpu_info": { "$ref": "#/kimchitype/cpu_info" }
},
"additionalProperties": false,
"error": "KCHAPI0001E"
diff --git a/src/kimchi/control/templates.py b/src/kimchi/control/templates.py
index e17fa54..54caa92 100644
--- a/src/kimchi/control/templates.py
+++ b/src/kimchi/control/templates.py
@@ -38,13 +38,14 @@ def __init__(self, model, ident):
self.update_params = ["name", "folder", "icon", "os_distro",
"storagepool", "os_version", "cpus",
"memory", "cdrom", "disks", "networks",
- "graphics"]
+ "graphics", "cpu_info"]
self.uri_fmt = "/templates/%s"
self.clone = self.generate_action_handler('clone')
@property
def data(self):
- return {'name': self.ident,
+ return_data = {
+ 'name': self.ident,
'icon': self.info['icon'],
'invalid': self.info['invalid'],
'os_distro': self.info['os_distro'],
@@ -56,4 +57,10 @@ def data(self):
'storagepool': self.info['storagepool'],
'networks': self.info['networks'],
'folder': self.info.get('folder', []),
- 'graphics': self.info['graphics']}
+ 'graphics': self.info['graphics']
+ }
+ if (self.info.get('cpu_info')):
+ return_data['cpu_info'] = self.info['cpu_info']
+ else:
+ return_data['cpu_info'] = ''
+ return return_data
diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py
index 1b543ce..9ce8e86 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -131,6 +131,7 @@
"KCHTMPL0022E": _("Disk size must be an integer greater than 1GB."),
"KCHTMPL0023E": _("Template base image must be a valid local image file"),
"KCHTMPL0024E": _("Cannot identify base image %(path)s format"),
+ "KCHTMPL0025E": _("When specifying CPU topology, VCPUs must be a product of sockets, cores, and threads."),
"KCHPOOL0001E": _("Storage pool %(name)s already exists"),
"KCHPOOL0002E": _("Storage pool %(name)s does not exist"),
diff --git a/src/kimchi/model/templates.py b/src/kimchi/model/templates.py
index 9278cdc..04d2af9 100644
--- a/src/kimchi/model/templates.py
+++ b/src/kimchi/model/templates.py
@@ -48,6 +48,22 @@ def create(self, params):
{'filename': iso, 'user': user,
'err': excp})
+ cpu_info = params.get('cpu_info')
+ if cpu_info:
+ cpu_info = dict(params['cpu_info'])
+ topology = cpu_info.get('topology')
+ # Check, even though currently only topology
+ # is supported.
+ if topology:
+ sockets = topology['sockets']
+ cores = topology['cores']
+ threads = topology['threads']
+ vcpus = params.get('cpus')
+ if vcpus is None:
+ params['cpus'] = sockets * cores * threads
+ elif sockets * cores * threads != vcpus:
+ raise InvalidParameter("KCHTMPL0025E")
+
conn = self.conn.get()
pool_uri = params.get(u'storagepool', '')
if pool_uri:
@@ -154,6 +170,9 @@ def delete(self, name):
def update(self, name, params):
old_t = self.lookup(name)
+ if not self._verify_updated_cpu_params(params, old_t):
+ raise InvalidParameter('KCHTMPL0025E')
+
new_t = copy.copy(old_t)
new_t.update(params)
ident = name
@@ -187,6 +206,39 @@ def update(self, name, params):
raise
return ident
+ def _verify_updated_cpu_params(self, params, old_t):
+ # Note: cpu_info is the parent of topology. cpus is vcpus
+ # Keep two check_ variables for what we'll verify at the end to
+ # keep from duplicating code.
+ check_cpu = None
+ check_topology = None
+ # First see what parameters were passed in.
+ new_vcpus = params.get('cpus')
+ new_cpu_info = params.get('cpu_info')
+ new_topology = ''
+ if new_cpu_info:
+ new_topology = new_cpu_info.get('topology')
+ # Now figure out what needs to be verified.
+ if new_vcpus and new_topology:
+ check_cpu = new_vcpus
+ check_topology = new_topology
+ elif new_vcpus:
+ old_cpu_info = old_t.get('cpu_info')
+ if old_cpu_info:
+ old_topology = old_cpu_info.get('topology')
+ if old_topology:
+ check_cpu = new_vcpus
+ check_topology = old_topology
+ elif new_topology:
+ check_cpu = old_t.get('cpus')
+ check_topology = new_topology
+ # Now verify the cpu and topoloy parameters
+ if check_cpu and (check_cpu != check_topology['sockets'] * \
+ check_topology['cores'] * check_topology['threads']):
+ return False
+
+ return True
+
class LibvirtVMTemplate(VMTemplate):
def __init__(self, args, scan=False, conn=None):
diff --git a/src/kimchi/osinfo.py b/src/kimchi/osinfo.py
index 6ee5e48..10d0ab0 100644
--- a/src/kimchi/osinfo.py
+++ b/src/kimchi/osinfo.py
@@ -32,7 +32,7 @@
'power': ('ppc', 'ppc64')}
-common_spec = {'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1, 'memory': 1024,
+common_spec = {'cpus': 1, 'cpu_sockets': '1', 'cpu_cores': 1, 'cpu_threads': 1, 'memory': 1024,
'disks': [{'index': 0, 'size': 10}], 'cdrom_bus': 'ide',
'cdrom_index': 2, 'mouse_bus': 'ps2'}
diff --git a/src/kimchi/vmtemplate.py b/src/kimchi/vmtemplate.py
index 5f22db9..34720de 100644
--- a/src/kimchi/vmtemplate.py
+++ b/src/kimchi/vmtemplate.py
@@ -358,6 +358,19 @@ def _get_input_output_xml(self):
input_output += sound % self.info
return input_output
+ def _get_cpu_xml(self):
+
+ cpu_info = self.info.get('cpu_info')
+ if cpu_info is not None:
+ cpu_topo = cpu_info.get('topology')
+ if cpu_topo is not None:
+ return etree.tostring(E.cpu(E.topology(
+ sockets=str(cpu_topo['sockets']),
+ cores=str(cpu_topo['cores']),
+ threads=str(cpu_topo['threads']))))
+
+ return ""
+
def to_vm_xml(self, vm_name, vm_uuid, **kwargs):
params = dict(self.info)
params['name'] = vm_name
@@ -369,6 +382,7 @@ def to_vm_xml(self, vm_name, vm_uuid, **kwargs):
params['qemu-stream-cmdline'] = ''
graphics = kwargs.get('graphics')
params['graphics'] = self._get_graphics_xml(graphics)
+ params['cpu_info'] = self._get_cpu_xml()
# Current implementation just allows to create disk in one single
# storage pool, so we cannot mix the types (scsi volumes vs img file)
@@ -400,6 +414,7 @@ def to_vm_xml(self, vm_name, vm_uuid, **kwargs):
<uuid>%(uuid)s</uuid>
<memory unit='MiB'>%(memory)s</memory>
<vcpu>%(cpus)s</vcpu>
+ %(cpu_info)s
<os>
<type arch='%(arch)s'>hvm</type>
<boot dev='hd'/>
--
1.9.3
10 years, 2 months
[v3 0/1] Ticket UI to set vnc ticket password
by Simon Jin
V3-V2:
- Increase the diablog width and height to avoid the scroll
and keep 'Console password' in one line.
V2-V1:
- Put new password input box on 'general' tab
instead of being a sperate tab.
- Rename the input lobel as 'Console password'
Simon Jin (1):
Ticket UI to set vnc ticket password
ui/css/theme-default/guest-edit.css | 2 +-
ui/js/src/kimchi.guest_edit_main.js | 4 ++++
ui/pages/guest-edit.html.tmpl | 11 +++++++++++
3 files changed, 16 insertions(+), 1 deletion(-)
--
1.9.3
10 years, 2 months
[RFC] UI to allow user adds disks from different pools to a Template
by Wen Wang
Hi all,
I have the following suggestions of this work item.
I agree with Aline's proposal that we should display the "Edit Template" dialogue the same way as "Edit Guest" in tabs.
Considering that we are going to add another function to allow users attaching multiple disks to template, My proposal
is to devide the "Edit Guest" into tabs:
1) General: includes the basic information of the template. Items included under this tab are:
--Name
--Vendor
--Version
--CPU Number
--Memory
--CDROM
--Graphics
2) Storage: Associated storage information of the template(Multiple lines with title of these two indicates multiple volumes)
--Storage Pool
--Disk(GB)
3) Interface:(Multiple lines of network information)
--Network
I will have the mock sent tomorrow and any comment is welcomed
--
=======================================================
Best Regards
Wang Wen
10 years, 2 months
[PATCH v2] Backend support for templates with sockets, cores, and threads
by Christy Perez
In order to allow a guest to use SMT/hyperthreading, we should
enable passing in of the sockets, cores, and threads values when
creating a template.
All three values must be specified, as per the topology descr at
http://libvirt.org/formatdomain.html#elementsCPU
v1->v2:
- Added a check to make sure that vcpus = sockets*cores*threads
- Set individual topoology params to required in API.json
- Change the topology object types from string to integer
- Always return cpu_info from templates lookup()
- Removed check for cpu_info in to_vm_xml
- Build cpu_info xml using lxml.builder instead of string
- CPU and topology verification on template update
Signed-off-by: Christy Perez <christy(a)linux.vnet.ibm.com>
---
docs/API.md | 11 +++++++++-
po/en_US.po | 3 +++
po/pt_BR.po | 3 +++
po/zh_CN.po | 3 +++
src/kimchi/API.json | 30 ++++++++++++++++++++++++--
src/kimchi/control/templates.py | 13 +++++++++---
src/kimchi/i18n.py | 1 +
src/kimchi/model/templates.py | 47 +++++++++++++++++++++++++++++++++++++++++
src/kimchi/osinfo.py | 2 +-
src/kimchi/vmtemplate.py | 15 +++++++++++++
10 files changed, 121 insertions(+), 7 deletions(-)
diff --git a/docs/API.md b/docs/API.md
index cc438cc..6281f70 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -179,7 +179,9 @@ Represents a snapshot of the Virtual Machine's primary monitor.
* name: The name of the Template. Used to identify the Template in this API
* os_distro *(optional)*: The operating system distribution
* os_version *(optional)*: The version of the operating system distribution
- * cpus *(optional)*: The number of CPUs assigned to the VM. Default is 1.
+ * cpus *(optional)*: The number of CPUs assigned to the VM.
+ Default is 1. If specifying a CPU topology, make sure this value is
+ equal to the product of sockets, cores, and threads.
* memory *(optional)*: The amount of memory assigned to the VM.
Default is 1024M.
* cdrom *(optional)*: A volume name or URI to an ISO image.
@@ -201,6 +203,13 @@ Represents a snapshot of the Virtual Machine's primary monitor.
Independent Computing Environments
* null: Graphics is disabled or type not supported
* listen: The network which the vnc/spice server listens on.
+ * cpu_info *(optional)*: CPU-specific information.
+ * topology: Specify sockets, threads, and cores to run the virtual CPU
+ threads on.
+ All three are required in order to specify cpu topology.
+ * sockets - The number of sockets to use.
+ * cores - The number of cores per socket.
+ * threads - The number of threads per core.
### Sub-Collection: Virtual Machine Network Interfaces
diff --git a/po/en_US.po b/po/en_US.po
index eb571ca..74f1ec6 100644
--- a/po/en_US.po
+++ b/po/en_US.po
@@ -371,6 +371,9 @@ msgstr ""
msgid "Cannot identify base image %(path)s format"
msgstr ""
+msgid "When specifying CPU topology, VCPUs must be a product of sockets, cores, and threads."
+msgstr "When specifying CPU topology, VCPUs must be a product of sockets, cores, and threads."
+
#, python-format
msgid "Storage pool %(name)s already exists"
msgstr ""
diff --git a/po/pt_BR.po b/po/pt_BR.po
index f2fba64..1bebe30 100644
--- a/po/pt_BR.po
+++ b/po/pt_BR.po
@@ -424,6 +424,9 @@ msgstr "Imagem base do modelo deve ser um arquivo de imagem local válido"
msgid "Cannot identify base image %(path)s format"
msgstr "Não foi possível identificar o formato da imagem base %(path)s"
+msgid "When specifying CPU topology, VCPUs must be a product of sockets, cores, and threads."
+msgstr ""
+
#, python-format
msgid "Storage pool %(name)s already exists"
msgstr "Storage pool %(name)s já existe"
diff --git a/po/zh_CN.po b/po/zh_CN.po
index f77e405..f944b8d 100644
--- a/po/zh_CN.po
+++ b/po/zh_CN.po
@@ -392,6 +392,9 @@ msgstr "模板基础镜像必须为一个有效的本地镜像文件"
msgid "Cannot identify base image %(path)s format"
msgstr "未能识别基础镜像%(path)s格式"
+msgid "When specifying CPU topology, VCPUs must be a product of sockets, cores, and threads."
+msgstr ""
+
#, python-format
msgid "Storage pool %(name)s already exists"
msgstr "存储池%(name)s已经存在"
diff --git a/src/kimchi/API.json b/src/kimchi/API.json
index 5b752dc..0fe3993 100644
--- a/src/kimchi/API.json
+++ b/src/kimchi/API.json
@@ -26,6 +26,30 @@
]
}
}
+ },
+ "cpu_info": {
+ "description": "Configure CPU specifics for a VM.",
+ "type": "object",
+ "properties": {
+ "topology": {
+ "description": "Configure the guest CPU topology.",
+ "type": "object",
+ "properties": {
+ "sockets": {
+ "type": "integer",
+ "required": true
+ },
+ "cores": {
+ "type": "integer",
+ "required": true
+ },
+ "threads": {
+ "type": "integer",
+ "required": true
+ }
+ }
+ }
+ }
}
},
"properties": {
@@ -448,7 +472,8 @@
"type": "array",
"items": { "type": "string" }
},
- "graphics": { "$ref": "#/kimchitype/graphics" }
+ "graphics": { "$ref": "#/kimchitype/graphics" },
+ "cpu_info": { "$ref": "#/kimchitype/cpu_info" }
},
"additionalProperties": false,
"error": "KCHAPI0001E"
@@ -612,7 +637,8 @@
"type": "array",
"items": { "type": "string" }
},
- "graphics": { "$ref": "#/kimchitype/graphics" }
+ "graphics": { "$ref": "#/kimchitype/graphics" },
+ "cpu_info": { "$ref": "#/kimchitype/cpu_info" }
},
"additionalProperties": false,
"error": "KCHAPI0001E"
diff --git a/src/kimchi/control/templates.py b/src/kimchi/control/templates.py
index e17fa54..54caa92 100644
--- a/src/kimchi/control/templates.py
+++ b/src/kimchi/control/templates.py
@@ -38,13 +38,14 @@ def __init__(self, model, ident):
self.update_params = ["name", "folder", "icon", "os_distro",
"storagepool", "os_version", "cpus",
"memory", "cdrom", "disks", "networks",
- "graphics"]
+ "graphics", "cpu_info"]
self.uri_fmt = "/templates/%s"
self.clone = self.generate_action_handler('clone')
@property
def data(self):
- return {'name': self.ident,
+ return_data = {
+ 'name': self.ident,
'icon': self.info['icon'],
'invalid': self.info['invalid'],
'os_distro': self.info['os_distro'],
@@ -56,4 +57,10 @@ def data(self):
'storagepool': self.info['storagepool'],
'networks': self.info['networks'],
'folder': self.info.get('folder', []),
- 'graphics': self.info['graphics']}
+ 'graphics': self.info['graphics']
+ }
+ if (self.info.get('cpu_info')):
+ return_data['cpu_info'] = self.info['cpu_info']
+ else:
+ return_data['cpu_info'] = ''
+ return return_data
diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py
index 1b543ce..9ce8e86 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -131,6 +131,7 @@
"KCHTMPL0022E": _("Disk size must be an integer greater than 1GB."),
"KCHTMPL0023E": _("Template base image must be a valid local image file"),
"KCHTMPL0024E": _("Cannot identify base image %(path)s format"),
+ "KCHTMPL0025E": _("When specifying CPU topology, VCPUs must be a product of sockets, cores, and threads."),
"KCHPOOL0001E": _("Storage pool %(name)s already exists"),
"KCHPOOL0002E": _("Storage pool %(name)s does not exist"),
diff --git a/src/kimchi/model/templates.py b/src/kimchi/model/templates.py
index 9278cdc..203c324 100644
--- a/src/kimchi/model/templates.py
+++ b/src/kimchi/model/templates.py
@@ -48,6 +48,22 @@ def create(self, params):
{'filename': iso, 'user': user,
'err': excp})
+ cpu_info = params.get('cpu_info')
+ if cpu_info:
+ cpu_info = dict(params['cpu_info'])
+ topology = cpu_info.get('topology')
+ # Check, even though currently only topology
+ # is supported.
+ if topology:
+ sockets = int(topology.get('sockets'))
+ cores = int(topology.get('cores'))
+ threads = int(topology.get('threads'))
+ vcpus = params.get('cpus')
+ if vcpus is None:
+ vcpus = 1
+ if sockets * cores * threads != vcpus:
+ raise InvalidParameter("KCHTMPL0025E")
+
conn = self.conn.get()
pool_uri = params.get(u'storagepool', '')
if pool_uri:
@@ -154,6 +170,37 @@ def delete(self, name):
def update(self, name, params):
old_t = self.lookup(name)
+ # If changing vcpus, check for topology values, and vice versa
+ # Note: cpu_info is the parent of topology. cpus is vcpus
+ # Keep two check_ variables for what we'll verify at the end to
+ # keep from duplicating code.
+ check_cpu = None
+ check_topology = None
+ # First see what parameters were passed in.
+ new_vcpus = params.get('cpus')
+ new_cpu_info = params.get('cpu_info')
+ new_topology = ''
+ if new_cpu_info:
+ new_topology = new_cpu_info.get('topology')
+ # Now figure out what needs to be verified.
+ if new_vcpus and new_topology:
+ check_cpu = new_vcpus
+ check_topology = new_topology
+ elif new_vcpus:
+ old_cpu_info = old_t.get('cpu_info')
+ if old_cpu_info:
+ old_topology = old_cpu_info.get('topology')
+ if old_topology:
+ check_cpu = new_vcpus
+ check_topology = old_topology
+ elif new_topology:
+ check_cpu = old_t.get('cpus')
+ check_topology = new_topology
+ # Now verify the cpu and topoloy parameters
+ if check_cpu and (check_cpu != check_topology['sockets'] * \
+ check_topology['cores'] * check_topology['threads']):
+ raise InvalidParameter('KCHTMPL0025E')
+
new_t = copy.copy(old_t)
new_t.update(params)
ident = name
diff --git a/src/kimchi/osinfo.py b/src/kimchi/osinfo.py
index 6ee5e48..10d0ab0 100644
--- a/src/kimchi/osinfo.py
+++ b/src/kimchi/osinfo.py
@@ -32,7 +32,7 @@
'power': ('ppc', 'ppc64')}
-common_spec = {'cpus': 1, 'cpu_cores': 1, 'cpu_threads': 1, 'memory': 1024,
+common_spec = {'cpus': 1, 'cpu_sockets': '1', 'cpu_cores': 1, 'cpu_threads': 1, 'memory': 1024,
'disks': [{'index': 0, 'size': 10}], 'cdrom_bus': 'ide',
'cdrom_index': 2, 'mouse_bus': 'ps2'}
diff --git a/src/kimchi/vmtemplate.py b/src/kimchi/vmtemplate.py
index 5f22db9..34720de 100644
--- a/src/kimchi/vmtemplate.py
+++ b/src/kimchi/vmtemplate.py
@@ -358,6 +358,19 @@ def _get_input_output_xml(self):
input_output += sound % self.info
return input_output
+ def _get_cpu_xml(self):
+
+ cpu_info = self.info.get('cpu_info')
+ if cpu_info is not None:
+ cpu_topo = cpu_info.get('topology')
+ if cpu_topo is not None:
+ return etree.tostring(E.cpu(E.topology(
+ sockets=str(cpu_topo['sockets']),
+ cores=str(cpu_topo['cores']),
+ threads=str(cpu_topo['threads']))))
+
+ return ""
+
def to_vm_xml(self, vm_name, vm_uuid, **kwargs):
params = dict(self.info)
params['name'] = vm_name
@@ -369,6 +382,7 @@ def to_vm_xml(self, vm_name, vm_uuid, **kwargs):
params['qemu-stream-cmdline'] = ''
graphics = kwargs.get('graphics')
params['graphics'] = self._get_graphics_xml(graphics)
+ params['cpu_info'] = self._get_cpu_xml()
# Current implementation just allows to create disk in one single
# storage pool, so we cannot mix the types (scsi volumes vs img file)
@@ -400,6 +414,7 @@ def to_vm_xml(self, vm_name, vm_uuid, **kwargs):
<uuid>%(uuid)s</uuid>
<memory unit='MiB'>%(memory)s</memory>
<vcpu>%(cpus)s</vcpu>
+ %(cpu_info)s
<os>
<type arch='%(arch)s'>hvm</type>
<boot dev='hd'/>
--
1.9.3
10 years, 2 months
[PATCH] Addition of custom mac address field for interfaces v4
by bbaude@redhat.com
From: Brent Baude <bbaude(a)redhat.com>
Included review feedback
---
docs/API.md | 1 +
src/kimchi/API.json | 6 ++++++
src/kimchi/control/vms.py | 2 +-
src/kimchi/exception.py | 1 -
src/kimchi/i18n.py | 4 ++++
src/kimchi/model/vmifaces.py | 38 +++++++++++++++++++++++++++++--------
tests/test_model.py | 12 ++++++++++++
ui/css/theme-default/guest-edit.css | 4 ++--
ui/js/src/kimchi.guest_edit_main.js | 18 ++++++++++++------
ui/pages/guest-edit.html.tmpl | 11 ++++++++---
10 files changed, 76 insertions(+), 21 deletions(-)
diff --git a/docs/API.md b/docs/API.md
index b679ce7..4f92eaa 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -221,6 +221,7 @@ Represents all network interfaces attached to a Virtual Machine.
interface type is network.
* type: The type of VM network interface that libvirt supports.
Now kimchi just supports 'network' type.
+ * custommac: A custom MAC address for the interface
### Sub-Resource: Virtual Machine Network Interface
diff --git a/src/kimchi/API.json b/src/kimchi/API.json
index b8604d2..6cea55d 100644
--- a/src/kimchi/API.json
+++ b/src/kimchi/API.json
@@ -334,6 +334,12 @@
"type": "string",
"pattern": "^ne2k_pci|i82551|i82557b|i82559er|rtl8139|e1000|pcnet|virtio$",
"error": "KCHVMIF0006E"
+ },
+ "custommac": {
+ "description": "optional custom mac address for the network interface",
+ "type": "string",
+ "pattern": "(^(?i)([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$)?",
+ "error": "KCHVMIF0009E"
}
}
},
diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py
index 88d8a81..627f1cc 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",
- "graphics"]
+ "graphics", "custommac"]
self.screenshot = VMScreenShot(model, ident)
self.uri_fmt = '/vms/%s'
for ident, node in sub_nodes.items():
diff --git a/src/kimchi/exception.py b/src/kimchi/exception.py
index 039152a..7ab1878 100644
--- a/src/kimchi/exception.py
+++ b/src/kimchi/exception.py
@@ -46,7 +46,6 @@ class KimchiException(Exception):
msg = self._get_translation()
else:
msg = _messages.get(code, code)
-
msg = unicode(msg, 'utf-8') % args
pattern = "%s: %s" % (code, msg)
Exception.__init__(self, pattern)
diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py
index 9e66c68..4473a99 100644
--- a/src/kimchi/i18n.py
+++ b/src/kimchi/i18n.py
@@ -105,6 +105,10 @@ messages = {
"KCHVMIF0006E": _("Invalid network model card specified for virtual machine interface"),
"KCHVMIF0007E": _("Specify type and network to add a new virtual machine interface"),
"KCHVMIF0008E": _("Specify type and network to update a virtual machine interface"),
+ "KCHVMIF0009E": _("Invalid MAC address format. Expected a 12 character hexadecimal value separated by colons(:)"),
+ "KCHVMIF0010E": _("The custom MAC address %(custommac)s is already in use on this guest"),
+ "KCHVMIF0011E": _("Libvirt requires a unicast MAC address which %(value)s is not."),
+
"KCHTMPL0001E": _("Template %(name)s already exists"),
"KCHTMPL0002E": _("Template %(name)s does not exist"),
diff --git a/src/kimchi/model/vmifaces.py b/src/kimchi/model/vmifaces.py
index 66b3827..ef67d34 100644
--- a/src/kimchi/model/vmifaces.py
+++ b/src/kimchi/model/vmifaces.py
@@ -18,6 +18,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import random
+import re
import libvirt
from lxml import etree, objectify
@@ -45,6 +46,20 @@ class VMIfacesModel(object):
random.randint(0x00, 0xff)]
return ':'.join(map(lambda x: "%02x" % x, mac))
+ def checkduplicatemacs(vm, custommac):
+ macs = self.get_list(vm)
+ if custommac in macs:
+ raise InvalidParameter("KCHVMIF0010E", {'custommac': mac})
+
+ def validMac(mymac):
+ pattern = r'^(?i)([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$'
+ if bool(re.match(pattern, mac)) is not True:
+ raise InvalidParameter("KCHVMIF0009E", {'custommac': mac})
+ else:
+ # Checking for multicast
+ if (int(mymac.split(":")[0], 16) & 0x1) == 1:
+ raise InvalidParameter("KCHVMIF0011E", {'custommac': mac})
+
conn = self.conn.get()
networks = conn.listNetworks() + conn.listDefinedNetworks()
@@ -56,14 +71,22 @@ class VMIfacesModel(object):
if DOM_STATE_MAP[dom.info()[0]] != "shutoff":
raise InvalidOperation("KCHVMIF0003E")
- macs = (iface.mac.get('address')
- for iface in self.get_vmifaces(vm, self.conn))
-
- mac = randomMAC()
- while True:
- if mac not in macs:
- break
+ if 'custommac' not in params:
+ macs = (iface.mac.get('address')
+ for iface in self.get_vmifaces(vm, self.conn))
mac = randomMAC()
+ while True:
+ if mac not in macs:
+ break
+ mac = randomMAC()
+
+ else:
+ if len(params['custommac']) == 0:
+ mac = randomMAC()
+ else:
+ mac = params["custommac"]
+ validMac(mac)
+ checkduplicatemacs(vm, mac)
children = [E.mac(address=mac)]
("network" in params.keys() and
@@ -75,7 +98,6 @@ class VMIfacesModel(object):
xml = etree.tostring(E.interface(*children, **attrib))
dom.attachDeviceFlags(xml, libvirt.VIR_DOMAIN_AFFECT_CURRENT)
-
return mac
@staticmethod
diff --git a/tests/test_model.py b/tests/test_model.py
index ceedc6f..6d67398 100644
--- a/tests/test_model.py
+++ b/tests/test_model.py
@@ -219,6 +219,18 @@ class ModelTests(unittest.TestCase):
self.assertEquals("default", iface['network'])
self.assertEquals("e1000", iface["model"])
+ # custom MAC address test
+ iface_args = {"type": "network",
+ "network": "test-network",
+ "model": "virtio",
+ "custommac": "52:54:00:17:cd:e7"}
+ mac = inst.vmifaces_create('kimchi-ifaces', iface_args)
+ rollback.prependDefer(inst.vmiface_delete, 'kimchi-ifaces', mac)
+ iface = inst.vmiface_lookup('kimchi-ifaces', mac)
+ self.assertEquals("52:54:00:17:cd:e7", iface['mac'])
+ self.assertRaises(InvalidParameter, inst.vmifaces_create,
+ 'kimchi-ifaces', iface_args)
+
@unittest.skipUnless(utils.running_as_root(), 'Must be run as root')
def test_vm_disk(self):
disk_path = '/tmp/existent2.iso'
diff --git a/ui/css/theme-default/guest-edit.css b/ui/css/theme-default/guest-edit.css
index 74c2237..f0e1bb0 100644
--- a/ui/css/theme-default/guest-edit.css
+++ b/ui/css/theme-default/guest-edit.css
@@ -112,7 +112,7 @@
#form-guest-edit-storage .cell,
.guest-edit-interface .cell {
display: inline-block;
- width: 250px;
+ width: 150px;
}
#form-guest-edit-storage .cell.dev {
@@ -130,7 +130,7 @@
}
.guest-edit-interface .body select {
- width: 180px;
+ width: 150px;
padding: 0px;
}
diff --git a/ui/js/src/kimchi.guest_edit_main.js b/ui/js/src/kimchi.guest_edit_main.js
index c281289..7baf296 100644
--- a/ui/js/src/kimchi.guest_edit_main.js
+++ b/ui/js/src/kimchi.guest_edit_main.js
@@ -176,7 +176,10 @@ kimchi.guest_edit_main = function() {
var toggleEdit = function(item, on){
$("label", item).toggleClass("hide", on);
$("select", item).toggleClass("hide", !on);
- $(".action-area", item).toggleClass("hide");
+ $("input", item).toggleClass("hide", !on);
+ $(".action-area#editmode", item).toggleClass("hide", !on);
+ $(".action-area#viewmode", item).toggleClass("hide", on);
+
};
var addItem = function(data) {
var itemNode = $.parseHTML(kimchi.substitute($('#interface-tmpl').html(),data));
@@ -215,20 +218,23 @@ kimchi.guest_edit_main = function() {
var item = $(this).parent().parent();
var interface = {
network: $("select", item).val(),
- type: "network"
+ type: "network",
+ custommac: $("input",item).val().toLowerCase()
};
- var postUpdate = function(){
+ var postUpdate = function(mymac){
$("label", item).text(interface.network);
- toggleEdit(item, false);
+ $("label#custommac", item).text(mymac);
+ toggleEdit(item.prop, false);
};
if(item.prop("id")==""){
kimchi.createGuestInterface(kimchi.selectedGuest, interface, function(data){
item.prop("id", data.mac);
- postUpdate();
+ $("label#custommac", item).text(data.mac)
+ postUpdate(data.mac);
});
}else{
kimchi.updateGuestInterface(kimchi.selectedGuest, item.prop("id"), interface, function(){
- postUpdate();
+ postUpdate(data.mac);
});
}
});
diff --git a/ui/pages/guest-edit.html.tmpl b/ui/pages/guest-edit.html.tmpl
index 0b7dad3..0aa9f3b 100644
--- a/ui/pages/guest-edit.html.tmpl
+++ b/ui/pages/guest-edit.html.tmpl
@@ -108,7 +108,8 @@
<div class="header">
<span class="cell">$_("Network")</span>
<span class="cell">$_("Type")</span>
- <button class="add action-area"></button>
+ <span class="cell">$_("MAC")</span>
+ <button id="interfaceadd" class="add action-area"></button>
</div>
<div class="body"></div>
</form>
@@ -192,10 +193,14 @@
<span class="cell">
<span>{type}</span>
</span>
- <span class="action-area {editMode}">
+ <span class="cell">
+ <label class="{viewMode}" id="custommac">{mac}</label>
+ <input type="text" id="insertmac" class="{editMode}"/>
+ </span>
+ <span class="action-area {editMode}" id="editmode">
<button class="save"></button><button class="cancel"></button>
</span>
- <span class="action-area {viewMode}">
+ <span class="action-area {viewMode}" id="viewmode">
<button class="edit"></button><button class="delete"></button>
</span>
<div>
--
1.9.3
10 years, 2 months