1+ use dropshot:: test_util:: ClientTestContext ;
12use http:: Method ;
23use http:: StatusCode ;
34use nexus_test_utils:: http_testing:: AuthnMode ;
@@ -6,16 +7,26 @@ use nexus_test_utils::http_testing::RequestBuilder;
67use nexus_test_utils:: resource_helpers:: DiskTest ;
78use nexus_test_utils:: resource_helpers:: create_default_ip_pool;
89use nexus_test_utils:: resource_helpers:: create_instance;
10+ use nexus_test_utils:: resource_helpers:: create_local_user;
911use nexus_test_utils:: resource_helpers:: create_project;
12+ use nexus_test_utils:: resource_helpers:: grant_iam;
13+ use nexus_test_utils:: resource_helpers:: link_ip_pool;
14+ use nexus_test_utils:: resource_helpers:: object_get;
15+ use nexus_test_utils:: resource_helpers:: object_put;
1016use nexus_test_utils:: resource_helpers:: objects_list_page_authz;
17+ use nexus_test_utils:: resource_helpers:: test_params;
1118use nexus_test_utils_macros:: nexus_test;
1219use nexus_types:: external_api:: params;
1320use nexus_types:: external_api:: params:: SiloQuotasCreate ;
21+ use nexus_types:: external_api:: views:: Silo ;
22+ use nexus_types:: external_api:: views:: SiloQuotas ;
1423use nexus_types:: external_api:: views:: SiloUtilization ;
1524use nexus_types:: external_api:: views:: Utilization ;
1625use nexus_types:: external_api:: views:: VirtualResourceCounts ;
1726use omicron_common:: api:: external:: ByteCount ;
1827use omicron_common:: api:: external:: IdentityMetadataCreateParams ;
28+ use omicron_common:: api:: external:: InstanceCpuCount ;
29+ use oxide_client:: types:: SiloRole ;
1930
2031static PROJECT_NAME : & str = "utilization-test-project" ;
2132static INSTANCE_NAME : & str = "utilization-test-instance" ;
@@ -24,108 +35,97 @@ type ControlPlaneTestContext =
2435 nexus_test_utils:: ControlPlaneTestContext < omicron_nexus:: Server > ;
2536
2637#[ nexus_test]
27- async fn test_utilization ( cptestctx : & ControlPlaneTestContext ) {
38+ async fn test_utilization_list ( cptestctx : & ControlPlaneTestContext ) {
2839 let client = & cptestctx. external_client ;
2940
3041 create_default_ip_pool ( & client) . await ;
3142
32- // set high quota for test silo
33- let _ = NexusRequest :: object_put (
34- client,
35- "/v1/system/silos/test-suite-silo/quotas" ,
36- Some ( & params:: SiloQuotasCreate :: arbitrarily_high_default ( ) ) ,
37- )
38- . authn_as ( AuthnMode :: PrivilegedUser )
39- . execute ( )
40- . await ;
43+ // default-silo has quotas, but is explicitly filtered out by ID in the
44+ // DB query to avoid user confusion. test-suite-silo also exists, but is
45+ // filtered out because it has no quotas, so list is empty
46+ assert ! ( util_list( client) . await . is_empty( ) ) ;
4147
42- let current_util = objects_list_page_authz :: < SiloUtilization > (
48+ // setting quotas will make test-suite-silo show up in the list
49+ let quotas_url = "/v1/system/silos/test-suite-silo/quotas" ;
50+ let _: SiloQuotas = object_put (
4351 client,
44- "/v1/system/utilization/silos" ,
52+ quotas_url,
53+ & params:: SiloQuotasCreate :: arbitrarily_high_default ( ) ,
4554 )
46- . await
47- . items ;
48-
49- // `default-silo` should be the only silo that shows up because
50- // it has a default quota set
51- assert_eq ! ( current_util. len( ) , 2 ) ;
55+ . await ;
5256
53- assert_eq ! ( current_util[ 0 ] . silo_name, "default-silo" ) ;
57+ // now test-suite-silo shows up in the list
58+ let current_util = util_list ( client) . await ;
59+ assert_eq ! ( current_util. len( ) , 1 ) ;
60+ assert_eq ! ( current_util[ 0 ] . silo_name, "test-suite-silo" ) ;
61+ // it's empty because it has no resources
5462 assert_eq ! ( current_util[ 0 ] . provisioned, SiloQuotasCreate :: empty( ) . into( ) ) ;
5563 assert_eq ! (
5664 current_util[ 0 ] . allocated,
5765 SiloQuotasCreate :: arbitrarily_high_default( ) . into( )
5866 ) ;
5967
60- assert_eq ! ( current_util[ 1 ] . silo_name, "test-suite-silo" ) ;
61- assert_eq ! ( current_util[ 1 ] . provisioned, SiloQuotasCreate :: empty( ) . into( ) ) ;
68+ // create the resources that should change the utilization
69+ create_resources_in_test_suite_silo ( client) . await ;
70+
71+ // list response shows provisioned resources
72+ let current_util = util_list ( client) . await ;
73+ assert_eq ! ( current_util. len( ) , 1 ) ;
74+ assert_eq ! ( current_util[ 0 ] . silo_name, "test-suite-silo" ) ;
75+ assert_eq ! (
76+ current_util[ 0 ] . provisioned,
77+ VirtualResourceCounts {
78+ cpus: 2 ,
79+ memory: ByteCount :: from_gibibytes_u32( 4 ) ,
80+ storage: ByteCount :: from( 0 )
81+ }
82+ ) ;
6283 assert_eq ! (
63- current_util[ 1 ] . allocated,
84+ current_util[ 0 ] . allocated,
6485 SiloQuotasCreate :: arbitrarily_high_default( ) . into( )
6586 ) ;
6687
67- let _ = NexusRequest :: object_put (
68- client,
69- "/v1/system/silos/test-suite-silo/quotas" ,
70- Some ( & params:: SiloQuotasCreate :: empty ( ) ) ,
71- )
72- . authn_as ( AuthnMode :: PrivilegedUser )
73- . execute ( )
74- . await ;
88+ // now we take the quota back off of test-suite-silo and end up empty again
89+ let _: SiloQuotas =
90+ object_put ( client, quotas_url, & params:: SiloQuotasCreate :: empty ( ) )
91+ . await ;
7592
76- let current_util = objects_list_page_authz :: < SiloUtilization > (
77- client,
78- "/v1/system/utilization/silos" ,
79- )
80- . await
81- . items ;
93+ assert ! ( util_list( client) . await . is_empty( ) ) ;
94+ }
8295
83- // Now that default-silo is the only one with a quota, it should be the only result
84- assert_eq ! ( current_util. len( ) , 1 ) ;
96+ // Even though default silo is filtered out of the list view, you can still
97+ // fetch utilization for it individiually, so we test that here
98+ #[ nexus_test]
99+ async fn test_utilization_view ( cptestctx : & ControlPlaneTestContext ) {
100+ let client = & cptestctx. external_client ;
85101
86- assert_eq ! ( current_util[ 0 ] . silo_name, "default-silo" ) ;
87- assert_eq ! ( current_util[ 0 ] . provisioned, SiloQuotasCreate :: empty( ) . into( ) ) ;
88- assert_eq ! (
89- current_util[ 0 ] . allocated,
90- SiloQuotasCreate :: arbitrarily_high_default( ) . into( )
91- ) ;
102+ create_default_ip_pool ( & client) . await ;
92103
93104 let _ = create_project ( & client, & PROJECT_NAME ) . await ;
94105 let _ = create_instance ( client, & PROJECT_NAME , & INSTANCE_NAME ) . await ;
95106
107+ let instance_start_url = format ! (
108+ "/v1/instances/{}/start?project={}" ,
109+ & INSTANCE_NAME , & PROJECT_NAME
110+ ) ;
111+
96112 // Start instance
97113 NexusRequest :: new (
98- RequestBuilder :: new (
99- client,
100- Method :: POST ,
101- format ! (
102- "/v1/instances/{}/start?project={}" ,
103- & INSTANCE_NAME , & PROJECT_NAME
104- )
105- . as_str ( ) ,
106- )
107- . body ( None as Option < & serde_json:: Value > )
108- . expect_status ( Some ( StatusCode :: ACCEPTED ) ) ,
114+ RequestBuilder :: new ( client, Method :: POST , & instance_start_url)
115+ . body ( None as Option < & serde_json:: Value > )
116+ . expect_status ( Some ( StatusCode :: ACCEPTED ) ) ,
109117 )
110118 . authn_as ( AuthnMode :: PrivilegedUser )
111119 . execute ( )
112120 . await
113121 . expect ( "failed to start instance" ) ;
114122
115123 // get utilization for just the default silo
116- let silo_util = NexusRequest :: object_get (
117- client,
118- "/v1/system/utilization/silos/default-silo" ,
119- )
120- . authn_as ( AuthnMode :: PrivilegedUser )
121- . execute ( )
122- . await
123- . expect ( "failed to fetch silo utilization" )
124- . parsed_body :: < SiloUtilization > ( )
125- . unwrap ( ) ;
124+ let default_silo_util: SiloUtilization =
125+ object_get ( client, "/v1/system/utilization/silos/default-silo" ) . await ;
126126
127127 assert_eq ! (
128- silo_util . provisioned,
128+ default_silo_util . provisioned,
129129 VirtualResourceCounts {
130130 cpus: 4 ,
131131 memory: ByteCount :: from_gibibytes_u32( 1 ) ,
@@ -136,45 +136,113 @@ async fn test_utilization(cptestctx: &ControlPlaneTestContext) {
136136 // Simulate space for disks
137137 DiskTest :: new ( & cptestctx) . await ;
138138
139+ let disk_url = format ! ( "/v1/disks?project={}" , & PROJECT_NAME ) ;
139140 // provision disk
140141 NexusRequest :: new (
141- RequestBuilder :: new (
142- client,
143- Method :: POST ,
144- format ! ( "/v1/disks?project={}" , & PROJECT_NAME ) . as_str ( ) ,
145- )
146- . body ( Some ( & params:: DiskCreate {
147- identity : IdentityMetadataCreateParams {
148- name : "test-disk" . parse ( ) . unwrap ( ) ,
149- description : "" . into ( ) ,
150- } ,
151- size : ByteCount :: from_gibibytes_u32 ( 2 ) ,
152- disk_source : params:: DiskSource :: Blank {
153- block_size : params:: BlockSize :: try_from ( 512 ) . unwrap ( ) ,
154- } ,
155- } ) )
156- . expect_status ( Some ( StatusCode :: CREATED ) ) ,
142+ RequestBuilder :: new ( client, Method :: POST , & disk_url)
143+ . body ( Some ( & params:: DiskCreate {
144+ identity : IdentityMetadataCreateParams {
145+ name : "test-disk" . parse ( ) . unwrap ( ) ,
146+ description : "" . into ( ) ,
147+ } ,
148+ size : ByteCount :: from_gibibytes_u32 ( 2 ) ,
149+ disk_source : params:: DiskSource :: Blank {
150+ block_size : params:: BlockSize :: try_from ( 512 ) . unwrap ( ) ,
151+ } ,
152+ } ) )
153+ . expect_status ( Some ( StatusCode :: CREATED ) ) ,
157154 )
158155 . authn_as ( AuthnMode :: PrivilegedUser )
159156 . execute ( )
160157 . await
161158 . expect ( "disk failed to create" ) ;
162159
163160 // Get the silo but this time using the silo admin view
164- let silo_util = NexusRequest :: object_get ( client, "/v1/utilization" )
165- . authn_as ( AuthnMode :: PrivilegedUser )
166- . execute ( )
167- . await
168- . expect ( "failed to fetch utilization for current (default) silo" )
169- . parsed_body :: < Utilization > ( )
170- . unwrap ( ) ;
161+ let default_silo_util: Utilization =
162+ object_get ( client, "/v1/utilization" ) . await ;
171163
172164 assert_eq ! (
173- silo_util . provisioned,
165+ default_silo_util . provisioned,
174166 VirtualResourceCounts {
175167 cpus: 4 ,
176168 memory: ByteCount :: from_gibibytes_u32( 1 ) ,
177169 storage: ByteCount :: from_gibibytes_u32( 2 )
178170 }
179171 ) ;
180172}
173+
174+ async fn util_list ( client : & ClientTestContext ) -> Vec < SiloUtilization > {
175+ objects_list_page_authz ( client, "/v1/system/utilization/silos" ) . await . items
176+ }
177+
178+ /// Could be inlined, but pulling it out makes the test much clearer
179+ async fn create_resources_in_test_suite_silo ( client : & ClientTestContext ) {
180+ // in order to create resources in test-suite-silo, we have to create a user
181+ // with the right perms so we have a user ID on hand to use in the authn_as
182+ let silo_url = "/v1/system/silos/test-suite-silo" ;
183+ let test_suite_silo: Silo = object_get ( client, silo_url) . await ;
184+ link_ip_pool ( client, "default" , & test_suite_silo. identity . id , true ) . await ;
185+ let user1 = create_local_user (
186+ client,
187+ & test_suite_silo,
188+ & "user1" . parse ( ) . unwrap ( ) ,
189+ test_params:: UserPassword :: LoginDisallowed ,
190+ )
191+ . await ;
192+ grant_iam (
193+ client,
194+ silo_url,
195+ SiloRole :: Collaborator ,
196+ user1. id ,
197+ AuthnMode :: PrivilegedUser ,
198+ )
199+ . await ;
200+
201+ let test_project_name = "test-suite-project" ;
202+
203+ // Create project in test-suite-silo as the test user
204+ NexusRequest :: objects_post (
205+ client,
206+ "/v1/projects" ,
207+ & params:: ProjectCreate {
208+ identity : IdentityMetadataCreateParams {
209+ name : test_project_name. parse ( ) . unwrap ( ) ,
210+ description : String :: new ( ) ,
211+ } ,
212+ } ,
213+ )
214+ . authn_as ( AuthnMode :: SiloUser ( user1. id ) )
215+ . execute ( )
216+ . await
217+ . expect ( "failed to create project in test-suite-silo" ) ;
218+
219+ // Create instance in test-suite-silo as the test user
220+ let instance_params = params:: InstanceCreate {
221+ identity : IdentityMetadataCreateParams {
222+ name : "test-inst" . parse ( ) . unwrap ( ) ,
223+ description : "test instance in test-suite-silo" . to_string ( ) ,
224+ } ,
225+ ncpus : InstanceCpuCount :: try_from ( 2 ) . unwrap ( ) ,
226+ memory : ByteCount :: from_gibibytes_u32 ( 4 ) ,
227+ hostname : "test-inst" . parse ( ) . unwrap ( ) ,
228+ user_data : vec ! [ ] ,
229+ ssh_public_keys : None ,
230+ network_interfaces : params:: InstanceNetworkInterfaceAttachment :: Default ,
231+ external_ips : vec ! [ ] ,
232+ disks : vec ! [ ] ,
233+ boot_disk : None ,
234+ start : true ,
235+ auto_restart_policy : Default :: default ( ) ,
236+ anti_affinity_groups : Vec :: new ( ) ,
237+ } ;
238+
239+ NexusRequest :: objects_post (
240+ client,
241+ & format ! ( "/v1/instances?project={}" , test_project_name) ,
242+ & instance_params,
243+ )
244+ . authn_as ( AuthnMode :: SiloUser ( user1. id ) )
245+ . execute ( )
246+ . await
247+ . expect ( "failed to create instance in test-suite-silo" ) ;
248+ }
0 commit comments