[PATCH V5 0/5] Add spice backend support for kimchi

v5-v4: 1. Rebase to upstream 2. Change the code to coordinate with new websockify mechanism 3. Add test code for ipv6 (Thanks Aline) v4-v3: 1. Remove not used module "copy" in vmtemplate.py(Thanks Aline) 2. Fix alignment problem in model.py(Thanks Aline) 3. Use type property of jsonschema to validate both ipv4 and ipv6(Thanks Aline) REF: http://stackoverflow.com/questions/14550235/json-schema-away-to-specify-an-a... 4. Use "$ref" to simply API.json, remove dumplicate content(Thanks Rodrigo) v3-v2: 1. Rebase to upstream 2. Improve/simplify some code according to reviewers' advice(Thanks Royce, Markwu and Aline). 3. Reimplement parameters validation using jsonschema. 4. Remove unnecessary test code in model.py. 5. Split patch to several smaller commits according to logic(Thanks Royce). v2-v1: Rebased to upstream and resend v1: Add spice backend support for kimchi apporc (5): Add spice backend support for kimchi Validate graphics parameters input by users Update mockmodel for spice support Update test case for graphics support Add graphics parameters description in API.md docs/API.md | 43 ++++++++++++++- src/kimchi/API.json | 30 +++++++++-- src/kimchi/control/templates.py | 6 ++- src/kimchi/control/utils.py | 4 +- src/kimchi/control/vms.py | 4 +- src/kimchi/mockmodel.py | 13 +++-- src/kimchi/model.py | 34 +++++++----- src/kimchi/osinfo.py | 7 +-- src/kimchi/vmtemplate.py | 31 ++++++++++- tests/test_mockmodel.py | 2 + tests/test_model.py | 27 +++++++++- tests/test_rest.py | 111 +++++++++++++++++++++++++++++++++++++-- tests/test_vmtemplate.py | 40 ++++++++++++-- 13 files changed, 313 insertions(+), 39 deletions(-) -- 1.7.9.5

1. Add spice support for kimchi template Now we can specify a spice parameter when creating a template and updating it. 2. Add spice support for kimchi vm When creating a vm, we have a chance to change the graphics configuration to override the value from the template. 3. Expose the real port for vnc/spice, so that users can connect vm with popular vnc/spice client tool. 4. Spice-vdagent support is added too, so that when users happended to install spice-tool/spice-vdagent in their vm, they will get improved spice graphics effect immediately. 5. Specifying graphics parameters when updating vm is not supported yet, it will be added separately in another patch. Signed-off-by: apporc <appleorchard2000@gmail.com> --- src/kimchi/control/templates.py | 6 ++++-- src/kimchi/control/vms.py | 4 +++- src/kimchi/model.py | 34 +++++++++++++++++++++------------- src/kimchi/osinfo.py | 7 ++++--- src/kimchi/vmtemplate.py | 31 +++++++++++++++++++++++++++++-- 5 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/kimchi/control/templates.py b/src/kimchi/control/templates.py index bf40e2c..a77936e 100644 --- a/src/kimchi/control/templates.py +++ b/src/kimchi/control/templates.py @@ -35,7 +35,8 @@ class Template(Resource): super(Template, self).__init__(model, ident) self.update_params = ["name", "folder", "icon", "os_distro", "storagepool", "os_version", "cpus", - "memory", "cdrom", "disks", "networks"] + "memory", "cdrom", "disks", "networks", + "graphics"] self.uri_fmt = "/templates/%s" @property @@ -50,4 +51,5 @@ class Template(Resource): 'disks': self.info['disks'], 'storagepool': self.info['storagepool'], 'networks': self.info['networks'], - 'folder': self.info.get('folder', [])} + 'folder': self.info.get('folder', []), + 'graphics': self.info['graphics']} diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index d722920..7843be7 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -53,7 +53,9 @@ class VM(Resource): 'screenshot': self.info['screenshot'], 'icon': self.info['icon'], 'graphics': {'type': self.info['graphics']['type'], - 'port': self.info['graphics']['port']}} + 'listen': self.info['graphics']['listen'], + 'port': self.info['graphics']['port']} + } class VMScreenShot(Resource): diff --git a/src/kimchi/model.py b/src/kimchi/model.py index ed613b1..4f4b837 100644 --- a/src/kimchi/model.py +++ b/src/kimchi/model.py @@ -503,7 +503,8 @@ class Model(object): info = dom.info() state = Model.dom_state_map[info[0]] screenshot = None - graphics_type, graphics_port = self._vm_get_graphics(name) + graphics_type, graphics_listen, graphics_port = self._vm_get_graphics(name) + graphics_port = graphics_port if state == 'running' else None try: if state == 'running': screenshot = self.vmscreenshot_lookup(name) @@ -536,7 +537,10 @@ class Model(object): 'cpus': info[3], 'screenshot': screenshot, 'icon': icon, - 'graphics': {"type": graphics_type, "port": graphics_port}} + 'graphics': {"type": graphics_type, + "listen": graphics_listen, + "port": graphics_port} + } def _vm_get_disk_paths(self, dom): xml = dom.XMLDesc(0) @@ -580,21 +584,21 @@ class Model(object): expr = "/domain/devices/graphics/@type" res = xmlutils.xpath_get_text(xml, expr) graphics_type = res[0] if res else None - port = None + expr = "/domain/devices/graphics/@listen" + res = xmlutils.xpath_get_text(xml, expr) + graphics_listen = res[0] if res else None + graphics_port = None if graphics_type: expr = "/domain/devices/graphics[@type='%s']/@port" % graphics_type res = xmlutils.xpath_get_text(xml, expr) - port = int(res[0]) if res else None - # FIX ME - # graphics_type should be 'vnc' or None. 'spice' should only be - # returned if we support it in the future. - graphics_type = None if graphics_type != "vnc" else graphics_type - return graphics_type, port + graphics_port = int(res[0]) if res else None + return graphics_type, graphics_listen, graphics_port def vm_connect(self, name): - graphics, port = self._vm_get_graphics(name) - if graphics == "vnc" and port != None: - vnc.add_proxy_token(name, port) + graphics_type, graphics_listen, graphics_port \ + = self._vm_get_graphics(name) + if graphics_port is not None: + vnc.add_proxy_token(name, graphics_port) else: raise OperationFailed("Only able to connect to running vm's vnc " "graphics.") @@ -628,8 +632,12 @@ class Model(object): session.store('vm', vm_uuid, {'icon': icon}) libvirt_stream = False if len(self.libvirt_stream_protocols) == 0 else True + graphics = params.get('graphics') - xml = t.to_vm_xml(name, vm_uuid, libvirt_stream, self.qemu_stream_dns) + xml = t.to_vm_xml(name, vm_uuid, + libvirt_stream=libvirt_stream, + qemu_stream_dns=self.qemu_stream_dns, + graphics=graphics) try: dom = conn.defineXML(xml.encode('utf-8')) except libvirt.libvirtError as e: diff --git a/src/kimchi/osinfo.py b/src/kimchi/osinfo.py index 0509622..f92db3d 100644 --- a/src/kimchi/osinfo.py +++ b/src/kimchi/osinfo.py @@ -73,8 +73,9 @@ isolinks = { defaults = {'networks': ['default'], 'storagepool': '/storagepools/default', - 'domain': 'kvm', 'arch': os.uname()[4] -} + 'domain': 'kvm', 'arch': os.uname()[4], + 'graphics': {'type': 'vnc', 'listen': '0.0.0.0'}} + def _get_arch(): @@ -90,7 +91,7 @@ def lookup(distro, version): 'defaults' and merging the parameters given for the identified OS. If known, a link to a remote install CD is added. """ - params = copy.copy(defaults) + params = copy.deepcopy(defaults) params['os_distro'] = distro params['os_version'] = version params['cdrom'] = isolinks.get(distro, {}).get(version, '') diff --git a/src/kimchi/vmtemplate.py b/src/kimchi/vmtemplate.py index 6587bbb..58147e3 100644 --- a/src/kimchi/vmtemplate.py +++ b/src/kimchi/vmtemplate.py @@ -76,6 +76,11 @@ class VMTemplate(object): self.info.update(entry) # Override with the passed in parameters + graph_args = args.get('graphics') + if graph_args: + graphics = dict(self.info['graphics']) + graphics.update(graph_args) + args['graphics'] = graphics self.info.update(args) def _get_cdrom_xml(self, libvirt_stream, qemu_stream_dns): @@ -157,6 +162,24 @@ class VMTemplate(object): """ % params return ret + def _get_graphics_xml(self, params): + graphics_xml = """ + <graphics type='%(type)s' autoport='yes' listen='%(listen)s'> + </graphics> + """ + spicevmc_xml = """ + <channel type='spicevmc'> + <target type='virtio' name='com.redhat.spice.0'/> + </channel> + """ + graphics = dict(self.info['graphics']) + if params: + graphics.update(params) + graphics_xml = graphics_xml % graphics + if graphics['type'] == 'spice': + graphics_xml = graphics_xml + spicevmc_xml + return graphics_xml + def to_volume_list(self, vm_uuid): storage_path = self._get_storage_path() ret = [] @@ -198,7 +221,7 @@ class VMTemplate(object): networks += network % net_info return networks - def to_vm_xml(self, vm_name, vm_uuid, libvirt_stream = False, qemu_stream_dns = False): + def to_vm_xml(self, vm_name, vm_uuid, **kwargs): params = dict(self.info) params['name'] = vm_name params['uuid'] = vm_uuid @@ -207,7 +230,11 @@ class VMTemplate(object): params['qemu-namespace'] = '' params['cdroms'] = '' params['qemu-stream-cmdline'] = '' + graphics = kwargs.get('graphics') + params['graphics'] = self._get_graphics_xml(graphics) + qemu_stream_dns = kwargs.get('qemu_stream_dns', False) + libvirt_stream = kwargs.get('libvirt_stream', False) cdrom_xml = self._get_cdrom_xml(libvirt_stream, qemu_stream_dns) if not libvirt_stream and params.get('iso_stream', False): params['qemu-namespace'] = QEMU_NAMESPACE @@ -238,7 +265,7 @@ class VMTemplate(object): %(disks)s %(cdroms)s %(networks)s - <graphics type='vnc' /> + %(graphics)s <sound model='ich6' /> <memballoon model='virtio' /> </devices> -- 1.7.9.5

