details: http://www.bx.psu.edu/hg/galaxy/rev/33c00350d29a changeset: 3054:33c00350d29a user: Enis Afgan <afgane@gmail.com> date: Wed Aug 26 12:29:54 2009 -0400 description: Added all basic functionality for instance management diffstat: lib/galaxy/model/__init__.py | 1 + lib/galaxy/model/mapping.py | 4 +- lib/galaxy/web/controllers/cloud.py | 151 +++++++++++++++++++++++++++++++------ templates/cloud/configure_cloud.mako | 114 +++++++++++++++------------ templates/cloud/view.mako | 11 ++- templates/cloud/viewInstance.mako | 104 ++++++++++++++++++++++++++ 6 files changed, 305 insertions(+), 80 deletions(-) diffs (548 lines): diff -r a47ad6a21a5d -r 33c00350d29a lib/galaxy/model/__init__.py --- a/lib/galaxy/model/__init__.py Tue Aug 25 17:42:34 2009 -0400 +++ b/lib/galaxy/model/__init__.py Wed Aug 26 12:29:54 2009 -0400 @@ -938,6 +938,7 @@ self.user = None self.name = None self.instance_id = None + self.ami = None self.state = None self.public_dns = None self.availability_zone = None diff -r a47ad6a21a5d -r 33c00350d29a lib/galaxy/model/mapping.py --- a/lib/galaxy/model/mapping.py Tue Aug 25 17:42:34 2009 -0400 +++ b/lib/galaxy/model/mapping.py Wed Aug 26 12:29:54 2009 -0400 @@ -420,6 +420,7 @@ Column( "secret_key", TEXT), Column( "defaultCred", Boolean, default=False) ) +# *************************************************************************** StoredWorkflow.table = Table( "stored_workflow", metadata, Column( "id", Integer, primary_key=True ), @@ -925,8 +926,9 @@ primaryjoin=( WorkflowStepConnection.table.c.input_step_id == WorkflowStep.table.c.id ) ), output_step=relation( WorkflowStep, backref="output_connections", cascade="all", primaryjoin=( WorkflowStepConnection.table.c.output_step_id == WorkflowStep.table.c.id ) ) ) ) - +# ************************************************************ # vvvvvvvvvvvvvvvv Start cloud table mappings vvvvvvvvvvvvvvvv +# ************************************************************ assign_mapper( context, UserInstances, UserInstances.table, properties=dict( user=relation( User ), cloud_image=relation( CloudImages ) diff -r a47ad6a21a5d -r 33c00350d29a lib/galaxy/web/controllers/cloud.py --- a/lib/galaxy/web/controllers/cloud.py Tue Aug 25 17:42:34 2009 -0400 +++ b/lib/galaxy/web/controllers/cloud.py Wed Aug 26 12:29:54 2009 -0400 @@ -41,12 +41,20 @@ .all() prevInstances = trans.sa_session.query( model.UserInstances ) \ + .filter_by( user=user, state="available" ) \ .order_by( desc( model.UserInstances.c.create_time ) ) \ .all() #TODO: diff between live and previous instances - + + liveInstances = trans.sa_session.query( model.UserInstances ) \ + .filter_by( user=user ) \ + .filter( or_(model.UserInstances.c.state=="running", model.UserInstances.c.state=="pending") ) \ + .order_by( desc( model.UserInstances.c.create_time ) ) \ + .all() + return trans.fill_template( "cloud/configure_cloud.mako", awsCredentials = awsCredentials, - prevInstances = prevInstances ) + prevInstances = prevInstances, + liveInstances = liveInstances ) @web.expose @web.require_login( "use Galaxy cloud" ) @@ -168,6 +176,45 @@ # Redirect to load galaxy frames. return trans.response.send_redirect( url_for( controller='workflow' ) ) + @web.expose + @web.require_login( "use Galaxy cloud instance" ) + def start( self, trans, id ): + instance = get_instance( trans, id ) + + + error( "Starting instance '%s' is not supported yet." % instance.name ) + + return self.list( trans ) + + @web.expose + @web.require_login( "stop Galaxy cloud instance" ) + def stop( self, trans, id ): + instance = get_instance( trans, id ) + + + error( "Stopping instance '%s' is not supported yet." % instance.name ) + + return self.list( trans ) + + @web.expose + @web.require_login( "delete Galaxy cloud instance" ) + def deleteInstance( self, trans, id ): + instance = get_instance( trans, id ) + + + error( "Deleting instance '%s' is not supported yet." % instance.name ) + + return self.list( trans ) + + @web.expose + @web.require_login( "delete Galaxy cloud instance" ) + def edit( self, trans, id ): + instance = get_instance( trans, id ) + + + error( "Editing instance '%s' is not supported yet." % instance.name ) + + return self.list( trans ) @web.expose @web.require_login( "use Galaxy cloud" ) @@ -176,35 +223,41 @@ Configure and add new cloud instance to user's instance pool """ user = trans.get_user() + # TODO: If more images are made available, must add code to choose between those ami = trans.app.model.CloudImages.filter( trans.app.model.CloudImages.table.c.id==1).first() - log.debug(ami.image_id) + inst_error = vol_error = None if instanceName: # Create new user configured instance - if len( instanceName ) > 255: - inst_error = "Instance name exceeds maximum allowable length." - elif trans.app.model.UserInstances.filter( - trans.app.model.UserInstances.table.c.name==instanceName ).first(): - inst_error = "An instance with that name already exist." - elif int( volSize ) > 1000: - vol_error = "Volume size cannot exceed 1000GB. You must specify an integer between 1 and 1000." - elif int( volSize ) < 1: - vol_error = "Volume size cannot be less than 1GB. You must specify an integer between 1 and 1000." - else: - instance = model.UserInstances() - instance.user_id = user - instance.name = instanceName - instance.ami = ami.image_id - #TODO: include storage volume size code - # Persist - session = trans.sa_session - session.save_or_update( instance ) - session.flush() - # Log and display the management page - trans.log_event( "User configured new cloud resource instance" ) - trans.set_message( "Instance '%s' configured" % credentials.name ) - return self.list( trans ) + try: + if len( instanceName ) > 255: + inst_error = "Instance name exceeds maximum allowable length." + elif trans.app.model.UserInstances.filter( + trans.app.model.UserInstances.table.c.name==instanceName ).first(): + inst_error = "An instance with that name already exist." + elif int( volSize ) > 1000: + vol_error = "Volume size cannot exceed 1000GB. You must specify an integer between 1 and 1000." + elif int( volSize ) < 1: + vol_error = "Volume size cannot be less than 1GB. You must specify an integer between 1 and 1000." + else: + instance = model.UserInstances() + instance.user = user + instance.name = instanceName + instance.ami = ami.image_id + # Valid states include: "available", "running" or "pending" + instance.state = "available" + #TODO: include storage volume size code + # Persist + session = trans.sa_session + session.save_or_update( instance ) + session.flush() + # Log and display the management page + trans.log_event( "User configured new cloud resource instance" ) + trans.set_message( "Instance '%s' configured" % instance.name ) + return self.list( trans ) + except ValueError: + vol_error = "Volume size must be specified as an integer value only, between 1 and 1000." return trans.show_form( web.FormBuilder( web.url_for(), "Configure new instance", submit_text="Add" ) @@ -257,6 +310,19 @@ .add_text( "new_name", "Credentials Name", value=stored.name ) @web.expose + @web.require_login( "use Galaxy cloud" ) + def renameInstance( self, trans, id, new_name=None ): + instance = get_instance( trans, id ) + if new_name is not None: + instance.name = new_name + trans.sa_session.flush() + trans.set_message( "Instance renamed to '%s'." % new_name ) + return self.list( trans ) + else: + return form( url_for( id=trans.security.encode_id(instance.id) ), "Rename instance", submit_text="Rename" ) \ + .add_text( "new_name", "Instance name", value=instance.name ) + + @web.expose @web.require_login( "add credentials" ) def add( self, trans, credName='', accessKey='', secretKey='', defaultCred=True ): """ @@ -304,7 +370,20 @@ stored = get_stored_credentials( trans, id ) return trans.fill_template( "cloud/view.mako", - credDetails = stored) + credDetails = stored ) + + @web.expose + @web.require_login( "view instance details" ) + def viewInstance( self, trans, id=None ): + """ + View details about running instance + """ + instance = get_instance( trans, id ) + log.debug ( instance.name ) + + return trans.fill_template( "cloud/viewInstance.mako", + liveInstance = instance ) + @web.expose @web.require_login( "delete credentials" ) @@ -797,6 +876,24 @@ return stored +def get_instance( trans, id, check_ownership=True ): + """ + Get a UserInstances from the database by id, verifying ownership. + """ + id = trans.security.decode_id( id ) + + live = trans.sa_session.query( model.UserInstances ).get( id ) + if not live: + error( "Instance not found" ) + # Verify ownership + user = trans.get_user() + if not user: + error( "Must be logged in to use the cloud." ) + if check_ownership and not( live.user == user ): + error( "Instance is not owned by current user." ) + # Looks good + return live + def attach_ordered_steps( workflow, steps ): ordered_steps = order_workflow_steps( steps ) if ordered_steps: diff -r a47ad6a21a5d -r 33c00350d29a templates/cloud/configure_cloud.mako --- a/templates/cloud/configure_cloud.mako Tue Aug 25 17:42:34 2009 -0400 +++ b/templates/cloud/configure_cloud.mako Wed Aug 26 12:29:54 2009 -0400 @@ -33,7 +33,7 @@ <table class="mange-table colored" border="0" cellspacing="0" cellpadding="0" width="100%"> <tr class="header"> - <th>Credentials Name</th> + <th>Credentials name</th> <th>Default</th> <th></th> </tr> @@ -41,7 +41,7 @@ <tr> <td> ${awsCredential.name} - <a id="wf-${i}-popup" class="popup-arrow" style="display: none;">▼</a> + <a id="cr-${i}-popup" class="popup-arrow" style="display: none;">▼</a> </td> ## Comment <td>${len(workflow.latest_workflow.steps)}</td> ##<td>${str(awsCredential.update_time)[:19]}</td> @@ -54,11 +54,11 @@ </td> <td> - <div popupmenu="wf-${i}-popup"> + <div popupmenu="cr-${i}-popup"> <a class="action-button" href="${h.url_for( action='view', id=trans.security.encode_id(awsCredential.id) )}">View</a> <a class="action-button" href="${h.url_for( action='rename', id=trans.security.encode_id(awsCredential.id) )}">Rename</a> <a class="action-button" href="${h.url_for( action='makeDefault', id=trans.security.encode_id(awsCredential.id) )}" target="_parent">Make default</a> - <a class="action-button" confirm="Are you sure you want to delete workflow '${awsCredential.name}'?" href="${h.url_for( action='delete', id=trans.security.encode_id(awsCredential.id) )}">Delete</a> + <a class="action-button" confirm="Are you sure you want to delete credentials '${awsCredential.name}'?" href="${h.url_for( action='delete', id=trans.security.encode_id(awsCredential.id) )}">Delete</a> </div> </td> </tr> @@ -81,46 +81,56 @@ <table class="mange-table colored" border="0" cellspacing="0" cellpadding="0" width="100%"> <tr class="header"> <th>Live instances</th> + <th>State</th> <th>Alive since</th> + <th>Action</th> <th></th> </tr> - %for i, awsCredential in enumerate( awsCredentials ): - <tr> - <td> - ${awsCredential.name} - <a id="wf-${i}-popup" class="popup-arrow" style="display: none;">▼</a> - </td> - <td> - ##${str(awsCredential.update_time)[:19]} - <% - from datetime import datetime - from datetime import timedelta - - # DB stores all times in GMT, so adjust for difference (4 hours) - adjustedStarttime = awsCredential.update_time - timedelta(hours=4) - - # (NOT CURRENTLY USED BLOCK OF CODE) Calculate time difference from now - delta = datetime.now() - adjustedStarttime - #context.write( str(datetime.utcnow() ) ) - #context.write( str(delta) ) - - # This is where current time and since duration is calculated - context.write( str( awsCredential.update_time ) ) - context.write( ' UTC (' ) - context.write( str(h.date.distance_of_time_in_words (awsCredential.update_time, h.date.datetime.utcnow() ) ) ) - %>) - </td> - - <td> - <div popupmenu="wf-${i}-popup"> - <a class="action-button" href="${h.url_for( action='view', id=trans.security.encode_id(awsCredential.id) )}">View</a> - <a class="action-button" href="${h.url_for( action='rename', id=trans.security.encode_id(awsCredential.id) )}">Rename</a> - <a class="action-button" href="${h.url_for( action='makeDefault', id=trans.security.encode_id(awsCredential.id) )}" target="_parent">Make default</a> - <a class="action-button" confirm="Are you sure you want to delete workflow '${awsCredential.name}'?" href="${h.url_for( action='delete', id=trans.security.encode_id(awsCredential.id) )}">Delete</a> - </div> - </td> - </tr> - %endfor + %if liveInstances: + %for i, liveInstance in enumerate( liveInstances ): + <tr> + <td> + ${liveInstance.name} + <a id="li-${i}-popup" class="popup-arrow" style="display: none;">▼</a> + </td> + <td>${str(liveInstance.state)}</td> + <td> + ${str(liveInstance.launch_time)[:16]} + <% + from datetime import datetime + from datetime import timedelta + + # DB stores all times in GMT, so adjust for difference (4 hours) + adjustedStarttime = liveInstance.launch_time - timedelta(hours=4) + + # (NOT CURRENTLY USED BLOCK OF CODE) Calculate time difference from now + delta = datetime.now() - adjustedStarttime + #context.write( str(datetime.utcnow() ) ) + #context.write( str(delta) ) + + # This is where current time and since duration is calculated + #context.write( str( liveInstance.launch_time ) ) + context.write( ' UTC (' ) + context.write( str(h.date.distance_of_time_in_words (liveInstance.launch_time, h.date.datetime.utcnow() ) ) ) + %>) + </td> + <td> + <a class="action-button" href="${h.url_for( action='stop', id=trans.security.encode_id(liveInstance.id) )}">Stop</a> + </td> + <td> + <div popupmenu="li-${i}-popup"> + <a class="action-button" href="${h.url_for( action='viewInstance', id=trans.security.encode_id(liveInstance.id) )}">View details</a> + <a class="action-button" href="${h.url_for( action='renameInstance', id=trans.security.encode_id(liveInstance.id) )}">Rename</a> + <a class="action-button" confirm="Are you sure you want to stop instance '${liveInstance.name}'?" href="${h.url_for( action='stop', id=trans.security.encode_id(liveInstance.id) )}">Stop</a> + </div> + </td> + </tr> + %endfor + %else: + <tr> + <td>Currently, you have no live instances.</td> + </tr> + %endif </table> ## ***************************************************** @@ -129,7 +139,8 @@ <table class="mange-table colored" border="0" cellspacing="0" cellpadding="0" width="100%"> <tr class="header"> <th>Previously configured instances</th> - <th>Action</th> + <th>Volume size</th> + <th>Action</th> <th></th> </tr> @@ -138,27 +149,28 @@ <tr> <td> ${prevInstance.name} - <a id="wf-${i}-popup" class="popup-arrow" style="display: none;">▼</a> + <a id="pi-${i}-popup" class="popup-arrow" style="display: none;">▼</a> </td> ## Comment <td>${len(workflow.latest_workflow.steps)}</td> - ##<td>${str(awsCredential.update_time)[:19]}</td> + ## Chnage to show vol size once available + <td>${str(prevInstance.name)}</td> <td> - <a class="action-button" href="${h.url_for( action='start', id=trans.security.encode_id(awsCredential.id) )}">Start</a> + <a class="action-button" href="${h.url_for( action='start', id=trans.security.encode_id(prevInstance.id) )}">Start</a> </td> <td> - <div popupmenu="wf-${i}-popup"> - <a class="action-button" href="${h.url_for( action='view', id=trans.security.encode_id(awsCredential.id) )}">View</a> - <a class="action-button" href="${h.url_for( action='rename', id=trans.security.encode_id(awsCredential.id) )}">Rename</a> - <a class="action-button" href="${h.url_for( action='makeDefault', id=trans.security.encode_id(awsCredential.id) )}" target="_parent">Make default</a> - <a class="action-button" confirm="Are you sure you want to delete workflow '${awsCredential.name}'?" href="${h.url_for( action='delete', id=trans.security.encode_id(awsCredential.id) )}">Delete</a> + <div popupmenu="pi-${i}-popup"> + ##<a class="action-button" href="${h.url_for( action='viewInstance', id=trans.security.encode_id(prevInstance.id) )}">View details</a> + <a class="action-button" href="${h.url_for( action='renameInstance', id=trans.security.encode_id(prevInstance.id) )}">Rename</a> + <a class="action-button" href="${h.url_for( action='edit', id=trans.security.encode_id(prevInstance.id) )}" target="_parent">Edit</a> + <a class="action-button" confirm="Are you sure you want to delete instance '${prevInstance.name}'?" href="${h.url_for( action='deleteInstance', id=trans.security.encode_id(prevInstance.id) )}">Delete</a> </div> </td> </tr> %endfor %else: <tr> - <td>You have no previously configured instances.</td> + <td>You have no previously configured instances (or they are all currently alive).</td> </tr> %endif </table> diff -r a47ad6a21a5d -r 33c00350d29a templates/cloud/view.mako --- a/templates/cloud/view.mako Tue Aug 25 17:42:34 2009 -0400 +++ b/templates/cloud/view.mako Wed Aug 26 12:29:54 2009 -0400 @@ -24,11 +24,20 @@ <td> <div popupmenu="wf-popup"> <a class="action-button" href="${h.url_for( action='rename', id=trans.security.encode_id(credDetails.id) )}">Rename</a> - <a class="action-button" confirm="Are you sure you want to delete workflow '${credDetails.name}'?" href="${h.url_for( action='delete', id=trans.security.encode_id(credDetails.id) )}">Delete</a> + <a class="action-button" confirm="Are you sure you want to delete credentials '${credDetails.name}'?" href="${h.url_for( action='delete', id=trans.security.encode_id(credDetails.id) )}">Delete</a> </div> </td> </tr> <tr> + <td> Last updated: </td> + <td> ${str(credDetails.update_time)[:16]} + <% + context.write( ' UTC (' ) + context.write( str(h.date.distance_of_time_in_words (credDetails.update_time, h.date.datetime.utcnow() ) ) ) + %> ago) + </td> + </tr> + <tr> <td> Access key: </td> <td> ${credDetails.access_key} diff -r a47ad6a21a5d -r 33c00350d29a templates/cloud/viewInstance.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/cloud/viewInstance.mako Wed Aug 26 12:29:54 2009 -0400 @@ -0,0 +1,104 @@ +<%inherit file="/base.mako"/> +<%def name="title()">Live instance details</%def> + + +<h2>Live instance details</h2> + +%if liveInstance: + <ul class="manage-table-actions"> + <li> + <a class="action-button" href="${h.url_for( action='list' )}"> + <img src="${h.url_for('/static/images/silk/resultset_previous.png')}" /> + <span>Return to cloud management console</span> + </a> + </li> + </ul> + + <table class="mange-table colored" border="0" cellspacing="0" cellpadding="0" width="100%"> + <tr> + <td> Instance name: </td> + <td> + ${liveInstance.name} + <a id="li-popup" class="popup-arrow" style="display: none;">▼</a> + </td> + <td> + <div popupmenu="li-popup"> + <a class="action-button" href="${h.url_for( action='renameInstance', id=trans.security.encode_id(liveInstance.id) )}">Rename</a> + <a class="action-button" confirm="Are you sure you want to stop instance '${liveInstance.name}'?" href="${h.url_for( action='stop', id=trans.security.encode_id(liveInstance.id) )}">Stop</a> + </div> + </td> + </tr> + <tr> + <td> Date created: </td> + <td> ${str(liveInstance.create_time)[:16]} + <% + context.write( ' UTC (' ) + context.write( str(h.date.distance_of_time_in_words (liveInstance.create_time, h.date.datetime.utcnow() ) ) ) + %> ago) + </td> + </tr> + <tr> + <td> Alive since: </td> + <td> ${str(liveInstance.launch_time)[:16]} + <% + context.write( ' UTC (' ) + context.write( str(h.date.distance_of_time_in_words (liveInstance.launch_time, h.date.datetime.utcnow() ) ) ) + %> ago) + </td> + </tr> + <tr> + <td> Instance ID: </td> + <td> ${liveInstance.instance_id} </td> + </tr> + <tr> + <td> Reservation ID: </td> + <td> ${liveInstance.reservation_id} </td> + </tr> + <tr> + <td> AMI: </td> + <td> ${liveInstance.ami} </td> + </tr> + <tr> + <td> State:</td> + <td> ${liveInstance.state} </td> + </tr> + <tr> + <td> Public DNS:</td> + <td> ${liveInstance.public_dns} </td> + </tr> + <tr> + <td> Private DNS:</td> + <td> ${liveInstance.private_dns} </td> + </tr> + <tr> + <td> Availabilty zone:</td> + <td> ${liveInstance.availability_zone} </td> + </tr> + <tr> + <td> Keypair fingerprint:</td> + <td> ${liveInstance.keypair_fingerprint} </td> + </tr> + <tr> + <td> Keypair private key:</td> + <td> + <div id="shortComment2"> + <a onclick="document.getElementById('fullComment2').style.display = 'block'; + document.getElementById('shortComment2').style.display = 'none'; return 0" + href="javascript:void(0)"> + + Show + </a> + </div> + <div id="fullComment2" style="DISPLAY: none"> + <nobr><b>${liveInstance.keypair_material}</b></nobr><br/> + <a onclick="document.getElementById('shortComment2').style.display = 'block'; + document.getElementById('fullComment2').style.display = 'none'; return 0;" + href="javascript:void(0)"> + - Hide + </a> + </div> + </td> + </tr> + </table> +%else: + There is no live instance under that name. +%endif