[PATCH v2] PEP8 for mockmodel.py

Updated mockmodel.py and makefile.am per Sheldon's comments. Adam King (1): PEP8 for mockmodel.py Makefile.am | 1 + src/kimchi/mockmodel.py | 69 ++++++++++++++++++++++++++++--------------------- 2 files changed, 40 insertions(+), 30 deletions(-) -- 1.8.1.4

Update mockmodel.py to be pep8 compliant, and update makekefile whitelist Signed-off-by: Adam King <rak@linux.vnet.ibm.com> --- Makefile.am | 1 + src/kimchi/mockmodel.py | 69 ++++++++++++++++++++++++++++--------------------- 2 files changed, 40 insertions(+), 30 deletions(-) diff --git a/Makefile.am b/Makefile.am index 378036e..47ba154 100644 --- a/Makefile.am +++ b/Makefile.am @@ -53,6 +53,7 @@ PEP8_WHITELIST = \ src/kimchi/featuretests.py \ src/kimchi/iscsi.py \ src/kimchi/isoinfo.py \ + src/kimchi/mockmodel.py \ src/kimchi/model/*.py \ src/kimchi/repositories.py \ src/kimchi/rollbackcontext.py \ diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index 8ef4431..ba9859b 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -17,8 +17,10 @@ # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# License along with this library; if not, write to the +# Free Software Foundation, Inc. +# 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301 USA import cherrypy import copy @@ -41,7 +43,6 @@ except ImportError: import ImageDraw - from kimchi import config from kimchi.asynctask import AsyncTask from kimchi.config import config as kconfig @@ -53,7 +54,8 @@ from kimchi.model.utils import get_vm_name from kimchi.model.vms import VM_STATIC_UPDATE_PARAMS from kimchi.objectstore import ObjectStore from kimchi.screenshot import VMScreenshot -from kimchi.utils import pool_name_from_uri, run_command, template_name_from_uri +from kimchi.utils import pool_name_from_uri, run_command +from kimchi.utils import template_name_from_uri from kimchi.vmtemplate import VMTemplate @@ -64,7 +66,8 @@ class MockModel(object): self.distros = self._get_distros() def capabilities_lookup(self, *ident): - return {'libvirt_stream_protocols': ['http', 'https', 'ftp', 'ftps', 'tftp'], + return {'libvirt_stream_protocols': + ['http', 'https', 'ftp', 'ftps', 'tftp'], 'qemu_stream': True, 'screenshot': True, 'system_report_tool': True} @@ -135,8 +138,7 @@ class MockModel(object): def vms_create(self, params): t_name = template_name_from_uri(params['template']) - name = get_vm_name(params.get('name'), t_name, - self._mock_vms.keys()) + name = get_vm_name(params.get('name'), t_name, self._mock_vms.keys()) if name in self._mock_vms: raise InvalidOperation("KCHVM0001E", {'name': name}) @@ -350,15 +352,16 @@ class MockModel(object): pool.info['source'] = params['source'] if not pool.info['source'].get('adapter_name'): raise MissingParameter('KCHPOOL0004E', - {'item': 'adapter_name', 'name': name}) - for vol in ['unit:0:0:1','unit:0:0:2', - 'unit:0:0:3','unit:0:0:4']: + {'item': 'adapter_name', + 'name': name}) + for vol in ['unit:0:0:1', 'unit:0:0:2', + 'unit:0:0:3', 'unit:0:0:4']: mockvol = MockStorageVolume(name, vol, - dict([('type','lun')])) + dict([('type', 'lun')])) pool._volumes[vol] = mockvol else: pool.info['path'] = params['path'] - if params['type'] in ['dir','scsi']: + if params['type'] in ['dir', 'scsi']: pool.info['autostart'] = True else: pool.info['autostart'] = False @@ -436,7 +439,7 @@ class MockModel(object): def storagevolume_lookup(self, pool, name): if self._get_storagepool(pool).info['state'] != 'active': raise InvalidOperation("KCHVOL0005E", {'pool': pool, - 'volume': name}) + 'volume': name}) storagevolume = self._get_storagevolume(pool, name) return storagevolume.info @@ -461,7 +464,7 @@ class MockModel(object): return res._volumes.keys() def devices_get_list(self, _cap=None): - return ['scsi_host3', 'scsi_host4','scsi_host5'] + return ['scsi_host3', 'scsi_host4', 'scsi_host5'] def device_lookup(self, nodedev_name): return { @@ -493,7 +496,7 @@ class MockModel(object): return iso_volumes def storageservers_get_list(self, _target_type=None): - # FIXME: When added new storage server support, this needs to be updated + # FIXME: This needs to be updted when adding new storage server support target_type = STORAGE_SOURCES.keys() \ if not _target_type else [_target_type] pools = self.storagepools_get_list() @@ -514,10 +517,12 @@ class MockModel(object): for pool in pools: try: pool_info = self.storagepool_lookup(pool) - if pool_info['source'] and pool_info['source']['addr'] == server: + if pool_info['source'] and \ + pool_info['source']['addr'] == server: return dict(host=server) except NotFoundError: - # Avoid inconsistent pool result because of lease between list and lookup + # Avoid inconsistent pool result because + # of lease between list and lookup pass raise NotFoundError("KCHSR0001E", {'server': server}) @@ -562,7 +567,7 @@ class MockModel(object): try: net = ipaddr.IPNetwork(subnet) except ValueError: - msg_args = {'subnet':subnet, 'network': name} + msg_args = {'subnet': subnet, 'network': name} raise InvalidParameter("KCHNET0003E", msg_args) network.info['dhcp'] = { @@ -581,8 +586,8 @@ class MockModel(object): def _get_vms_attach_to_a_network(self, network): vms = [] for name, dom in self._mock_vms.iteritems(): - if network in dom.networks: - vms.append(name) + if network in dom.networks: + vms.append(name) return vms def network_lookup(self, name): @@ -650,7 +655,7 @@ class MockModel(object): def vmifaces_create(self, vm, params): if (params["type"] == "network" and - params["network"] not in self.networks_get_list()): + params["network"] not in self.networks_get_list()): msg_args = {'network': params["network"], 'name': vm} raise InvalidParameter("KCHVMIF0002E", msg_args) @@ -679,7 +684,7 @@ class MockModel(object): def vmiface_delete(self, vm, mac): dom = self._get_vm(vm) try: - del dom.ifaces[mac] + del dom.ifaces[mac] except KeyError: raise NotFoundError("KCHVMIF0001E", {'iface': mac, 'name': vm}) @@ -835,7 +840,7 @@ class MockModel(object): raise OperationFailed("KCHREPOS0007E", {'repo_id': repo_id}) def repository_disable(self, repo_id): - if not self._mock_host_repositories.disableRepository(repo_id): + if not self._mock_host_repositories.disableRepository(repo_id): raise OperationFailed("KCHREPOS0008E", {'repo_id': repo_id}) def repository_update(self, repo_id, params): @@ -895,7 +900,8 @@ class MockVMIface(object): self.__class__.counter += 1 self.info = {'type': 'network', 'model': 'virtio', - 'network': network if network else "net-%s" % self.counter, + 'network': network if network + else "net-%s" % self.counter, 'mac': self.get_mac() } @@ -917,13 +923,15 @@ class MockVM(object): self.ifaces = dict([(iface.info['mac'], iface) for iface in ifaces]) self.info = {'state': 'shutoff', 'stats': "{'cpu_utilization': 20, 'net_throughput' : 35, \ - 'net_throughput_peak': 100, 'io_throughput': 45, \ + 'net_throughput_peak': 100, \ + 'io_throughput': 45, \ 'io_throughput_peak': 100}", 'uuid': self.uuid, 'memory': template_info['memory'], 'cpus': template_info['cpus'], 'icon': None, - 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', 'port': None} + 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', + 'port': None} } self.info['graphics'].update(template_info['graphics']) @@ -974,6 +982,7 @@ class MockTask(object): def __init__(self, id): self.id = id + class MockStorageVolume(object): def __init__(self, pool, name, params={}): self.name = name @@ -983,9 +992,9 @@ class MockStorageVolume(object): params = self._def_lun(name) fmt = params.get('format', 'raw') capacity = params.get('capacity', 1024) - self.info = {'type': params.get('type','disk'), + self.info = {'type': params.get('type', 'disk'), 'capacity': capacity << 20, - 'allocation': params.get('allocation','512'), + 'allocation': params.get('allocation', '512'), 'path': params.get('path'), 'format': fmt} if fmt == 'iso': @@ -1000,10 +1009,10 @@ class MockStorageVolume(object): return { "capacity": capacity, "name": name, - "format": random.choice(['dos','unknown']), + "format": random.choice(['dos', 'unknown']), "allocation": capacity, "path": path + name[-1], - "type": "block" } + "type": "block"} class MockVMScreenshot(VMScreenshot): -- 1.8.1.4

This patch set improves the guest tab implementation by: Implementing the circle gauge as a fully reusable jQuery widget Using DOM updates to update the guests tab rather than string manipulation Improving the usability of the guest livetile Adam King (4): Update the guest.html.tmpl to use the new circleGauge widget Update (mock)model to generate proper JSON Create a reusable jQuery circleGauge widget Change guests tab to update the VM List by DOM manipulation src/kimchi/mockmodel.py | 11 +- src/kimchi/model/vms.py | 2 +- tests/test_mockmodel.py | 2 +- tests/test_model.py | 2 +- ui/css/theme-default/circle.css | 26 --- ui/css/theme-default/circleGauge.css | 26 +++ ui/css/theme-default/list.css | 29 ++- ui/js/src/kimchi.circle.js | 88 --------- ui/js/src/kimchi.guest_main.js | 366 +++++++++++++++++++---------------- ui/js/widgets/circleGauge.js | 104 ++++++++++ ui/pages/guest.html.tmpl | 41 ++-- ui/pages/tabs/guests.html.tmpl | 2 +- 12 files changed, 393 insertions(+), 306 deletions(-) delete mode 100644 ui/css/theme-default/circle.css create mode 100644 ui/css/theme-default/circleGauge.css delete mode 100644 ui/js/src/kimchi.circle.js create mode 100644 ui/js/widgets/circleGauge.js -- 1.8.1.4

Updated the template to use the new circle gauge widget. Also retooled the template so that insertions can be handled by DOM manipulation rather than string manipulation. Signed-off-by: Adam King <rak@linux.vnet.ibm.com> --- ui/css/theme-default/list.css | 27 +++++++++++++++++++++++++-- ui/pages/guest.html.tmpl | 41 +++++++++++++++++++++-------------------- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/ui/css/theme-default/list.css b/ui/css/theme-default/list.css index c435261..57ad610 100644 --- a/ui/css/theme-default/list.css +++ b/ui/css/theme-default/list.css @@ -92,17 +92,38 @@ .list-vm .tile .imgload { display: none; - max-height: 110px; +} + +.list-vm .tile.shutoff .imgactive { +max-height: 110px; + max-width: 170px; + height: auto; + width: auto; + display:inline; + border: none; +} + +.list-vm .tile.running .imgactive{ + max-height: 110px; max-width: 170px; height: auto; width: auto; + display:inline; + border: none; + cursor: crosshair; + cursor: -moz-zoom-in; + cursor: -webkit-zoom-in; } -.list-vm .tile .imgactive { +.list-vm .tile .overlay { max-height: 110px; max-width: 170px; height: auto; width: auto; + position:absolute; + bottom:0; + right:0; + display:none; } .guest-type { @@ -221,10 +242,12 @@ margin: 10px; } + .list-vm .tile:not(.shutoff) img { box-shadow: -1px -1px 2px rgb(0, 0, 0, .25), 3px 3px 3px #fff; } + .list-vm .shutoff { box-shadow: none !important; } diff --git a/ui/pages/guest.html.tmpl b/ui/pages/guest.html.tmpl index 6d83d57..cbf76ca 100644 --- a/ui/pages/guest.html.tmpl +++ b/ui/pages/guest.html.tmpl @@ -5,6 +5,7 @@ * * Authors: * Hongliang Wang <hlwanghl@cn.ibm.com> + * Adam King <rak@us.ibm.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,43 +25,43 @@ #silent t = gettext.translation($lang.domain, $lang.localedir, languages=$lang.lang) #silent _ = t.gettext #silent _t = t.gettext - <li id="{name}"> + <li name="guest" class="guest"> <div class="sortable guest-type"> <div class="guest-general"> <h2 class="title" title="{name}">{name}</h2> </div> </div> - <div class="sortable guest-cpu"> - <div class="circle" data-value="{stats}"></div> + <div name="cpu_utilization" class="sortable"> + <div class="circleGauge"></div> </div> - <div class="sortable guest-network"> - <div class="circle" data-value="{stats}"></div> + <div name="io_throughput" class="sortable"> + <div class="circleGauge"></div> <div class="subtitle">KB/s</div> </div> - <div class="sortable guest-storage"> - <div class="circle" data-value="{stats}"></div> + <div name="net_throughput" class="sortable"> + <div class="circleGauge"></div> <div class="subtitle">KB/s</div> </div> - <div class="sortable guest-tile"> - <div class="tile {state}"> - <img class="imgactive" alt="" src="{tile-src}"> - <img class="imgload" alt="" src="{load-src}"> + <div name="guest-tile" class="sortable guest-tile"> + <div class="tile "> + <img class="imgactive" alt="" src=""> + <img class="imgload" alt="" src=""> + <img class="overlay shutoff-hidden" alt="$_("Start")" src="/images/theme-default/icon-power-down.png" > </div> </div> - <div class="sortable guest-actions"> + <div class="sortable guest-actions" name="guest-actions"> <div class="top"> - <a class="btn vm-reset" data-vmstate="{state}" data-vm="{name}" href="javascript:void(0);" title="$_("Reset")"><span class="icon reset"></span></a> - <a class="btn vm-start" data-vmstate="{state}" data-vm="{name}" href="javascript:void(0);" title="$_("Start")"><span class="icon power-down"></span></a> - <a class="btn vm-stop" data-vmstate="{state}" data-vm="{name}" href="javascript:void(0);" title="$_("Stop")"><span class="icon power-up"></span></a> + <a class="btn shutoff-disabled" name="vm-reset" href="javascript:void(0);" title="$_("Reset")"><span class="icon reset"></span></a> + <a class="btn running-hidden" name="vm-start" href="javascript:void(0);" title="$_("Start")"><span class="icon power-down"></span></a> + <a class="btn shutoff-hidden" name="vm-stop" href="javascript:void(0);" title="$_("Stop")"><span class="icon power-up"></span></a> </div> <div class="bottom"> - <div class="btn dropdown popable vm-action" data-vmstate="{state}" data-graphics="{graphics.type}" data-vm="{name}" style="width: 70px"> + <div name="actionmenu" class="btn dropdown popable vm-action" style="width: 70px"> <span class="text">$_("Actions")</span><span class="arrow"></span> <div class="popover actionsheet right-side" style="width: 250px"> - <button class="button-big vm-vnc" data-vm="{name}"><span class="text">VNC</span></button> - <button class="button-big vm-spice" data-vm="{name}"><span class="text">SPICE</span></button> - <button class="button-big vm-edit" data-vm="{name}"><span class="text">$_("Edit")</span></button> - <a class="button-big red vm-delete" data-vm="{name}">$_("Delete")</a> + <button class="button-big shutoff-disabled" name="vm-console" ><span class="text">$_("Console")</span></button> + <button class="button-big running-disabled" name="vm-edit"><span class="text">$_("Edit")</span></button> + <a class="button-big red " name="vm-delete">$_("Delete")</a> </div> </div> </div> -- 1.8.1.4

Update the model and the mock model to generate proper JSON. Update the automated tests to validate against the new data format. Signed-off-by: Adam King <rak@linux.vnet.ibm.com> --- src/kimchi/mockmodel.py | 11 +++++++---- src/kimchi/model/vms.py | 2 +- tests/test_mockmodel.py | 2 +- tests/test_model.py | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index ba9859b..6a44bf7 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -921,11 +921,14 @@ class MockVM(object): ifaces = [MockVMIface(net) for net in self.networks] self.storagedevices = {} self.ifaces = dict([(iface.info['mac'], iface) for iface in ifaces]) + + stats = {'cpu_utilization': 20, + 'net_throughput': 35, + 'net_throughput_peak': 100, + 'io_throughput': 45, + 'io_throughput_peak': 100} self.info = {'state': 'shutoff', - 'stats': "{'cpu_utilization': 20, 'net_throughput' : 35, \ - 'net_throughput_peak': 100, \ - 'io_throughput': 45, \ - 'io_throughput_peak': 100}", + 'stats': stats, 'uuid': self.uuid, 'memory': template_info['memory'], 'cpus': template_info['cpus'], diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index b482e80..9da6688 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -306,7 +306,7 @@ class VMModel(object): res['io_throughput_peak'] = vm_stats.get('max_disk_io', 100) return {'state': state, - 'stats': str(res), + 'stats': res, 'uuid': dom.UUIDString(), 'memory': info[2] >> 10, 'cpus': info[3], diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py index 4ac08dd..ab5eb59 100644 --- a/tests/test_mockmodel.py +++ b/tests/test_mockmodel.py @@ -149,7 +149,7 @@ class MockModelTests(unittest.TestCase): self.assertEquals(1024, info['memory']) self.assertEquals(1, info['cpus']) self.assertEquals('images/icon-vm.png', info['icon']) - self.assertEquals(stats_keys, set(eval(info['stats']).keys())) + self.assertEquals(stats_keys, set(info['stats'].keys())) self.assertEquals('vnc', info['graphics']['type']) self.assertEquals('0.0.0.0', info['graphics']['listen']) diff --git a/tests/test_model.py b/tests/test_model.py index 298a39e..d3315a0 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -67,7 +67,7 @@ class ModelTests(unittest.TestCase): self.assertEquals(2048, info['memory']) self.assertEquals(2, info['cpus']) self.assertEquals(None, info['icon']) - self.assertEquals(stats_keys, set(eval(info['stats']).keys())) + self.assertEquals(stats_keys, set(info['stats'].keys())) self.assertRaises(NotFoundError, inst.vm_lookup, 'nosuchvm') @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') -- 1.8.1.4

Reviewed-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> On 02/21/2014 09:59 AM, Adam King wrote:
Update the model and the mock model to generate proper JSON. Update the automated tests to validate against the new data format.
Signed-off-by: Adam King <rak@linux.vnet.ibm.com> --- src/kimchi/mockmodel.py | 11 +++++++---- src/kimchi/model/vms.py | 2 +- tests/test_mockmodel.py | 2 +- tests/test_model.py | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index ba9859b..6a44bf7 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -921,11 +921,14 @@ class MockVM(object): ifaces = [MockVMIface(net) for net in self.networks] self.storagedevices = {} self.ifaces = dict([(iface.info['mac'], iface) for iface in ifaces]) + + stats = {'cpu_utilization': 20, + 'net_throughput': 35, + 'net_throughput_peak': 100, + 'io_throughput': 45, + 'io_throughput_peak': 100} self.info = {'state': 'shutoff', - 'stats': "{'cpu_utilization': 20, 'net_throughput' : 35, \ - 'net_throughput_peak': 100, \ - 'io_throughput': 45, \ - 'io_throughput_peak': 100}", + 'stats': stats, 'uuid': self.uuid, 'memory': template_info['memory'], 'cpus': template_info['cpus'], diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index b482e80..9da6688 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -306,7 +306,7 @@ class VMModel(object): res['io_throughput_peak'] = vm_stats.get('max_disk_io', 100)
return {'state': state, - 'stats': str(res), + 'stats': res, 'uuid': dom.UUIDString(), 'memory': info[2] >> 10, 'cpus': info[3], diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py index 4ac08dd..ab5eb59 100644 --- a/tests/test_mockmodel.py +++ b/tests/test_mockmodel.py @@ -149,7 +149,7 @@ class MockModelTests(unittest.TestCase): self.assertEquals(1024, info['memory']) self.assertEquals(1, info['cpus']) self.assertEquals('images/icon-vm.png', info['icon']) - self.assertEquals(stats_keys, set(eval(info['stats']).keys())) + self.assertEquals(stats_keys, set(info['stats'].keys())) self.assertEquals('vnc', info['graphics']['type']) self.assertEquals('0.0.0.0', info['graphics']['listen'])
diff --git a/tests/test_model.py b/tests/test_model.py index 298a39e..d3315a0 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -67,7 +67,7 @@ class ModelTests(unittest.TestCase): self.assertEquals(2048, info['memory']) self.assertEquals(2, info['cpus']) self.assertEquals(None, info['icon']) - self.assertEquals(stats_keys, set(eval(info['stats']).keys())) + self.assertEquals(stats_keys, set(info['stats'].keys())) self.assertRaises(NotFoundError, inst.vm_lookup, 'nosuchvm')
@unittest.skipUnless(utils.running_as_root(), 'Must be run as root')
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

Create a reusable jQuery circleGauge widget to replace the circle previously used in the guests tab. Signed-off-by: Adam King <rak@linux.vnet.ibm.com> --- ui/css/theme-default/circle.css | 26 --------- ui/css/theme-default/circleGauge.css | 26 +++++++++ ui/js/src/kimchi.circle.js | 88 ----------------------------- ui/js/widgets/circleGauge.js | 104 +++++++++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 114 deletions(-) delete mode 100644 ui/css/theme-default/circle.css create mode 100644 ui/css/theme-default/circleGauge.css delete mode 100644 ui/js/src/kimchi.circle.js create mode 100644 ui/js/widgets/circleGauge.js diff --git a/ui/css/theme-default/circle.css b/ui/css/theme-default/circle.css deleted file mode 100644 index 8008219..0000000 --- a/ui/css/theme-default/circle.css +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Project Kimchi - * - * Copyright IBM, Corp. 2013 - * - * Authors: - * Lise Noble <lwnoble@us.ibm.com> - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -.circle { - position: relative; - margin: 30px 10px 10px 10px; - width: 70px; - height: 70px; -} diff --git a/ui/css/theme-default/circleGauge.css b/ui/css/theme-default/circleGauge.css new file mode 100644 index 0000000..cf9970e --- /dev/null +++ b/ui/css/theme-default/circleGauge.css @@ -0,0 +1,26 @@ +/* + * Project Kimchi + * + * Copyright IBM, Corp. 2013 + * + * Authors: + * Lise Noble <lwnoble@us.ibm.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +.circleGauge { + position: relative; + margin: 30px 10px 10px 10px; + width: 70px; + height: 70px; +} diff --git a/ui/js/src/kimchi.circle.js b/ui/js/src/kimchi.circle.js deleted file mode 100644 index 7b9ccc7..0000000 --- a/ui/js/src/kimchi.circle.js +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Project Kimchi - * - * Copyright IBM, Corp. 2013 - * - * Authors: - * Hongliang Wang <hlwanghl@cn.ibm.com> - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -(function($) { - $.fn.circle = function(options) { - var settings = $.extend({ - color : '#87C004', - fillColor : '#87C004', - fontFamily : 'Geneva, sans-serif', - fontSize : 13, - radius : 35, - lineWidth : 20 - }, options); - - $(this).each(function() { - var that = $(this); - var json = eval("(" + that.data('value') + ")"); - var parentNode = that.parent() - var type = ''; - var display = 0; - var circle = 0; - - if (parentNode.hasClass('guest-cpu')) { - display = parseInt(json['cpu_utilization']); - circle = display; - type = '%'; - } else if (parentNode.hasClass('guest-network')) { - display = parseInt(json['net_throughput']); - circle = (display * 100) / parseInt(json['net_throughput_peak']); - } else if (parentNode.hasClass('guest-storage')) { - display = parseInt(json['io_throughput']); - circle = (display * 100) / parseInt(json['io_throughput_peak']); - } - - that.empty(); - var canvas = document.createElement('canvas'); - that.append($(canvas)); - var ctx = canvas.getContext('2d'); - var lineWidth = settings['lineWidth']; - var radius = settings['radius']; - var fontSize = settings['fontSize']; - var shadowSize = 2; - var width = height = radius * 2; - $(canvas).attr('height', height); - $(canvas).attr('width', width); - $(canvas).css({ - 'boxShadow' : shadowSize + 'px ' + shadowSize + 'px ' + shadowSize + 'px #fff, -' + shadowSize + 'px -' + shadowSize + 'px ' + shadowSize + 'px #eaeaea', - borderRadius : radius + 'px' - }); - ctx.clearRect(0, 0, width, height); - - ctx.fillStyle = settings['fillColor']; - ctx.font = 'bold ' + fontSize + 'px ' + settings['fontFamily']; - ctx.textAlign = 'center'; - var originPos = radius; - ctx.textBaseline = 'middle'; - ctx.fillText(display + type, originPos, originPos); - ctx.strokeStyle = settings['color']; - ctx.lineWidth = lineWidth; - ctx.beginPath(); - ctx.arc(originPos, originPos, radius, -.5 * Math.PI, (circle / 50 - .5) * Math.PI); - ctx.stroke(); - }); - - return this; - }; -}(jQuery)); - -kimchi.circle = function(selector) { - $(selector).circle(); -}; diff --git a/ui/js/widgets/circleGauge.js b/ui/js/widgets/circleGauge.js new file mode 100644 index 0000000..d0887fd --- /dev/null +++ b/ui/js/widgets/circleGauge.js @@ -0,0 +1,104 @@ +/* + * Project Kimchi + * + * Copyright IBM, Corp. 2013 + * + * Authors: + * Hongliang Wang <hlwanghl@cn.ibm.com> + * Adam King <rak@linux.vnet.ibm.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + (function($) { + $.widget('kimchi.circleGauge', { + + options : { + color : '#87C004', + fillColor : '#87C004', + lineWidth : 20, + shadowSize : '2px', + font : 'bold 13px Geneva, sans-serif', + textAlign : 'center', + radius : 35, + peakRate : 100, + display : 0, + circle : 0, + label : '' + }, + + _create : function() { + //valuesAttr="{" + this.element.data('value')+ "}"; + //console.info(valuesAttr); + //values=eval("(" + valuesAttr + ")"); + //$.extend(this.options, values); + this.options.display=this.element.data('display'); + this.options.percentage=this.element.data('percentage'); + this._fixupPeakRate(); + this._draw(); + }, + + setValues : function(values) { + $.extend(this.options, values); + this._fixupPeakRate(); + this._draw(); + }, + + _fixupPeakRate : function() { + if (this.options.circle>this.options.peakRate) { + this.options.peakRate=this.options.circle; + } + }, + + _draw : function() { + this.element.empty(); + var canvas = document.createElement('canvas'); + //this.element.append($(canvas)); //I don't quite understand this line so trying the one below... + this.element.append(canvas); + + var ctx = canvas.getContext('2d'); + var radius = this.options.radius; + + var shadowSize = 2; + var width = height = radius * 2; + $(canvas).attr('height', height); + $(canvas).attr('width', width); + + $(canvas).css({ + 'boxShadow' : shadowSize + 'px ' + shadowSize + 'px ' + shadowSize + 'px #fff, -' + shadowSize + 'px -' + shadowSize + 'px ' + shadowSize + 'px #eaeaea', + borderRadius : radius + 'px' + }); + + ctx.clearRect(0, 0, width, height); + ctx.fillStyle = this.options.fillColor; + ctx.font = this.options.font; + ctx.textAlign = 'center'; + var originPos = radius; + ctx.textBaseline = 'middle'; + ctx.fillText(this.options.display, originPos, originPos); + ctx.strokeStyle = this.options.color; + ctx.lineWidth = this.options.lineWidth; + ctx.beginPath(); + ctx.arc(originPos, originPos, radius, -.5 * Math.PI, (this.options.percentage / 50 - .5) * Math.PI); + ctx.stroke(); + }, + + destroy : function() { + this.element.empty(); + $.Widget.prototype.destroy.call(this); + } + }); +}(jQuery)); + +kimchi.circleGauge = function(selector) { + $(selector).circleGauge(); +}; -- 1.8.1.4

Update guests.html.template to manipulate the VM List my DOM manipulation rather than by string manipulation. Also update the tile to function as a start when the VM is stopped, or as a console connection when it is running. Signed-off-by: Adam King <rak@linux.vnet.ibm.com> --- ui/css/theme-default/list.css | 2 + ui/js/src/kimchi.guest_main.js | 366 +++++++++++++++++++++++------------------ ui/pages/tabs/guests.html.tmpl | 2 +- 3 files changed, 207 insertions(+), 163 deletions(-) diff --git a/ui/css/theme-default/list.css b/ui/css/theme-default/list.css index 57ad610..af7ac1f 100644 --- a/ui/css/theme-default/list.css +++ b/ui/css/theme-default/list.css @@ -101,6 +101,7 @@ max-height: 110px; width: auto; display:inline; border: none; + position: relative; } .list-vm .tile.running .imgactive{ @@ -249,6 +250,7 @@ max-height: 110px; .list-vm .shutoff { + position: relative; box-shadow: none !important; } diff --git a/ui/js/src/kimchi.guest_main.js b/ui/js/src/kimchi.guest_main.js index 99cb84a..15354a4 100644 --- a/ui/js/src/kimchi.guest_main.js +++ b/ui/js/src/kimchi.guest_main.js @@ -5,6 +5,7 @@ * * Authors: * Hongliang Wang <hlwanghl@cn.ibm.com> + * Adam King <rak@us.ibm.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,164 +19,111 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -kimchi.initVmButtonsAction = function() { - - var vmstart = function(event) { - if (!$(this).hasClass('loading')) { - $(this).addClass('loading'); - kimchi.startVM($(this).data('vm'), function(result) { - kimchi.listVmsAuto(); - }, function(err) { - kimchi.message.error(err.responseJSON.reason); - }); - } else { - event.preventDefault(); - event.stopPropagation(); - return; - } - }; - - var vmstop = function(event) { - if (!$(this).hasClass('loading')) { - $(this).addClass('loading'); - kimchi.stopVM($(this).data('vm'), function(result) { - kimchi.listVmsAuto(); - }, function(err) { - kimchi.message.error(err.responseJSON.reason); - }); - } else { - event.preventDefault(); - event.stopPropagation(); - } - }; - - $('.circle').circle(); - - $(".vm-start").each(function(index) { - if ('running' === $(this).data('vmstate')) { - $(this).hide(); - } else { - $(this).show(); - } - }); - - $(".vm-stop").each(function(index) { - if ('running' === $(this).data('vmstate')) { - $(this).show(); - } else { - $(this).hide(); - } - }); - - $(".vm-start").on({ - click : vmstart, - }); - - $(".vm-stop").on({ - click : vmstop, - }); - - $(".vm-reset").on("click", function(event) { - if ('running' === $(this).data('vmstate')) { - kimchi.resetVM($(this).data('vm'), function(result) { - kimchi.listVmsAuto(); - }, function(err) { - kimchi.message.error(err.responseJSON.reason); - }); - } else { - kimchi.startVM($(this).data('vm'), function(result) { - kimchi.listVmsAuto(); - }, function(err) { - kimchi.message.error(err.responseJSON.reason); - }); - } - }); - - $(".vm-delete").on("click", function(event) { - var vm = $(this); - var settings = { - title : i18n['KCHAPI6001M'], - content : i18n['KCHVM6001M'], - confirm : i18n['KCHAPI6002M'], - cancel : i18n['KCHAPI6003M'] - }; - kimchi.confirm(settings, function() { - kimchi.deleteVM(vm.data('vm'), function(result) { - kimchi.listVmsAuto(); - }, function(err) { - kimchi.message.error(err.responseJSON.reason); - }); - }, function() { - }); - }); - - $(".vm-edit").on("click", function(event) { - var vmName = $(this).data('vm'); - kimchi.selectedGuest = vmName; - kimchi.window.open("guest-edit.html"); - }); - $(".vm-vnc").on("click", function(event) { - kimchi.vncToVM($(this).data('vm')); - }); +kimchi.vmstart = function(event) { + var button=$(this); + if (!button.hasClass('loading')) { + button.addClass('loading'); + var vm=$(this).closest('li[name=guest]'); + var vm_id=vm.attr("id"); + kimchi.startVM(vm_id, function(result) { + button.removeClass('loading'); + kimchi.listVmsAuto(); + }, function() { + startButton.removeClass('loading'); + kimchi.message.error(i18n['msg.fail.start']); + } + ); + } else { + event.preventDefault(); + event.stopPropagation(); + return; + } +}; - $(".vm-spice").on("click", function(event) { - kimchi.spiceToVM($(this).data('vm')); - }); +kimchi.vmstop = function(event) { + var button=$(this); + if (!button.hasClass('loading')) { + button.addClass('loading'); + var vm=button.closest('li[name=guest]'); + var vm_id=vm.attr("id"); + kimchi.stopVM(vm_id, function(result) { + button.removeClass('loading'); + kimchi.listVmsAuto(); + }, function() { + kimchi.message.error(i18n['msg.fail.stop']); + }); + } else { + event.preventDefault(); + event.stopPropagation(); + } +}; - kimchi.init_button_stat(); +kimchi.vmreset = function(event){ + var vm=$(this).closest('li[name=guest]'); + var vm_id=vm.attr("id"); + kimchi.resetVM(vm_id, function(result) { + kimchi.listVmsAuto(); + }, function() { + kimchi.message.error(i18n['msg.fail.reset']); + } + ); +}; +kimchi.vmdelete = function(event) { + var vm = $(this).closest('li[name=guest]'); + var vm_id=vm.attr("id"); + var settings = { + title : i18n['msg.confirm.delete.title'], + content : i18n['msg.vm.confirm.delete'], + confirm : i18n['msg.confirm.delete.confirm'], + cancel : i18n['msg.confirm.delete.cancel'] + }; + kimchi.confirm(settings, function() { + kimchi.deleteVM(vm_id, function(result) { + kimchi.listVmsAuto(); + }, function() { + kimchi.message.error(i18n['msg.fail.delete']); + }); + }, function() { + }); }; -kimchi.init_button_stat = function() { - $('.vm-action').each(function() { - var vm_action = $(this); - var vm_vnc = vm_action.find('.vm-vnc'); - var vm_spice = vm_action.find('.vm-spice'); - var vm_graphics; - if (vm_action.data('graphics') === 'vnc') { - vm_spice.hide(); - vm_graphics = vm_vnc; - } else if (vm_action.data('graphics') === 'spice') { - vm_vnc.hide(); - vm_graphics = vm_spice; - } else { - vm_vnc.hide(); - vm_spice.hide(); - vm_graphics = null; - } +kimchi.vmedit = function(event) { + var vm = $(this).closest('li[name=guest]'); + var vm_id=vm.attr("id"); + kimchi.selectedGuest = vm_id; + kimchi.window.open("guest-edit.html"); +}; - if (vm_graphics !== null) { - if (vm_action.data('vmstate') === 'running') { - vm_graphics.removeAttr('disabled'); - } else { - vm_graphics.attr('disabled', 'disabled'); - } - } +kimchi.openVmConsole = function(event) { + var vm=$(this).closest('li[name=guest]'); + var vmObject=vm.data(); + if (vmObject.graphics['type'] == 'vnc') { + kimchi.vncToVM(vm.attr('id')); + } + else if (vmObject.graphics['type'] == 'spice') { + kimchi.spiceToVM(vm.attr('id')); + } - var editButton = vm_action.find('.vm-edit'); - editButton.prop('disabled', vm_action.data('vmstate') !== 'shutoff'); - }) }; -kimchi.getVmsOldImg = function() { +kimchi.getVmsCurrentConsoleImgs = function() { var res = new Object(); $('#guestList').children().each(function() { - res[$(this).attr('id')] = $(this).find('img').attr('src'); + res[$(this).attr('id')] = $(this).find('img.imgactive').attr('src'); }) return res; }; -kimchi.getVmsOldPopStats = function() { - var oldSettings = new Object(); - $('#guestList').children().each(function() { - if ($(this).find('.popable').hasClass('open')) { - oldSettings[$(this).attr('id')] = true; - } else { - oldSettings[$(this).attr('id')] = false; - } - }) - return oldSettings; +kimchi.getOpenMenuVmId = function() { + var result; + var openMenu = $('#guestList .open:first') + if(openMenu) { + var li_element=openMenu.closest('li'); + result=li_element.attr('id'); + } + return result; }; kimchi.listVmsAuto = function() { @@ -185,28 +133,18 @@ kimchi.listVmsAuto = function() { kimchi.listVMs(function(result, textStatus, jqXHR) { if (result && textStatus=="success") { if(result.length) { - $('#guestListField').show(); - $('#noGuests').hide(); var listHtml = ''; var guestTemplate = kimchi.guestTemplate; - var oldImages = kimchi.getVmsOldImg(); - var oldSettings = kimchi.getVmsOldPopStats(); - $.each(result, function(index, value) { - var oldImg = oldImages[value.name]; - curImg = value.state == 'running' ? value.screenshot : value.icon; - value['load-src'] = curImg || 'images/icon-vm.png'; - value['tile-src'] = oldImg || value['load-src']; - var statusTemplate = kimchi.editTemplate(guestTemplate, oldSettings[value.name]); - listHtml += kimchi.template(statusTemplate, value); - }); - $('#guestList').html(listHtml); - $('#guestList').find('.imgload').each(function() { - this.onload = function() { - $(this).prev('.imgactive').remove(); - $(this).show(); - } + var currentConsoleImages = kimchi.getVmsCurrentConsoleImgs(); + var openMenuGuest = kimchi.getOpenMenuVmId(); + $('#guestList').html(''); + $('#guestListField').show(); + $('#noGuests').hide(); + + $.each(result, function(index, vm) { + var guestLI = kimchi.createGuestLi(vm, currentConsoleImages[vm.name], vm.name==openMenuGuest); + $('#guestList').append(guestLI); }); - kimchi.initVmButtonsAction(); } else { $('#guestListField').hide(); $('#noGuests').show(); @@ -220,6 +158,109 @@ kimchi.listVmsAuto = function() { }); }; +kimchi.createGuestLi = function(vmObject, prevScreenImage, openMenu) { + var result=kimchi.guestElem.clone(); + + //Setup the VM list entry + var vmRunningBool=(vmObject.state=="running"); + result.attr('id',vmObject.name); + result.data(vmObject); + + //Add the Name + var guestTitle=result.find('.title').attr('title',vmObject.name); + guestTitle.html(vmObject.name); + + //Setup the VM console thumbnail display + var curImg = vmObject.state == 'running' ? vmObject.screenshot : vmObject.icon; + var load_src = curImg || 'images/icon-vm.png'; + var tile_src = prevScreenImage || vmObject['load-src']; + var liveTile=result.find('div[name=guest-tile] > .tile'); + liveTile.addClass(vmObject.state); + liveTile.find('.imgactive').attr('src',tile_src); + var imgLoad=liveTile.find('.imgload'); + imgLoad.on('load', function() { + var oldImg=$(this).parent().find('.imgactive'); + oldImg.removeClass("imgactive").addClass("imgload"); + oldImg.attr("src",""); + $(this).addClass("imgactive").removeClass("imgload"); + $(this).off('load'); + }); + imgLoad.attr('src',load_src); + + //Link the stopped tile to the start action, the running tile to open the console + if (vmRunningBool) { + liveTile.off("click", kimchi.vmstart); + liveTile.on("click", kimchi.openVmConsole); + } + else { + liveTile.off("click", kimchi.openVmConsole); + liveTile.on("click", kimchi.vmstart); + liveTile.hover(function(event){$(this).find('.overlay').show()}, function(event){$(this).find('.overlay').hide()}); + } + + + //Setup the gauges + var stats=vmObject.stats; + var gaugeValue=0; + gaugeValue=parseInt(stats.net_throughput); + kimchi.circleGaugeInit(result, "net_throughput",gaugeValue,(gaugeValue*100/stats.net_throughput_peak)); + gaugeValue=parseInt(stats.io_throughput); + kimchi.circleGaugeInit(result, "io_throughput",gaugeValue,(gaugeValue*100/stats.io_throughput_peak)); + gaugeValue=parseInt(stats.cpu_utilization); + kimchi.circleGaugeInit(result, "cpu_utilization",gaugeValue+"%",gaugeValue); + + //Setup the VM Actions + var guestActions=result.find("div[name=guest-actions]"); + guestActions.find(".shutoff-disabled").prop('disabled', !vmRunningBool ); + guestActions.find(".running-disabled").prop('disabled', vmRunningBool ); + + if (vmRunningBool) { + guestActions.find(".running-hidden").hide(); + } + else { + guestActions.find(".shutoff-hidden").hide(); + } + + var consoleActions=guestActions.find("[name=vm-console]"); + + if ((vmObject.graphics['type'] == 'vnc') || (vmObject.graphics['type'] == 'spice')) { + consoleActions.on("click", kimchi.openVmConsole); + consoleActions.show(); + } else { //we don't recognize the VMs supported graphics, so hide the menu choice + consoleActions.hide(); + consoleActions.off("click",kimchi.openVmConsole); + } + + //Setup action event handlers + guestActions.find("[name=vm-start]").on({click : kimchi.vmstart}); + guestActions.find("[name=vm-stop]").on({click : kimchi.vmstop}); + if (vmRunningBool) { //If the guest is not running, do not enable reset + guestActions.find("[name=vm-reset]").on({click : kimchi.vmreset}); + } + guestActions.find("[name=vm-edit]").on({click : kimchi.vmedit}); + guestActions.find("[name=vm-delete]").on({click : kimchi.vmdelete}); + + //Maintain menu open state + var actionMenu=guestActions.find("div[name=actionmenu]"); + if (openMenu) { + actionMenu.addClass("open"); + } + + return result; +}; + +kimchi.circleGaugeInit = function(topElement, divName, display, percentage){ + var gauge=topElement.find('div[name="' + divName + '"] .circleGauge'); + if(gauge) { + var data=Object(); + data.percentage = percentage; + data.display = display; + gauge.data(data); + } + gauge.circleGauge(); + return(gauge); +}; + kimchi.guestSetRequestHeader = function(xhr) { xhr.setRequestHeader('Accept', 'text/html'); }; @@ -229,6 +270,7 @@ kimchi.guest_main = function() { kimchi.window.open('guest-add.html'); }); kimchi.guestTemplate = $('#guest-tmpl').html(); + kimchi.guestElem=$('<div/>').html(kimchi.guestTemplate).find('li'); $('#guests-root-container').on('remove', function() { kimchi.vmTimeout && clearTimeout(kimchi.vmTimeout); }); @@ -236,7 +278,7 @@ kimchi.guest_main = function() { }; kimchi.editTemplate = function(guestTemplate, oldPopStat) { - if (oldPopStat != null && oldPopStat) { + if (oldPopStat) { return guestTemplate.replace("vm-action", "vm-action open"); } return guestTemplate; diff --git a/ui/pages/tabs/guests.html.tmpl b/ui/pages/tabs/guests.html.tmpl index 8b530c7..e86718b 100644 --- a/ui/pages/tabs/guests.html.tmpl +++ b/ui/pages/tabs/guests.html.tmpl @@ -40,8 +40,8 @@ <ul class="list-title"> <li class="guest-type">$_("Name")</li> <li class="guest-cpu">$_("CPU")</li> - <li class="guest-network">$_("Network I/O")</li> <li class="guest-storage">$_("Disk I/O")</li> + <li class="guest-network">$_("Network I/O")</li> <li class="guest-tile">$_("Livetile")</li> <li class="guest-actions">$_("Actions")</li> </ul> -- 1.8.1.4

Just a minor comment below On 02/21/2014 06:06 AM, Adam King wrote:
Update mockmodel.py to be pep8 compliant, and update makekefile whitelist
Signed-off-by: Adam King <rak@linux.vnet.ibm.com> --- Makefile.am | 1 + src/kimchi/mockmodel.py | 69 ++++++++++++++++++++++++++++--------------------- 2 files changed, 40 insertions(+), 30 deletions(-)
diff --git a/Makefile.am b/Makefile.am index 378036e..47ba154 100644 --- a/Makefile.am +++ b/Makefile.am @@ -53,6 +53,7 @@ PEP8_WHITELIST = \ src/kimchi/featuretests.py \ src/kimchi/iscsi.py \ src/kimchi/isoinfo.py \ + src/kimchi/mockmodel.py \ src/kimchi/model/*.py \ src/kimchi/repositories.py \ src/kimchi/rollbackcontext.py \ diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index 8ef4431..ba9859b 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -17,8 +17,10 @@ # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# License along with this library; if not, write to the +# Free Software Foundation, Inc. +# 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301 USA
import cherrypy import copy @@ -41,7 +43,6 @@ except ImportError: import ImageDraw
- from kimchi import config from kimchi.asynctask import AsyncTask from kimchi.config import config as kconfig @@ -53,7 +54,8 @@ from kimchi.model.utils import get_vm_name from kimchi.model.vms import VM_STATIC_UPDATE_PARAMS from kimchi.objectstore import ObjectStore from kimchi.screenshot import VMScreenshot -from kimchi.utils import pool_name_from_uri, run_command, template_name_from_uri +from kimchi.utils import pool_name_from_uri, run_command +from kimchi.utils import template_name_from_uri from kimchi.vmtemplate import VMTemplate
@@ -64,7 +66,8 @@ class MockModel(object): self.distros = self._get_distros()
def capabilities_lookup(self, *ident): - return {'libvirt_stream_protocols': ['http', 'https', 'ftp', 'ftps', 'tftp'], + return {'libvirt_stream_protocols': + ['http', 'https', 'ftp', 'ftps', 'tftp'], 'qemu_stream': True, 'screenshot': True, 'system_report_tool': True} @@ -135,8 +138,7 @@ class MockModel(object):
def vms_create(self, params): t_name = template_name_from_uri(params['template']) - name = get_vm_name(params.get('name'), t_name, - self._mock_vms.keys()) + name = get_vm_name(params.get('name'), t_name, self._mock_vms.keys()) if name in self._mock_vms: raise InvalidOperation("KCHVM0001E", {'name': name})
@@ -350,15 +352,16 @@ class MockModel(object): pool.info['source'] = params['source'] if not pool.info['source'].get('adapter_name'): raise MissingParameter('KCHPOOL0004E', - {'item': 'adapter_name', 'name': name}) - for vol in ['unit:0:0:1','unit:0:0:2', - 'unit:0:0:3','unit:0:0:4']: + {'item': 'adapter_name', + 'name': name}) + for vol in ['unit:0:0:1', 'unit:0:0:2', + 'unit:0:0:3', 'unit:0:0:4']: mockvol = MockStorageVolume(name, vol, - dict([('type','lun')])) + dict([('type', 'lun')])) pool._volumes[vol] = mockvol else: pool.info['path'] = params['path'] - if params['type'] in ['dir','scsi']: + if params['type'] in ['dir', 'scsi']: pool.info['autostart'] = True else: pool.info['autostart'] = False @@ -436,7 +439,7 @@ class MockModel(object): def storagevolume_lookup(self, pool, name): if self._get_storagepool(pool).info['state'] != 'active': raise InvalidOperation("KCHVOL0005E", {'pool': pool, - 'volume': name}) + 'volume': name})
storagevolume = self._get_storagevolume(pool, name) return storagevolume.info @@ -461,7 +464,7 @@ class MockModel(object): return res._volumes.keys()
def devices_get_list(self, _cap=None): - return ['scsi_host3', 'scsi_host4','scsi_host5'] + return ['scsi_host3', 'scsi_host4', 'scsi_host5']
def device_lookup(self, nodedev_name): return { @@ -493,7 +496,7 @@ class MockModel(object): return iso_volumes
def storageservers_get_list(self, _target_type=None): - # FIXME: When added new storage server support, this needs to be updated + # FIXME: This needs to be updted when adding new storage server support target_type = STORAGE_SOURCES.keys() \ if not _target_type else [_target_type] pools = self.storagepools_get_list() @@ -514,10 +517,12 @@ class MockModel(object): for pool in pools: try: pool_info = self.storagepool_lookup(pool) - if pool_info['source'] and pool_info['source']['addr'] == server: + if pool_info['source'] and \ + pool_info['source']['addr'] == server: return dict(host=server) except NotFoundError: - # Avoid inconsistent pool result because of lease between list and lookup + # Avoid inconsistent pool result because + # of lease between list and lookup pass
raise NotFoundError("KCHSR0001E", {'server': server}) @@ -562,7 +567,7 @@ class MockModel(object): try: net = ipaddr.IPNetwork(subnet) except ValueError: - msg_args = {'subnet':subnet, 'network': name} + msg_args = {'subnet': subnet, 'network': name} raise InvalidParameter("KCHNET0003E", msg_args)
network.info['dhcp'] = { @@ -581,8 +586,8 @@ class MockModel(object): def _get_vms_attach_to_a_network(self, network): vms = [] for name, dom in self._mock_vms.iteritems(): - if network in dom.networks: - vms.append(name) + if network in dom.networks: + vms.append(name) return vms
def network_lookup(self, name): @@ -650,7 +655,7 @@ class MockModel(object):
def vmifaces_create(self, vm, params): if (params["type"] == "network" and - params["network"] not in self.networks_get_list()): + params["network"] not in self.networks_get_list()): msg_args = {'network': params["network"], 'name': vm} raise InvalidParameter("KCHVMIF0002E", msg_args)
@@ -679,7 +684,7 @@ class MockModel(object): def vmiface_delete(self, vm, mac): dom = self._get_vm(vm) try: - del dom.ifaces[mac] + del dom.ifaces[mac] except KeyError: raise NotFoundError("KCHVMIF0001E", {'iface': mac, 'name': vm})
@@ -835,7 +840,7 @@ class MockModel(object): raise OperationFailed("KCHREPOS0007E", {'repo_id': repo_id})
def repository_disable(self, repo_id): - if not self._mock_host_repositories.disableRepository(repo_id): + if not self._mock_host_repositories.disableRepository(repo_id): raise OperationFailed("KCHREPOS0008E", {'repo_id': repo_id})
def repository_update(self, repo_id, params): @@ -895,7 +900,8 @@ class MockVMIface(object): self.__class__.counter += 1 self.info = {'type': 'network', 'model': 'virtio', - 'network': network if network else "net-%s" % self.counter, + 'network': network if network + else "net-%s" % self.counter, 'mac': self.get_mac() }
@@ -917,13 +923,15 @@ class MockVM(object): self.ifaces = dict([(iface.info['mac'], iface) for iface in ifaces]) self.info = {'state': 'shutoff', 'stats': "{'cpu_utilization': 20, 'net_throughput' : 35, \ - 'net_throughput_peak': 100, 'io_throughput': 45, \ + 'net_throughput_peak': 100, \ + 'io_throughput': 45, \ "\" is OK here. but usually, for brackets, Parentheses and braces, we can remove "\".
'io_throughput_peak': 100}", 'uuid': self.uuid, 'memory': template_info['memory'], 'cpus': template_info['cpus'], 'icon': None, - 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', 'port': None} + 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', + 'port': None} } self.info['graphics'].update(template_info['graphics'])
@@ -974,6 +982,7 @@ class MockTask(object): def __init__(self, id): self.id = id
+ class MockStorageVolume(object): def __init__(self, pool, name, params={}): self.name = name @@ -983,9 +992,9 @@ class MockStorageVolume(object): params = self._def_lun(name) fmt = params.get('format', 'raw') capacity = params.get('capacity', 1024) - self.info = {'type': params.get('type','disk'), + self.info = {'type': params.get('type', 'disk'), 'capacity': capacity << 20, - 'allocation': params.get('allocation','512'), + 'allocation': params.get('allocation', '512'), 'path': params.get('path'), 'format': fmt} if fmt == 'iso': @@ -1000,10 +1009,10 @@ class MockStorageVolume(object): return { "capacity": capacity, "name": name, - "format": random.choice(['dos','unknown']), + "format": random.choice(['dos', 'unknown']), "allocation": capacity, "path": path + name[-1], - "type": "block" } + "type": "block"}
class MockVMScreenshot(VMScreenshot):
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

Patch assume that edit API has been updated to accept changes to CPU and memory Patch depends on [PATCH 0/3] Create and use jQuery form extensions Adam King (1): VM Edit: CPU and memory ui/js/src/kimchi.guest_edit_main.js | 10 ++-------- ui/pages/guest-edit.html.tmpl | 12 +++++------- 2 files changed, 7 insertions(+), 15 deletions(-) -- 1.8.1.4

Update the edit VM form to use the new form extensions. Enable the cpu and memory fields to be edited. Signed-off-by: Adam King <rak@linux.vnet.ibm.com> --- ui/js/src/kimchi.guest_edit_main.js | 10 ++-------- ui/pages/guest-edit.html.tmpl | 12 +++++------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/ui/js/src/kimchi.guest_edit_main.js b/ui/js/src/kimchi.guest_edit_main.js index 9375c51..4758dc0 100644 --- a/ui/js/src/kimchi.guest_edit_main.js +++ b/ui/js/src/kimchi.guest_edit_main.js @@ -80,9 +80,7 @@ kimchi.guest_edit_main = function() { var initContent = function(guest) { guest['icon'] = guest['icon'] || 'images/icon-vm.png'; - for ( var prop in guest) { - $('input[name="' + prop + '"]', guestEditForm).val(guest[prop]); - } + $('#form-guest-edit-general').fillWithObject(guest); refreshCDROMs(); @@ -116,11 +114,7 @@ kimchi.guest_edit_main = function() { var submitForm = function(event) { $(saveButton).prop('disabled', true); - var editableFields = [ 'name' ]; - var data = {}; - $.each(editableFields, function(i, field) { - data[field] = $('#form-guest-edit [name="' + field + '"]').val(); - }); + var data=$('#form-guest-edit-general').serializeObject(); kimchi.updateVM(kimchi.selectedGuest, data, function() { kimchi.listVmsAuto(); kimchi.window.close(); diff --git a/ui/pages/guest-edit.html.tmpl b/ui/pages/guest-edit.html.tmpl index 804fc39..bf7ea56 100644 --- a/ui/pages/guest-edit.html.tmpl +++ b/ui/pages/guest-edit.html.tmpl @@ -59,8 +59,7 @@ <input id="guest-edit-cores-textbox" name="cpus" - type="text" - disabled="disabled" /> + type="text" /> </div> </div> <div> @@ -72,8 +71,7 @@ <div class="guest-edit-wrapper-controls"> <input id="guest-edit-memory-textbox" name="memory" - type="text" - disabled="disabled" /> + type="text" /> </div> </div> <div> @@ -98,7 +96,7 @@ <div> <button id="guest-edit-attach-cdrom-button" class="guest-edit-cdrom-button attach" - title="$_("Attach")"> + title='$_("Attach")'> </button> </div> </fieldset> @@ -124,11 +122,11 @@ value="{path}" readonly="readonly" /> <button class="guest-edit-cdrom-button replace" data-vm="{vm}" data-dev="{dev}" - title="$_("Replace")"> + title='$_("Replace")'> </button> <button class="guest-edit-cdrom-button detach" data-vm="{vm}" data-dev="{dev}" - title="$_("Detach")"> + title='$_("Detach")'> </button> </div> </div> -- 1.8.1.4

Oops, didn't mean this to be a reply to PEP8. Sending again. On 4/7/2014 2:25 PM, Adam King wrote:
Patch assume that edit API has been updated to accept changes to CPU and memory Patch depends on [PATCH 0/3] Create and use jQuery form extensions
Adam King (1): VM Edit: CPU and memory
ui/js/src/kimchi.guest_edit_main.js | 10 ++-------- ui/pages/guest-edit.html.tmpl | 12 +++++------- 2 files changed, 7 insertions(+), 15 deletions(-)
-- Adam King <rak@linux.vnet.ibm.com> IBM C&SI
participants (3)
-
Adam King
-
Aline Manera
-
Sheldon