-
Notifications
You must be signed in to change notification settings - Fork 1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Empower Users to Select Storage Destination #14073
Conversation
71ed0e0
to
69507d0
Compare
6aa086f
to
6a6eb8c
Compare
5aafbd1
to
6720d32
Compare
6720d32
to
e399a43
Compare
e399a43
to
c10e0ab
Compare
b6a117b
to
2cdcf15
Compare
Maybe it was porting the changes from sql-migrate to alembic but I was able to recreate the migration issue and I believe it should be fixed with d1a2eab. |
object_store: BaseObjectStore = depends(BaseObjectStore) | ||
|
||
@router.get( | ||
"/api/object_store", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"/api/object_store", | |
"/api/object_stores", |
it seems like most (every?) collection in our API is in plural. I think that's a good convention. Is there a reason you're deviating from this ?
) -> List[Dict[str, Any]]: | ||
if not selectable: | ||
raise RequestParameterInvalidException( | ||
"The object store index query currently needs to be called with selectable=true" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for that ? Seems a little odd. If there's a good reason SelectableQueryParam
should be marked as mandatory and be the only valid value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a narrow view of the object stores and just returns like concrete object stores for selection here and what is needed for selection (pydantic models implemented in the follow up already but namely description, badges, name, etc...). I don't want to think through what a nested store should return - we don't have an application requiring that currently. By having this parameter and making it clear we expect returns to be for selection - we are keeping this open for other applications that might require a more expansive view of the object stores.
|
||
@router.get( | ||
"/api/object_store/{object_store_id}", | ||
summary="Return boolean to indicate if Galaxy's default object store allows selection.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems like this should work for all object stores, and it also doesn't return a boolean.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in the follow PR already I think.
concrete_object_store = self.object_store.get_concrete_store_by_object_store_id(object_store_id) | ||
if concrete_object_store is None: | ||
raise ObjectNotFound() | ||
as_dict = concrete_object_store.to_dict() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably quite important to have a pydantic model for this, so we're not accidentally leaking sensitive data from an object store. Also should this be filtered down by the user, so users can't list object stores they have no business knowing about ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes - absolutely and this is already done in the follow up PR.
source: Optional[str] = Field( | ||
description="The quota source label corresponding to the object store the dataset is stored in (or would be stored in)" | ||
) | ||
enabled: bool = Field( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the description match the enabled
variable ? Given the description should this maybe be tracks_quota
?
subworkflow_output = subworkflow.workflow_output_for(step_output.output_name) | ||
if subworkflow_output is not None: | ||
output_dict = EffectiveOutput( | ||
output_name=subworkflow_output.output_name, step_id=subworkflow_output.workflow_step_id |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the output_name and step_id don't necessarily make this unique if the same subworkflow is embedded more than once, is that going to be a problem ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll create an issue to write a test - I'm not sure off the top of my head.
return self._call_method("_get_concrete_store_badges", obj, [], False) | ||
|
||
def _is_private(self, obj): | ||
return self._call_method("_is_private", obj, ObjectNotFound, True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apart from these issues it's working super well, I set up a scratch backend in the distributed object store and it's all working as it should. The popover stuff is a little annoying, but that's minor UX stuff, and i think you're addressing a bunch of things from my review in #15654 already. |
A long time goal for hosted Galaxy options is bring you own storage. In my opinion, this paradigm doesn’t work well without abstractions at every level of Galaxy from the backend configs, database, object store, the quota agent, API, and front-end for displaying information about where data has been stored where and selecting where future new data will be stored. This pull request attempts to put these abstractions in place for future user-defined object stores in such a way that we gracefully enable a large number important new features such as scratch histories in this pull request on the path forward.
The User Story
Once an admin has declared a nested object store to have selectable components (all backward compatible and described in detail below). The user will be will have new options available in the GUI. The options include selecting a default object store for the user account and selecting one at the history level. These options will not be displayed if the admin has not declared any nested object stores as “selectable”.
GUI
The user will be allowed to select a default preferred object store in the user preferences (User -> User Preferences -> Preferred Object Store).
When this option is selected, a modal will be displayed for that option that displays the available object store options selections. A visual language has been crafted to quickly and consistently summarize structured information annotated by the admin about the target object store using font awesome icon layers and Bootstrap Vue colors.
On mouseover, this selection displays a lot more information. Including the full markdown description of the object store (added in #10233). This pull request overhauls that metadata to include information about the target quota (using variants of UI components integrated in #13113) as well as integrating the ideas and implementation of per-objectstore quotas (from #14047 / #10221 / #10977 ).
The icons and quota allow users to quickly visually summarize the differences between the object stores. In addition, much more information can be attached by Galaxy administrators via Markdown in Galaxy’ s object store (XML or YAML) that will be displayed on mouseover. The icon might show this storage is faster or backed up and this other storage is not, but the mouseover allows admins to link out to institutional information, summarize hardware or policies, etc…
(More information about the icons - where the information is coming from, what the icons mean, what the colors mean, and why… is provided in the next section of this PR.)
A preferred object store ID can be set at the user level, but it can also be set for individual histories and on a per tool execution basis (no UI currently for this later option).
When selectable object stores are configured, on mouseover of the data storage icon in the history shows the object store selection that will be used for a history is displayed - as well if it is being set at the user or history level. Clicking the button will allow the user to select a different target for that history.
In addition to the setting this at the history level - it can be set at the tool level or at the workflow level. At the workflow level - different object stores can be set for tool outputs and intermediate datasets.
Here is the tool interface:
The workflow interface is a bit rough but looks something like this:
Finally, overhauling the metadata and display of information about concrete object stores available for selection means much more information can also be displayed in the “Dataset Storage” section of the “Dataset Details” page of existing datasets - since these are using similar APIs and the same GUI components.
A Visual Language for Object Store Selection.
My thought processes and the current icons as well as documentation for how to set them can be found in this JS Fiddle.
https://jsfiddle.net/uw8jz2ry/5/
Hopefully the JS Fiddle empowers reviewers to fork and provide specific feedback on new badges, alterations to style, etc..
Creating a visual language around nested object store selections will help semi-technical users make decisions without getting into the nitty gritty. Displaying additional information on mouseover will allow power users to dig into details and will allow institutional admins to link out to relevant hardware pages, SLAs, policy documents, etc..
The core of the visual language is a set of badges (i.e. icons) with specific meaning. These icons can be broken into two categories - ones determined by Galaxy from existing functional configuration options and ones specified by the Galaxy Administrator. All administrator specified badge options may include a markdown message that is displayed when the user digs into the relevant badge.
Rather than giving admin's the ability to just define arbitrary tags and icons and meaning, keeping things structured means we can provide higher quality help and that in the future Galaxy can provide potentially even higher level selection options, wizard dialogs, or methods of dynamically determining which object store use. For instance - user's may be able to select "it is important that this analysis runs as fast as possible" vs "it is important that I can share and published this analysis" and let Galaxy determine the appropriate object store in the future. Currently though, a specific selection is still required.
Another aspect of the visual language is colors. Three different colors underpin the relevant badges. These colors correspond to user "Advantages" of the storage, user "Disadvantages" of the storage, and "Neutral" aspects of the storage. These are defined relative to the perspective of a researcher desiring resources on a shared infrastructure. Something like "quota enforced" is an advantage to the administrators or maintainers of the infrastructure but likely not perceived as an advantage to the researcher - so it is colored as a "disadvantage".
The Icons (JS Fiddle Screenshots)
The API Story
The selectable object stores are available now via
The information on a specific object store ID is available at
The new APIs from per-objectstore quotas are included in this pull request:
and
The Admin Story
Per Object Store Quotas
(From #14047)
This pull request allows different object stores or different groups of object stores to have different quotas or no quota at all. This enables uses cases such as sending job to cheaper data when a user's quota is getting near full or allowing admin to setup tool and/of workflow parameters to send job outputs higher quality, more redundant storage based on user selected options or user preferences.
This adds the quota tag to XML/YAML object store declarations - that allow specifying a "quota source label" for each objectstore in a nested objectstore or disabling quota all together on objectstores.
The following quota block would assign all this storage to a quota source labelled with s3.
Whereas this would disable quota usage for this object store altogether.
In order to implement this a new table/model has been added to track a user's usage per quota source label - namely UserQuotaSourceUsage. Object stores that did not have a source label are still tracked using the User model's disk_usage attribute. I've updated all the scripts that recalculate user usage.
Private Object Stores
(From #14044)
private
- indicating datasets stored in them should not be shared.sharable
property tomodel.Dataset
that checks itsobject_store_id
against the configured object store to determine if it is not stored in aprivate
objectstore.security_agent
to check if a dataset is restricted to a single user and augmentgalaxy.jobs.JobWrapper._set_object_store_ids
andObjectStorePopulator
to prevent jobs that might create non-private datasets in private objectstores.Badges and Pulling it All Together
Quota and private object store information is exposed as badges.
The following example is used for the screenshots above and will likely power future end-to-end testing. It demonstrates badge usage and how to attached Markdown description to specific badges.
In Comparison to Other Approaches
My concern about most alternative approaches that have been proposed or attempted is that they over-fit Galaxy to use cases that too specific - and make more general approaches as outlined above more difficult.
For instance, Vahid’s user based object store #4840 approach worked only with the global quota in a very specific way and would have prevented user-based quota decisions, multiple quotas on different existing object stores, etc…
Nate has proposed simply adding a field to Dataset that dispatches between a tracked quota and untracked quota that gets deleted routinely. A UI for this could be rapidly prototyped and it would implement scratch histories very quickly for main but it makes very rich solutions such as this more difficult - where as this solution can readily be adapted with two lines of object store change to implement 85% of that functionality. Clearly, in the use case described above - multiple storage sources might be thought of as “temporary” or “scratch”. I invented certain aspects of the use case but I think the illustrates the issues with simple binaries when it comes to storage and Galaxy.
Thinking about how much a solution is “over fitted” to this use case is a criteria we should apply when evaluating this approach as well. I think making user’s pick specific object stores requires perhaps too much work in complex scenarios but I think I’ve argued how the structured information about badges above could readily enable higher level decisions - either on the front end or on the backend.
We may want to restrict certain options to certain groups of people - this feels like it would be easy to integrate with some added markup for groups, etc… on the object store selection or even more so by allowing dynamic selection ID criteria to be enabled.
I also, designed this with an eye toward per-user object stores and I think it is a good set of abstractions for extended in that direction but I will need to work through an implementation in order to prove it I think.
On the other end of the spectrum, essentially all of this is possible functionally with the inclusion of (#6552) years ago. A dynamic job destination can read user preferences, job resource parameters, etc... and pick an object store based on that. Additionally, the job destination can read tags perhaps on a history and dispatch based on that (as we have done for training days). If anything in this pull request is… an approach on top of that that makes specific decisions that may be over fitted in some complex situations. Galaxy admins can fallback to this older mechanism in those cases but I hope I’ve articulated why this mechanism works for a wide variety of existing use cases we wish to target.
TODO:
I'd really like to push all of this to a subsequent PR - it is fiddly stuff compared to the huge shifts in abstractions and such.
How to test the changes?
(Select all options that apply)
License