Skip to content

Commit 29e1411

Browse files
committed
Fix iPXE NICo branding
Signed-off-by: Hasan Khan <hasank@nvidia.com>
1 parent 1070e96 commit 29e1411

5 files changed

Lines changed: 117 additions & 109 deletions

File tree

crates/dhcp/tests/booturl.rs

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17+
use std::io::ErrorKind;
1718
use std::net::UdpSocket;
18-
use std::time::Duration;
19+
use std::time::{Duration, Instant};
1920

2021
use dhcp::mock_api_server;
2122
use dhcproto::{Decodable, Decoder, v4};
@@ -24,7 +25,41 @@ mod common;
2425

2526
use common::{DHCPFactory, Kea, RELAY_IP};
2627

27-
const READ_TIMEOUT: Duration = Duration::from_millis(500);
28+
const READ_TIMEOUT: Duration = Duration::from_millis(200);
29+
const OFFER_TIMEOUT: Duration = Duration::from_secs(10);
30+
31+
fn recv_offer(socket: &UdpSocket, request: &[u8]) -> Result<v4::Message, eyre::Report> {
32+
let deadline = Instant::now() + OFFER_TIMEOUT;
33+
let mut recv_buf = [0u8; 1500]; // packet is 470 bytes, but allow for full MTU
34+
35+
loop {
36+
// Retransmit on each receive timeout, matching DHCP-over-UDP retry
37+
// behavior and avoiding a race with Kea finishing startup.
38+
socket.send(request)?;
39+
match socket.recv(&mut recv_buf) {
40+
Ok(n) => {
41+
let msg = v4::Message::decode(&mut Decoder::new(&recv_buf[..n]))
42+
.map_err(|err| eyre::eyre!("failed to decode DHCP response: {err}"))?;
43+
return Ok(msg);
44+
}
45+
Err(err)
46+
if matches!(
47+
err.kind(),
48+
ErrorKind::WouldBlock | ErrorKind::TimedOut | ErrorKind::Interrupted
49+
) =>
50+
{
51+
if Instant::now() >= deadline {
52+
return Err(eyre::eyre!(
53+
"timed out waiting for DHCP offer after {OFFER_TIMEOUT:?}: {err}"
54+
));
55+
}
56+
}
57+
Err(err) => {
58+
return Err(eyre::eyre!("socket recv unhandled error: {err}"));
59+
}
60+
}
61+
}
62+
}
2863

