1
+ use dropshot:: test_util:: ClientTestContext ;
1
2
use http:: Method ;
2
3
use http:: StatusCode ;
3
4
use nexus_test_utils:: http_testing:: AuthnMode ;
@@ -6,16 +7,26 @@ use nexus_test_utils::http_testing::RequestBuilder;
6
7
use nexus_test_utils:: resource_helpers:: DiskTest ;
7
8
use nexus_test_utils:: resource_helpers:: create_default_ip_pool;
8
9
use nexus_test_utils:: resource_helpers:: create_instance;
10
+ use nexus_test_utils:: resource_helpers:: create_local_user;
9
11
use 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;
10
16
use nexus_test_utils:: resource_helpers:: objects_list_page_authz;
17
+ use nexus_test_utils:: resource_helpers:: test_params;
11
18
use nexus_test_utils_macros:: nexus_test;
12
19
use nexus_types:: external_api:: params;
13
20
use nexus_types:: external_api:: params:: SiloQuotasCreate ;
21
+ use nexus_types:: external_api:: views:: Silo ;
22
+ use nexus_types:: external_api:: views:: SiloQuotas ;
14
23
use nexus_types:: external_api:: views:: SiloUtilization ;
15
24
use nexus_types:: external_api:: views:: Utilization ;
16
25
use nexus_types:: external_api:: views:: VirtualResourceCounts ;
17
26
use omicron_common:: api:: external:: ByteCount ;
18
27
use omicron_common:: api:: external:: IdentityMetadataCreateParams ;
28
+ use omicron_common:: api:: external:: InstanceCpuCount ;
29
+ use oxide_client:: types:: SiloRole ;
19
30
20
31
static PROJECT_NAME : & str = "utilization-test-project" ;
21
32
static INSTANCE_NAME : & str = "utilization-test-instance" ;
@@ -24,108 +35,97 @@ type ControlPlaneTestContext =
24
35
nexus_test_utils:: ControlPlaneTestContext < omicron_nexus:: Server > ;
25
36
26
37
#[ nexus_test]
27
- async fn test_utilization ( cptestctx : & ControlPlaneTestContext ) {
38
+ async fn test_utilization_list ( cptestctx : & ControlPlaneTestContext ) {
28
39
let client = & cptestctx. external_client ;
29
40
30
41
create_default_ip_pool ( & client) . await ;
31
42
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( ) ) ;
41
47
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 (
43
51
client,
44
- "/v1/system/utilization/silos" ,
52
+ quotas_url,
53
+ & params:: SiloQuotasCreate :: arbitrarily_high_default ( ) ,
45
54
)
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 ;
52
56
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
54
62
assert_eq ! ( current_util[ 0 ] . provisioned, SiloQuotasCreate :: empty( ) . into( ) ) ;
55
63
assert_eq ! (
56
64
current_util[ 0 ] . allocated,
57
65
SiloQuotasCreate :: arbitrarily_high_default( ) . into( )
58
66
) ;
59
67
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
+ ) ;
62
83
assert_eq ! (
63
- current_util[ 1 ] . allocated,
84
+ current_util[ 0 ] . allocated,
64
85
SiloQuotasCreate :: arbitrarily_high_default( ) . into( )
65
86
) ;
66
87
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 ;
75
92
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
+ }
82
95
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 ;
85
101
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 ;
92
103
93
104
let _ = create_project ( & client, & PROJECT_NAME ) . await ;
94
105
let _ = create_instance ( client, & PROJECT_NAME , & INSTANCE_NAME ) . await ;
95
106
107
+ let instance_start_url = format ! (
108
+ "/v1/instances/{}/start?project={}" ,
109
+ & INSTANCE_NAME , & PROJECT_NAME
110
+ ) ;
111
+
96
112
// Start instance
97
113
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 ) ) ,
109
117
)
110
118
. authn_as ( AuthnMode :: PrivilegedUser )
111
119
. execute ( )
112
120
. await
113
121
. expect ( "failed to start instance" ) ;
114
122
115
123
// 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 ;
126
126
127
127
assert_eq ! (
128
- silo_util . provisioned,
128
+ default_silo_util . provisioned,
129
129
VirtualResourceCounts {
130
130
cpus: 4 ,
131
131
memory: ByteCount :: from_gibibytes_u32( 1 ) ,
@@ -136,45 +136,113 @@ async fn test_utilization(cptestctx: &ControlPlaneTestContext) {
136
136
// Simulate space for disks
137
137
DiskTest :: new ( & cptestctx) . await ;
138
138
139
+ let disk_url = format ! ( "/v1/disks?project={}" , & PROJECT_NAME ) ;
139
140
// provision disk
140
141
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 ) ) ,
157
154
)
158
155
. authn_as ( AuthnMode :: PrivilegedUser )
159
156
. execute ( )
160
157
. await
161
158
. expect ( "disk failed to create" ) ;
162
159
163
160
// 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 ;
171
163
172
164
assert_eq ! (
173
- silo_util . provisioned,
165
+ default_silo_util . provisioned,
174
166
VirtualResourceCounts {
175
167
cpus: 4 ,
176
168
memory: ByteCount :: from_gibibytes_u32( 1 ) ,
177
169
storage: ByteCount :: from_gibibytes_u32( 2 )
178
170
}
179
171
) ;
180
172
}
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