Reviewed-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> On 01/10/2014 08:44 PM, apporc wrote:
1. Add spice support for kimchi template Now we can specify a spice parameter when creating a template and updating it.
2. Add spice support for kimchi vm When creating a vm, we have a chance to change the graphics configuration to override the value from the template.
3. Expose the real port for vnc/spice, so that users can connect vm with popular vnc/spice client tool.
4. Spice-vdagent support is added too, so that when users happended to install spice-tool/spice-vdagent in their vm, they will get improved spice graphics effect immediately.
5. Specifying graphics parameters when updating vm is not supported yet, it will be added separately in another patch.
Signed-off-by: apporc <appleorchard2000@gmail.com> --- src/kimchi/control/templates.py | 6 ++++-- src/kimchi/control/vms.py | 4 +++- src/kimchi/model.py | 34 +++++++++++++++++++++------------- src/kimchi/osinfo.py | 7 ++++--- src/kimchi/vmtemplate.py | 31 +++++++++++++++++++++++++++++-- 5 files changed, 61 insertions(+), 21 deletions(-)
diff --git a/src/kimchi/control/templates.py b/src/kimchi/control/templates.py index bf40e2c..a77936e 100644 --- a/src/kimchi/control/templates.py +++ b/src/kimchi/control/templates.py @@ -35,7 +35,8 @@ class Template(Resource): super(Template, self).__init__(model, ident) self.update_params = ["name", "folder", "icon", "os_distro", "storagepool", "os_version", "cpus", - "memory", "cdrom", "disks", "networks"] + "memory", "cdrom", "disks", "networks", + "graphics"] self.uri_fmt = "/templates/%s"
@property @@ -50,4 +51,5 @@ class Template(Resource): 'disks': self.info['disks'], 'storagepool': self.info['storagepool'], 'networks': self.info['networks'], - 'folder': self.info.get('folder', [])} + 'folder': self.info.get('folder', []), + 'graphics': self.info['graphics']} diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index d722920..7843be7 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -53,7 +53,9 @@ class VM(Resource): 'screenshot': self.info['screenshot'], 'icon': self.info['icon'], 'graphics': {'type': self.info['graphics']['type'], - 'port': self.info['graphics']['port']}} + 'listen': self.info['graphics']['listen'], + 'port': self.info['graphics']['port']} + }
class VMScreenShot(Resource): diff --git a/src/kimchi/model.py b/src/kimchi/model.py index ed613b1..4f4b837 100644 --- a/src/kimchi/model.py +++ b/src/kimchi/model.py @@ -503,7 +503,8 @@ class Model(object): info = dom.info() state = Model.dom_state_map[info[0]] screenshot = None - graphics_type, graphics_port = self._vm_get_graphics(name) + graphics_type, graphics_listen, graphics_port = self._vm_get_graphics(name) + graphics_port = graphics_port if state == 'running' else None try: if state == 'running': screenshot = self.vmscreenshot_lookup(name) @@ -536,7 +537,10 @@ class Model(object): 'cpus': info[3], 'screenshot': screenshot, 'icon': icon, - 'graphics': {"type": graphics_type, "port": graphics_port}} + 'graphics': {"type": graphics_type, + "listen": graphics_listen, + "port": graphics_port} + }
def _vm_get_disk_paths(self, dom): xml = dom.XMLDesc(0) @@ -580,21 +584,21 @@ class Model(object): expr = "/domain/devices/graphics/@type" res = xmlutils.xpath_get_text(xml, expr) graphics_type = res[0] if res else None - port = None + expr = "/domain/devices/graphics/@listen" + res = xmlutils.xpath_get_text(xml, expr) + graphics_listen = res[0] if res else None + graphics_port = None if graphics_type: expr = "/domain/devices/graphics[@type='%s']/@port" % graphics_type res = xmlutils.xpath_get_text(xml, expr) - port = int(res[0]) if res else None - # FIX ME - # graphics_type should be 'vnc' or None. 'spice' should only be - # returned if we support it in the future. - graphics_type = None if graphics_type != "vnc" else graphics_type - return graphics_type, port + graphics_port = int(res[0]) if res else None + return graphics_type, graphics_listen, graphics_port
def vm_connect(self, name): - graphics, port = self._vm_get_graphics(name) - if graphics == "vnc" and port != None: - vnc.add_proxy_token(name, port) + graphics_type, graphics_listen, graphics_port \ + = self._vm_get_graphics(name) + if graphics_port is not None: + vnc.add_proxy_token(name, graphics_port) else: raise OperationFailed("Only able to connect to running vm's vnc " "graphics.") @@ -628,8 +632,12 @@ class Model(object): session.store('vm', vm_uuid, {'icon': icon})
libvirt_stream = False if len(self.libvirt_stream_protocols) == 0 else True + graphics = params.get('graphics')
- xml = t.to_vm_xml(name, vm_uuid, libvirt_stream, self.qemu_stream_dns) + xml = t.to_vm_xml(name, vm_uuid, + libvirt_stream=libvirt_stream, + qemu_stream_dns=self.qemu_stream_dns, + graphics=graphics) try: dom = conn.defineXML(xml.encode('utf-8')) except libvirt.libvirtError as e: diff --git a/src/kimchi/osinfo.py b/src/kimchi/osinfo.py index 0509622..f92db3d 100644 --- a/src/kimchi/osinfo.py +++ b/src/kimchi/osinfo.py @@ -73,8 +73,9 @@ isolinks = {
defaults = {'networks': ['default'], 'storagepool': '/storagepools/default', - 'domain': 'kvm', 'arch': os.uname()[4] -} + 'domain': 'kvm', 'arch': os.uname()[4], + 'graphics': {'type': 'vnc', 'listen': '0.0.0.0'}} +
def _get_arch(): @@ -90,7 +91,7 @@ def lookup(distro, version): 'defaults' and merging the parameters given for the identified OS. If known, a link to a remote install CD is added. """ - params = copy.copy(defaults) + params = copy.deepcopy(defaults) params['os_distro'] = distro params['os_version'] = version params['cdrom'] = isolinks.get(distro, {}).get(version, '') diff --git a/src/kimchi/vmtemplate.py b/src/kimchi/vmtemplate.py index 6587bbb..58147e3 100644 --- a/src/kimchi/vmtemplate.py +++ b/src/kimchi/vmtemplate.py @@ -76,6 +76,11 @@ class VMTemplate(object): self.info.update(entry)
# Override with the passed in parameters + graph_args = args.get('graphics') + if graph_args: + graphics = dict(self.info['graphics']) + graphics.update(graph_args) + args['graphics'] = graphics self.info.update(args)
def _get_cdrom_xml(self, libvirt_stream, qemu_stream_dns): @@ -157,6 +162,24 @@ class VMTemplate(object): """ % params return ret
+ def _get_graphics_xml(self, params): + graphics_xml = """ + <graphics type='%(type)s' autoport='yes' listen='%(listen)s'> + </graphics> + """ + spicevmc_xml = """ + <channel type='spicevmc'> + <target type='virtio' name='com.redhat.spice.0'/> + </channel> + """ + graphics = dict(self.info['graphics']) + if params: + graphics.update(params) + graphics_xml = graphics_xml % graphics + if graphics['type'] == 'spice': + graphics_xml = graphics_xml + spicevmc_xml + return graphics_xml + def to_volume_list(self, vm_uuid): storage_path = self._get_storage_path() ret = [] @@ -198,7 +221,7 @@ class VMTemplate(object): networks += network % net_info return networks
- def to_vm_xml(self, vm_name, vm_uuid, libvirt_stream = False, qemu_stream_dns = False): + def to_vm_xml(self, vm_name, vm_uuid, **kwargs): params = dict(self.info) params['name'] = vm_name params['uuid'] = vm_uuid @@ -207,7 +230,11 @@ class VMTemplate(object): params['qemu-namespace'] = '' params['cdroms'] = '' params['qemu-stream-cmdline'] = '' + graphics = kwargs.get('graphics') + params['graphics'] = self._get_graphics_xml(graphics)
+ qemu_stream_dns = kwargs.get('qemu_stream_dns', False) + libvirt_stream = kwargs.get('libvirt_stream', False) cdrom_xml = self._get_cdrom_xml(libvirt_stream, qemu_stream_dns) if not libvirt_stream and params.get('iso_stream', False): params['qemu-namespace'] = QEMU_NAMESPACE @@ -238,7 +265,7 @@ class VMTemplate(object): %(disks)s %(cdroms)s %(networks)s - <graphics type='vnc' /> + %(graphics)s <sound model='ich6' /> <memballoon model='virtio' /> </devices>
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

Reviewed-by: zhoumeina <zhoumein@linux.vnet.ibm.com> On 01/10/2014 08:44 PM, apporc wrote:
1. Add spice support for kimchi template Now we can specify a spice parameter when creating a template and updating it.
2. Add spice support for kimchi vm When creating a vm, we have a chance to change the graphics configuration to override the value from the template.
3. Expose the real port for vnc/spice, so that users can connect vm with popular vnc/spice client tool.
4. Spice-vdagent support is added too, so that when users happended to install spice-tool/spice-vdagent in their vm, they will get improved spice graphics effect immediately.
5. Specifying graphics parameters when updating vm is not supported yet, it will be added separately in another patch.
Signed-off-by: apporc <appleorchard2000@gmail.com> --- src/kimchi/control/templates.py | 6 ++++-- src/kimchi/control/vms.py | 4 +++- src/kimchi/model.py | 34 +++++++++++++++++++++------------- src/kimchi/osinfo.py | 7 ++++--- src/kimchi/vmtemplate.py | 31 +++++++++++++++++++++++++++++-- 5 files changed, 61 insertions(+), 21 deletions(-)
diff --git a/src/kimchi/control/templates.py b/src/kimchi/control/templates.py index bf40e2c..a77936e 100644 --- a/src/kimchi/control/templates.py +++ b/src/kimchi/control/templates.py @@ -35,7 +35,8 @@ class Template(Resource): super(Template, self).__init__(model, ident) self.update_params = ["name", "folder", "icon", "os_distro", "storagepool", "os_version", "cpus", - "memory", "cdrom", "disks", "networks"] + "memory", "cdrom", "disks", "networks", + "graphics"] self.uri_fmt = "/templates/%s"
@property @@ -50,4 +51,5 @@ class Template(Resource): 'disks': self.info['disks'], 'storagepool': self.info['storagepool'], 'networks': self.info['networks'], - 'folder': self.info.get('folder', [])} + 'folder': self.info.get('folder', []), + 'graphics': self.info['graphics']} diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index d722920..7843be7 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -53,7 +53,9 @@ class VM(Resource): 'screenshot': self.info['screenshot'], 'icon': self.info['icon'], 'graphics': {'type': self.info['graphics']['type'], - 'port': self.info['graphics']['port']}} + 'listen': self.info['graphics']['listen'], + 'port': self.info['graphics']['port']} + }
class VMScreenShot(Resource): diff --git a/src/kimchi/model.py b/src/kimchi/model.py index ed613b1..4f4b837 100644 --- a/src/kimchi/model.py +++ b/src/kimchi/model.py @@ -503,7 +503,8 @@ class Model(object): info = dom.info() state = Model.dom_state_map[info[0]] screenshot = None - graphics_type, graphics_port = self._vm_get_graphics(name) + graphics_type, graphics_listen, graphics_port = self._vm_get_graphics(name) + graphics_port = graphics_port if state == 'running' else None try: if state == 'running': screenshot = self.vmscreenshot_lookup(name) @@ -536,7 +537,10 @@ class Model(object): 'cpus': info[3], 'screenshot': screenshot, 'icon': icon, - 'graphics': {"type": graphics_type, "port": graphics_port}} + 'graphics': {"type": graphics_type, + "listen": graphics_listen, + "port": graphics_port} + }
def _vm_get_disk_paths(self, dom): xml = dom.XMLDesc(0) @@ -580,21 +584,21 @@ class Model(object): expr = "/domain/devices/graphics/@type" res = xmlutils.xpath_get_text(xml, expr) graphics_type = res[0] if res else None - port = None + expr = "/domain/devices/graphics/@listen" + res = xmlutils.xpath_get_text(xml, expr) + graphics_listen = res[0] if res else None + graphics_port = None if graphics_type: expr = "/domain/devices/graphics[@type='%s']/@port" % graphics_type res = xmlutils.xpath_get_text(xml, expr) - port = int(res[0]) if res else None - # FIX ME - # graphics_type should be 'vnc' or None. 'spice' should only be - # returned if we support it in the future. - graphics_type = None if graphics_type != "vnc" else graphics_type - return graphics_type, port + graphics_port = int(res[0]) if res else None + return graphics_type, graphics_listen, graphics_port
def vm_connect(self, name): - graphics, port = self._vm_get_graphics(name) - if graphics == "vnc" and port != None: - vnc.add_proxy_token(name, port) + graphics_type, graphics_listen, graphics_port \ + = self._vm_get_graphics(name) + if graphics_port is not None: + vnc.add_proxy_token(name, graphics_port) else: raise OperationFailed("Only able to connect to running vm's vnc " "graphics.") @@ -628,8 +632,12 @@ class Model(object): session.store('vm', vm_uuid, {'icon': icon})
libvirt_stream = False if len(self.libvirt_stream_protocols) == 0 else True + graphics = params.get('graphics')
- xml = t.to_vm_xml(name, vm_uuid, libvirt_stream, self.qemu_stream_dns) + xml = t.to_vm_xml(name, vm_uuid, + libvirt_stream=libvirt_stream, + qemu_stream_dns=self.qemu_stream_dns, + graphics=graphics) try: dom = conn.defineXML(xml.encode('utf-8')) except libvirt.libvirtError as e: diff --git a/src/kimchi/osinfo.py b/src/kimchi/osinfo.py index 0509622..f92db3d 100644 --- a/src/kimchi/osinfo.py +++ b/src/kimchi/osinfo.py @@ -73,8 +73,9 @@ isolinks = {
defaults = {'networks': ['default'], 'storagepool': '/storagepools/default', - 'domain': 'kvm', 'arch': os.uname()[4] -} + 'domain': 'kvm', 'arch': os.uname()[4], + 'graphics': {'type': 'vnc', 'listen': '0.0.0.0'}} +
def _get_arch(): @@ -90,7 +91,7 @@ def lookup(distro, version): 'defaults' and merging the parameters given for the identified OS. If known, a link to a remote install CD is added. """ - params = copy.copy(defaults) + params = copy.deepcopy(defaults) params['os_distro'] = distro params['os_version'] = version params['cdrom'] = isolinks.get(distro, {}).get(version, '') diff --git a/src/kimchi/vmtemplate.py b/src/kimchi/vmtemplate.py index 6587bbb..58147e3 100644 --- a/src/kimchi/vmtemplate.py +++ b/src/kimchi/vmtemplate.py @@ -76,6 +76,11 @@ class VMTemplate(object): self.info.update(entry)
# Override with the passed in parameters + graph_args = args.get('graphics') + if graph_args: + graphics = dict(self.info['graphics']) + graphics.update(graph_args) + args['graphics'] = graphics self.info.update(args)
def _get_cdrom_xml(self, libvirt_stream, qemu_stream_dns): @@ -157,6 +162,24 @@ class VMTemplate(object): """ % params return ret
+ def _get_graphics_xml(self, params): + graphics_xml = """ + <graphics type='%(type)s' autoport='yes' listen='%(listen)s'> + </graphics> + """ + spicevmc_xml = """ + <channel type='spicevmc'> + <target type='virtio' name='com.redhat.spice.0'/> + </channel> + """ + graphics = dict(self.info['graphics']) + if params: + graphics.update(params) + graphics_xml = graphics_xml % graphics + if graphics['type'] == 'spice': + graphics_xml = graphics_xml + spicevmc_xml + return graphics_xml + def to_volume_list(self, vm_uuid): storage_path = self._get_storage_path() ret = [] @@ -198,7 +221,7 @@ class VMTemplate(object): networks += network % net_info return networks
- def to_vm_xml(self, vm_name, vm_uuid, libvirt_stream = False, qemu_stream_dns = False): + def to_vm_xml(self, vm_name, vm_uuid, **kwargs): params = dict(self.info) params['name'] = vm_name params['uuid'] = vm_uuid @@ -207,7 +230,11 @@ class VMTemplate(object): params['qemu-namespace'] = '' params['cdroms'] = '' params['qemu-stream-cmdline'] = '' + graphics = kwargs.get('graphics') + params['graphics'] = self._get_graphics_xml(graphics)
+ qemu_stream_dns = kwargs.get('qemu_stream_dns', False) + libvirt_stream = kwargs.get('libvirt_stream', False) cdrom_xml = self._get_cdrom_xml(libvirt_stream, qemu_stream_dns) if not libvirt_stream and params.get('iso_stream', False): params['qemu-namespace'] = QEMU_NAMESPACE @@ -238,7 +265,7 @@ class VMTemplate(object): %(disks)s %(cdroms)s %(networks)s - <graphics type='vnc' /> + %(graphics)s <sound model='ich6' /> <memballoon model='virtio' /> </devices>

