@@ -28,6 +28,7 @@ use nexus_test_utils::resource_helpers::create_default_ip_pools;
2828use nexus_test_utils:: resource_helpers:: create_disk;
2929use nexus_test_utils:: resource_helpers:: create_instance;
3030use nexus_test_utils:: resource_helpers:: create_project;
31+ use nexus_test_utils:: resource_helpers:: object_create_error;
3132use nexus_test_utils_macros:: nexus_test;
3233use nexus_types:: external_api:: params;
3334use nexus_types:: external_api:: views;
@@ -2934,6 +2935,106 @@ async fn test_create_read_only_disk_from_image(
29342935 assert_eq ! ( rw_disk. size, image. size) ;
29352936}
29362937
2938+ #[ nexus_test]
2939+ async fn test_cannot_snapshot_read_only_disk (
2940+ cptestctx : & ControlPlaneTestContext ,
2941+ ) {
2942+ let client = & cptestctx. external_client ;
2943+ DiskTest :: new ( & cptestctx) . await ;
2944+ create_project_and_pool ( client) . await ;
2945+ let disks_url = get_disks_url ( ) ;
2946+
2947+ // Define a global image
2948+ let image_create_params = params:: ImageCreate {
2949+ identity : IdentityMetadataCreateParams {
2950+ name : "alpine" . parse ( ) . unwrap ( ) ,
2951+ description : String :: from (
2952+ "you can boot any image, as long as it's alpine" ,
2953+ ) ,
2954+ } ,
2955+ source : params:: ImageSource :: YouCanBootAnythingAsLongAsItsAlpine ,
2956+ os : "alpine" . to_string ( ) ,
2957+ version : "edge" . to_string ( ) ,
2958+ } ;
2959+
2960+ let images_url = format ! ( "/v1/images?project={}" , PROJECT_NAME ) ;
2961+ let image =
2962+ NexusRequest :: objects_post ( client, & images_url, & image_create_params)
2963+ . authn_as ( AuthnMode :: PrivilegedUser )
2964+ . execute_and_parse_unwrap :: < views:: Image > ( )
2965+ . await ;
2966+
2967+ // Create a base disk from this image, which we will then create a snapshot
2968+ // from in order to create our read-only disk from that snapshot.
2969+ let disk_size = ByteCount :: from_gibibytes_u32 ( 2 ) ;
2970+ let base_disk_name = "base-disk" ;
2971+ let base_disk = params:: DiskCreate {
2972+ identity : IdentityMetadataCreateParams {
2973+ name : base_disk_name. parse ( ) . unwrap ( ) ,
2974+ description : String :: from ( "sells rainsticks" ) ,
2975+ } ,
2976+ disk_backend : params:: DiskBackend :: Distributed {
2977+ disk_source : params:: DiskSource :: Image {
2978+ image_id : image. identity . id ,
2979+ read_only : false ,
2980+ } ,
2981+ } ,
2982+ size : disk_size,
2983+ } ;
2984+
2985+ NexusRequest :: new (
2986+ RequestBuilder :: new ( client, Method :: POST , & disks_url)
2987+ . body ( Some ( & base_disk) )
2988+ . expect_status ( Some ( StatusCode :: CREATED ) ) ,
2989+ )
2990+ . authn_as ( AuthnMode :: PrivilegedUser )
2991+ . execute_and_parse_unwrap :: < Disk > ( )
2992+ . await ;
2993+
2994+ // Assert the base disk is detached
2995+ let base_disk = disk_get ( client, & get_disk_url ( base_disk_name) ) . await ;
2996+ assert_eq ! ( base_disk. state, DiskState :: Detached ) ;
2997+
2998+ // Create a snapshot of the base disk.
2999+ let snapshot = resource_helpers:: create_snapshot (
3000+ client,
3001+ PROJECT_NAME ,
3002+ base_disk_name,
3003+ "ro-snapshot" ,
3004+ )
3005+ . await ;
3006+ assert_eq ! ( snapshot. disk_id, base_disk. identity. id) ;
3007+ assert_eq ! ( snapshot. size, base_disk. size) ;
3008+
3009+ // Okay, now make a read-only disk out of that snapshot.
3010+ let ro_disk = resource_helpers:: create_disk_from_snapshot (
3011+ client,
3012+ PROJECT_NAME ,
3013+ "ro-disk-from-snap" ,
3014+ & snapshot,
3015+ true ,
3016+ )
3017+ . await ;
3018+
3019+ assert ! ( ro_disk. read_only) ;
3020+
3021+ // Now try to take a snapshot of the read-only disk. It should fail with a
3022+ // 400.
3023+ object_create_error (
3024+ client,
3025+ & format ! ( "/v1/snapshots?project={}" , PROJECT_NAME ) ,
3026+ & params:: SnapshotCreate {
3027+ identity : IdentityMetadataCreateParams {
3028+ name : "will-not-work" . parse ( ) . unwrap ( ) ,
3029+ description : String :: from ( "no thanks" ) ,
3030+ } ,
3031+ disk : ro_disk. identity . name . clone ( ) . try_into ( ) . unwrap ( ) ,
3032+ } ,
3033+ http:: StatusCode :: BAD_REQUEST ,
3034+ )
3035+ . await ;
3036+ }
3037+
29373038async fn disk_get ( client : & ClientTestContext , disk_url : & str ) -> Disk {
29383039 NexusRequest :: object_get ( client, disk_url)
29393040 . authn_as ( AuthnMode :: PrivilegedUser )
0 commit comments