Skip to content

Commit d4c14d8

Browse files
0x00A5alxiord
authored andcommitted
Add PATCH MachineConfig API Call
1. Make all machine_config parameters mandatory except CPU Template for a PUT request. 2. Replace current PUT MachineConfig with PATCH. 3. Add PUT that requires all fields. Signed-off-by: YLyu <[email protected]>
1 parent 73f9447 commit d4c14d8

File tree

7 files changed

+136
-14
lines changed

7 files changed

+136
-14
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
- New `devtool` command: `tag`. This creates a new git tag for the specified
1111
release number, based on the changelog contents.
1212
- New doc section about building with glibc.
13+
- New API call: `PATCH /machine-config/`, used to update VM configuration,
14+
before the microVM boots.
1315

1416
### Changed
1517

@@ -21,6 +23,8 @@
2123
than `String`, `Array`, `Object` will return status code 400.
2224
- Improved multiple error messages.
2325
- Removed all kernel modules from the recommended kernel config.
26+
- `vcpu_count`, `mem_size_mib` and `ht_enabled` have been changed to be mandatory
27+
for `PUT` requests on `/machine-config/`.
2428

2529
### Fixed
2630

api_server/src/http_service.rs

+49-2
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,20 @@ fn parse_machine_config_req<'a>(
305305
Error::Generic(StatusCode::BadRequest, s)
306306
})?)
307307
}
308+
309+
0 if method == Method::Patch => {
310+
METRICS.patch_api_requests.machine_cfg_count.inc();
311+
Ok(serde_json::from_slice::<VmConfig>(body)
312+
.map_err(|e| {
313+
METRICS.patch_api_requests.machine_cfg_fails.inc();
314+
Error::SerdeJson(e)
315+
})?
316+
.into_parsed_request(None, method)
317+
.map_err(|s| {
318+
METRICS.patch_api_requests.machine_cfg_fails.inc();
319+
Error::Generic(StatusCode::BadRequest, s)
320+
})?)
321+
}
308322
_ => Err(Error::InvalidPathMethod(path, method)),
309323
}
310324
}
@@ -1158,6 +1172,26 @@ mod tests {
11581172
_ => assert!(false),
11591173
}
11601174

1175+
// PATCH
1176+
let vm_config = VmConfig {
1177+
vcpu_count: Some(32),
1178+
mem_size_mib: None,
1179+
ht_enabled: None,
1180+
cpu_template: None,
1181+
};
1182+
let body = r#"{
1183+
"vcpu_count": 32
1184+
}"#;
1185+
match vm_config.into_parsed_request(None, Method::Patch) {
1186+
Ok(parsed_req) => {
1187+
match parse_machine_config_req(&path, Method::Patch, &Chunk::from(body)) {
1188+
Ok(other_parsed_req) => assert!(parsed_req.eq(&other_parsed_req)),
1189+
_ => assert!(false),
1190+
}
1191+
}
1192+
_ => assert!(false),
1193+
}
1194+
11611195
// Error cases
11621196
// Error Case: Invalid payload (cannot deserialize the body into a VmConfig object).
11631197
assert!(
@@ -1168,9 +1202,9 @@ mod tests {
11681202
// Error Case: Invalid payload (payload is empty).
11691203
let expected_err = Err(Error::Generic(
11701204
StatusCode::BadRequest,
1171-
String::from("Empty request."),
1205+
String::from("Empty PATCH request."),
11721206
));
1173-
assert!(parse_machine_config_req(path, Method::Put, &Chunk::from("{}")) == expected_err);
1207+
assert!(parse_machine_config_req(path, Method::Patch, &Chunk::from("{}")) == expected_err);
11741208

11751209
// Error Case: cpu count exceeds limitation
11761210
let json = "{
@@ -1185,6 +1219,19 @@ mod tests {
11851219
} else {
11861220
assert!(false);
11871221
}
1222+
1223+
// Error Case: PUT request with missing parameter
1224+
let json = "{
1225+
\"mem_size_mib\": 1025,
1226+
\"ht_enabled\": true,
1227+
\"cpu_template\": \"T2\"
1228+
}";
1229+
let body: Chunk = Chunk::from(json);
1230+
let expected_err = Err(Error::Generic(
1231+
StatusCode::BadRequest,
1232+
String::from("Missing mandatory fields."),
1233+
));
1234+
assert!(parse_machine_config_req(path, Method::Put, &body) == expected_err);
11881235
}
11891236

