# -*- coding: utf-8 -*- ######################################################################### ## This is a samples controller ## - index is the default action of any application ## - user is required for authentication and authorization ## - download is for downloading files uploaded in the db (does streaming) ## - call exposes all registered services (none by default) ######################################################################### from lxml import etree from lxml import objectify import sys, os module_path = os.path.join(request.folder, 'modules') if not module_path in sys.path: sys.path.append(module_path) from storage import * from groupframe import * from groupselector import * from jsondatatable import * from autoform import * from volumecreateform import * from volumecloneform import * from poolmanagestateform import * from pooldeleteform import * from poolrenameform import * from volumedeleteform import * from poolcreateform import * import json import filter_parser as dataFilter def returnError( errorText ): onClick = 'VirtManager.Storage.menuBar.editMenu.openPanel(1);' return DIV( H3( 'Error: "%s"' % errorText ), BR(), INPUT( _type='button', _value=T('OK'), _onClick=onClick) ) def returnSuccess( statusText ): onClick = 'VirtManager.Storage.menuBar.editMenu.openPanel(1);' return DIV( H3( 'Success: "%s"' % statusText ), BR(), INPUT( _type='button', _value=T('OK'), _onClick=onClick) ) def returnLibVirtError(err): return returnError( err.get_error_message()) def returnLibVirtErrorResponse( err ): return dict( content = { 'form' : returnLibVirtError( err) }, result = { 'message' : err.get_error_message(), 'code' : err.get_error_code()} ) def returnSuccessResponse( status ): return dict( content = { 'form' : returnSuccess( status) }, result = { 'message' : status, 'code' : 200} ) def index(): """ example action using the internationalization operator T and flash rendered by views/default/index.html or views/generic.html """ poolSchema = [ {'key':'uuid', 'hidden' : True }, {'key':'name', 'label': T('Name'), 'sortable':'true' }, {'key':'state', 'label': T('State'), 'sortable':'true', 'formatter': 'VirtManager.Storage.FormatPoolState', 'width' : '20px' }, {'key':'capacity', 'label': T('Capacity'), 'sortable':'true', 'formatter': 'VirtManager.Storage.ByteFormatter', 'width' :'10px' }, {'key':'allocated', 'label': T('Allocated'), 'sortable':'true', 'formatter':'VirtManager.Storage.ByteFormatter', 'width' :'10px' }, {'key':'free', 'label': T('Free'), 'sortable':'true', 'formatter': 'VirtManager.Storage.ByteFormatter', 'width' : '10px' } ] poolDataUrl = URL(request.application, 'default', 'call/json/getStoragePoolList?') volumeSchema = [ {'key':'key', 'hidden' : True }, {'key':'name', 'label': T('Name'), 'sortable':'true' }, {'key':'type', 'label': T('Type'), 'sortable':'true', 'formatter':'VirtManager.Storage.FormatVolumeType', 'width' : '10px' }, {'key':'size', 'label': T('Size'), 'sortable':'true', 'formatter':'VirtManager.Storage.ByteFormatter', 'width' : '10px' } ] volumeDataUrl = URL(request.application, 'default', 'call/json/getVolumeListForPool?') poolInventory = JSONDATATABLE( poolDataUrl, 'VirtManager.Storage.PoolEditOpperation', 'VirtManager.Storage.PoolMenuOpperation', poolSchema, _id = 'pool_table' ) volumeInventory = JSONDATATABLE( volumeDataUrl, 'VirtManager.Storage.VolumeEditOpperation', 'VirtManager.Storage.VolumeMenuOpperation', volumeSchema, _id= 'volume_table' ) return dict( poolInventory=poolInventory, volumeInventory=volumeInventory ) @service.xml def poolDetails(): pool = None message = 'ok' try: pool = connection.storagePoolLookupByUUIDString(request.vars['pool_uuid']) except libvirtError, err: return returnLibVirtErrorResponse( err ) poolInfo = PoolFactory().getPool( pool.XMLDesc(0) ) details = DIV( LABEL(T('Name:')), SPAN( poolInfo.name ), BR(), HR(), LABEL(T('UUID:')), SPAN( poolInfo.uuid ), BR(), LABEL(T('Type:')), SPAN( poolInfo.pool_type ), BR(), LABEL(T('Allocation:')), SPAN( StorageFormatter().formatBytes(poolInfo.allocation) ), BR(), LABEL(T('Capacity:')), SPAN( StorageFormatter().formatBytes(poolInfo.capacity) ), BR(), LABEL(T('Available:')), SPAN( StorageFormatter().formatBytes(poolInfo.available) ), BR(), HR() ) if hasattr( poolInfo, 'source_host' ): details.append( DIV( LABEL(T('Source host:')), SPAN( poolInfo.source_host ), BR() ) ) if hasattr( poolInfo, 'source_port' ): details.append( DIV( LABEL(T('Source port:')), SPAN( poolInfo.source_port ), BR() ) ) if hasattr( poolInfo, 'source_path' ): details.append( DIV( LABEL(T('Source path:')), SPAN( poolInfo.source_path ), BR() ) ) if hasattr( poolInfo, 'source_format' ): details.append( DIV( LABEL(T('Source filesystem:')), SPAN( poolInfo.source_format ), BR() ) ) if hasattr( poolInfo, 'target_path' ): details.append( DIV( LABEL(T('Target path:')), SPAN( poolInfo.target_path ), BR() ) ) if hasattr( poolInfo, 'target_format' ): details.append( DIV( LABEL(T('Target filesystem:')), SPAN( poolInfo.target_format ), BR() ) ) return dict( content = { 'form' : details}, result = { 'message' : message, 'code' : 200 } ) @service.xml def volumeDetails(): volume = None pool = None message = 'ok' try: volume = connection.storageVolLookupByKey(request.vars['volume_key']) pool = volume.storagePoolLookupByVolume() except libvirtError, err: return returnLibVirtErrorResponse( err ) volumeInfo = Volume( volume.XMLDesc(0) ) details = DIV( LABEL(T('Name:')), SPAN( volumeInfo.name ), BR(), HR(), LABEL(T('Pool:')), SPAN( pool.name()), BR(), LABEL(T('Format:')), SPAN( volumeInfo.format ), BR(), LABEL(T('Allocation:')), SPAN( StorageFormatter().formatBytes(volumeInfo.allocation) ), BR(), LABEL(T('Capacity:')), SPAN( StorageFormatter().formatBytes(volumeInfo.capacity) ), BR(), ) if volumeInfo.backing_store_path: details.append( DIV( LABEL(T('Backing store source:')), SPAN( volumeInfo.backing_store_path ), BR() ) ) if volumeInfo.backing_store_format: details.append( DIV( LABEL(T('Backing store format:')), SPAN( volumeInfo.backing_store_format ), BR() ) ) details.append( DIV( LABEL(T('System path:')), SPAN( volumeInfo.path ), BR() ) ) return dict( content = { 'form' : details}, result = { 'message' : message, 'code' : 200 } ) @service.xml def volumeDeleteForm(): volume = None message = '' try: volume = connection.storageVolLookupByKey(request.vars['volume_key']) except libvirtError, err: return returnLibVirtErrorResponse( err ) form = VolumeDeleteForm( request.vars['volume_key'], T, _id='delete_volume_form' ) result = form.execute( request.vars ) if result == form.VALID: message = T('Volume successfully deleted.') try: volume.delete(0) except libvirtError, err: return returnLibVirtErrorResponse( err ) return returnSuccessResponse( message ) elif form.ERRORS: message = T('Input errors on form, please correct.') else: message = T('Please fill the form') return dict( content = form.content(), result = { 'message' : message, 'code' : 200 } ) @service.xml def volumeCloneForm(): volume = None pool = None message = 'ok' copyonwrite = False try: volume = connection.storageVolLookupByKey(request.vars['volume_key']) pool = volume.storagePoolLookupByVolume() except libvirtError, err: return returnLibVirtErrorResponse( err ) volumeInfo = Volume( volume.XMLDesc(0) ) form = VolumeCloneForm( request, pool.name(), volumeInfo.format , T, _id='clone_volume_form' ) result = form.execute( request.vars ) if result == form.VALID: message = T('Successfully created new volume') volumeInfo.name = request.vars['name'] volumeInfo.format = request.vars['format'] if 'cow' in request.vars: copyonwrite = True volumeInfo.backing_store_path = volumeInfo.path volumeInfo.backing_store_format = volumeInfo.format volumeInfo.format = 'qcow2' else: volumeInfo.backing_store_path = None volumeInfo.backing_store_format = None volumeInfo.path = None try: targetPool = connection.storagePoolLookupByName( request.vars['target_pool']) if copyonwrite: targetPool.createXML( volumeInfo.xml(), 0 ) else: targetPool.createXMLFrom( volumeInfo.xml(), volume, 0 ) except libvirtError, err: return returnLibVirtErrorResponse( err ) except libvirtError, err: return returnLibVirtErrorResponse( err ) return returnSuccessResponse( message ) elif result == form.ERRORS: message = T('Input errors on form, please correct.') else: message = T('Please fill the form') return dict( content = form.content(), result = { 'message' : message, 'code' : result } ) @service.xml def volumeCreateForm(): pool = None message = '' try: pool = connection.storagePoolLookupByUUIDString(request.vars['pool_uuid']) except libvirtError, err: return returnLibVirtErrorResponse( err ) form = VolumeCreateForm( request, T, _id='create_volume_form' ) result = form.execute( request.vars ) if result == form.VALID: message = T('Successfully created new volume') volumeInfo = Volume() volumeInfo.name = request.vars.get('name', None) volumeInfo.format = request.vars.get('format', None) volumeInfo.capacity = request.vars.get('capacity', None) volumeInfo.allocation = request.vars.get('allocation', None) if volumeInfo.format == 'auto': volumeInfo.format = None if request.vars.get('backing_store_enable', 'off') == 'on': volumeInfo.backing_store_path = request.vars.get('backing_store_path', None) volumeInfo.backing_store_format = request.vars.get('backing_store_format', None) if volumeInfo.backing_store_format == 'auto': volumeInfo.backing_store_format = None try: newVol = pool.createXML( volumeInfo.xml(), 0 ) except libvirtError, err: return returnLibVirtErrorResponse( err ) return returnSuccessResponse( message ) elif result == form.ERRORS: message = T('Input errors on form, please correct.') else: message = T('Please fill the form') return dict( content = form.content(), result = { 'message' : message, 'code' : result } ) @service.xml def poolDeleteForm(): pool = None message = '' try: pool = connection.storagePoolLookupByUUIDString(request.vars['pool_uuid']) except libvirtError, err: return returnLibVirtErrorResponse( err ) form = PoolDeleteForm( pool, T, _id='delete_pool_form' ) result = form.execute( request.vars ) if result == form.VALID: message = T('Pool successfully deleted.') try: pool.destroy() if request.vars.get('pool_delete', 'off') == 'on': pool.delete(0) if request.vars.get('pool_undefine', 'off' ) == 'on': pool.undefine() except libvirtError, err: return returnLibVirtErrorResponse( err ) return returnSuccessResponse( message ) elif form.ERRORS: message = T('Input errors on form, please correct.') else: message = T('Please fill the form') return dict( content = form.content(), result = { 'message' : message, 'code' : 200 } ) @service.xml def poolRenameForm(): pool = None message = '' try: pool = connection.storagePoolLookupByUUIDString(request.vars['pool_uuid']) except libvirtError, err: return returnLibVirtErrorResponse( err ) poolInfo = objectify.fromstring( pool.XMLDesc(0) ) autostart = False active = False if pool.autostart(): autostart = True if pool.info()[0]: active = True form = PoolRenameForm( pool, T, _id='rename_pool_form' ) result = form.execute( request.vars ) if result == form.VALID: message = T('Pool successfully renamed') poolInfo.name = request.vars['pool_name'] try: pool.destroy() pool.undefine() result = connection.storagePoolDefineXML(etree.tostring(poolInfo), 0 ) result.setAutostart( autostart ) if active: result.create(0) except libvirtError, err: return returnLibVirtErrorResponse( err ) return returnSuccessResponse( message ) elif form.ERRORS: message = T('Input errors on form, please correct.') else: message = T('Please fill the form') return dict( content = form.content(), result = { 'message' : message, 'code' : 200 } ) @service.xml def poolCreateForm(): message = '' form = PoolCreateForm(request.vars.get('pool_type', None), T, _id="create_pool_form" ) result = form.execute( request.vars ) if result == form.VALID: pool = None message = T('Pool successfully created.') if request.vars.get('pool_type', None) == 'dir': pool = DirectoryPool() pool.target_path = request.vars.get('dir_target_path', None) elif request.vars.get('pool_type', None) == 'fs': pool = FsPool() pool.target_path = request.vars.get('fs_target_path', None) pool.target_format = request.vars.get('fs_target_format', 'auto') pool.source_path = request.vars.get('fs_source_path', None) elif request.vars.get('pool_type', None) == 'netfs': pool = NetFsPool() pool.target_path = request.vars.get('netfs_target_path', None) pool.source_format = request.vars.get('netfs_source_format', 'auto') pool.source_path = request.vars.get('netfs_source_path', None) pool.source_host = request.vars.get('netfs_source_host', None) elif request.vars.get('pool_type', None) == 'iscsi': pool = IscsiPool() pool.target_path = request.vars.get('iscsi_target_path', None) pool.source_host = request.vars.get('iscsi_source_host', None) pool.source_port = request.vars.get('iscsi_source_port', None) pool.source_path = request.vars.get('iscsi_source_path', None) pool.name = request.vars.get('pool_name', None) pool.uuid = request.vars.get('pool_uuid', None) try: newPool = connection.storagePoolDefineXML( pool.createXml(), 0 ) if request.vars.get('pool_activate', None ) == 'on': newPool.create(0) else: newPool.destroy() newPool.setAutostart( request.vars.get('pool_autostart', None ) == 'on' ) except libvirtError, err: return returnLibVirtErrorResponse( err ) return returnSuccessResponse( message ) elif result == form.ERRORS: message = T('Input errors on form, please correct.') else: message = T('Please fill the form') return dict( content = form.content(), result = { 'message' : message, 'code' : 200 } ) @service.xml def poolManageStateForm(): pool = None message = '' try: pool = connection.storagePoolLookupByUUIDString(request.vars['pool_uuid']) except libvirtError, err: return returnLibVirtErrorResponse( err ) form = PoolManageStateForm( pool, T, _id='manage_pool_form' ) result = form.execute( request.vars ) if result == form.VALID: message = T('Updated pool status.') try: if request.vars['pool_state'] == 'active': pool.create(0) else: pool.destroy() pool.setAutostart( request.vars['pool_autostart'] == 'on' ) except libvirtError, err: return returnLibVirtErrorResponse( err ) return returnSuccessResponse( message ) elif form.ERRORS: message = T('Input errors on form, please correct.') else: message = T('Please fill the form') return dict( content = form.content(), result = { 'message' : message, 'code' : 200 } ) @service.xml def getForm(): form = FORM( FIELDGROUP( "This is a test FOO", LABEL('Text1'), INPUT(_type='text', _name='text1'), BR(), LABEL('Test2'), INPUT(_type='text', _name='test2') ), INPUT( _value='Test', _type='button', _id='button', _class='action_button' ) ) if form.accepts(request.vars): form = BEAUTIFY(request.vars) elif form.errors: message = T('Input errors on form, please correct.') else: message = T('Please fill the form') return dict( content = form, result = { 'message' : message, 'code' : 200 } ) def user(): """ exposes: http://..../[app]/default/user/login http://..../[app]/default/user/logout http://..../[app]/default/user/register http://..../[app]/default/user/profile http://..../[app]/default/user/retrieve_password http://..../[app]/default/user/change_password use @auth.requires_login() @auth.requires_membership('group name') @auth.requires_permission('read','table name',record_id) to decorate functions that need access control """ return dict(form=auth()) def download(): """ allows downloading of uploaded files http://..../[app]/default/download/[filename] """ return response.download(request,db) def call(): """ exposes services. for example: http://..../[app]/default/call/jsonrpc decorate with @services.jsonrpc the functions to expose supports xml, json, xmlrpc, jsonrpc, amfrpc, rss, csv """ session.forget() return service() @service.json def addStoragePool( name, type, props): """ Add a pool to the inventory: >>> addStoragePool( 'test', 'dir', '{"target_path" : "/mnt/store/foop"}' ) {'result': ...} """ properties = json.loads(props) newPool = DirectoryPool() newPool.name = name newPool.pool_type = type if newPool.pool_type is 'dir': newPool.target_path = properties['target_path'] else: return { 'result' : 0 } pool = connection.storagePoolDefineXML( newPool.createXml(), 0 ) if pool is None: return { 'result' : 0 } else: return { 'result' : pool.UUIDString() } @service.json def storagePoolInformation(uuid): """ Remove a pool from the inventory: >>> dummy = addStoragePool( 'test', 'dir', '{"target_path" : "/mnt/store/foop"}' ) >>> pool = connection.storagePoolLookupByName('test') >>> storagePoolInformation( pool.UUIDString() ) {'available': 0, 'allocation': 0, 'capacity': 0, 'name': 'test', 'pool_type': 'dir', 'target_path': '/mnt/store/foop', 'uuid': ...} """ pool = connection.storagePoolLookupByUUIDString(uuid) poolFactory = PoolFactory() properties = poolFactory.getPool(pool.XMLDesc(0)).__dict__ properties['state'] = connection.storagePoolLookupByName(pool).info()[0] return properties @service.json def removeStoragePool( uuid ): """ Remove a pool from the inventory: >>> pool = connection.storagePoolLookupByName('test') >>> removeStoragePool( pool.UUIDString() ) {'result': True} """ pool = connection.storagePoolLookupByUUIDString( uuid ) if pool is None: return {'result' : False } if pool.undefine() is 0: return { 'result' : True } else: return { 'result' : False } @service.json def getShortStoragePoolList(currentValue): fullPoolList = connection.listDefinedStoragePools() + connection.listStoragePools() return { 'results' : [ { 'text' : pool, 'value' : connection.storagePoolLookupByName(pool).UUIDString(), } for pool in fullPoolList ] , 'current' : currentValue or '' } @service.json def getStoragePoolList( sort, dir, startIndex, results ): desc = False if dir == 'desc': desc = True filterString = "" fullPoolList = connection.listDefinedStoragePools() + connection.listStoragePools() pools = dataFilter.filterData ( [ { 'name' : pool, 'uuid' : connection.storagePoolLookupByName(pool).UUIDString(), 'state': connection.storagePoolLookupByName(pool).info()[0], 'capacity': connection.storagePoolLookupByName(pool).info()[1], 'allocated': connection.storagePoolLookupByName(pool).info()[2], 'free' : connection.storagePoolLookupByName(pool).info()[3] } for pool in fullPoolList ], filterString ) sortedPools = sorted(pools, key=lambda k: k[sort], reverse=desc)[ int(startIndex): int(results) + int(startIndex) ] return { 'result' : sortedPools, 'totalRecords' : len( pools ), 'recordsReturned' : min( len(sortedPools), int(results)), 'startIndex' : int(startIndex), 'sort' : sort, 'dir' : dir, 'pageSize' : results } @service.json def getImageFormats( formats , currentValue): results = { 'results' : [ { 'value' : 'raw', 'text' : str( T('Raw format') )}, { 'value' : 'qcow2', 'text' : str( T('QEMU format'))}, { 'value' : 'qcow', 'text' : str( T('Old QEMU format'))}, { 'value' : 'cow', 'text' : str( T('UML copy on write format'))}, { 'value' : 'vmdk', 'text' : str( T('VMware 3 and 4 compatible format'))}, { 'value' : 'cloop', 'text' : str( T('Linux compressed loop format'))}, { 'value' : 'dmg', 'text' : str( T('Apple DMG format'))}, { 'value' : 'bochs', 'text' : str( T('BOCHS format'))}, { 'value' : 'vpc', 'text' : str( T('Microsoft VPC format'))}, { 'value' : 'parallels', 'text' : str( T('Parallels format'))} ], 'current' : currentValue } if formats == 'readonly': results['results'].insert(0, { 'value' : 'auto', 'text' : str(T('Autodetect format')) }) return results @service.json def getShortVolumeListForPool( uuid, currentValue ): pool = connection.storagePoolLookupByUUIDString( uuid ) return { 'results' : [ { 'text' : vol, 'value' : pool.storageVolLookupByName(vol).key() } for vol in pool.listVolumes() ], 'current' : currentValue or '' } @service.json def getVolumeListForPool( uuid, sort, dir, startIndex, results ): desc = False if dir == 'desc': desc = True #filterString = "((type = 0 ) and (name : '.iso') ) or size > 32000000000" filterString = "" volumes = [] pool = connection.storagePoolLookupByUUIDString( uuid ) if pool.info()[0]: volumes = dataFilter.filterData ( [ { 'name' : vol, 'key' : pool.storageVolLookupByName(vol).key(), 'type' : pool.storageVolLookupByName(vol).info()[0], 'size' : pool.storageVolLookupByName(vol).info()[1] } for vol in pool.listVolumes() ], filterString ) sortedVolumes= sorted(volumes, key=lambda k: k[sort], reverse=desc)[ int(startIndex): int(results) + int(startIndex) ] return { 'result' : sortedVolumes, 'totalRecords' : len( volumes ), 'recordsReturned' : min( len(sortedVolumes), int(results)), 'startIndex' : int(startIndex), 'sort' : sort, 'dir' : dir, 'pageSize' : results } @service.json def getFullDomainList(): states = [ T("No State"), T("Running"), T("Blocked"), T("Paused"), T("Shutting down"), T("Stopped"), T("Crashed") ] result = [ { 'uuid' : connection.lookupByID( domid ).UUIDString(), 'name' : connection.lookupByID( domid ).name(), 'state' : states[connection.lookupByID( domid ).info()[0]], 'maxMemory' : connection.lookupByID( domid ).info()[1], 'memory' : connection.lookupByID( domid ).info()[2], 'cputime' : connection.lookupByID( domid ).info()[4] / 6000000000 } for domid in connection.listDomainsID() ] for domname in connection.listDefinedDomains(): domain = connection.lookupByName( domname ) info = domain.info( ) uuid = domain.UUIDString() result.append( { 'uuid' : uuid, 'name' : domname, 'state' : states[info[0]], 'maxMemory' : info[1] } ) return result @service.json def getDomainInformation( domainUUID ): domain = connection.lookupByUUIDString( domainUUID ) domainInfo = objectify.fromstring(domain.XMLDesc(3)) result = { 'type' : domainInfo.get('type'), 'uuid' : domainInfo.uuid, 'memory' : domainInfo.memory, 'os' : { 'arch' : domainInfo.os.get('arch'), 'machine' : domainInfo.os.get('machine'), 'type' : domainInfo.os.type } } return result