-
Notifications
You must be signed in to change notification settings - Fork 62
v1 instance APIs as per RFD-322 #1957
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
Changes from all commits
233dc6d
0be1778
6e3210f
a2681b9
18bc9cf
07ec450
5b2f217
8631e9f
41672aa
954ffdb
c8d191b
6708bac
26b5651
f70f84f
e6374fe
cdaa977
d0a19c6
f439f59
b4e818a
7bdfeb0
cf5f540
8c7ce21
87f4d25
393283d
2a87558
0b227b0
78962c2
ce7da01
2092012
96165f5
e616c03
3c13edc
f26c18a
12bc365
fc517af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,7 @@ use omicron_common::api::external::InstanceState; | |
| use omicron_common::api::external::InternalContext; | ||
| use omicron_common::api::external::ListResultVec; | ||
| use omicron_common::api::external::LookupResult; | ||
| use omicron_common::api::external::NameOrId; | ||
| use omicron_common::api::external::UpdateResult; | ||
| use omicron_common::api::external::Vni; | ||
| use omicron_common::api::internal::nexus; | ||
|
|
@@ -53,18 +54,84 @@ use uuid::Uuid; | |
| const MAX_KEYS_PER_INSTANCE: u32 = 8; | ||
|
|
||
| impl super::Nexus { | ||
| pub async fn instance_lookup( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the thought that we'll eventually create a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I like that idea a lot. I'll explore it a little more. There've been some iterations on this that have slowly started to feel better. It went from names, to ids, to authz objects. Maybe the lookups are the last step to really make it compositional. Thanks for the suggestion. |
||
| &self, | ||
| opctx: &OpContext, | ||
| instance_selector: params::InstanceSelector, | ||
| ) -> LookupResult<authz::Instance> { | ||
| match instance_selector { | ||
| params::InstanceSelector { instance: NameOrId::Id(id), .. } => { | ||
| // TODO: 400 if project or organization are present | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For what it's worth, I think these cases are valuable to fix early because they can help catch otherwise nasty client bugs, and also fixing them later seems likely a breaking change. |
||
| let (.., authz_instance) = | ||
| LookupPath::new(opctx, &self.db_datastore) | ||
| .instance_id(id) | ||
| .lookup_for(authz::Action::Read) | ||
| .await?; | ||
| Ok(authz_instance) | ||
| } | ||
| params::InstanceSelector { | ||
| instance: NameOrId::Name(instance_name), | ||
| project: Some(NameOrId::Id(project_id)), | ||
| .. | ||
| } => { | ||
| // TODO: 400 if organization is present | ||
| let (.., authz_instance) = | ||
| LookupPath::new(opctx, &self.db_datastore) | ||
| .project_id(project_id) | ||
| .instance_name(&Name(instance_name)) | ||
| .lookup_for(authz::Action::Read) | ||
| .await?; | ||
| Ok(authz_instance) | ||
| } | ||
| params::InstanceSelector { | ||
| instance: NameOrId::Name(instance_name), | ||
| project: Some(NameOrId::Name(project_name)), | ||
| organization: Some(NameOrId::Id(organization_id)), | ||
| } => { | ||
| let (.., authz_instance) = | ||
| LookupPath::new(opctx, &self.db_datastore) | ||
| .organization_id(organization_id) | ||
| .project_name(&Name(project_name)) | ||
| .instance_name(&Name(instance_name.clone())) | ||
| .lookup_for(authz::Action::Read) | ||
| .await?; | ||
| Ok(authz_instance) | ||
| } | ||
| params::InstanceSelector { | ||
| instance: NameOrId::Name(instance_name), | ||
| project: Some(NameOrId::Name(project_name)), | ||
| organization: Some(NameOrId::Name(organization_name)), | ||
| } => { | ||
| let (.., authz_instance) = | ||
| LookupPath::new(opctx, &self.db_datastore) | ||
| .organization_name(&Name(organization_name)) | ||
| .project_name(&Name(project_name)) | ||
| .instance_name(&Name(instance_name.clone())) | ||
| .lookup_for(authz::Action::Read) | ||
| .await?; | ||
| Ok(authz_instance) | ||
| } | ||
| // TODO: Add a better error message | ||
| _ => Err(Error::InvalidRequest { | ||
| message: " | ||
| Unable to resolve instance. Expected one of | ||
| - instance: Uuid | ||
| - instance: Name, project: Uuid | ||
| - instance: Name, project: Name, organization: Uuid | ||
| - instance: Name, project: Name, organization: Name | ||
| " | ||
| .to_string(), | ||
| }), | ||
| } | ||
| } | ||
|
|
||
| pub async fn project_create_instance( | ||
| self: &Arc<Self>, | ||
| opctx: &OpContext, | ||
| organization_name: &Name, | ||
| project_name: &Name, | ||
| authz_project: &authz::Project, | ||
| params: ¶ms::InstanceCreate, | ||
| ) -> CreateResult<db::model::Instance> { | ||
| let (.., authz_project) = LookupPath::new(opctx, &self.db_datastore) | ||
| .organization_name(organization_name) | ||
| .project_name(project_name) | ||
| .lookup_for(authz::Action::CreateChild) | ||
| .await?; | ||
| opctx.authorize(authz::Action::CreateChild, authz_project).await?; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I gather we've basically decoupled the lookup (which is now done by the caller) from the authz check. It makes sense, just a little sad because coupling those was intentional to make it harder to mess up the authz part. (And I see now why you were talking about wanting to express the authz checks that were done in the type.) Actually, I think if you have the As this is now, I think we'll also wind up doing 2x the authz checks now and we may wind up wanting to explore caching those. As I think about it, what we probably want to cache are the roles and not the result of the authz check itself. That's because when you call |
||
|
|
||
| // Validate parameters | ||
| if params.disks.len() > MAX_DISKS_PER_INSTANCE as usize { | ||
|
|
@@ -133,8 +200,6 @@ impl super::Nexus { | |
|
|
||
| let saga_params = sagas::instance_create::Params { | ||
| serialized_authn: authn::saga::Serialized::for_opctx(opctx), | ||
| organization_name: organization_name.clone().into(), | ||
| project_name: project_name.clone().into(), | ||
| project_id: authz_project.id(), | ||
| create_params: params.clone(), | ||
| }; | ||
|
|
@@ -195,15 +260,10 @@ impl super::Nexus { | |
| pub async fn project_list_instances( | ||
| &self, | ||
| opctx: &OpContext, | ||
| organization_name: &Name, | ||
| project_name: &Name, | ||
| authz_project: &authz::Project, | ||
| pagparams: &DataPageParams<'_, Name>, | ||
| ) -> ListResultVec<db::model::Instance> { | ||
| let (.., authz_project) = LookupPath::new(opctx, &self.db_datastore) | ||
| .organization_name(organization_name) | ||
| .project_name(project_name) | ||
| .lookup_for(authz::Action::ListChildren) | ||
| .await?; | ||
| opctx.authorize(authz::Action::ListChildren, authz_project).await?; | ||
| self.db_datastore | ||
| .project_list_instances(opctx, &authz_project, pagparams) | ||
| .await | ||
|
|
@@ -212,26 +272,10 @@ impl super::Nexus { | |
| pub async fn instance_fetch( | ||
| &self, | ||
| opctx: &OpContext, | ||
| organization_name: &Name, | ||
| project_name: &Name, | ||
| instance_name: &Name, | ||
| ) -> LookupResult<db::model::Instance> { | ||
| let (.., db_instance) = LookupPath::new(opctx, &self.db_datastore) | ||
| .organization_name(organization_name) | ||
| .project_name(project_name) | ||
| .instance_name(instance_name) | ||
| .fetch() | ||
| .await?; | ||
| Ok(db_instance) | ||
| } | ||
|
|
||
| pub async fn instance_fetch_by_id( | ||
| &self, | ||
| opctx: &OpContext, | ||
| instance_id: &Uuid, | ||
| authz_instance: &authz::Instance, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this pattern of function makes a lot less sense after this change. (That's not a bad thing.) We presumably just did a lookup of the Instance, dropped the database part, and now we're doing a whole other lookup just to fetch the database part again. It seems like wherever we're using |
||
| ) -> LookupResult<db::model::Instance> { | ||
| let (.., db_instance) = LookupPath::new(opctx, &self.db_datastore) | ||
| .instance_id(*instance_id) | ||
| .instance_id(authz_instance.id()) | ||
| .fetch() | ||
| .await?; | ||
| Ok(db_instance) | ||
|
|
@@ -243,20 +287,12 @@ impl super::Nexus { | |
| pub async fn project_destroy_instance( | ||
| &self, | ||
| opctx: &OpContext, | ||
| organization_name: &Name, | ||
| project_name: &Name, | ||
| instance_name: &Name, | ||
| authz_instance: &authz::Instance, | ||
| ) -> DeleteResult { | ||
| // TODO-robustness We need to figure out what to do with Destroyed | ||
| // instances? Presumably we need to clean them up at some point, but | ||
| // not right away so that callers can see that they've been destroyed. | ||
| let (.., authz_instance, _) = | ||
| LookupPath::new(opctx, &self.db_datastore) | ||
| .organization_name(organization_name) | ||
| .project_name(project_name) | ||
| .instance_name(instance_name) | ||
| .fetch() | ||
| .await?; | ||
| opctx.authorize(authz::Action::Delete, authz_instance).await?; | ||
|
|
||
| self.db_datastore | ||
| .project_delete_instance(opctx, &authz_instance) | ||
|
|
@@ -274,17 +310,10 @@ impl super::Nexus { | |
| pub async fn project_instance_migrate( | ||
| self: &Arc<Self>, | ||
| opctx: &OpContext, | ||
| organization_name: &Name, | ||
| project_name: &Name, | ||
| instance_name: &Name, | ||
| authz_instance: &authz::Instance, | ||
| params: params::InstanceMigrate, | ||
| ) -> UpdateResult<db::model::Instance> { | ||
| let (.., authz_instance) = LookupPath::new(opctx, &self.db_datastore) | ||
| .organization_name(organization_name) | ||
| .project_name(project_name) | ||
| .instance_name(instance_name) | ||
| .lookup_for(authz::Action::Modify) | ||
| .await?; | ||
| opctx.authorize(authz::Action::Modify, authz_instance).await?; | ||
|
|
||
| // Kick off the migration saga | ||
| let saga_params = sagas::instance_migrate::Params { | ||
|
|
@@ -338,9 +367,7 @@ impl super::Nexus { | |
| pub async fn instance_reboot( | ||
| &self, | ||
| opctx: &OpContext, | ||
| organization_name: &Name, | ||
| project_name: &Name, | ||
| instance_name: &Name, | ||
| authz_instance: &authz::Instance, | ||
| ) -> UpdateResult<db::model::Instance> { | ||
| // To implement reboot, we issue a call to the sled agent to set a | ||
| // runtime state of "reboot". We cannot simply stop the Instance and | ||
|
|
@@ -355,9 +382,7 @@ impl super::Nexus { | |
| // running. | ||
| let (.., authz_instance, db_instance) = | ||
| LookupPath::new(opctx, &self.db_datastore) | ||
| .organization_name(organization_name) | ||
| .project_name(project_name) | ||
| .instance_name(instance_name) | ||
| .instance_id(authz_instance.id()) | ||
| .fetch() | ||
| .await?; | ||
| let requested = InstanceRuntimeStateRequested { | ||
|
|
@@ -378,15 +403,11 @@ impl super::Nexus { | |
| pub async fn instance_start( | ||
| &self, | ||
| opctx: &OpContext, | ||
| organization_name: &Name, | ||
| project_name: &Name, | ||
| instance_name: &Name, | ||
| authz_instance: &authz::Instance, | ||
| ) -> UpdateResult<db::model::Instance> { | ||
| let (.., authz_instance, db_instance) = | ||
| LookupPath::new(opctx, &self.db_datastore) | ||
| .organization_name(organization_name) | ||
| .project_name(project_name) | ||
| .instance_name(instance_name) | ||
| .instance_id(authz_instance.id()) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here as above -- we already did a lookup in the caller. It'd be nice to just use that here instead of doing another lookup. (You could even wind up with a different result here than the caller got.) |
||
| .fetch() | ||
| .await?; | ||
| let requested = InstanceRuntimeStateRequested { | ||
|
|
@@ -407,15 +428,11 @@ impl super::Nexus { | |
| pub async fn instance_stop( | ||
| &self, | ||
| opctx: &OpContext, | ||
| organization_name: &Name, | ||
| project_name: &Name, | ||
| instance_name: &Name, | ||
| authz_instance: &authz::Instance, | ||
| ) -> UpdateResult<db::model::Instance> { | ||
| let (.., authz_instance, db_instance) = | ||
| LookupPath::new(opctx, &self.db_datastore) | ||
| .organization_name(organization_name) | ||
| .project_name(project_name) | ||
| .instance_name(instance_name) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is all a big improvement, thanks for showing what this would look like. |
||
| .instance_id(authz_instance.id()) | ||
| .fetch() | ||
| .await?; | ||
| let requested = InstanceRuntimeStateRequested { | ||
|
|
@@ -1110,19 +1127,10 @@ impl super::Nexus { | |
| pub(crate) async fn instance_serial_console_data( | ||
| &self, | ||
| opctx: &OpContext, | ||
| organization_name: &Name, | ||
| project_name: &Name, | ||
| instance_name: &Name, | ||
| authz_instance: &authz::Instance, | ||
| params: ¶ms::InstanceSerialConsoleRequest, | ||
| ) -> Result<params::InstanceSerialConsoleData, Error> { | ||
| let db_instance = self | ||
| .instance_fetch( | ||
| opctx, | ||
| organization_name, | ||
| project_name, | ||
| instance_name, | ||
| ) | ||
| .await?; | ||
| let db_instance = self.instance_fetch(opctx, authz_instance).await?; | ||
|
|
||
| let sa = self.instance_sled(&db_instance).await?; | ||
| let data = sa | ||
|
|
@@ -1146,18 +1154,9 @@ impl super::Nexus { | |
| &self, | ||
| opctx: &OpContext, | ||
| conn: dropshot::WebsocketConnection, | ||
| organization_name: &Name, | ||
| project_name: &Name, | ||
| instance_name: &Name, | ||
| authz_instance: &authz::Instance, | ||
| ) -> Result<(), Error> { | ||
| let instance = self | ||
| .instance_fetch( | ||
| opctx, | ||
| organization_name, | ||
| project_name, | ||
| instance_name, | ||
| ) | ||
| .await?; | ||
| let instance = self.instance_fetch(opctx, authz_instance).await?; | ||
| let ip_addr = instance | ||
| .runtime_state | ||
| .propolis_ip | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.