Reviewed-by: Rodrigo Trujillo <rodrigo.trujillo@linux.vnet.ibm.com> On 01/10/2014 10:44 AM, apporc wrote:
1. Add spice support for kimchi template Now we can specify a spice parameter when creating a template and updating it.
2. Add spice support for kimchi vm When creating a vm, we have a chance to change the graphics configuration to override the value from the template.
3. Expose the real port for vnc/spice, so that users can connect vm with popular vnc/spice client tool.
4. Spice-vdagent support is added too, so that when users happended to install spice-tool/spice-vdagent in their vm, they will get improved spice graphics effect immediately.
5. Specifying graphics parameters when updating vm is not supported yet, it will be added separately in another patch.
Signed-off-by: apporc <appleorchard2000@gmail.com> --- src/kimchi/control/templates.py | 6 ++++-- src/kimchi/control/vms.py | 4 +++- src/kimchi/model.py | 34 +++++++++++++++++++++------------- src/kimchi/osinfo.py | 7 ++++--- src/kimchi/vmtemplate.py | 31 +++++++++++++++++++++++++++++-- 5 files changed, 61 insertions(+), 21 deletions(-)
diff --git a/src/kimchi/control/templates.py b/src/kimchi/control/templates.py index bf40e2c..a77936e 100644 --- a/src/kimchi/control/templates.py +++ b/src/kimchi/control/templates.py @@ -35,7 +35,8 @@ class Template(Resource): super(Template, self).__init__(model, ident) self.update_params = ["name", "folder", "icon", "os_distro", "storagepool", "os_version", "cpus", - "memory", "cdrom", "disks", "networks"] + "memory", "cdrom", "disks", "networks", + "graphics"] self.uri_fmt = "/templates/%s"
@property @@ -50,4 +51,5 @@ class Template(Resource): 'disks': self.info['disks'], 'storagepool': self.info['storagepool'], 'networks': self.info['networks'], - 'folder': self.info.get('folder', [])} + 'folder': self.info.get('folder', []), + 'graphics': self.info['graphics']} diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index d722920..7843be7 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -53,7 +53,9 @@ class VM(Resource): 'screenshot': self.info['screenshot'], 'icon': self.info['icon'], 'graphics': {'type': self.info['graphics']['type'], - 'port': self.info['graphics']['port']}} + 'listen': self.info['graphics']['listen'], + 'port': self.info['graphics']['port']} + }
class VMScreenShot(Resource): diff --git a/src/kimchi/model.py b/src/kimchi/model.py index ed613b1..4f4b837 100644 --- a/src/kimchi/model.py +++ b/src/kimchi/model.py @@ -503,7 +503,8 @@ class Model(object): info = dom.info() state = Model.dom_state_map[info[0]] screenshot = None - graphics_type, graphics_port = self._vm_get_graphics(name) + graphics_type, graphics_listen, graphics_port = self._vm_get_graphics(name) + graphics_port = graphics_port if state == 'running' else None try: if state == 'running': screenshot = self.vmscreenshot_lookup(name) @@ -536,7 +537,10 @@ class Model(object): 'cpus': info[3], 'screenshot': screenshot, 'icon': icon, - 'graphics': {"type": graphics_type, "port": graphics_port}} + 'graphics': {"type": graphics_type, + "listen": graphics_listen, + "port": graphics_port} + }
def _vm_get_disk_paths(self, dom): xml = dom.XMLDesc(0) @@ -580,21 +584,21 @@ class Model(object): expr = "/domain/devices/graphics/@type" res = xmlutils.xpath_get_text(xml, expr) graphics_type = res[0] if res else None - port = None + expr = "/domain/devices/graphics/@listen" + res = xmlutils.xpath_get_text(xml, expr) + graphics_listen = res[0] if res else None + graphics_port = None if graphics_type: expr = "/domain/devices/graphics[@type='%s']/@port" % graphics_type res = xmlutils.xpath_get_text(xml, expr) - port = int(res[0]) if res else None - # FIX ME - # graphics_type should be 'vnc' or None. 'spice' should only be - # returned if we support it in the future. - graphics_type = None if graphics_type != "vnc" else graphics_type - return graphics_type, port + graphics_port = int(res[0]) if res else None + return graphics_type, graphics_listen, graphics_port
def vm_connect(self, name): - graphics, port = self._vm_get_graphics(name) - if graphics == "vnc" and port != None: - vnc.add_proxy_token(name, port) + graphics_type, graphics_listen, graphics_port \ + = self._vm_get_graphics(name) + if graphics_port is not None: + vnc.add_proxy_token(name, graphics_port) else: raise OperationFailed("Only able to connect to running vm's vnc " "graphics.") @@ -628,8 +632,12 @@ class Model(object): session.store('vm', vm_uuid, {'icon': icon})
libvirt_stream = False if len(self.libvirt_stream_protocols) == 0 else True + graphics = params.get('graphics')
- xml = t.to_vm_xml(name, vm_uuid, libvirt_stream, self.qemu_stream_dns) + xml = t.to_vm_xml(name, vm_uuid, + libvirt_stream=libvirt_stream, + qemu_stream_dns=self.qemu_stream_dns, + graphics=graphics) try: dom = conn.defineXML(xml.encode('utf-8')) except libvirt.libvirtError as e: diff --git a/src/kimchi/osinfo.py b/src/kimchi/osinfo.py index 0509622..f92db3d 100644 --- a/src/kimchi/osinfo.py +++ b/src/kimchi/osinfo.py @@ -73,8 +73,9 @@ isolinks = {
defaults = {'networks': ['default'], 'storagepool': '/storagepools/default', - 'domain': 'kvm', 'arch': os.uname()[4] -} + 'domain': 'kvm', 'arch': os.uname()[4], + 'graphics': {'type': 'vnc', 'listen': '0.0.0.0'}} +
def _get_arch(): @@ -90,7 +91,7 @@ def lookup(distro, version): 'defaults' and merging the parameters given for the identified OS. If known, a link to a remote install CD is added. """ - params = copy.copy(defaults) + params = copy.deepcopy(defaults) params['os_distro'] = distro params['os_version'] = version params['cdrom'] = isolinks.get(distro, {}).get(version, '') diff --git a/src/kimchi/vmtemplate.py b/src/kimchi/vmtemplate.py index 6587bbb..58147e3 100644 --- a/src/kimchi/vmtemplate.py +++ b/src/kimchi/vmtemplate.py @@ -76,6 +76,11 @@ class VMTemplate(object): self.info.update(entry)
# Override with the passed in parameters + graph_args = args.get('graphics') + if graph_args: + graphics = dict(self.info['graphics']) + graphics.update(graph_args) + args['graphics'] = graphics self.info.update(args)
def _get_cdrom_xml(self, libvirt_stream, qemu_stream_dns): @@ -157,6 +162,24 @@ class VMTemplate(object): """ % params return ret
+ def _get_graphics_xml(self, params): + graphics_xml = """ + <graphics type='%(type)s' autoport='yes' listen='%(listen)s'> + </graphics> + """ + spicevmc_xml = """ + <channel type='spicevmc'> + <target type='virtio' name='com.redhat.spice.0'/> + </channel> + """ + graphics = dict(self.info['graphics']) + if params: + graphics.update(params) + graphics_xml = graphics_xml % graphics + if graphics['type'] == 'spice': + graphics_xml = graphics_xml + spicevmc_xml + return graphics_xml + def to_volume_list(self, vm_uuid): storage_path = self._get_storage_path() ret = [] @@ -198,7 +221,7 @@ class VMTemplate(object): networks += network % net_info return networks
- def to_vm_xml(self, vm_name, vm_uuid, libvirt_stream = False, qemu_stream_dns = False): + def to_vm_xml(self, vm_name, vm_uuid, **kwargs): params = dict(self.info) params['name'] = vm_name params['uuid'] = vm_uuid @@ -207,7 +230,11 @@ class VMTemplate(object): params['qemu-namespace'] = '' params['cdroms'] = '' params['qemu-stream-cmdline'] = '' + graphics = kwargs.get('graphics') + params['graphics'] = self._get_graphics_xml(graphics)
+ qemu_stream_dns = kwargs.get('qemu_stream_dns', False) + libvirt_stream = kwargs.get('libvirt_stream', False) cdrom_xml = self._get_cdrom_xml(libvirt_stream, qemu_stream_dns) if not libvirt_stream and params.get('iso_stream', False): params['qemu-namespace'] = QEMU_NAMESPACE @@ -238,7 +265,7 @@ class VMTemplate(object): %(disks)s %(cdroms)s %(networks)s - <graphics type='vnc' /> + %(graphics)s <sound model='ich6' /> <memballoon model='virtio' /> </devices>

1. Validate graphics parameters from rest requester, with newly added json schema in kimchi. 2. To use "format" property, i need to hook format_checker for Draft3Validator in controller.py. Signed-off-by: apporc <appleorchard2000@gmail.com> --- src/kimchi/API.json | 30 +++++++++++++++++++++++++++--- src/kimchi/control/utils.py | 4 ++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/kimchi/API.json b/src/kimchi/API.json index 3a3c48f..d79281e 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -3,6 +3,27 @@ "title": "Kimchi API", "description": "Json schema for Kimchi API", "type": "object", + "kimchitype": { + "graphics": { + "description": "Configure graphics parameters for the new VM", + "type": "object", + "properties": { + "type": { "enum": ["spice", "vnc"] }, + "listen": { + "type": [ + { + "type": "string", + "format": "ip-address" + }, + { + "type": "string", + "format": "ipv6" + } + ] + } + } + } + }, "properties": { "storagepools_create": { "type": "object", @@ -90,7 +111,8 @@ "description": "Assign a specefic Storage Pool to the new VM", "type": "string", "pattern": "^/storagepools/[^/]+/?$" - } + }, + "graphics": { "$ref": "#/kimchitype/graphics" } } }, "vm_update": { @@ -204,7 +226,8 @@ "description": "Folder", "type": "array", "items": { "type": "string" } - } + }, + "graphics": { "$ref": "#/kimchitype/graphics" } }, "additionalProperties": false }, @@ -283,7 +306,8 @@ "description": "Folder", "type": "array", "items": { "type": "string" } - } + }, + "graphics": { "$ref": "#/kimchitype/graphics" } }, "additionalProperties": false } diff --git a/src/kimchi/control/utils.py b/src/kimchi/control/utils.py index c3c5f8e..814ba20 100644 --- a/src/kimchi/control/utils.py +++ b/src/kimchi/control/utils.py @@ -27,7 +27,7 @@ import cherrypy import json -from jsonschema import Draft3Validator, ValidationError +from jsonschema import Draft3Validator, ValidationError, FormatChecker from kimchi.exception import InvalidParameter @@ -95,7 +95,7 @@ def validate_params(params, instance, action): return operation = model_fn(instance, action) - validator = Draft3Validator(api_schema) + validator = Draft3Validator(api_schema, format_checker=FormatChecker()) request = {operation: params} try: -- 1.7.9.5