2964
#[test]
3065
fn test_booturl_internal_with_mtu() -> Result<(), eyre::Report> {
@@ -48,22 +83,13 @@ fn test_booturl_internal_with_mtu() -> Result<(), eyre::Report> {
4883
socket.connect(format!("127.0.0.1:{dhcp_in_port}"))?;
4984
socket.set_read_timeout(Some(READ_TIMEOUT))?;
5085

51-
{
86+
let pkt = {
5287
let mut msg = DHCPFactory::discover(1);
5388
msg.set_xid(0);
54-
let pkt = DHCPFactory::encode(msg)?;
55-
socket.send(&pkt)?;
56-
}
57-
58-
let mut recv_buf = [0u8; 1500]; // packet is 470 bytes, but allow for full MTU
59-
let n = match socket.recv(&mut recv_buf) {
60-
Ok(n) => n,
61-
Err(err) => {
62-
panic!("socket recv unhandled error: {err}");
63-
}
89+
DHCPFactory::encode(msg)?
6490
};
6591

66-
let msg = v4::Message::decode(&mut Decoder::new(&recv_buf[..n])).unwrap();
92+
let msg = recv_offer(&socket, &pkt)?;
6793
let wanted_location = "http://127.0.0.1:8080/public/blobs/internal/x86_64/ipxe.efi"
6894
.to_string()
6995
.into_bytes();
@@ -111,22 +137,13 @@ fn test_booturl_from_api() -> Result<(), eyre::Report> {
111137
socket.connect(format!("127.0.0.1:{dhcp_in_port}"))?;
112138
socket.set_read_timeout(Some(READ_TIMEOUT))?;
113139

114-
{
140+
let pkt = {
115141
let mut msg = DHCPFactory::discover(0xAA);
116142
msg.set_xid(0);
117-
let pkt = DHCPFactory::encode(msg)?;
118-
socket.send(&pkt)?;
119-
}
120-
121-
let mut recv_buf = [0u8; 1500]; // packet is 470 bytes, but allow for full MTU
122-
let n = match socket.recv(&mut recv_buf) {
123-
Ok(n) => n,
124-
Err(err) => {
125-
panic!("socket recv unhandled error: {err}");
126-
}
143+
DHCPFactory::encode(msg)?
127144
};
128145

129-
let msg = v4::Message::decode(&mut Decoder::new(&recv_buf[..n])).unwrap();
146+
let msg = recv_offer(&socket, &pkt)?;
130147

131148
let wanted_location =
132149
"https://api-specified-ipxe-url.forge/public/blobs/internal/x86_64/ipxe.efi"

crates/ipxe-renderer/src/lib.rs

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ use std::collections::HashMap;
2020
use serde::Deserialize;
2121
use sha2::{Digest, Sha256};
2222

23+
const STATIC_IPXE_MENU_TEMPLATE_ID: &str = "c816a939-0993-5ebf-82dd-5227ad215703";
24+
const STATIC_IPXE_MENU_TEMPLATE: &str = include_str!("../../../pxe/ipxe/local/embed.ipxe");
25+
2326
/// iPXE OS definition with template-based rendering support
2427
#[derive(Debug, Clone)]
2528
pub struct IpxeScript {
@@ -64,7 +67,7 @@ pub struct IpxeTemplateArtifact {
6467
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
6568
#[serde(rename_all = "lowercase")]
6669
pub enum IpxeTemplateScope {
67-
/// Carbide-core usage only.
70+
/// NICo Core usage only.
6871
#[default]
6972
Internal,
7073
/// Usable by tenant.
@@ -135,7 +138,7 @@ pub trait IpxeScriptRenderer {
135138
/// Render generates the final iPXE script from an IpxeScript object.
136139
/// Artifact URLs are replaced by local cached URLs when available (cached_url).
137140
/// `reserved_params` must contain exactly the reserved parameters defined
138-
/// in the template (provided by carbide-core).
141+
/// in the template (provided by NICo Core).
139142
fn render(
140143
&self,
141144
ipxeos: &IpxeScript,
@@ -190,7 +193,13 @@ impl DefaultIpxeScriptRenderer {
190193
let templates = template_collection
191194
.templates
192195
.into_iter()
193-
.map(|t| (t.name.clone(), t))
196+
.map(|mut t| {
197+
if t.id == STATIC_IPXE_MENU_TEMPLATE_ID {
198+
t.template = STATIC_IPXE_MENU_TEMPLATE.to_string();
199+
}
200+
201+
(t.name.clone(), t)
202+
})
194203
.collect();
195204

196205
Self { templates }
@@ -1049,6 +1058,47 @@ mod tests {
10491058
assert!(templates.len() >= 11);
10501059
}
10511060

1061+
#[test]
1062+
fn test_static_ipxe_menu_uses_nico_branding() {
1063+
const BRANDING_H: &str = include_str!("../../../pxe/ipxe/local/branding.h");
1064+
1065+
let renderer = DefaultIpxeScriptRenderer::new();
1066+
let menu_template = renderer
1067+
.get_template_by_id(STATIC_IPXE_MENU_TEMPLATE_ID)
1068+
.expect("static iPXE menu template should exist");
1069+
1070+
assert_eq!(menu_template.template, STATIC_IPXE_MENU_TEMPLATE);
1071+
1072+
for (name, contents) in [
1073+
("embedded iPXE script", STATIC_IPXE_MENU_TEMPLATE),
1074+
("iPXE branding header", BRANDING_H),
1075+
(
1076+
"renderer static menu description",
1077+
menu_template.description.as_str(),
1078+
),
1079+
] {
1080+
assert!(contents.contains("NICo"), "{name} should mention NICo");
1081+
}
1082+
1083+
for (name, contents) in [
1084+
("embedded iPXE script", STATIC_IPXE_MENU_TEMPLATE),
1085+
("iPXE branding header", BRANDING_H),
1086+
(
1087+
"renderer static menu description",
1088+
menu_template.description.as_str(),
1089+
),
1090+
] {
1091+
assert!(
1092+
!contents.contains("Carbide") && !contents.contains("carbide"),
1093+
"{name} should not mention Carbide"
1094+
);
1095+
assert!(
1096+
!contents.contains("Forge") && !contents.contains("forge"),
1097+
"{name} should not mention Forge"
1098+
);
1099+
}
1100+
}
1101+
10521102
#[test]
10531103
fn test_get_template_by_name() {
10541104
let renderer = DefaultIpxeScriptRenderer::new();

crates/ipxe-renderer/templates.yaml

Lines changed: 5 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ templates:
2424
# Safety check:
2525
iseq ${arch} unknown && echo "Unsupported architecture." && sleep 5 && exit 1 ||
2626
27-
# 2. Get carbide-core-generated reserved values (base URL for local artifacts and console specific to hardware):
27+
# 2. Get NICo Core generated reserved values (base URL for local artifacts and console specific to hardware):
2828
set base_url {{base_url}}
2929
set console {{console}}
3030
@@ -53,7 +53,7 @@ templates:
5353
# Safety check:
5454
iseq ${arch} unknown && echo "Unsupported architecture." && sleep 5 && exit 1 ||
5555
56-
# 2. Get carbide-core-generated reserved values (base URL for local artifacts and console specific to hardware):
56+
# 2. Get NICo Core generated reserved values (base URL for local artifacts and console specific to hardware):
5757
set base_url {{base_url}}
5858
set console {{console}}
5959
@@ -326,66 +326,7 @@ templates:
326326
327327
- id: c816a939-0993-5ebf-82dd-5227ad215703
328328
name: carbide-menu-static-ipxe
329-
description: Carbide Menu
329+
description: NICo Menu
330330
scope: public
331-
template: |
332-
#!ipxe
333-
334-
set esc:hex 1b
335-
set bold ${esc:string}[1m
336-
set boldoff ${esc:string}[22m
337-
338-
set white ${esc:string}[37m
339-
340-
#color --rgb 0x76b900 7
341-
#cpair --foreground 7 --background 9 0
342-
343-
:start
344-
echo ${bold}${white}Carbide${boldoff} - ${version}
345-
prompt --key p --timeout 4000 Hit the ${bold}p${boldoff} key to open carbide menu... && goto carbide_menu || goto carbide
346-
347-
:whoami
348-
ifconf -c dhcp
349-
# carbide-pxe identifies the booting machine by its client IP (read from
350-
# X-Forwarded-For when proxied, TCP socket peer otherwise) and resolves
351-
# to a machine_interface_id via find_by_ip. iPXE doesn't need to encode
352-
# anything identity-related in the URL -- the network layer carries it.
353-
time chain --autofree http://${next-server}:8080/api/v0/pxe/whoami
354-
goto carbide_menu
355-
356-
:carbide
357-
ifconf -c dhcp
358-
time chain --autofree http://${next-server}:8080/api/v0/pxe/boot?buildarch=${buildarch}&platform=${platform}&manufacturer=${manufacturer}&product=${product}&serial=${serial} && exit 1 || goto error_handler
359-
360-
:carbide_menu
361-
menu Carbide - ${hostname}
362-
item carbide Boot according to Carbide workflow
363-
item whoami Print information about this machine
364-
item localboot Boot to local drive
365-
#item netconfig Manual network configuration
366-
#item vlan Manual VLAN configuration
367-
item retry Retry boot
368-
item debug iPXE Debug Shell
369-
item reboot Reboot System
370-
choose --default carbide --timeout 5000 target && goto ${target}
371-
prompt --key p --timeout 4000 Hit the ${bold}p${boldoff} key to open carbide menu...
372-
goto carbide_menu
373-
374-
:localboot
375-
exit
376-
377-
:retry
378-
goto start
379-
380-
:error_handler
381-
prompt --key p --timeout 4000 Hit the ${bold}p${boldoff} key to continue (4 seconds timeout)...
382-
goto carbide_menu
383-
384-
:debug
385-
echo Type "exit" to return to menu
386-
shell
387-
goto carbide_menu
388-
389-
:reboot
390-
reboot
391-
goto start
331+
# Populated from pxe/ipxe/local/embed.ipxe by DefaultIpxeScriptRenderer::new.
332+
template: ""

pxe/ipxe/local/branding.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#undef PRODUCT_NAME
2-
#define PRODUCT_NAME "Carbide"
2+
#define PRODUCT_NAME "NICo"
33

44
#undef PRODUCT_SHORT_NAME
5-
#define PRODUCT_SHORT_NAME "carbide"
5+
#define PRODUCT_SHORT_NAME "nico"
66

77
#undef PRODUCT_URI
88
#define PRODUCT_URI "http://nvidia.com"
99

1010
#undef PRODUCT_TAG_LINE
11-
#define PRODUCT_TAG_LINE "Carbide Bare Metal provisioning"
11+
#define PRODUCT_TAG_LINE "NICo bare-metal provisioning"

pxe/ipxe/local/embed.ipxe

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,35 @@ set white ${esc:string}[37m
1010
#cpair --foreground 7 --background 9 0
1111

1212
:start
13-
echo ${bold}${white}Carbide${boldoff} - ${version}
14-
prompt --key p --timeout 4000 Hit the ${bold}p${boldoff} key to open carbide menu... && goto carbide_menu || goto carbide
13+
echo ${bold}${white}NICo${boldoff} - ${version}
14+
prompt --key p --timeout 4000 Hit the ${bold}p${boldoff} key to open NICo menu... && goto nico_menu || goto nico
1515

1616
:whoami
1717
ifconf -c dhcp
18-
# carbide-pxe identifies the booting machine by its client IP (read from
18+
# nico-pxe identifies the booting machine by its client IP (read from
1919
# X-Forwarded-For when proxied, TCP socket peer otherwise) and resolves
2020
# to a machine_interface_id via find_by_ip. iPXE doesn't need to encode
2121
# anything identity-related in the URL -- the network layer carries it.
2222
time chain --autofree http://${next-server}:8080/api/v0/pxe/whoami
23-
goto carbide_menu
23+
goto nico_menu
2424

25-
:carbide
25+
:nico
2626
ifconf -c dhcp
2727
time chain --autofree http://${next-server}:8080/api/v0/pxe/boot?buildarch=${buildarch}&platform=${platform}&manufacturer=${manufacturer}&product=${product}&serial=${serial} && exit 1 || goto error_handler
2828

29-
:carbide_menu
30-
menu Carbide - ${hostname}
31-
item carbide Boot according to Carbide workflow
29+
:nico_menu
30+
menu NICo - ${hostname}
31+
item nico Boot according to NICo workflow
3232
item whoami Print information about this machine
3333
item localboot Boot to local drive
3434
#item netconfig Manual network configuration
3535
#item vlan Manual VLAN configuration
3636
item retry Retry boot
3737
item debug iPXE Debug Shell
3838
item reboot Reboot System
39-
choose --default carbide --timeout 5000 target && goto ${target}
40-
prompt --key p --timeout 4000 Hit the ${bold}p${boldoff} key to open carbide menu...
41-
goto carbide_menu
39+
choose --default nico --timeout 5000 target && goto ${target}
40+
prompt --key p --timeout 4000 Hit the ${bold}p${boldoff} key to open NICo menu...
41+
goto nico_menu
4242

4343
:localboot
4444
exit
@@ -48,12 +48,12 @@ goto start
4848

4949
:error_handler
5050
prompt --key p --timeout 4000 Hit the ${bold}p${boldoff} key to continue (4 seconds timeout)...
51-
goto carbide_menu
51+
goto nico_menu
5252

5353
:debug
5454
echo Type "exit" to return to menu
5555
shell
56-
goto carbide_menu
56+
goto nico_menu
5757

5858
:reboot
5959
reboot

0 commit comments

Comments
 (0)