[v5 1/1] Virtual machine migration

From: Simon Jin <simonjin@linux.vnet.ibm.com> v5-v4:check whether the vm is already in migration. Signed-off-by: Simon Jin <simonjin@linux.vnet.ibm.com> --- docs/API.md | 5 ++ src/kimchi/control/vms.py | 1 + src/kimchi/i18n.py | 8 +++ src/kimchi/model/vms.py | 170 +++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 182 insertions(+), 2 deletions(-) diff --git a/docs/API.md b/docs/API.md index 9b866f3..eb02e80 100644 --- a/docs/API.md +++ b/docs/API.md @@ -132,6 +132,11 @@ the following general conventions: It emulates the power reset button on a machine. Note that there is a risk of data loss caused by reset without the guest OS shutdown. * connect: Prepare the connection for spice or vnc +* migrate: Migrate a virtual machine to a remote server, only support live mode without block migration. + * remoteHost: IP address or hostname of the remote server. + * user: User of the remote server. + * passwd: Passwd of user on remote server. + * secure: Migrate in P2P secure tunel mode.(optional) * clone: Create a new VM identical to this VM. The new VM's name, UUID and network MAC addresses will be generated automatically. Each existing diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index a1589ef..e6870e0 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -42,6 +42,7 @@ class VM(Resource): for ident, node in sub_nodes.items(): setattr(self, ident, node(model, self.ident)) self.start = self.generate_action_handler('start') + self.migrate = self.generate_action_handler_task('migrate', ['remoteHost', 'user', 'passwd', 'secure']) self.poweroff = self.generate_action_handler('poweroff') self.shutdown = self.generate_action_handler('shutdown') self.reset = self.generate_action_handler('reset') diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index e823f2b..ffd8e98 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -105,6 +105,14 @@ messages = { "KCHVM0033E": _("Virtual machine '%(name)s' must be stopped before cloning it."), "KCHVM0034E": _("Insufficient disk space to clone virtual machine '%(name)s'"), "KCHVM0035E": _("Unable to clone VM '%(name)s'. Details: %(err)s"), + "KCHVM0036E": _("Migrate %(name)s to localhost %(remoteHost)s is not allowed."), + "KCHVM0037E": _("vm %(name)s is already in migrating, can not perform migrate on it again."), + "KCHVM0038E": _("Can't migrate vm %(name)s on Hypervisor arch %(srcarch)s.to a different Hypervisor arch %(destarch)s."), + "KCHVM0039E": _("Can't migrate %(name)s from %(srchyp)s to a different Hypervisor type %(desthyp)s."), + "KCHVM0040E": _("Can't migrate vm %(name)s, vm has passthrough device %(hostdevs)s."), + "KCHVM0041E": _("Can not migrate vm %(name)s when its in %(state)s state."), + "KCHVM0042E": _("Failed Migrate vm %(name)s due error: %(err)s"), + "KCHVM0043E": _("Failed to destroy vm %(name)s from migration recover. Details: %(err)s"), "KCHVMHDEV0001E": _("VM %(vmid)s does not contain directly assigned host device %(dev_name)s."), "KCHVMHDEV0002E": _("The host device %(dev_name)s is not allowed to directly assign to VM."), diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index d194049..2f934a4 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -33,9 +33,9 @@ from cherrypy.process.plugins import BackgroundTask from kimchi import model, vnc from kimchi.config import READONLY_POOL_TYPE from kimchi.exception import InvalidOperation, InvalidParameter -from kimchi.exception import NotFoundError, OperationFailed +from kimchi.exception import NotFoundError, OperationFailed, KimchiException from kimchi.model.config import CapabilitiesModel -from kimchi.model.tasks import TaskModel +from kimchi.model.tasks import TaskModel, TasksModel from kimchi.model.templates import TemplateModel from kimchi.model.utils import get_vm_name from kimchi.model.utils import get_metadata_node @@ -265,6 +265,7 @@ class VMModel(object): self.groups = import_class('kimchi.model.host.GroupsModel')(**kargs) self.vms = VMsModel(**kargs) self.task = TaskModel(**kargs) + self.tasks = TasksModel(**kargs) self.storagepool = model.storagepools.StoragePoolModel(**kargs) self.storagevolume = model.storagevolumes.StorageVolumeModel(**kargs) self.storagevolumes = model.storagevolumes.StorageVolumesModel(**kargs) @@ -790,6 +791,171 @@ class VMModel(object): except libvirt.libvirtError as e: raise OperationFailed("KCHVM0019E", {'name': name, 'err': e.get_error_message()}) + + def _preCheck(self, name, destConn, remoteHost): + """Pre check before migration""" + + kimchi_log.debug('precheck migrate %s' % name) + _destConn = destConn.get() + if remoteHost in ['localhost', '127.0.0.1']: + kimchi_log.debug('vm %s Can not migrate to localhost.' % name) + raise OperationFailed("KCHVM0036E", {'name': name, + 'remoteHost': remoteHost}) + #check if the vm already in migration + try: + alltasks = self.tasks.get_list() + for taskid in alltasks: + taskobj = self.task.lookup(taskid) + if taskobj['target_uri'] == "/vms/%s/migrate" % name \ + and taskobj['status'] == "running": + kimchi_log.debug('vm %s is already in migrating.' % name) + raise OperationFailed("KCHVM0037E", {'name': name}) + except NotFoundError as e: + kimchi_log.debug('No migrate on vm %s.' % name) + + srcarch = self.conn.get().getInfo()[0] + destarch = _destConn.getInfo()[0] + if srcarch != destarch: + kimchi_log.debug('vm %s can not migrate to different arch server.' % (name, destarch)) + raise OperationFailed("KCHVM0038E", {'name': name, + 'srcarch': srcarch, + 'destarch': destarch}) + srchyp = self.conn.get().getType() + desthyp = _destConn.getType() + if srchyp != desthyp: + kimchi_log.debug('vm %s can not migrate to a different hypervisor' % name) + raise OperationFailed("KCHVM0039E", {'name': name, + 'srchyp': srchyp, + 'desthyp':desthyp}) + + #model.host import DOM_STATE_MAP, vmhostdevs import host, + #to void loop import DOM_STATE_MAP, move import VMHostDevsModel here + from kimchi.model.vmhostdevs import VMHostDevsModel + #check if there is any passthrough devices belong to this vm + vmhostdevs = VMHostDevsModel(conn = self.conn) + hostdevs = vmhostdevs.get_list(name) + if hostdevs: + raise OperationFailed("KCHVM0040E", {'name' : name, + 'hostdevs': hostdevs}) + @staticmethod + def _getRemoteConn(remoteHost, user, passwd, transport = 'ssh'): + """open destination connect to migrate""" + + duri = 'qemu+%s://%s@%s/system' % (transport, user,remoteHost) + conn = LibvirtConnection(duri) + return conn + + def _do_migrate(self, cb, params): + """Asynchronous function which performs the migrate operation. + + Arguments: + cb -- A callback function to signal the Task's progress. + params -- A dict with the following values: + "name": Name of the virtual machine to be migrated. + "destConn": A LibvirtConnection to destination server. + "remoteHost": The migration destination server. + "secure": Migrate in P2P secure tunel mode.(optional) + """ + + name = params['name'].decode('utf-8') + destConn = params['destConn'] + remoteHost = params['remoteHost'] + secure = params['secure'] + _destConn = destConn.get() + + cb('starting a migration') + kimchi_log.debug('migrate %s start' % name) + dom = self.get_vm(name, self.conn) + flag = 0 + if secure: + flag |= (libvirt.VIR_MIGRATE_PEER2PEER | + libvirt.VIR_MIGRATE_TUNNELLED) + + state = DOM_STATE_MAP[dom.info()[0]] + if state in ['running','paused']: + flag |= libvirt.VIR_MIGRATE_LIVE + elif state == 'shutoff': + flag |= (libvirt.VIR_MIGRATE_OFFLINE| + libvirt.VIR_MIGRATE_PERSIST_DEST) + else: + kimchi_log.debug('Can not migrate vm %s when its in %s.' % (name, state)) + raise OperationFailed("KCHVM0041E", {'name': name, + 'state': state}) + try: + dom.migrate(_destConn, flag, None, None, 0) + except libvirt.libvirtError as e: + kimchi_log.error('migrate %s to %s failed' % (name, remoteHost)) + self._recover(name, destConn, cb) + self._finish(name, False, cb) + raise OperationFailed('KCHVM0042E', {'err': e.message, + 'name': name}) + self._finish(name, True, cb) + + def migrate(self, name, remoteHost, user, passwd = None, secure = False): + """Migrate a virtual machine to a remote server. + + Arguments: + name -- The name of the virtual machine to be migrated. + remoteHost -- Remote server that the VM will be migrated to. + user -- Login user on remote server,prefer root. + passwd -- The password of the user on remote server, if the local server + has ssk key exchanged, then this param can be NULL. + secure -- Migrate in P2P secure tunel mode.(optional) + + Return: + A Task running the clone operation. + """ + name = name.decode('utf-8') + remoteHost = remoteHost.decode('utf-8') + user = user.decode('utf-8') + + destConn = self._getRemoteConn(remoteHost, user, passwd) + self._preCheck(name, destConn, remoteHost) + + params = {'name': name, + 'destConn': destConn, + 'remoteHost': remoteHost, + 'secure': secure} + task_id = add_task('/vms/%s/migrate' % name, self._do_migrate, + self.objstore, params) + + return self.task.lookup(task_id) + + def _recover(self, name, destconn, cb): + """Recover from a failed migration. + + Arguments: + "cb": A callback function to signal the Task's progress. + "name": Name of the virtual machine to be migrated. + "destConn": A LibvirtConnection to destination server. + """ + try: + destvm = self.get_vm(name, destconn) + except KimchiException as e: + if type(e) == NotFoundError: + return + + cb('recover from a failed migration',False) + kimchi_log.debug('recover from a failed migrate %s' % name) + try: + destvm.destroy() + kimchi_log.error("Failed to destory migrate vm %s on \ + destination server" % name) + except libvirt.libvirtError as e: + raise OperationFailed('KCHVM0043E', {'name' : name, + 'err': e.message}) + + def _finish(self, name, success, cb): + """Clean up after a migration finished and set the task finished. + + Arguments: + "name": Name of the virtual machine to be migrated. + "success": Whether the migration successed or faild and set the tast + finish state according to it. + "cb": A callback function to signal the Task's progress. + """ + kimchi_log.debug('finished migrate %s' % name) + cb('Migrate finished', success) def poweroff(self, name): dom = self.get_vm(name, self.conn) -- 1.8.3.1