Reviewed-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> On 01/10/2014 08:44 PM, apporc wrote:
1. Validate graphics parameters from rest requester, with newly added json schema in kimchi.
2. To use "format" property, i need to hook format_checker for Draft3Validator in controller.py.
Signed-off-by: apporc <appleorchard2000@gmail.com> --- src/kimchi/API.json | 30 +++++++++++++++++++++++++++--- src/kimchi/control/utils.py | 4 ++-- 2 files changed, 29 insertions(+), 5 deletions(-)
diff --git a/src/kimchi/API.json b/src/kimchi/API.json index 3a3c48f..d79281e 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -3,6 +3,27 @@ "title": "Kimchi API", "description": "Json schema for Kimchi API", "type": "object", + "kimchitype": { + "graphics": { + "description": "Configure graphics parameters for the new VM", + "type": "object", + "properties": { + "type": { "enum": ["spice", "vnc"] }, + "listen": { + "type": [ + { + "type": "string", + "format": "ip-address" + }, + { + "type": "string", + "format": "ipv6" + } + ] + } + } + } + }, "properties": { "storagepools_create": { "type": "object", @@ -90,7 +111,8 @@ "description": "Assign a specefic Storage Pool to the new VM", "type": "string", "pattern": "^/storagepools/[^/]+/?$" - } + }, + "graphics": { "$ref": "#/kimchitype/graphics" } } }, "vm_update": { @@ -204,7 +226,8 @@ "description": "Folder", "type": "array", "items": { "type": "string" } - } + }, + "graphics": { "$ref": "#/kimchitype/graphics" } }, "additionalProperties": false }, @@ -283,7 +306,8 @@ "description": "Folder", "type": "array", "items": { "type": "string" } - } + }, + "graphics": { "$ref": "#/kimchitype/graphics" } }, "additionalProperties": false } diff --git a/src/kimchi/control/utils.py b/src/kimchi/control/utils.py index c3c5f8e..814ba20 100644 --- a/src/kimchi/control/utils.py +++ b/src/kimchi/control/utils.py @@ -27,7 +27,7 @@ import cherrypy import json
-from jsonschema import Draft3Validator, ValidationError +from jsonschema import Draft3Validator, ValidationError, FormatChecker
from kimchi.exception import InvalidParameter @@ -95,7 +95,7 @@ def validate_params(params, instance, action): return
operation = model_fn(instance, action) - validator = Draft3Validator(api_schema) + validator = Draft3Validator(api_schema, format_checker=FormatChecker()) request = {operation: params}
try:
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

Reviewed-by: zhoumeina <zhoumein@linux.vnet.ibm.com> On 01/10/2014 08:44 PM, apporc wrote:
1. Validate graphics parameters from rest requester, with newly added json schema in kimchi.
2. To use "format" property, i need to hook format_checker for Draft3Validator in controller.py.
Signed-off-by: apporc <appleorchard2000@gmail.com> --- src/kimchi/API.json | 30 +++++++++++++++++++++++++++--- src/kimchi/control/utils.py | 4 ++-- 2 files changed, 29 insertions(+), 5 deletions(-)
diff --git a/src/kimchi/API.json b/src/kimchi/API.json index 3a3c48f..d79281e 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -3,6 +3,27 @@ "title": "Kimchi API", "description": "Json schema for Kimchi API", "type": "object", + "kimchitype": { + "graphics": { + "description": "Configure graphics parameters for the new VM", + "type": "object", + "properties": { + "type": { "enum": ["spice", "vnc"] }, + "listen": { + "type": [ + { + "type": "string", + "format": "ip-address" + }, + { + "type": "string", + "format": "ipv6" + } + ] + } + } + } + }, "properties": { "storagepools_create": { "type": "object", @@ -90,7 +111,8 @@ "description": "Assign a specefic Storage Pool to the new VM", "type": "string", "pattern": "^/storagepools/[^/]+/?$" - } + }, + "graphics": { "$ref": "#/kimchitype/graphics" } } }, "vm_update": { @@ -204,7 +226,8 @@ "description": "Folder", "type": "array", "items": { "type": "string" } - } + }, + "graphics": { "$ref": "#/kimchitype/graphics" } }, "additionalProperties": false }, @@ -283,7 +306,8 @@ "description": "Folder", "type": "array", "items": { "type": "string" } - } + }, + "graphics": { "$ref": "#/kimchitype/graphics" } }, "additionalProperties": false } diff --git a/src/kimchi/control/utils.py b/src/kimchi/control/utils.py index c3c5f8e..814ba20 100644 --- a/src/kimchi/control/utils.py +++ b/src/kimchi/control/utils.py @@ -27,7 +27,7 @@ import cherrypy import json
-from jsonschema import Draft3Validator, ValidationError +from jsonschema import Draft3Validator, ValidationError, FormatChecker
from kimchi.exception import InvalidParameter @@ -95,7 +95,7 @@ def validate_params(params, instance, action): return
operation = model_fn(instance, action) - validator = Draft3Validator(api_schema) + validator = Draft3Validator(api_schema, format_checker=FormatChecker()) request = {operation: params}
try:

Reviewed-by: Rodrigo Trujillo <rodrigo.trujillo@linux.vnet.ibm.com> On 01/10/2014 10:44 AM, apporc wrote:
1. Validate graphics parameters from rest requester, with newly added json schema in kimchi.
2. To use "format" property, i need to hook format_checker for Draft3Validator in controller.py.
Signed-off-by: apporc <appleorchard2000@gmail.com> --- src/kimchi/API.json | 30 +++++++++++++++++++++++++++--- src/kimchi/control/utils.py | 4 ++-- 2 files changed, 29 insertions(+), 5 deletions(-)
diff --git a/src/kimchi/API.json b/src/kimchi/API.json index 3a3c48f..d79281e 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -3,6 +3,27 @@ "title": "Kimchi API", "description": "Json schema for Kimchi API", "type": "object", + "kimchitype": { + "graphics": { + "description": "Configure graphics parameters for the new VM", + "type": "object", + "properties": { + "type": { "enum": ["spice", "vnc"] }, + "listen": { + "type": [ + { + "type": "string", + "format": "ip-address" + }, + { + "type": "string", + "format": "ipv6" + } + ] + } + } + } + }, "properties": { "storagepools_create": { "type": "object", @@ -90,7 +111,8 @@ "description": "Assign a specefic Storage Pool to the new VM", "type": "string", "pattern": "^/storagepools/[^/]+/?$" - } + }, + "graphics": { "$ref": "#/kimchitype/graphics" } } }, "vm_update": { @@ -204,7 +226,8 @@ "description": "Folder", "type": "array", "items": { "type": "string" } - } + }, + "graphics": { "$ref": "#/kimchitype/graphics" } }, "additionalProperties": false }, @@ -283,7 +306,8 @@ "description": "Folder", "type": "array", "items": { "type": "string" } - } + }, + "graphics": { "$ref": "#/kimchitype/graphics" } }, "additionalProperties": false } diff --git a/src/kimchi/control/utils.py b/src/kimchi/control/utils.py index c3c5f8e..814ba20 100644 --- a/src/kimchi/control/utils.py +++ b/src/kimchi/control/utils.py @@ -27,7 +27,7 @@ import cherrypy import json
-from jsonschema import Draft3Validator, ValidationError +from jsonschema import Draft3Validator, ValidationError, FormatChecker
from kimchi.exception import InvalidParameter @@ -95,7 +95,7 @@ def validate_params(params, instance, action): return
operation = model_fn(instance, action) - validator = Draft3Validator(api_schema) + validator = Draft3Validator(api_schema, format_checker=FormatChecker()) request = {operation: params}
try:

1. Validate graphics parameters from rest requester, with newly added json schema in kimchi.
2. To use "format" property, i need to hook format_checker for Draft3Validator in controller.py.
Signed-off-by: apporc <appleorchard2000@gmail.com> --- src/kimchi/API.json | 30 +++++++++++++++++++++++++++--- src/kimchi/control/utils.py | 4 ++-- 2 files changed, 29 insertions(+), 5 deletions(-)
diff --git a/src/kimchi/API.json b/src/kimchi/API.json index 3a3c48f..d79281e 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -3,6 +3,27 @@ "title": "Kimchi API", "description": "Json schema for Kimchi API", "type": "object", + "kimchitype": { + "graphics": { + "description": "Configure graphics parameters for the new VM", + "type": "object", + "properties": { + "type": { "enum": ["spice", "vnc"] }, + "listen": { + "type": [ + { + "type": "string", + "format": "ip-address" + }, + { + "type": "string", + "format": "ipv6" + } + ] + } + } + } + }, "properties": { "storagepools_create": { "type": "object", @@ -90,7 +111,8 @@ "description": "Assign a specefic Storage Pool to the new VM", "type": "string", "pattern": "^/storagepools/[^/]+/?$" - } + }, + "graphics": { "$ref": "#/kimchitype/graphics" } } }, "vm_update": { @@ -204,7 +226,8 @@ "description": "Folder", "type": "array", "items": { "type": "string" } - } + }, + "graphics": { "$ref": "#/kimchitype/graphics" } }, "additionalProperties": false }, @@ -283,7 +306,8 @@ "description": "Folder", "type": "array", "items": { "type": "string" } - } + }, + "graphics": { "$ref": "#/kimchitype/graphics" }
Hi Xinding and Meina: This patch also support template_update "graphics" you can update the UI template edit page. Thanks. On 01/10/2014 08:44 PM, apporc wrote: this patch also support template_update "graphics"
}, "additionalProperties": false } diff --git a/src/kimchi/control/utils.py b/src/kimchi/control/utils.py index c3c5f8e..814ba20 100644 --- a/src/kimchi/control/utils.py +++ b/src/kimchi/control/utils.py @@ -27,7 +27,7 @@ import cherrypy import json
-from jsonschema import Draft3Validator, ValidationError +from jsonschema import Draft3Validator, ValidationError, FormatChecker
from kimchi.exception import InvalidParameter @@ -95,7 +95,7 @@ def validate_params(params, instance, action): return
operation = model_fn(instance, action) - validator = Draft3Validator(api_schema) + validator = Draft3Validator(api_schema, format_checker=FormatChecker()) request = {operation: params}
try:
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

Update mockmodel for spice support. Code about graphics port are removed. Signed-off-by: apporc <appleorchard2000@gmail.com> --- src/kimchi/mockmodel.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index 9ecef77..7210482 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -143,8 +143,13 @@ class MockModel(object): t = self._get_template(t_name, vm_overrides) t.validate() - vm = MockVM(vm_uuid, name, t.info) - icon = t.info.get('icon') + t_info = copy.deepcopy(t.info) + graphics = params.get('graphics') + if graphics: + t_info.update({'graphics': graphics}) + + vm = MockVM(vm_uuid, name, t_info) + icon = t_info.get('icon') if icon: vm.info['icon'] = icon @@ -626,7 +631,9 @@ class MockVM(object): 'memory': template_info['memory'], 'cpus': template_info['cpus'], 'icon': None, - 'graphics': {'type': 'vnc', 'port': None}} + 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', 'port': None} + } + self.info['graphics'].update(template_info['graphics']) class MockStoragePool(object): -- 1.7.9.5

Reviewed-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> On 01/10/2014 08:44 PM, apporc wrote:
Update mockmodel for spice support. Code about graphics port are removed.
Signed-off-by: apporc <appleorchard2000@gmail.com> --- src/kimchi/mockmodel.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index 9ecef77..7210482 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -143,8 +143,13 @@ class MockModel(object): t = self._get_template(t_name, vm_overrides) t.validate()
- vm = MockVM(vm_uuid, name, t.info) - icon = t.info.get('icon') + t_info = copy.deepcopy(t.info) + graphics = params.get('graphics') + if graphics: + t_info.update({'graphics': graphics}) + + vm = MockVM(vm_uuid, name, t_info) + icon = t_info.get('icon') if icon: vm.info['icon'] = icon
@@ -626,7 +631,9 @@ class MockVM(object): 'memory': template_info['memory'], 'cpus': template_info['cpus'], 'icon': None, - 'graphics': {'type': 'vnc', 'port': None}} + 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', 'port': None} + } + self.info['graphics'].update(template_info['graphics'])
class MockStoragePool(object):
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

Reviewed-by: zhoumeina <zhoumein@linux.vnet.ibm.com> On 01/10/2014 08:44 PM, apporc wrote:
Update mockmodel for spice support. Code about graphics port are removed.
Signed-off-by: apporc <appleorchard2000@gmail.com> --- src/kimchi/mockmodel.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index 9ecef77..7210482 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -143,8 +143,13 @@ class MockModel(object): t = self._get_template(t_name, vm_overrides) t.validate()
- vm = MockVM(vm_uuid, name, t.info) - icon = t.info.get('icon') + t_info = copy.deepcopy(t.info) + graphics = params.get('graphics') + if graphics: + t_info.update({'graphics': graphics}) + + vm = MockVM(vm_uuid, name, t_info) + icon = t_info.get('icon') if icon: vm.info['icon'] = icon
@@ -626,7 +631,9 @@ class MockVM(object): 'memory': template_info['memory'], 'cpus': template_info['cpus'], 'icon': None, - 'graphics': {'type': 'vnc', 'port': None}} + 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', 'port': None} + } + self.info['graphics'].update(template_info['graphics'])
class MockStoragePool(object):

Reviewed-by: Rodrigo Trujillo <rodrigo.trujillo@linux.vnet.ibm.com> On 01/10/2014 10:44 AM, apporc wrote:
Update mockmodel for spice support. Code about graphics port are removed.
Signed-off-by: apporc <appleorchard2000@gmail.com> --- src/kimchi/mockmodel.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index 9ecef77..7210482 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -143,8 +143,13 @@ class MockModel(object): t = self._get_template(t_name, vm_overrides) t.validate()
- vm = MockVM(vm_uuid, name, t.info) - icon = t.info.get('icon') + t_info = copy.deepcopy(t.info) + graphics = params.get('graphics') + if graphics: + t_info.update({'graphics': graphics}) + + vm = MockVM(vm_uuid, name, t_info) + icon = t_info.get('icon') if icon: vm.info['icon'] = icon
@@ -626,7 +631,9 @@ class MockVM(object): 'memory': template_info['memory'], 'cpus': template_info['cpus'], 'icon': None, - 'graphics': {'type': 'vnc', 'port': None}} + 'graphics': {'type': 'vnc', 'listen': '0.0.0.0', 'port': None} + } + self.info['graphics'].update(template_info['graphics'])
class MockStoragePool(object):

