@@ -19,7 +19,6 @@ use omicron_common::api::external;
19
19
use omicron_common:: api:: external:: CreateResult ;
20
20
use omicron_common:: api:: external:: DataPageParams ;
21
21
use omicron_common:: api:: external:: DeleteResult ;
22
- use omicron_common:: api:: external:: DiskState ;
23
22
use omicron_common:: api:: external:: Error ;
24
23
use omicron_common:: api:: external:: InstanceState ;
25
24
use omicron_common:: api:: external:: ListResultVec ;
@@ -152,17 +151,9 @@ impl super::Nexus {
152
151
Ok ( db_instance)
153
152
}
154
153
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.
166
157
pub async fn project_destroy_instance (
167
158
& self ,
168
159
opctx : & OpContext ,
@@ -173,58 +164,14 @@ impl super::Nexus {
173
164
// TODO-robustness We need to figure out what to do with Destroyed
174
165
// instances? Presumably we need to clean them up at some point, but
175
166
// not right away so that callers can see that they've been destroyed.
176
- let ( .., authz_instance, db_instance ) =
167
+ let ( .., authz_instance, _ ) =
177
168
LookupPath :: new ( opctx, & self . db_datastore )
178
169
. organization_name ( organization_name)
179
170
. project_name ( project_name)
180
171
. instance_name ( instance_name)
181
172
. fetch ( )
182
173
. await ?;
183
174
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
-
228
175
self . db_datastore . project_delete_instance ( opctx, & authz_instance) . await
229
176
}
230
177
@@ -586,144 +533,43 @@ impl super::Nexus {
586
533
instance_name : & Name ,
587
534
disk_name : & Name ,
588
535
) -> UpdateResult < db:: model:: Disk > {
589
- let ( .., authz_project, authz_disk, db_disk ) =
536
+ let ( .., authz_project, authz_disk, _ ) =
590
537
LookupPath :: new ( opctx, & self . db_datastore )
591
538
. organization_name ( organization_name)
592
539
. project_name ( project_name)
593
540
. disk_name ( disk_name)
594
541
. fetch ( )
595
542
. await ?;
596
- let ( .., authz_instance, db_instance ) =
543
+ let ( .., authz_instance, _ ) =
597
544
LookupPath :: new ( opctx, & self . db_datastore )
598
545
. project_id ( authz_project. id ( ) )
599
546
. instance_name ( instance_name)
600
547
. fetch ( )
601
548
. await ?;
602
- let instance_id = & authz_instance. id ( ) ;
603
549
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 ,
617
570
)
618
571
. 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)
727
573
}
728
574
729
575
/// Detach a disk from an instance.
@@ -735,83 +581,38 @@ impl super::Nexus {
735
581
instance_name : & Name ,
736
582
disk_name : & Name ,
737
583
) -> UpdateResult < db:: model:: Disk > {
738
- let ( .., authz_project, authz_disk, db_disk ) =
584
+ let ( .., authz_project, authz_disk, _ ) =
739
585
LookupPath :: new ( opctx, & self . db_datastore )
740
586
. organization_name ( organization_name)
741
587
. project_name ( project_name)
742
588
. disk_name ( disk_name)
743
589
. fetch ( )
744
590
. await ?;
745
- let ( .., authz_instance, db_instance ) =
591
+ let ( .., authz_instance, _ ) =
746
592
LookupPath :: new ( opctx, & self . db_datastore )
747
593
. project_id ( authz_project. id ( ) )
748
594
. instance_name ( instance_name)
749
595
. fetch ( )
750
596
. 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
- }
813
597
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)
815
616
}
816
617
817
618
/// Create a network interface attached to the provided instance.
0 commit comments