How to test: 1. ssh key exchange with remote server 2. lunch kimchid with root( if you use sudo here, will still need input passwd of remote user) 3. sudo curl -k -u user:password -H "Content-Type: application/json" -H "Accept: application/json" https://localhost:8001/vms/RHEL-7/migrate -X POST -d '{"remoteHost": "....", "user": "root", "passwd": "passw0rd", "secure": []}' -Simon 于 2014年11月18日 16:18, simonjin@linux.vnet.ibm.com 写道:
From: Simon Jin <simonjin@linux.vnet.ibm.com>
v5-v4:check whether the vm is already in migration. Signed-off-by: Simon Jin <simonjin@linux.vnet.ibm.com> --- docs/API.md | 5 ++ src/kimchi/control/vms.py | 1 + src/kimchi/i18n.py | 8 +++ src/kimchi/model/vms.py | 170 +++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 182 insertions(+), 2 deletions(-)
diff --git a/docs/API.md b/docs/API.md index 9b866f3..eb02e80 100644 --- a/docs/API.md +++ b/docs/API.md @@ -132,6 +132,11 @@ the following general conventions: It emulates the power reset button on a machine. Note that there is a risk of data loss caused by reset without the guest OS shutdown. * connect: Prepare the connection for spice or vnc +* migrate: Migrate a virtual machine to a remote server, only support live mode without block migration. + * remoteHost: IP address or hostname of the remote server. + * user: User of the remote server. + * passwd: Passwd of user on remote server. + * secure: Migrate in P2P secure tunel mode.(optional)
* clone: Create a new VM identical to this VM. The new VM's name, UUID and network MAC addresses will be generated automatically. Each existing diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index a1589ef..e6870e0 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -42,6 +42,7 @@ class VM(Resource): for ident, node in sub_nodes.items(): setattr(self, ident, node(model, self.ident)) self.start = self.generate_action_handler('start') + self.migrate = self.generate_action_handler_task('migrate', ['remoteHost', 'user', 'passwd', 'secure']) self.poweroff = self.generate_action_handler('poweroff') self.shutdown = self.generate_action_handler('shutdown') self.reset = self.generate_action_handler('reset') diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index e823f2b..ffd8e98 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -105,6 +105,14 @@ messages = { "KCHVM0033E": _("Virtual machine '%(name)s' must be stopped before cloning it."), "KCHVM0034E": _("Insufficient disk space to clone virtual machine '%(name)s'"), "KCHVM0035E": _("Unable to clone VM '%(name)s'. Details: %(err)s"), + "KCHVM0036E": _("Migrate %(name)s to localhost %(remoteHost)s is not allowed."), + "KCHVM0037E": _("vm %(name)s is already in migrating, can not perform migrate on it again."), + "KCHVM0038E": _("Can't migrate vm %(name)s on Hypervisor arch %(srcarch)s.to a different Hypervisor arch %(destarch)s."), + "KCHVM0039E": _("Can't migrate %(name)s from %(srchyp)s to a different Hypervisor type %(desthyp)s."), + "KCHVM0040E": _("Can't migrate vm %(name)s, vm has passthrough device %(hostdevs)s."), + "KCHVM0041E": _("Can not migrate vm %(name)s when its in %(state)s state."), + "KCHVM0042E": _("Failed Migrate vm %(name)s due error: %(err)s"), + "KCHVM0043E": _("Failed to destroy vm %(name)s from migration recover. Details: %(err)s"),
"KCHVMHDEV0001E": _("VM %(vmid)s does not contain directly assigned host device %(dev_name)s."), "KCHVMHDEV0002E": _("The host device %(dev_name)s is not allowed to directly assign to VM."), diff --git a/src/kimchi/model/vms.py b/src/kimchi/model/vms.py index d194049..2f934a4 100644 --- a/src/kimchi/model/vms.py +++ b/src/kimchi/model/vms.py @@ -33,9 +33,9 @@ from cherrypy.process.plugins import BackgroundTask from kimchi import model, vnc from kimchi.config import READONLY_POOL_TYPE from kimchi.exception import InvalidOperation, InvalidParameter -from kimchi.exception import NotFoundError, OperationFailed +from kimchi.exception import NotFoundError, OperationFailed, KimchiException from kimchi.model.config import CapabilitiesModel -from kimchi.model.tasks import TaskModel +from kimchi.model.tasks import TaskModel, TasksModel from kimchi.model.templates import TemplateModel from kimchi.model.utils import get_vm_name from kimchi.model.utils import get_metadata_node @@ -265,6 +265,7 @@ class VMModel(object): self.groups = import_class('kimchi.model.host.GroupsModel')(**kargs) self.vms = VMsModel(**kargs) self.task = TaskModel(**kargs) + self.tasks = TasksModel(**kargs) self.storagepool = model.storagepools.StoragePoolModel(**kargs) self.storagevolume = model.storagevolumes.StorageVolumeModel(**kargs) self.storagevolumes = model.storagevolumes.StorageVolumesModel(**kargs) @@ -790,6 +791,171 @@ class VMModel(object): except libvirt.libvirtError as e: raise OperationFailed("KCHVM0019E", {'name': name, 'err': e.get_error_message()}) + + def _preCheck(self, name, destConn, remoteHost): + """Pre check before migration""" + + kimchi_log.debug('precheck migrate %s' % name) + _destConn = destConn.get() + if remoteHost in ['localhost', '127.0.0.1']: + kimchi_log.debug('vm %s Can not migrate to localhost.' % name) + raise OperationFailed("KCHVM0036E", {'name': name, + 'remoteHost': remoteHost}) + #check if the vm already in migration + try: + alltasks = self.tasks.get_list() + for taskid in alltasks: + taskobj = self.task.lookup(taskid) + if taskobj['target_uri'] == "/vms/%s/migrate" % name \ + and taskobj['status'] == "running": + kimchi_log.debug('vm %s is already in migrating.' % name) + raise OperationFailed("KCHVM0037E", {'name': name}) + except NotFoundError as e: + kimchi_log.debug('No migrate on vm %s.' % name) + + srcarch = self.conn.get().getInfo()[0] + destarch = _destConn.getInfo()[0] + if srcarch != destarch: + kimchi_log.debug('vm %s can not migrate to different arch server.' % (name, destarch)) + raise OperationFailed("KCHVM0038E", {'name': name, + 'srcarch': srcarch, + 'destarch': destarch}) + srchyp = self.conn.get().getType() + desthyp = _destConn.getType() + if srchyp != desthyp: + kimchi_log.debug('vm %s can not migrate to a different hypervisor' % name) + raise OperationFailed("KCHVM0039E", {'name': name, + 'srchyp': srchyp, + 'desthyp':desthyp}) + + #model.host import DOM_STATE_MAP, vmhostdevs import host, + #to void loop import DOM_STATE_MAP, move import VMHostDevsModel here + from kimchi.model.vmhostdevs import VMHostDevsModel + #check if there is any passthrough devices belong to this vm + vmhostdevs = VMHostDevsModel(conn = self.conn) + hostdevs = vmhostdevs.get_list(name) + if hostdevs: + raise OperationFailed("KCHVM0040E", {'name' : name, + 'hostdevs': hostdevs}) + @staticmethod + def _getRemoteConn(remoteHost, user, passwd, transport = 'ssh'): + """open destination connect to migrate""" + + duri = 'qemu+%s://%s@%s/system' % (transport, user,remoteHost) + conn = LibvirtConnection(duri) + return conn + + def _do_migrate(self, cb, params): + """Asynchronous function which performs the migrate operation. + + Arguments: + cb -- A callback function to signal the Task's progress. + params -- A dict with the following values: + "name": Name of the virtual machine to be migrated. + "destConn": A LibvirtConnection to destination server. + "remoteHost": The migration destination server. + "secure": Migrate in P2P secure tunel mode.(optional) + """ + + name = params['name'].decode('utf-8') + destConn = params['destConn'] + remoteHost = params['remoteHost'] + secure = params['secure'] + _destConn = destConn.get() + + cb('starting a migration') + kimchi_log.debug('migrate %s start' % name) + dom = self.get_vm(name, self.conn) + flag = 0 + if secure: + flag |= (libvirt.VIR_MIGRATE_PEER2PEER | + libvirt.VIR_MIGRATE_TUNNELLED) + + state = DOM_STATE_MAP[dom.info()[0]] + if state in ['running','paused']: + flag |= libvirt.VIR_MIGRATE_LIVE + elif state == 'shutoff': + flag |= (libvirt.VIR_MIGRATE_OFFLINE| + libvirt.VIR_MIGRATE_PERSIST_DEST) + else: + kimchi_log.debug('Can not migrate vm %s when its in %s.' % (name, state)) + raise OperationFailed("KCHVM0041E", {'name': name, + 'state': state}) + try: + dom.migrate(_destConn, flag, None, None, 0) + except libvirt.libvirtError as e: + kimchi_log.error('migrate %s to %s failed' % (name, remoteHost)) + self._recover(name, destConn, cb) + self._finish(name, False, cb) + raise OperationFailed('KCHVM0042E', {'err': e.message, + 'name': name}) + self._finish(name, True, cb) + + def migrate(self, name, remoteHost, user, passwd = None, secure = False): + """Migrate a virtual machine to a remote server. + + Arguments: + name -- The name of the virtual machine to be migrated. + remoteHost -- Remote server that the VM will be migrated to. + user -- Login user on remote server,prefer root. + passwd -- The password of the user on remote server, if the local server + has ssk key exchanged, then this param can be NULL. + secure -- Migrate in P2P secure tunel mode.(optional) + + Return: + A Task running the clone operation. + """ + name = name.decode('utf-8') + remoteHost = remoteHost.decode('utf-8') + user = user.decode('utf-8') + + destConn = self._getRemoteConn(remoteHost, user, passwd) + self._preCheck(name, destConn, remoteHost) + + params = {'name': name, + 'destConn': destConn, + 'remoteHost': remoteHost, + 'secure': secure} + task_id = add_task('/vms/%s/migrate' % name, self._do_migrate, + self.objstore, params) + + return self.task.lookup(task_id) + + def _recover(self, name, destconn, cb): + """Recover from a failed migration. + + Arguments: + "cb": A callback function to signal the Task's progress. + "name": Name of the virtual machine to be migrated. + "destConn": A LibvirtConnection to destination server. + """ + try: + destvm = self.get_vm(name, destconn) + except KimchiException as e: + if type(e) == NotFoundError: + return + + cb('recover from a failed migration',False) + kimchi_log.debug('recover from a failed migrate %s' % name) + try: + destvm.destroy() + kimchi_log.error("Failed to destory migrate vm %s on \ + destination server" % name) + except libvirt.libvirtError as e: + raise OperationFailed('KCHVM0043E', {'name' : name, + 'err': e.message}) + + def _finish(self, name, success, cb): + """Clean up after a migration finished and set the task finished. + + Arguments: + "name": Name of the virtual machine to be migrated. + "success": Whether the migration successed or faild and set the tast + finish state according to it. + "cb": A callback function to signal the Task's progress. + """ + kimchi_log.debug('finished migrate %s' % name) + cb('Migrate finished', success)
def poweroff(self, name): dom = self.get_vm(name, self.conn)