11901237
#[test]

api_server/src/request/machine_configuration.rs

+26-4
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,25 @@ impl IntoParsedRequest for VmConfig {
4242
VmmAction::GetVmConfiguration(sender),
4343
receiver,
4444
)),
45-
Method::Put => {
45+
Method::Patch => {
4646
if self.vcpu_count.is_none()
4747
&& self.mem_size_mib.is_none()
4848
&& self.cpu_template.is_none()
4949
&& self.ht_enabled.is_none()
5050
{
51-
return Err(String::from("Empty request."));
51+
return Err(String::from("Empty PATCH request."));
52+
}
53+
Ok(ParsedRequest::Sync(
54+
VmmAction::SetVmConfiguration(self, sender),
55+
receiver,
56+
))
57+
}
58+
Method::Put => {
59+
if self.vcpu_count.is_none()
60+
|| self.mem_size_mib.is_none()
61+
|| self.ht_enabled.is_none()
62+
{
63+
return Err(String::from("Missing mandatory fields."));
5264
}
5365
Ok(ParsedRequest::Sync(
5466
VmmAction::SetVmConfiguration(self, sender),
@@ -81,6 +93,7 @@ mod tests {
8193
VmmAction::SetVmConfiguration(body, sender),
8294
receiver
8395
))));
96+
8497
let uninitialized = VmConfig {
8598
vcpu_count: None,
8699
mem_size_mib: None,
@@ -91,14 +104,23 @@ mod tests {
91104
.clone()
92105
.into_parsed_request(None, Method::Get)
93106
.is_ok());
107+
108+
// Empty PATCH
94109
assert!(uninitialized
95110
.clone()
96111
.into_parsed_request(None, Method::Patch)
97112
.is_err());
98113

99-
match uninitialized.into_parsed_request(None, Method::Put) {
114+
// Incomplete PUT payload
115+
let body = VmConfig {
116+
vcpu_count: Some(8),
117+
mem_size_mib: Some(1024),
118+
ht_enabled: None,
119+
cpu_template: Some(CpuFeaturesTemplate::T2),
120+
};
121+
match body.into_parsed_request(None, Method::Put) {
100122
Ok(_) => assert!(false),
101-
Err(e) => assert_eq!(e, String::from("Empty request.")),
123+
Err(e) => assert_eq!(e, String::from("Missing mandatory fields.")),
102124
};
103125
}
104126
}

api_server/swagger/firecracker.yaml

+24
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,30 @@ paths:
215215
schema:
216216
$ref: "#/definitions/Error"
217217

218+
patch:
219+
summary: Partially updates the Machine Configuration of the VM.
220+
description:
221+
Partially updates the Virtual Machine Configuration with the specified input.
222+
If any of the parameters has an incorrect value, the whole update fails.
223+
operationId: patchMachineConfiguration
224+
parameters:
225+
- name: body
226+
in: body
227+
description: A subset of Machine Configuration Parameters
228+
schema:
229+
$ref: "#/definitions/MachineConfiguration"
230+
responses:
231+
204:
232+
description: Machine Configuration created/updated
233+
400:
234+
description: Machine Configuration cannot be updated due to bad input
235+
schema:
236+
$ref: "#/definitions/Error"
237+
default:
238+
description: Internal server error
239+
schema:
240+
$ref: "#/definitions/Error"
241+
218242
/mmds:
219243
put:
220244
summary: Creates a MMDS (Microvm Metadata Service) data store.

logger/src/metrics.rs

+4
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ pub struct PatchRequestsMetrics {
186186
pub network_count: SharedMetric,
187187
/// Number of failures in PATCHing a net device.
188188
pub network_fails: SharedMetric,
189+
/// Number of PATCHs for configuring the machine.
190+
pub machine_cfg_count: SharedMetric,
191+
/// Number of failures in configuring the machine.
192+
pub machine_cfg_fails: SharedMetric,
189193
}
190194

191195
/// Block Device associated metrics.

tests/integration_tests/functional/test_api.py

+15-6
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,20 @@ def test_api_put_update_post_boot(test_microvm_with_api):
254254
assert "The update operation is not allowed after boot" in response.text
255255

256256
# Valid updates to the machine configuration are not allowed after boot.
257-
response = test_microvm.machine_cfg.put(
257+
response = test_microvm.machine_cfg.patch(
258258
vcpu_count=4
259259
)
260260
assert test_microvm.api_session.is_status_bad_request(response.status_code)
261261
assert "The update operation is not allowed after boot" in response.text
262262

263+
response = test_microvm.machine_cfg.put(
264+
vcpu_count=4,
265+
ht_enabled=False,
266+
mem_size_mib=128
267+
)
268+
assert test_microvm.api_session.is_status_bad_request(response.status_code)
269+
assert "The update operation is not allowed after boot" in response.text
270+
263271
# Network interface update is not allowed after boot.
264272
response = test_microvm.network.put(
265273
iface_id='1',
@@ -472,10 +480,11 @@ def test_api_patch_pre_boot(test_microvm_with_api):
472480
assert test_microvm.api_session.is_status_bad_request(response.status_code)
473481
assert "Invalid request method" in response.text
474482

475-
# Partial updates to the machine configuration are not allowed.
483+
# Partial updates to the machine configuration are allowed before boot.
476484
response = test_microvm.machine_cfg.patch(vcpu_count=4)
477-
assert test_microvm.api_session.is_status_bad_request(response.status_code)
478-
assert "Invalid request method" in response.text
485+
assert test_microvm.api_session.is_status_no_content(response.status_code)
486+
response_json = test_microvm.machine_cfg.get().json()
487+
assert response_json['vcpu_count'] == 4
479488

480489
# Partial updates to the logger configuration are not allowed.
481490
response = test_microvm.logger.patch(level='Error')
@@ -534,10 +543,10 @@ def test_api_patch_post_boot(test_microvm_with_api):
534543
assert test_microvm.api_session.is_status_bad_request(response.status_code)
535544
assert "Invalid request method" in response.text
536545

537-
# Partial updates to the machine configuration are not allowed.
546+
# Partial updates to the machine configuration are not allowed after boot.
538547
response = test_microvm.machine_cfg.patch(vcpu_count=4)
539548
assert test_microvm.api_session.is_status_bad_request(response.status_code)
540-
assert "Invalid request method" in response.text
549+
assert "The update operation is not allowed after boot." in response.text
541550

542551
# Partial updates to the logger configuration are not allowed.
543552
response = test_microvm.logger.patch(level='Error')

tests/integration_tests/functional/test_logging.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,23 @@ def test_api_requests_logs(test_microvm_with_api):
191191

192192
expected_log_strings = []
193193

194-
# Check that a Put request on /machine-config is logged.
195-
response = microvm.machine_cfg.put(vcpu_count=4)
194+
# Check that a Patch request on /machine-config is logged.
195+
response = microvm.machine_cfg.patch(vcpu_count=4)
196196
assert microvm.api_session.is_status_no_content(response.status_code)
197197
# We are not interested in the actual body. Just check that the log
198198
# message also has the string "body" in it.
199+
expected_log_strings.append(
200+
"The API server received a synchronous Patch request "
201+
"on \"/machine-config\" with body"
202+
)
203+
204+
# Check that a Put request on /machine-config is logged.
205+
response = microvm.machine_cfg.put(
206+
vcpu_count=4,
207+
ht_enabled=False,
208+
mem_size_mib=128
209+
)
210+
assert microvm.api_session.is_status_no_content(response.status_code)
199211
expected_log_strings.append(
200212
"The API server received a synchronous Put request "
201213
"on \"/machine-config\" with body"

0 commit comments

Comments
 (0)