@@ -19,7 +19,6 @@ use omicron_common::api::external;
1919use omicron_common:: api:: external:: CreateResult ;
2020use omicron_common:: api:: external:: DataPageParams ;
2121use omicron_common:: api:: external:: DeleteResult ;
22- use omicron_common:: api:: external:: DiskState ;
2322use omicron_common:: api:: external:: Error ;
2423use omicron_common:: api:: external:: InstanceState ;
2524use omicron_common:: api:: external:: ListResultVec ;
@@ -152,17 +151,9 @@ impl super::Nexus {
152151 Ok ( db_instance)
153152 }
154153
155- // TODO-correctness It's not totally clear what the semantics and behavior
156- // should be here. It might be nice to say that you can only do this
157- // operation if the Instance is already stopped, in which case we can
158- // execute this immediately by just removing it from the database, with the
159- // same race we have with disk delete (i.e., if someone else is requesting
160- // an instance boot, we may wind up in an inconsistent state). On the other
161- // hand, we could always allow this operation, issue the request to the SA
162- // to destroy the instance (not just stop it), and proceed with deletion
163- // when that finishes. But in that case, although the HTTP DELETE request
164- // completed, the object will still appear for a little while, which kind of
165- // sucks.
154+ // This operation may only occur on stopped instances, which implies that
155+ // the attached disks do not have any running "upstairs" process running
156+ // within the sled.
166157 pub async fn project_destroy_instance (
167158 & self ,
168159 opctx : & OpContext ,
@@ -173,58 +164,14 @@ impl super::Nexus {
173164 // TODO-robustness We need to figure out what to do with Destroyed
174165 // instances? Presumably we need to clean them up at some point, but
175166 // not right away so that callers can see that they've been destroyed.
176- let ( .., authz_instance, db_instance ) =
167+ let ( .., authz_instance, _ ) =
177168 LookupPath :: new ( opctx, & self . db_datastore )
178169 . organization_name ( organization_name)
179170 . project_name ( project_name)
180171 . instance_name ( instance_name)
181172 . fetch ( )
182173 . await ?;
183174
184- opctx. authorize ( authz:: Action :: Delete , & authz_instance) . await ?;
185-
186- match db_instance. runtime_state . state . state ( ) {
187- InstanceState :: Stopped | InstanceState :: Failed => {
188- // ok
189- }
190-
191- state => {
192- return Err ( Error :: InvalidRequest {
193- message : format ! (
194- "instance cannot be deleted in state \" {}\" " ,
195- state,
196- ) ,
197- } ) ;
198- }
199- }
200-
201- // Detach all attached disks
202- let disks = self
203- . instance_list_disks (
204- opctx,
205- organization_name,
206- project_name,
207- instance_name,
208- & DataPageParams {
209- marker : None ,
210- direction : dropshot:: PaginationOrder :: Ascending ,
211- limit : std:: num:: NonZeroU32 :: new ( MAX_DISKS_PER_INSTANCE )
212- . unwrap ( ) ,
213- } ,
214- )
215- . await ?;
216-
217- for disk in & disks {
218- self . instance_detach_disk (
219- opctx,
220- organization_name,
221- project_name,
222- instance_name,
223- & disk. name ( ) ,
224- )
225- . await ?;
226- }
227-
228175 self . db_datastore . project_delete_instance ( opctx, & authz_instance) . await
229176 }
230177
@@ -586,144 +533,43 @@ impl super::Nexus {
586533 instance_name : & Name ,
587534 disk_name : & Name ,
588535 ) -> UpdateResult < db:: model:: Disk > {
589- let ( .., authz_project, authz_disk, db_disk ) =
536+ let ( .., authz_project, authz_disk, _ ) =
590537 LookupPath :: new ( opctx, & self . db_datastore )
591538 . organization_name ( organization_name)
592539 . project_name ( project_name)
593540 . disk_name ( disk_name)
594541 . fetch ( )
595542 . await ?;
596- let ( .., authz_instance, db_instance ) =
543+ let ( .., authz_instance, _ ) =
597544 LookupPath :: new ( opctx, & self . db_datastore )
598545 . project_id ( authz_project. id ( ) )
599546 . instance_name ( instance_name)
600547 . fetch ( )
601548 . await ?;
602- let instance_id = & authz_instance. id ( ) ;
603549
604- // Enforce attached disks limit
605- let attached_disks = self
606- . instance_list_disks (
607- opctx,
608- organization_name,
609- project_name,
610- instance_name,
611- & DataPageParams {
612- marker : None ,
613- direction : dropshot:: PaginationOrder :: Ascending ,
614- limit : std:: num:: NonZeroU32 :: new ( MAX_DISKS_PER_INSTANCE )
615- . unwrap ( ) ,
616- } ,
550+ // TODO(https://github.com/oxidecomputer/omicron/issues/811):
551+ // Disk attach is only implemented for instances that are not
552+ // currently running. This operation therefore can operate exclusively
553+ // on database state.
554+ //
555+ // To implement hot-plug support, we should do the following in a saga:
556+ // - Update the state to "Attaching", rather than "Attached".
557+ // - If the instance is running...
558+ // - Issue a request to "disk attach" to the associated sled agent,
559+ // using the "state generation" value from the moment we attached.
560+ // - Update the DB if the request succeeded (hopefully to "Attached").
561+ // - If the instance is not running...
562+ // - Update the disk state in the DB to "Attached".
563+ let ( _instance, disk) = self
564+ . db_datastore
565+ . instance_attach_disk (
566+ & opctx,
567+ & authz_instance,
568+ & authz_disk,
569+ MAX_DISKS_PER_INSTANCE ,
617570 )
618571 . await ?;
619-
620- if attached_disks. len ( ) == MAX_DISKS_PER_INSTANCE as usize {
621- return Err ( Error :: invalid_request ( & format ! (
622- "cannot attach more than {} disks to instance!" ,
623- MAX_DISKS_PER_INSTANCE
624- ) ) ) ;
625- }
626-
627- fn disk_attachment_error (
628- disk : & db:: model:: Disk ,
629- ) -> CreateResult < db:: model:: Disk > {
630- let disk_status = match disk. runtime ( ) . state ( ) . into ( ) {
631- DiskState :: Destroyed => "disk is destroyed" ,
632- DiskState :: Faulted => "disk is faulted" ,
633- DiskState :: Creating => "disk is detached" ,
634- DiskState :: Detached => "disk is detached" ,
635-
636- // It would be nice to provide a more specific message here, but
637- // the appropriate identifier to provide the user would be the
638- // other instance's name. Getting that would require another
639- // database hit, which doesn't seem worth it for this.
640- DiskState :: Attaching ( _) => {
641- "disk is attached to another instance"
642- }
643- DiskState :: Attached ( _) => {
644- "disk is attached to another instance"
645- }
646- DiskState :: Detaching ( _) => {
647- "disk is attached to another instance"
648- }
649- } ;
650- let message = format ! (
651- "cannot attach disk \" {}\" : {}" ,
652- disk. name( ) . as_str( ) ,
653- disk_status
654- ) ;
655- Err ( Error :: InvalidRequest { message } )
656- }
657-
658- match & db_disk. state ( ) . into ( ) {
659- // If we're already attaching or attached to the requested instance,
660- // there's nothing else to do.
661- // TODO-security should it be an error if you're not authorized to
662- // do this and we did not actually have to do anything?
663- DiskState :: Attached ( id) if id == instance_id => return Ok ( db_disk) ,
664-
665- // If the disk is currently attaching or attached to another
666- // instance, fail this request. Users must explicitly detach first
667- // if that's what they want. If it's detaching, they have to wait
668- // for it to become detached.
669- // TODO-debug: the error message here could be better. We'd have to
670- // look up the other instance by id (and gracefully handle it not
671- // existing).
672- DiskState :: Attached ( id) => {
673- assert_ne ! ( id, instance_id) ;
674- return disk_attachment_error ( & db_disk) ;
675- }
676- DiskState :: Detaching ( _) => {
677- return disk_attachment_error ( & db_disk) ;
678- }
679- DiskState :: Attaching ( id) if id != instance_id => {
680- return disk_attachment_error ( & db_disk) ;
681- }
682- DiskState :: Destroyed => {
683- return disk_attachment_error ( & db_disk) ;
684- }
685- DiskState :: Faulted => {
686- return disk_attachment_error ( & db_disk) ;
687- }
688-
689- DiskState :: Creating => ( ) ,
690- DiskState :: Detached => ( ) ,
691- DiskState :: Attaching ( id) => {
692- assert_eq ! ( id, instance_id) ;
693- }
694- }
695-
696- match & db_instance. runtime_state . state . state ( ) {
697- // If there's a propolis zone for this instance, ask the Sled Agent
698- // to hot-plug the disk.
699- //
700- // TODO this will probably involve volume construction requests as
701- // well!
702- InstanceState :: Running | InstanceState :: Starting => {
703- self . disk_set_runtime (
704- opctx,
705- & authz_disk,
706- & db_disk,
707- self . instance_sled ( & db_instance) . await ?,
708- sled_agent_client:: types:: DiskStateRequested :: Attached (
709- * instance_id,
710- ) ,
711- )
712- . await ?;
713- }
714-
715- _ => {
716- // If there is not a propolis zone, then disk attach only occurs
717- // in the DB.
718- let new_runtime = db_disk. runtime ( ) . attach ( * instance_id) ;
719-
720- self . db_datastore
721- . disk_update_runtime ( opctx, & authz_disk, & new_runtime)
722- . await ?;
723- }
724- }
725-
726- self . db_datastore . disk_refetch ( opctx, & authz_disk) . await
572+ Ok ( disk)
727573 }
728574
729575 /// Detach a disk from an instance.
@@ -735,83 +581,38 @@ impl super::Nexus {
735581 instance_name : & Name ,
736582 disk_name : & Name ,
737583 ) -> UpdateResult < db:: model:: Disk > {
738- let ( .., authz_project, authz_disk, db_disk ) =
584+ let ( .., authz_project, authz_disk, _ ) =
739585 LookupPath :: new ( opctx, & self . db_datastore )
740586 . organization_name ( organization_name)
741587 . project_name ( project_name)
742588 . disk_name ( disk_name)
743589 . fetch ( )
744590 . await ?;
745- let ( .., authz_instance, db_instance ) =
591+ let ( .., authz_instance, _ ) =
746592 LookupPath :: new ( opctx, & self . db_datastore )
747593 . project_id ( authz_project. id ( ) )
748594 . instance_name ( instance_name)
749595 . fetch ( )
750596 . await ?;
751- let instance_id = & authz_instance. id ( ) ;
752-
753- match & db_disk. state ( ) . into ( ) {
754- // This operation is a noop if the disk is not attached or already
755- // detaching from the same instance.
756- // TODO-security should it be an error if you're not authorized to
757- // do this and we did not actually have to do anything?
758- DiskState :: Creating => return Ok ( db_disk) ,
759- DiskState :: Detached => return Ok ( db_disk) ,
760- DiskState :: Destroyed => return Ok ( db_disk) ,
761- DiskState :: Faulted => return Ok ( db_disk) ,
762- DiskState :: Detaching ( id) if id == instance_id => {
763- return Ok ( db_disk)
764- }
765-
766- // This operation is not allowed if the disk is attached to some
767- // other instance.
768- DiskState :: Attaching ( id) if id != instance_id => {
769- return Err ( Error :: InvalidRequest {
770- message : String :: from ( "disk is attached elsewhere" ) ,
771- } ) ;
772- }
773- DiskState :: Attached ( id) if id != instance_id => {
774- return Err ( Error :: InvalidRequest {
775- message : String :: from ( "disk is attached elsewhere" ) ,
776- } ) ;
777- }
778- DiskState :: Detaching ( _) => {
779- return Err ( Error :: InvalidRequest {
780- message : String :: from ( "disk is attached elsewhere" ) ,
781- } ) ;
782- }
783-
784- // These are the cases where we have to do something.
785- DiskState :: Attaching ( _) => ( ) ,
786- DiskState :: Attached ( _) => ( ) ,
787- }
788-
789- // If there's a propolis zone for this instance, ask the Sled
790- // Agent to hot-remove the disk.
791- match & db_instance. runtime_state . state . state ( ) {
792- InstanceState :: Running | InstanceState :: Starting => {
793- self . disk_set_runtime (
794- opctx,
795- & authz_disk,
796- & db_disk,
797- self . instance_sled ( & db_instance) . await ?,
798- sled_agent_client:: types:: DiskStateRequested :: Detached ,
799- )
800- . await ?;
801- }
802-
803- _ => {
804- // If there is not a propolis zone, then disk detach only occurs
805- // in the DB.
806- let new_runtime = db_disk. runtime ( ) . detach ( ) ;
807-
808- self . db_datastore
809- . disk_update_runtime ( opctx, & authz_disk, & new_runtime)
810- . await ?;
811- }
812- }
813597
814- self . db_datastore . disk_refetch ( opctx, & authz_disk) . await
598+ // TODO(https://github.com/oxidecomputer/omicron/issues/811):
599+ // Disk detach is only implemented for instances that are not
600+ // currently running. This operation therefore can operate exclusively
601+ // on database state.
602+ //
603+ // To implement hot-unplug support, we should do the following in a saga:
604+ // - Update the state to "Detaching", rather than "Detached".
605+ // - If the instance is running...
606+ // - Issue a request to "disk detach" to the associated sled agent,
607+ // using the "state generation" value from the moment we attached.
608+ // - Update the DB if the request succeeded (hopefully to "Detached").
609+ // - If the instance is not running...
610+ // - Update the disk state in the DB to "Detached".
611+ let disk = self
612+ . db_datastore
613+ . instance_detach_disk ( & opctx, & authz_instance, & authz_disk)
614+ . await ?;
615+ Ok ( disk)
815616 }
816617
817618 /// Create a network interface attached to the provided instance.
0 commit comments