On 18-11-2014 06:18, simonjin@linux.vnet.ibm.com wrote:
+ def _finish(self, name, success, cb): + """Clean up after a migration finished and set the task finished. + + Arguments: + "name": Name of the virtual machine to be migrated. + "success": Whether the migration successed or faild and set the tast + finish state according to it. + "cb": A callback function to signal the Task's progress. + """ + kimchi_log.debug('finished migrate %s' % name) + cb('Migrate finished', success)
What's the purpose of the function "_finish"? It only calls "cb('Migrate finished', success)", you can run that directly instead of running "_finish". General comments: - There are formatting issues on this patch. Please run "make check-local" to find and fix them. There are also a few trailing whitespaces which need to be removed from the code. - This patch doesn't apply to the latest commit (34ba2f1). - When I tried to run the code, I got the following error: 127.0.0.1 - - [25/Nov/2014:17:52:49] "POST /vms/landofooo/migrate HTTP/1.0" 500 929 "" "curl/7.32.0" [25/Nov/2014:17:53:06] HTTP Request Headers: AUTHORIZATION: Basic dmlhbmFjOmNoMW0xbjR6eik= Content-Length: 87 HOST: 127.0.0.1 CONNECTION: close Remote-Addr: 127.0.0.1 X-REAL-IP: 127.0.0.1 ACCEPT: application/json USER-AGENT: curl/7.32.0 X-FORWARDED-FOR: 127.0.0.1 Content-Type: application/json [25/Nov/2014:17:53:06] HTTP Traceback (most recent call last): File "/usr/lib/python2.7/site-packages/cherrypy/_cprequest.py", line 656, in respond response.body = self.handler() File "/usr/lib/python2.7/site-packages/cherrypy/lib/encoding.py", line 188, in __call__ self.body = self.oldhandler(*args, **kwargs) File "/usr/lib/python2.7/site-packages/cherrypy/_cpdispatch.py", line 34, in __call__ return self.callable(*self.args, **self.kwargs) File "/home/vianac/LTC/kimchi/src/kimchi/control/base.py", line 104, in wrapper action_result = action_fn(*model_args) File "/home/vianac/LTC/kimchi/src/kimchi/model/vms.py", line 983, in migrate destConn = self._getRemoteConn(remoteHost, user, passwd) File "/home/vianac/LTC/kimchi/src/kimchi/model/vms.py", line 916, in _getRemoteConn conn = LibvirtConnection(duri) NameError: global name 'LibvirtConnection' is not defined
participants (3)
-
Crístian Viana
-
simonjin
-
simonjin@linux.vnet.ibm.com