Because all of params validation are moved to control, i removed the test code about graphics data validation in model.py. Test code for ipv6 is added too. Signed-off-by: apporc <appleorchard2000@gmail.com> --- tests/test_mockmodel.py | 2 + tests/test_model.py | 27 ++++++++++- tests/test_rest.py | 111 ++++++++++++++++++++++++++++++++++++++++++++-- tests/test_vmtemplate.py | 40 +++++++++++++++-- 4 files changed, 171 insertions(+), 9 deletions(-) diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py index f167dc6..29413a9 100644 --- a/tests/test_mockmodel.py +++ b/tests/test_mockmodel.py @@ -150,3 +150,5 @@ class MockModelTests(unittest.TestCase): self.assertEquals(1, info['cpus']) self.assertEquals('images/icon-vm.png', info['icon']) self.assertEquals(stats_keys, set(eval(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 c689bcc..d502bbf 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -67,7 +67,6 @@ class ModelTests(unittest.TestCase): self.assertEquals(2, info['cpus']) self.assertEquals(None, info['icon']) self.assertEquals(stats_keys, set(eval(info['stats']).keys())) - self.assertRaises(NotFoundError, inst.vm_lookup, 'nosuchvm') @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') @@ -96,6 +95,32 @@ class ModelTests(unittest.TestCase): self.assertFalse('kimchi-vm' in vms) @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') + def test_vm_graphics(self): + inst = kimchi.model.Model(objstore_loc=self.tmp_store) + params = {'name': 'test', 'disks': []} + inst.templates_create(params) + with RollbackContext() as rollback: + params = {'name': 'kimchi-vnc', 'template': '/templates/test'} + inst.vms_create(params) + rollback.prependDefer(inst.vm_delete, 'kimchi-vnc') + + info = inst.vm_lookup('kimchi-vnc') + self.assertEquals('vnc', info['graphics']['type']) + self.assertEquals('0.0.0.0', info['graphics']['listen']) + + graphics = {'type': 'spice', 'listen': '127.0.0.1'} + params = {'name': 'kimchi-spice', 'template': '/templates/test', + 'graphics': graphics} + inst.vms_create(params) + rollback.prependDefer(inst.vm_delete, 'kimchi-spice') + + info = inst.vm_lookup('kimchi-spice') + self.assertEquals('spice', info['graphics']['type']) + self.assertEquals('127.0.0.1', info['graphics']['listen']) + + inst.template_delete('test') + + @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_vm_storage_provisioning(self): inst = kimchi.model.Model(objstore_loc=self.tmp_store) diff --git a/tests/test_rest.py b/tests/test_rest.py index a960868..8ab1bc3 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -255,9 +255,86 @@ class RestTests(unittest.TestCase): resp = self.request('/vms/test-vm', '{}', 'DELETE') self.assertEquals(204, resp.status) + # Delete the Template + resp = self.request('/templates/test', '{}', 'DELETE') + self.assertEquals(204, resp.status) + # Verify the volume was deleted self.assertHTTPStatus(404, vol_uri) + def test_vm_graphics(self): + # Create a Template + req = json.dumps({'name': 'test', 'cdrom': '/nonexistent.iso'}) + resp = self.request('/templates', req, 'POST') + self.assertEquals(201, resp.status) + + # Create a VM with default args + req = json.dumps({'name': 'test-vm', 'template': '/templates/test'}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(201, resp.status) + # Verify the VM + vm = json.loads(self.request('/vms/test-vm').read()) + self.assertEquals('0.0.0.0', vm['graphics']['listen']) + self.assertEquals('vnc', vm['graphics']['type']) + # Delete the VM + resp = self.request('/vms/test-vm', '{}', 'DELETE') + self.assertEquals(204, resp.status) + + # Create a VM with specified graphics type and listen + graphics = {'type': 'vnc', 'listen': '127.0.0.1'} + req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(201, resp.status) + # Verify the VM + vm = json.loads(self.request('/vms/test-vm').read()) + self.assertEquals('127.0.0.1', vm['graphics']['listen']) + self.assertEquals('vnc', vm['graphics']['type']) + # Delete the VM + resp = self.request('/vms/test-vm', '{}', 'DELETE') + self.assertEquals(204, resp.status) + + # Create a VM with listen as ipv6 address + graphics = {'type': 'spice', 'listen': 'fe00::0'} + req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(201, resp.status) + # Verify the VM + vm = json.loads(self.request('/vms/test-vm').read()) + self.assertEquals('fe00::0', vm['graphics']['listen']) + self.assertEquals('spice', vm['graphics']['type']) + # Delete the VM + resp = self.request('/vms/test-vm', '{}', 'DELETE') + self.assertEquals(204, resp.status) + + # Create a VM with specified graphics type and default listen + graphics = {'type': 'spice'} + req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(201, resp.status) + # Verify the VM + vm = json.loads(self.request('/vms/test-vm').read()) + self.assertEquals('0.0.0.0', vm['graphics']['listen']) + self.assertEquals('spice', vm['graphics']['type']) + # Delete the VM + resp = self.request('/vms/test-vm', '{}', 'DELETE') + self.assertEquals(204, resp.status) + + # Try to create a VM with invalid graphics type + graphics = {'type': 'invalid'} + req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(400, resp.status) + + # Try to create a VM with invalid graphics listen + graphics = {'type': 'spice', 'listen': 'invalid'} + req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(400, resp.status) + + # Delete the Template + resp = self.request('/templates/test', '{}', 'DELETE') + self.assertEquals(204, resp.status) + def test_vm_customise_storage(self): # Create a Template req = json.dumps({'name': 'test', 'cdrom': '/nonexistent.iso', @@ -638,9 +715,10 @@ class RestTests(unittest.TestCase): def test_templates(self): def verify_template(t, res): - for field in ('name', 'os_distro', 'os_version', - 'memory', 'cpus', 'storagepool'): - self.assertEquals(t[field], res[field]) + for field in ('name', 'os_distro', 'os_version', 'memory', + 'cpus', 'storagepool', 'graphics'): + if field in t: + self.assertEquals(t[field], res[field]) resp = self.request('/templates') self.assertEquals(200, resp.status) @@ -655,9 +733,11 @@ class RestTests(unittest.TestCase): self.assertEquals(400, resp.status) # Create a template + graphics = {'type': 'spice', 'listen': '127.0.0.1'} t = {'name': 'test', 'os_distro': 'ImagineOS', 'os_version': '1.0', 'memory': 1024, 'cpus': 1, - 'storagepool': '/storagepools/alt', 'cdrom': '/nonexistent.iso'} + 'storagepool': '/storagepools/alt', 'cdrom': '/nonexistent.iso', + 'graphics': graphics} req = json.dumps(t) resp = self.request('/templates', req, 'POST') self.assertEquals(201, resp.status) @@ -678,6 +758,17 @@ class RestTests(unittest.TestCase): # Update the template t['os_distro'] = 'Linux.ISO' t['os_version'] = '1.1' + t['graphics'] = {'type': 'vnc', 'listen': '0.0.0.0'} + req = json.dumps(t) + resp = self.request('/templates/%s' % t['name'], req, 'PUT') + self.assertEquals(200, resp.status) + + # Verify the template + res = json.loads(self.request('/templates/test').read()) + verify_template(t, res) + + # Update the template with ipv6 address as listen + t['graphics'] = {'type': 'vnc', 'listen': 'fe00::0'} req = json.dumps(t) resp = self.request('/templates/%s' % t['name'], req, 'PUT') self.assertEquals(200, resp.status) @@ -736,6 +827,18 @@ class RestTests(unittest.TestCase): resp = self.request('/templates/%s' % tmpl_name, req, 'PUT') self.assertEquals(400, resp.status) + # Try to change template graphics type to invalid value + t['graphics'] = {'type': 'invalid'} + req = json.dumps(t) + resp = self.request('/templates/%s' % tmpl_name, req, 'PUT') + self.assertEquals(400, resp.status) + + # Try to change template graphics type to invalid listen + t['graphics'] = {'type': 'vnc', 'listen': 'invalid'} + req = json.dumps(t) + resp = self.request('/templates/%s' % tmpl_name, req, 'PUT') + self.assertEquals(400, resp.status) + # Try to clean up template cpus value t['cpus'] = ' ' req = json.dumps(t) diff --git a/tests/test_vmtemplate.py b/tests/test_vmtemplate.py index dc9c0ef..ee20dbf 100644 --- a/tests/test_vmtemplate.py +++ b/tests/test_vmtemplate.py @@ -33,7 +33,8 @@ class VMTemplateTests(unittest.TestCase): fields = (('name', 'test'), ('os_distro', 'unknown'), ('os_version', 'unknown'), ('cpus', 1), ('memory', 1024), ('cdrom', ''), ('networks', ['default']), - ('disk_bus', 'ide'), ('nic_model', 'e1000')) + ('disk_bus', 'ide'), ('nic_model', 'e1000'), + ('graphics', {'type': 'vnc', 'listen': '0.0.0.0'})) args = {'name': 'test'} t = VMTemplate(args) @@ -41,27 +42,58 @@ class VMTemplateTests(unittest.TestCase): self.assertEquals(val, t.info.get(name)) def test_construct_overrides(self): - args = {'name': 'test', 'disks': [{'size': 10}, {'size': 20}]} + graphics = {'type': 'spice', 'listen': '127.0.0.1'} + args = {'name': 'test', 'disks': [{'size': 10}, {'size': 20}], + 'graphics': graphics} t = VMTemplate(args) self.assertEquals(2, len(t.info['disks'])) + self.assertEquals(graphics, t.info['graphics']) + + def test_specified_graphics(self): + # Test specified listen + graphics = {'type': 'vnc', 'listen': '127.0.0.1'} + args = {'name': 'test', 'disks': [{'size': 10}, {'size': 20}], + 'graphics': graphics} + t = VMTemplate(args) + self.assertEquals(graphics, t.info['graphics']) + + # Test specified type + graphics = {'type': 'spice', 'listen': '0.0.0.0'} + args['graphics'] = graphics + t = VMTemplate(args) + self.assertEquals(graphics, t.info['graphics']) + + # If no listen specified, test the default listen + graphics = {'type': 'vnc'} + args['graphics'] = graphics + t = VMTemplate(args) + self.assertEquals(graphics['type'], t.info['graphics']['type']) + self.assertEquals('0.0.0.0', t.info['graphics']['listen']) def test_to_xml(self): + graphics = {'type': 'spice', 'listen': '0.0.0.0'} vm_uuid = str(uuid.uuid4()).replace('-', '') t = VMTemplate({'name': 'test-template'}) - xml = t.to_vm_xml('test-vm', vm_uuid) + xml = t.to_vm_xml('test-vm', vm_uuid, graphics=graphics) self.assertEquals(vm_uuid, xpath_get_text(xml, "/domain/uuid")[0]) self.assertEquals('test-vm', xpath_get_text(xml, "/domain/name")[0]) + expr = "/domain/devices/graphics/@type" + self.assertEquals(graphics['type'], xpath_get_text(xml, expr)[0]) + expr = "/domain/devices/graphics/@listen" + self.assertEquals(graphics['listen'], xpath_get_text(xml, expr)[0]) def test_arg_merging(self): """ Make sure that default parameters from osinfo do not override user- provided parameters. """ + graphics = {'type': 'vnc', 'listen': '127.0.0.1'} args = {'name': 'test', 'os_distro': 'opensuse', 'os_version': '12.3', 'cpus': 2, 'memory': 2048, 'networks': ['foo'], - 'cdrom': '/cd.iso'} + 'cdrom': '/cd.iso', 'graphics': graphics} t = VMTemplate(args) self.assertEquals(2, t.info.get('cpus')) self.assertEquals(2048, t.info.get('memory')) self.assertEquals(['foo'], t.info.get('networks')) self.assertEquals('/cd.iso', t.info.get('cdrom')) + self.assertEquals(graphics, t.info.get('graphics')) -- 1.7.9.5

Reviewed-by: zhoumeina <zhoumein@linux.vnet.ibm.com> On 01/10/2014 08:44 PM, apporc wrote:
Because all of params validation are moved to control, i removed the test code about graphics data validation in model.py. Test code for ipv6 is added too.
Signed-off-by: apporc <appleorchard2000@gmail.com> --- tests/test_mockmodel.py | 2 + tests/test_model.py | 27 ++++++++++- tests/test_rest.py | 111 ++++++++++++++++++++++++++++++++++++++++++++-- tests/test_vmtemplate.py | 40 +++++++++++++++-- 4 files changed, 171 insertions(+), 9 deletions(-)
diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py index f167dc6..29413a9 100644 --- a/tests/test_mockmodel.py +++ b/tests/test_mockmodel.py @@ -150,3 +150,5 @@ class MockModelTests(unittest.TestCase): self.assertEquals(1, info['cpus']) self.assertEquals('images/icon-vm.png', info['icon']) self.assertEquals(stats_keys, set(eval(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 c689bcc..d502bbf 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -67,7 +67,6 @@ class ModelTests(unittest.TestCase): self.assertEquals(2, info['cpus']) self.assertEquals(None, info['icon']) self.assertEquals(stats_keys, set(eval(info['stats']).keys())) - self.assertRaises(NotFoundError, inst.vm_lookup, 'nosuchvm')
@unittest.skipUnless(utils.running_as_root(), 'Must be run as root') @@ -96,6 +95,32 @@ class ModelTests(unittest.TestCase): self.assertFalse('kimchi-vm' in vms)
@unittest.skipUnless(utils.running_as_root(), 'Must be run as root') + def test_vm_graphics(self): + inst = kimchi.model.Model(objstore_loc=self.tmp_store) + params = {'name': 'test', 'disks': []} + inst.templates_create(params) + with RollbackContext() as rollback: + params = {'name': 'kimchi-vnc', 'template': '/templates/test'} + inst.vms_create(params) + rollback.prependDefer(inst.vm_delete, 'kimchi-vnc') + + info = inst.vm_lookup('kimchi-vnc') + self.assertEquals('vnc', info['graphics']['type']) + self.assertEquals('0.0.0.0', info['graphics']['listen']) + + graphics = {'type': 'spice', 'listen': '127.0.0.1'} + params = {'name': 'kimchi-spice', 'template': '/templates/test', + 'graphics': graphics} + inst.vms_create(params) + rollback.prependDefer(inst.vm_delete, 'kimchi-spice') + + info = inst.vm_lookup('kimchi-spice') + self.assertEquals('spice', info['graphics']['type']) + self.assertEquals('127.0.0.1', info['graphics']['listen']) + + inst.template_delete('test') + + @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_vm_storage_provisioning(self): inst = kimchi.model.Model(objstore_loc=self.tmp_store)
diff --git a/tests/test_rest.py b/tests/test_rest.py index a960868..8ab1bc3 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -255,9 +255,86 @@ class RestTests(unittest.TestCase): resp = self.request('/vms/test-vm', '{}', 'DELETE') self.assertEquals(204, resp.status)
+ # Delete the Template + resp = self.request('/templates/test', '{}', 'DELETE') + self.assertEquals(204, resp.status) + # Verify the volume was deleted self.assertHTTPStatus(404, vol_uri)
+ def test_vm_graphics(self): + # Create a Template + req = json.dumps({'name': 'test', 'cdrom': '/nonexistent.iso'}) + resp = self.request('/templates', req, 'POST') + self.assertEquals(201, resp.status) + + # Create a VM with default args + req = json.dumps({'name': 'test-vm', 'template': '/templates/test'}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(201, resp.status) + # Verify the VM + vm = json.loads(self.request('/vms/test-vm').read()) + self.assertEquals('0.0.0.0', vm['graphics']['listen']) + self.assertEquals('vnc', vm['graphics']['type']) + # Delete the VM + resp = self.request('/vms/test-vm', '{}', 'DELETE') + self.assertEquals(204, resp.status) + + # Create a VM with specified graphics type and listen + graphics = {'type': 'vnc', 'listen': '127.0.0.1'} + req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(201, resp.status) + # Verify the VM + vm = json.loads(self.request('/vms/test-vm').read()) + self.assertEquals('127.0.0.1', vm['graphics']['listen']) + self.assertEquals('vnc', vm['graphics']['type']) + # Delete the VM + resp = self.request('/vms/test-vm', '{}', 'DELETE') + self.assertEquals(204, resp.status) + + # Create a VM with listen as ipv6 address + graphics = {'type': 'spice', 'listen': 'fe00::0'} + req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(201, resp.status) + # Verify the VM + vm = json.loads(self.request('/vms/test-vm').read()) + self.assertEquals('fe00::0', vm['graphics']['listen']) + self.assertEquals('spice', vm['graphics']['type']) + # Delete the VM + resp = self.request('/vms/test-vm', '{}', 'DELETE') + self.assertEquals(204, resp.status) + + # Create a VM with specified graphics type and default listen + graphics = {'type': 'spice'} + req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(201, resp.status) + # Verify the VM + vm = json.loads(self.request('/vms/test-vm').read()) + self.assertEquals('0.0.0.0', vm['graphics']['listen']) + self.assertEquals('spice', vm['graphics']['type']) + # Delete the VM + resp = self.request('/vms/test-vm', '{}', 'DELETE') + self.assertEquals(204, resp.status) + + # Try to create a VM with invalid graphics type + graphics = {'type': 'invalid'} + req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(400, resp.status) + + # Try to create a VM with invalid graphics listen + graphics = {'type': 'spice', 'listen': 'invalid'} + req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(400, resp.status) + + # Delete the Template + resp = self.request('/templates/test', '{}', 'DELETE') + self.assertEquals(204, resp.status) + def test_vm_customise_storage(self): # Create a Template req = json.dumps({'name': 'test', 'cdrom': '/nonexistent.iso', @@ -638,9 +715,10 @@ class RestTests(unittest.TestCase):
def test_templates(self): def verify_template(t, res): - for field in ('name', 'os_distro', 'os_version', - 'memory', 'cpus', 'storagepool'): - self.assertEquals(t[field], res[field]) + for field in ('name', 'os_distro', 'os_version', 'memory', + 'cpus', 'storagepool', 'graphics'): + if field in t: + self.assertEquals(t[field], res[field])
resp = self.request('/templates') self.assertEquals(200, resp.status) @@ -655,9 +733,11 @@ class RestTests(unittest.TestCase): self.assertEquals(400, resp.status)
# Create a template + graphics = {'type': 'spice', 'listen': '127.0.0.1'} t = {'name': 'test', 'os_distro': 'ImagineOS', 'os_version': '1.0', 'memory': 1024, 'cpus': 1, - 'storagepool': '/storagepools/alt', 'cdrom': '/nonexistent.iso'} + 'storagepool': '/storagepools/alt', 'cdrom': '/nonexistent.iso', + 'graphics': graphics} req = json.dumps(t) resp = self.request('/templates', req, 'POST') self.assertEquals(201, resp.status) @@ -678,6 +758,17 @@ class RestTests(unittest.TestCase): # Update the template t['os_distro'] = 'Linux.ISO' t['os_version'] = '1.1' + t['graphics'] = {'type': 'vnc', 'listen': '0.0.0.0'} + req = json.dumps(t) + resp = self.request('/templates/%s' % t['name'], req, 'PUT') + self.assertEquals(200, resp.status) + + # Verify the template + res = json.loads(self.request('/templates/test').read()) + verify_template(t, res) + + # Update the template with ipv6 address as listen + t['graphics'] = {'type': 'vnc', 'listen': 'fe00::0'} req = json.dumps(t) resp = self.request('/templates/%s' % t['name'], req, 'PUT') self.assertEquals(200, resp.status) @@ -736,6 +827,18 @@ class RestTests(unittest.TestCase): resp = self.request('/templates/%s' % tmpl_name, req, 'PUT') self.assertEquals(400, resp.status)
+ # Try to change template graphics type to invalid value + t['graphics'] = {'type': 'invalid'} + req = json.dumps(t) + resp = self.request('/templates/%s' % tmpl_name, req, 'PUT') + self.assertEquals(400, resp.status) + + # Try to change template graphics type to invalid listen + t['graphics'] = {'type': 'vnc', 'listen': 'invalid'} + req = json.dumps(t) + resp = self.request('/templates/%s' % tmpl_name, req, 'PUT') + self.assertEquals(400, resp.status) + # Try to clean up template cpus value t['cpus'] = ' ' req = json.dumps(t) diff --git a/tests/test_vmtemplate.py b/tests/test_vmtemplate.py index dc9c0ef..ee20dbf 100644 --- a/tests/test_vmtemplate.py +++ b/tests/test_vmtemplate.py @@ -33,7 +33,8 @@ class VMTemplateTests(unittest.TestCase): fields = (('name', 'test'), ('os_distro', 'unknown'), ('os_version', 'unknown'), ('cpus', 1), ('memory', 1024), ('cdrom', ''), ('networks', ['default']), - ('disk_bus', 'ide'), ('nic_model', 'e1000')) + ('disk_bus', 'ide'), ('nic_model', 'e1000'), + ('graphics', {'type': 'vnc', 'listen': '0.0.0.0'}))
args = {'name': 'test'} t = VMTemplate(args) @@ -41,27 +42,58 @@ class VMTemplateTests(unittest.TestCase): self.assertEquals(val, t.info.get(name))
def test_construct_overrides(self): - args = {'name': 'test', 'disks': [{'size': 10}, {'size': 20}]} + graphics = {'type': 'spice', 'listen': '127.0.0.1'} + args = {'name': 'test', 'disks': [{'size': 10}, {'size': 20}], + 'graphics': graphics} t = VMTemplate(args) self.assertEquals(2, len(t.info['disks'])) + self.assertEquals(graphics, t.info['graphics']) + + def test_specified_graphics(self): + # Test specified listen + graphics = {'type': 'vnc', 'listen': '127.0.0.1'} + args = {'name': 'test', 'disks': [{'size': 10}, {'size': 20}], + 'graphics': graphics} + t = VMTemplate(args) + self.assertEquals(graphics, t.info['graphics']) + + # Test specified type + graphics = {'type': 'spice', 'listen': '0.0.0.0'} + args['graphics'] = graphics + t = VMTemplate(args) + self.assertEquals(graphics, t.info['graphics']) + + # If no listen specified, test the default listen + graphics = {'type': 'vnc'} + args['graphics'] = graphics + t = VMTemplate(args) + self.assertEquals(graphics['type'], t.info['graphics']['type']) + self.assertEquals('0.0.0.0', t.info['graphics']['listen'])
def test_to_xml(self): + graphics = {'type': 'spice', 'listen': '0.0.0.0'} vm_uuid = str(uuid.uuid4()).replace('-', '') t = VMTemplate({'name': 'test-template'}) - xml = t.to_vm_xml('test-vm', vm_uuid) + xml = t.to_vm_xml('test-vm', vm_uuid, graphics=graphics) self.assertEquals(vm_uuid, xpath_get_text(xml, "/domain/uuid")[0]) self.assertEquals('test-vm', xpath_get_text(xml, "/domain/name")[0]) + expr = "/domain/devices/graphics/@type" + self.assertEquals(graphics['type'], xpath_get_text(xml, expr)[0]) + expr = "/domain/devices/graphics/@listen" + self.assertEquals(graphics['listen'], xpath_get_text(xml, expr)[0])
def test_arg_merging(self): """ Make sure that default parameters from osinfo do not override user- provided parameters. """ + graphics = {'type': 'vnc', 'listen': '127.0.0.1'} args = {'name': 'test', 'os_distro': 'opensuse', 'os_version': '12.3', 'cpus': 2, 'memory': 2048, 'networks': ['foo'], - 'cdrom': '/cd.iso'} + 'cdrom': '/cd.iso', 'graphics': graphics} t = VMTemplate(args) self.assertEquals(2, t.info.get('cpus')) self.assertEquals(2048, t.info.get('memory')) self.assertEquals(['foo'], t.info.get('networks')) self.assertEquals('/cd.iso', t.info.get('cdrom')) + self.assertEquals(graphics, t.info.get('graphics'))

Reviewed-by: Rodrigo Trujillo <rodrigo.trujillo@linux.vnet.ibm.com> On 01/10/2014 10:44 AM, apporc wrote:
Because all of params validation are moved to control, i removed the test code about graphics data validation in model.py. Test code for ipv6 is added too.
Signed-off-by: apporc <appleorchard2000@gmail.com> --- tests/test_mockmodel.py | 2 + tests/test_model.py | 27 ++++++++++- tests/test_rest.py | 111 ++++++++++++++++++++++++++++++++++++++++++++-- tests/test_vmtemplate.py | 40 +++++++++++++++-- 4 files changed, 171 insertions(+), 9 deletions(-)
diff --git a/tests/test_mockmodel.py b/tests/test_mockmodel.py index f167dc6..29413a9 100644 --- a/tests/test_mockmodel.py +++ b/tests/test_mockmodel.py @@ -150,3 +150,5 @@ class MockModelTests(unittest.TestCase): self.assertEquals(1, info['cpus']) self.assertEquals('images/icon-vm.png', info['icon']) self.assertEquals(stats_keys, set(eval(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 c689bcc..d502bbf 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -67,7 +67,6 @@ class ModelTests(unittest.TestCase): self.assertEquals(2, info['cpus']) self.assertEquals(None, info['icon']) self.assertEquals(stats_keys, set(eval(info['stats']).keys())) - self.assertRaises(NotFoundError, inst.vm_lookup, 'nosuchvm')
@unittest.skipUnless(utils.running_as_root(), 'Must be run as root') @@ -96,6 +95,32 @@ class ModelTests(unittest.TestCase): self.assertFalse('kimchi-vm' in vms)
@unittest.skipUnless(utils.running_as_root(), 'Must be run as root') + def test_vm_graphics(self): + inst = kimchi.model.Model(objstore_loc=self.tmp_store) + params = {'name': 'test', 'disks': []} + inst.templates_create(params) + with RollbackContext() as rollback: + params = {'name': 'kimchi-vnc', 'template': '/templates/test'} + inst.vms_create(params) + rollback.prependDefer(inst.vm_delete, 'kimchi-vnc') + + info = inst.vm_lookup('kimchi-vnc') + self.assertEquals('vnc', info['graphics']['type']) + self.assertEquals('0.0.0.0', info['graphics']['listen']) + + graphics = {'type': 'spice', 'listen': '127.0.0.1'} + params = {'name': 'kimchi-spice', 'template': '/templates/test', + 'graphics': graphics} + inst.vms_create(params) + rollback.prependDefer(inst.vm_delete, 'kimchi-spice') + + info = inst.vm_lookup('kimchi-spice') + self.assertEquals('spice', info['graphics']['type']) + self.assertEquals('127.0.0.1', info['graphics']['listen']) + + inst.template_delete('test') + + @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_vm_storage_provisioning(self): inst = kimchi.model.Model(objstore_loc=self.tmp_store)
diff --git a/tests/test_rest.py b/tests/test_rest.py index a960868..8ab1bc3 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -255,9 +255,86 @@ class RestTests(unittest.TestCase): resp = self.request('/vms/test-vm', '{}', 'DELETE') self.assertEquals(204, resp.status)
+ # Delete the Template + resp = self.request('/templates/test', '{}', 'DELETE') + self.assertEquals(204, resp.status) + # Verify the volume was deleted self.assertHTTPStatus(404, vol_uri)
+ def test_vm_graphics(self): + # Create a Template + req = json.dumps({'name': 'test', 'cdrom': '/nonexistent.iso'}) + resp = self.request('/templates', req, 'POST') + self.assertEquals(201, resp.status) + + # Create a VM with default args + req = json.dumps({'name': 'test-vm', 'template': '/templates/test'}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(201, resp.status) + # Verify the VM + vm = json.loads(self.request('/vms/test-vm').read()) + self.assertEquals('0.0.0.0', vm['graphics']['listen']) + self.assertEquals('vnc', vm['graphics']['type']) + # Delete the VM + resp = self.request('/vms/test-vm', '{}', 'DELETE') + self.assertEquals(204, resp.status) + + # Create a VM with specified graphics type and listen + graphics = {'type': 'vnc', 'listen': '127.0.0.1'} + req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(201, resp.status) + # Verify the VM + vm = json.loads(self.request('/vms/test-vm').read()) + self.assertEquals('127.0.0.1', vm['graphics']['listen']) + self.assertEquals('vnc', vm['graphics']['type']) + # Delete the VM + resp = self.request('/vms/test-vm', '{}', 'DELETE') + self.assertEquals(204, resp.status) + + # Create a VM with listen as ipv6 address + graphics = {'type': 'spice', 'listen': 'fe00::0'} + req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(201, resp.status) + # Verify the VM + vm = json.loads(self.request('/vms/test-vm').read()) + self.assertEquals('fe00::0', vm['graphics']['listen']) + self.assertEquals('spice', vm['graphics']['type']) + # Delete the VM + resp = self.request('/vms/test-vm', '{}', 'DELETE') + self.assertEquals(204, resp.status) + + # Create a VM with specified graphics type and default listen + graphics = {'type': 'spice'} + req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(201, resp.status) + # Verify the VM + vm = json.loads(self.request('/vms/test-vm').read()) + self.assertEquals('0.0.0.0', vm['graphics']['listen']) + self.assertEquals('spice', vm['graphics']['type']) + # Delete the VM + resp = self.request('/vms/test-vm', '{}', 'DELETE') + self.assertEquals(204, resp.status) + + # Try to create a VM with invalid graphics type + graphics = {'type': 'invalid'} + req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(400, resp.status) + + # Try to create a VM with invalid graphics listen + graphics = {'type': 'spice', 'listen': 'invalid'} + req = json.dumps({'name': 'test-vm', 'template': '/templates/test', 'graphics': graphics}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(400, resp.status) + + # Delete the Template + resp = self.request('/templates/test', '{}', 'DELETE') + self.assertEquals(204, resp.status) + def test_vm_customise_storage(self): # Create a Template req = json.dumps({'name': 'test', 'cdrom': '/nonexistent.iso', @@ -638,9 +715,10 @@ class RestTests(unittest.TestCase):
def test_templates(self): def verify_template(t, res): - for field in ('name', 'os_distro', 'os_version', - 'memory', 'cpus', 'storagepool'): - self.assertEquals(t[field], res[field]) + for field in ('name', 'os_distro', 'os_version', 'memory', + 'cpus', 'storagepool', 'graphics'): + if field in t: + self.assertEquals(t[field], res[field])
resp = self.request('/templates') self.assertEquals(200, resp.status) @@ -655,9 +733,11 @@ class RestTests(unittest.TestCase): self.assertEquals(400, resp.status)
# Create a template + graphics = {'type': 'spice', 'listen': '127.0.0.1'} t = {'name': 'test', 'os_distro': 'ImagineOS', 'os_version': '1.0', 'memory': 1024, 'cpus': 1, - 'storagepool': '/storagepools/alt', 'cdrom': '/nonexistent.iso'} + 'storagepool': '/storagepools/alt', 'cdrom': '/nonexistent.iso', + 'graphics': graphics} req = json.dumps(t) resp = self.request('/templates', req, 'POST') self.assertEquals(201, resp.status) @@ -678,6 +758,17 @@ class RestTests(unittest.TestCase): # Update the template t['os_distro'] = 'Linux.ISO' t['os_version'] = '1.1' + t['graphics'] = {'type': 'vnc', 'listen': '0.0.0.0'} + req = json.dumps(t) + resp = self.request('/templates/%s' % t['name'], req, 'PUT') + self.assertEquals(200, resp.status) + + # Verify the template + res = json.loads(self.request('/templates/test').read()) + verify_template(t, res) + + # Update the template with ipv6 address as listen + t['graphics'] = {'type': 'vnc', 'listen': 'fe00::0'} req = json.dumps(t) resp = self.request('/templates/%s' % t['name'], req, 'PUT') self.assertEquals(200, resp.status) @@ -736,6 +827,18 @@ class RestTests(unittest.TestCase): resp = self.request('/templates/%s' % tmpl_name, req, 'PUT') self.assertEquals(400, resp.status)
+ # Try to change template graphics type to invalid value + t['graphics'] = {'type': 'invalid'} + req = json.dumps(t) + resp = self.request('/templates/%s' % tmpl_name, req, 'PUT') + self.assertEquals(400, resp.status) + + # Try to change template graphics type to invalid listen + t['graphics'] = {'type': 'vnc', 'listen': 'invalid'} + req = json.dumps(t) + resp = self.request('/templates/%s' % tmpl_name, req, 'PUT') + self.assertEquals(400, resp.status) + # Try to clean up template cpus value t['cpus'] = ' ' req = json.dumps(t) diff --git a/tests/test_vmtemplate.py b/tests/test_vmtemplate.py index dc9c0ef..ee20dbf 100644 --- a/tests/test_vmtemplate.py +++ b/tests/test_vmtemplate.py @@ -33,7 +33,8 @@ class VMTemplateTests(unittest.TestCase): fields = (('name', 'test'), ('os_distro', 'unknown'), ('os_version', 'unknown'), ('cpus', 1), ('memory', 1024), ('cdrom', ''), ('networks', ['default']), - ('disk_bus', 'ide'), ('nic_model', 'e1000')) + ('disk_bus', 'ide'), ('nic_model', 'e1000'), + ('graphics', {'type': 'vnc', 'listen': '0.0.0.0'}))
args = {'name': 'test'} t = VMTemplate(args) @@ -41,27 +42,58 @@ class VMTemplateTests(unittest.TestCase): self.assertEquals(val, t.info.get(name))
def test_construct_overrides(self): - args = {'name': 'test', 'disks': [{'size': 10}, {'size': 20}]} + graphics = {'type': 'spice', 'listen': '127.0.0.1'} + args = {'name': 'test', 'disks': [{'size': 10}, {'size': 20}], + 'graphics': graphics} t = VMTemplate(args) self.assertEquals(2, len(t.info['disks'])) + self.assertEquals(graphics, t.info['graphics']) + + def test_specified_graphics(self): + # Test specified listen + graphics = {'type': 'vnc', 'listen': '127.0.0.1'} + args = {'name': 'test', 'disks': [{'size': 10}, {'size': 20}], + 'graphics': graphics} + t = VMTemplate(args) + self.assertEquals(graphics, t.info['graphics']) + + # Test specified type + graphics = {'type': 'spice', 'listen': '0.0.0.0'} + args['graphics'] = graphics + t = VMTemplate(args) + self.assertEquals(graphics, t.info['graphics']) + + # If no listen specified, test the default listen + graphics = {'type': 'vnc'} + args['graphics'] = graphics + t = VMTemplate(args) + self.assertEquals(graphics['type'], t.info['graphics']['type']) + self.assertEquals('0.0.0.0', t.info['graphics']['listen'])
def test_to_xml(self): + graphics = {'type': 'spice', 'listen': '0.0.0.0'} vm_uuid = str(uuid.uuid4()).replace('-', '') t = VMTemplate({'name': 'test-template'}) - xml = t.to_vm_xml('test-vm', vm_uuid) + xml = t.to_vm_xml('test-vm', vm_uuid, graphics=graphics) self.assertEquals(vm_uuid, xpath_get_text(xml, "/domain/uuid")[0]) self.assertEquals('test-vm', xpath_get_text(xml, "/domain/name")[0]) + expr = "/domain/devices/graphics/@type" + self.assertEquals(graphics['type'], xpath_get_text(xml, expr)[0]) + expr = "/domain/devices/graphics/@listen" + self.assertEquals(graphics['listen'], xpath_get_text(xml, expr)[0])
def test_arg_merging(self): """ Make sure that default parameters from osinfo do not override user- provided parameters. """ + graphics = {'type': 'vnc', 'listen': '127.0.0.1'} args = {'name': 'test', 'os_distro': 'opensuse', 'os_version': '12.3', 'cpus': 2, 'memory': 2048, 'networks': ['foo'], - 'cdrom': '/cd.iso'} + 'cdrom': '/cd.iso', 'graphics': graphics} t = VMTemplate(args) self.assertEquals(2, t.info.get('cpus')) self.assertEquals(2048, t.info.get('memory')) self.assertEquals(['foo'], t.info.get('networks')) self.assertEquals('/cd.iso', t.info.get('cdrom')) + self.assertEquals(graphics, t.info.get('graphics'))

Add graphics parameters description in API.md Signed-off-by: apporc <appleorchard2000@gmail.com> --- docs/API.md | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/docs/API.md b/docs/API.md index 4d7bdff..671d0e7 100644 --- a/docs/API.md +++ b/docs/API.md @@ -47,6 +47,15 @@ the following general conventions: API. If omitted, a name will be chosen based on the template used. * template: The URI of a Template to use when building the VM * storagepool *(optional)*: Assign a specific Storage Pool to the new VM + * graphics *(optional)*: Specify the graphics paramenter for this vm + * type: The type of graphics. It can be VNC or spice or None. + * vnc: Graphical display using the Virtual Network + Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments + * null: Graphics is disabled or type not supported + * listen: The network which the vnc/spice server listens on. + ### Resource: Virtual Machine @@ -75,14 +84,16 @@ the following general conventions: * screenshot: A link to a recent capture of the screen in PNG format * icon: A link to an icon that represents the VM * graphics: A dict to show detail of VM graphics. - * type: The type of graphics, It can be VNC or None. + * type: The type of graphics. It can be VNC or spice or None. * vnc: Graphical display using the Virtual Network Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments * null: Graphics is disabled or type not supported - * port: The port number of graphics. It will remain None until a connect - call is issued. - The port number exposed will support the websockets protocol and - may support graphics type over plain TCP as well. + * listen: The network which the vnc/spice server listens on. + * port: The real port number of the graphics, vnc or spice. Users + can use this port to connect to the vm with general vnc/spice + clients. * **DELETE**: Remove the Virtual Machine * **PUT**: update the parameters of existed VM * name: New name for this VM (only applied for shutoff VM) @@ -92,6 +103,7 @@ the following general conventions: * start: Power on a VM * stop: Power off forcefully +* connect: Prepare the connection for spice or vnc ### Sub-resource: Virtual Machine Screenshot @@ -128,6 +140,14 @@ Represents a snapshot of the Virtual Machine's primary monitor. * size: The device size in GB * volume: A volume name that contains the initial disk contents + * graphics *(optional)*: The graphics paramenters of this template + * type: The type of graphics. It can be VNC or spice or None. + * vnc: Graphical display using the Virtual Network + Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments + * null: Graphics is disabled or type not supported + * listen: The network which the vnc/spice server listens on. ### Resource: Template @@ -152,6 +172,15 @@ Represents a snapshot of the Virtual Machine's primary monitor. * index: The device index * size: The device size in GB * volume: A volume name that contains the initial disk contents + * graphcis: A dict of graphics paramenters of this template + * type: The type of graphics. It can be VNC or spice or None. + * vnc: Graphical display using the Virtual Network + Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments + * null: Graphics is disabled or type not supported + * listen: The network which the vnc/spice server listens on. + * **DELETE**: Remove the Template * **POST**: *See Template Actions* * **PUT**: update the parameters of existed template @@ -171,6 +200,14 @@ Represents a snapshot of the Virtual Machine's primary monitor. * index: The device index * size: The device size in GB * volume: A volume name that contains the initial disk contents + * graphcis *(optional)*: A dict of graphics paramenters of this template + * type: The type of graphics. It can be VNC or spice or None. + * vnc: Graphical display using the Virtual Network + Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments + * null: Graphics is disabled or type not supported + * listen: The network which the vnc/spice server listens on. **Actions (POST):** -- 1.7.9.5

Reviewed-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> On 01/10/2014 08:44 PM, apporc wrote:
Add graphics parameters description in API.md
Signed-off-by: apporc <appleorchard2000@gmail.com> --- docs/API.md | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-)
diff --git a/docs/API.md b/docs/API.md index 4d7bdff..671d0e7 100644 --- a/docs/API.md +++ b/docs/API.md @@ -47,6 +47,15 @@ the following general conventions: API. If omitted, a name will be chosen based on the template used. * template: The URI of a Template to use when building the VM * storagepool *(optional)*: Assign a specific Storage Pool to the new VM + * graphics *(optional)*: Specify the graphics paramenter for this vm + * type: The type of graphics. It can be VNC or spice or None. + * vnc: Graphical display using the Virtual Network + Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments + * null: Graphics is disabled or type not supported + * listen: The network which the vnc/spice server listens on. +
### Resource: Virtual Machine
@@ -75,14 +84,16 @@ the following general conventions: * screenshot: A link to a recent capture of the screen in PNG format * icon: A link to an icon that represents the VM * graphics: A dict to show detail of VM graphics. - * type: The type of graphics, It can be VNC or None. + * type: The type of graphics. It can be VNC or spice or None. * vnc: Graphical display using the Virtual Network Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments * null: Graphics is disabled or type not supported - * port: The port number of graphics. It will remain None until a connect - call is issued. - The port number exposed will support the websockets protocol and - may support graphics type over plain TCP as well. + * listen: The network which the vnc/spice server listens on. + * port: The real port number of the graphics, vnc or spice. Users + can use this port to connect to the vm with general vnc/spice + clients. * **DELETE**: Remove the Virtual Machine * **PUT**: update the parameters of existed VM * name: New name for this VM (only applied for shutoff VM) @@ -92,6 +103,7 @@ the following general conventions:
* start: Power on a VM * stop: Power off forcefully +* connect: Prepare the connection for spice or vnc
### Sub-resource: Virtual Machine Screenshot
@@ -128,6 +140,14 @@ Represents a snapshot of the Virtual Machine's primary monitor. * size: The device size in GB * volume: A volume name that contains the initial disk contents
+ * graphics *(optional)*: The graphics paramenters of this template + * type: The type of graphics. It can be VNC or spice or None. + * vnc: Graphical display using the Virtual Network + Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments + * null: Graphics is disabled or type not supported + * listen: The network which the vnc/spice server listens on.
### Resource: Template
@@ -152,6 +172,15 @@ Represents a snapshot of the Virtual Machine's primary monitor. * index: The device index * size: The device size in GB * volume: A volume name that contains the initial disk contents + * graphcis: A dict of graphics paramenters of this template + * type: The type of graphics. It can be VNC or spice or None. + * vnc: Graphical display using the Virtual Network + Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments + * null: Graphics is disabled or type not supported + * listen: The network which the vnc/spice server listens on. + * **DELETE**: Remove the Template * **POST**: *See Template Actions* * **PUT**: update the parameters of existed template @@ -171,6 +200,14 @@ Represents a snapshot of the Virtual Machine's primary monitor. * index: The device index * size: The device size in GB * volume: A volume name that contains the initial disk contents + * graphcis *(optional)*: A dict of graphics paramenters of this template + * type: The type of graphics. It can be VNC or spice or None. + * vnc: Graphical display using the Virtual Network + Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments + * null: Graphics is disabled or type not supported + * listen: The network which the vnc/spice server listens on.
**Actions (POST):**
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

Reviewed-by: zhoumeina <zhoumein@linux.vnet.ibm.com> On 01/10/2014 08:44 PM, apporc wrote:
Add graphics parameters description in API.md
Signed-off-by: apporc <appleorchard2000@gmail.com> --- docs/API.md | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-)
diff --git a/docs/API.md b/docs/API.md index 4d7bdff..671d0e7 100644 --- a/docs/API.md +++ b/docs/API.md @@ -47,6 +47,15 @@ the following general conventions: API. If omitted, a name will be chosen based on the template used. * template: The URI of a Template to use when building the VM * storagepool *(optional)*: Assign a specific Storage Pool to the new VM + * graphics *(optional)*: Specify the graphics paramenter for this vm + * type: The type of graphics. It can be VNC or spice or None. + * vnc: Graphical display using the Virtual Network + Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments + * null: Graphics is disabled or type not supported + * listen: The network which the vnc/spice server listens on. +
### Resource: Virtual Machine
@@ -75,14 +84,16 @@ the following general conventions: * screenshot: A link to a recent capture of the screen in PNG format * icon: A link to an icon that represents the VM * graphics: A dict to show detail of VM graphics. - * type: The type of graphics, It can be VNC or None. + * type: The type of graphics. It can be VNC or spice or None. * vnc: Graphical display using the Virtual Network Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments * null: Graphics is disabled or type not supported - * port: The port number of graphics. It will remain None until a connect - call is issued. - The port number exposed will support the websockets protocol and - may support graphics type over plain TCP as well. + * listen: The network which the vnc/spice server listens on. + * port: The real port number of the graphics, vnc or spice. Users + can use this port to connect to the vm with general vnc/spice + clients. * **DELETE**: Remove the Virtual Machine * **PUT**: update the parameters of existed VM * name: New name for this VM (only applied for shutoff VM) @@ -92,6 +103,7 @@ the following general conventions:
* start: Power on a VM * stop: Power off forcefully +* connect: Prepare the connection for spice or vnc
### Sub-resource: Virtual Machine Screenshot
@@ -128,6 +140,14 @@ Represents a snapshot of the Virtual Machine's primary monitor. * size: The device size in GB * volume: A volume name that contains the initial disk contents
+ * graphics *(optional)*: The graphics paramenters of this template + * type: The type of graphics. It can be VNC or spice or None. + * vnc: Graphical display using the Virtual Network + Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments + * null: Graphics is disabled or type not supported + * listen: The network which the vnc/spice server listens on.
### Resource: Template
@@ -152,6 +172,15 @@ Represents a snapshot of the Virtual Machine's primary monitor. * index: The device index * size: The device size in GB * volume: A volume name that contains the initial disk contents + * graphcis: A dict of graphics paramenters of this template + * type: The type of graphics. It can be VNC or spice or None. + * vnc: Graphical display using the Virtual Network + Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments + * null: Graphics is disabled or type not supported + * listen: The network which the vnc/spice server listens on. + * **DELETE**: Remove the Template * **POST**: *See Template Actions* * **PUT**: update the parameters of existed template @@ -171,6 +200,14 @@ Represents a snapshot of the Virtual Machine's primary monitor. * index: The device index * size: The device size in GB * volume: A volume name that contains the initial disk contents + * graphcis *(optional)*: A dict of graphics paramenters of this template + * type: The type of graphics. It can be VNC or spice or None. + * vnc: Graphical display using the Virtual Network + Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments + * null: Graphics is disabled or type not supported + * listen: The network which the vnc/spice server listens on.
**Actions (POST):**

Reviewed-by: Rodrigo Trujillo <rodrigo.trujillo@linux.vnet.ibm.com> On 01/10/2014 10:44 AM, apporc wrote:
Add graphics parameters description in API.md
Signed-off-by: apporc <appleorchard2000@gmail.com> --- docs/API.md | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-)
diff --git a/docs/API.md b/docs/API.md index 4d7bdff..671d0e7 100644 --- a/docs/API.md +++ b/docs/API.md @@ -47,6 +47,15 @@ the following general conventions: API. If omitted, a name will be chosen based on the template used. * template: The URI of a Template to use when building the VM * storagepool *(optional)*: Assign a specific Storage Pool to the new VM + * graphics *(optional)*: Specify the graphics paramenter for this vm + * type: The type of graphics. It can be VNC or spice or None. + * vnc: Graphical display using the Virtual Network + Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments + * null: Graphics is disabled or type not supported + * listen: The network which the vnc/spice server listens on. +
### Resource: Virtual Machine
@@ -75,14 +84,16 @@ the following general conventions: * screenshot: A link to a recent capture of the screen in PNG format * icon: A link to an icon that represents the VM * graphics: A dict to show detail of VM graphics. - * type: The type of graphics, It can be VNC or None. + * type: The type of graphics. It can be VNC or spice or None. * vnc: Graphical display using the Virtual Network Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments * null: Graphics is disabled or type not supported - * port: The port number of graphics. It will remain None until a connect - call is issued. - The port number exposed will support the websockets protocol and - may support graphics type over plain TCP as well. + * listen: The network which the vnc/spice server listens on. + * port: The real port number of the graphics, vnc or spice. Users + can use this port to connect to the vm with general vnc/spice + clients. * **DELETE**: Remove the Virtual Machine * **PUT**: update the parameters of existed VM * name: New name for this VM (only applied for shutoff VM) @@ -92,6 +103,7 @@ the following general conventions:
* start: Power on a VM * stop: Power off forcefully +* connect: Prepare the connection for spice or vnc
### Sub-resource: Virtual Machine Screenshot
@@ -128,6 +140,14 @@ Represents a snapshot of the Virtual Machine's primary monitor. * size: The device size in GB * volume: A volume name that contains the initial disk contents
+ * graphics *(optional)*: The graphics paramenters of this template + * type: The type of graphics. It can be VNC or spice or None. + * vnc: Graphical display using the Virtual Network + Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments + * null: Graphics is disabled or type not supported + * listen: The network which the vnc/spice server listens on.
### Resource: Template
@@ -152,6 +172,15 @@ Represents a snapshot of the Virtual Machine's primary monitor. * index: The device index * size: The device size in GB * volume: A volume name that contains the initial disk contents + * graphcis: A dict of graphics paramenters of this template + * type: The type of graphics. It can be VNC or spice or None. + * vnc: Graphical display using the Virtual Network + Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments + * null: Graphics is disabled or type not supported + * listen: The network which the vnc/spice server listens on. + * **DELETE**: Remove the Template * **POST**: *See Template Actions* * **PUT**: update the parameters of existed template @@ -171,6 +200,14 @@ Represents a snapshot of the Virtual Machine's primary monitor. * index: The device index * size: The device size in GB * volume: A volume name that contains the initial disk contents + * graphcis *(optional)*: A dict of graphics paramenters of this template + * type: The type of graphics. It can be VNC or spice or None. + * vnc: Graphical display using the Virtual Network + Computing protocol + * spice: Graphical display using the Simple Protocol for + Independent Computing Environments + * null: Graphics is disabled or type not supported + * listen: The network which the vnc/spice server listens on.
**Actions (POST):**

Aline, I think this is ready to merge, could you pls take a look? On 2014年01月10日 20:44, apporc wrote:
v5-v4: 1. Rebase to upstream 2. Change the code to coordinate with new websockify mechanism 3. Add test code for ipv6 (Thanks Aline)
v4-v3: 1. Remove not used module "copy" in vmtemplate.py(Thanks Aline) 2. Fix alignment problem in model.py(Thanks Aline) 3. Use type property of jsonschema to validate both ipv4 and ipv6(Thanks Aline) REF: http://stackoverflow.com/questions/14550235/json-schema-away-to-specify-an-a... 4. Use "$ref" to simply API.json, remove dumplicate content(Thanks Rodrigo)
v3-v2: 1. Rebase to upstream 2. Improve/simplify some code according to reviewers' advice(Thanks Royce, Markwu and Aline). 3. Reimplement parameters validation using jsonschema. 4. Remove unnecessary test code in model.py. 5. Split patch to several smaller commits according to logic(Thanks Royce).
v2-v1: Rebased to upstream and resend
v1: Add spice backend support for kimchi
apporc (5): Add spice backend support for kimchi Validate graphics parameters input by users Update mockmodel for spice support Update test case for graphics support Add graphics parameters description in API.md
docs/API.md | 43 ++++++++++++++- src/kimchi/API.json | 30 +++++++++-- src/kimchi/control/templates.py | 6 ++- src/kimchi/control/utils.py | 4 +- src/kimchi/control/vms.py | 4 +- src/kimchi/mockmodel.py | 13 +++-- src/kimchi/model.py | 34 +++++++----- src/kimchi/osinfo.py | 7 +-- src/kimchi/vmtemplate.py | 31 ++++++++++- tests/test_mockmodel.py | 2 + tests/test_model.py | 27 +++++++++- tests/test_rest.py | 111 +++++++++++++++++++++++++++++++++++++-- tests/test_vmtemplate.py | 40 ++++++++++++-- 13 files changed, 313 insertions(+), 39 deletions(-)
participants (6)
-
Aline Manera
-
apporc
-
Rodrigo Trujillo
-
Royce Lv
-
Sheldon
-
zhoumeina