Skip to content

Commit f8a2a9b

Browse files
mcanevetsmira
authored andcommitted
fix(machined): opennebula: process ETH*_ vars regardless of NETWORK context flag
The NETWORK=YES context variable is a server-side OpenNebula directive that instructs the OpenNebula server to auto-inject ETH*_ variables into the VM context from the NIC definitions. It is not a guest-side signal. The official OpenNebula guest contextualization scripts (one-apps/context-linux) never read this variable. Their get_context_interfaces() function uses ETH*_MAC key presence as the sole trigger, with no NETWORK=YES check. Gating on NETWORK=YES breaks setups where NETWORK=NO (or absent) is intentional — for example, VMs using ETHER-type address ranges, where setting NETWORK=YES would cause the server to overwrite all manually-specified ETH*_ variables with empty strings (ETHER ARs carry no IP data). Remove the outer NETWORK=YES guard, relying solely on ETH*_MAC key presence (same as the reference implementation). Add a test case with NETWORK=NO to explicitly cover this scenario. Signed-off-by: Mickaël Canévet <mickael.canevet@proton.ch> Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com> (cherry picked from commit ad29417)
1 parent db9ff23 commit f8a2a9b

File tree

4 files changed

+202
-100
lines changed

4 files changed

+202
-100
lines changed

internal/app/machined/pkg/runtime/v1alpha1/platform/opennebula/opennebula.go

Lines changed: 104 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -57,122 +57,126 @@ func (o *OpenNebula) ParseMetadata(st state.State, oneContextPlain []byte) (*run
5757
}
5858
}
5959

60-
if oneContext["NETWORK"] == "YES" {
61-
// Iterate through parsed environment variables
62-
for key := range oneContext {
63-
// Dereference the pointer here
64-
if strings.HasPrefix(key, "ETH") && strings.HasSuffix(key, "_MAC") {
65-
ifaceName := strings.TrimSuffix(key, "_MAC")
66-
ifaceNameLower := strings.ToLower(ifaceName)
67-
68-
if oneContext[ifaceName+"_METHOD"] == "dhcp" {
69-
// Create DHCP4 OperatorSpec entry
70-
networkConfig.Operators = append(networkConfig.Operators,
71-
network.OperatorSpecSpec{
72-
Operator: network.OperatorDHCP4,
73-
LinkName: ifaceNameLower,
74-
RequireUp: true,
75-
DHCP4: network.DHCP4OperatorSpec{
76-
RouteMetric: 1024,
77-
SkipHostnameRequest: true,
78-
},
79-
ConfigLayer: network.ConfigPlatform,
60+
// Iterate through parsed environment variables looking for ETHn_MAC keys.
61+
// The presence of ETHn_MAC is the sole trigger for interface configuration,
62+
// matching the behavior of the official OpenNebula guest contextualization
63+
// scripts (one-apps/context-linux: get_context_interfaces() uses ETH*_MAC
64+
// presence exclusively). The NETWORK context variable is a server-side
65+
// directive that tells OpenNebula to auto-inject ETH*_ variables from NIC
66+
// definitions; it is not a guest-side signal and is never read by the
67+
// official scripts.
68+
for key := range oneContext {
69+
if strings.HasPrefix(key, "ETH") && strings.HasSuffix(key, "_MAC") {
70+
ifaceName := strings.TrimSuffix(key, "_MAC")
71+
ifaceNameLower := strings.ToLower(ifaceName)
72+
73+
if oneContext[ifaceName+"_METHOD"] == "dhcp" {
74+
// Create DHCP4 OperatorSpec entry
75+
networkConfig.Operators = append(networkConfig.Operators,
76+
network.OperatorSpecSpec{
77+
Operator: network.OperatorDHCP4,
78+
LinkName: ifaceNameLower,
79+
RequireUp: true,
80+
DHCP4: network.DHCP4OperatorSpec{
81+
RouteMetric: 1024,
82+
SkipHostnameRequest: true,
8083
},
81-
)
84+
ConfigLayer: network.ConfigPlatform,
85+
},
86+
)
87+
} else {
88+
// Parse IP address and create AddressSpecSpec entry
89+
ipPrefix, err := address.IPPrefixFrom(oneContext[ifaceName+"_IP"], oneContext[ifaceName+"_MASK"])
90+
if err != nil {
91+
return nil, fmt.Errorf("failed to parse IP address: %w", err)
92+
}
93+
94+
networkConfig.Addresses = append(networkConfig.Addresses,
95+
network.AddressSpecSpec{
96+
Address: ipPrefix,
97+
LinkName: ifaceNameLower,
98+
Family: nethelpers.FamilyInet4,
99+
Scope: nethelpers.ScopeGlobal,
100+
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
101+
AnnounceWithARP: false,
102+
ConfigLayer: network.ConfigPlatform,
103+
},
104+
)
105+
106+
var mtu uint32
107+
108+
if oneContext[ifaceName+"_MTU"] == "" {
109+
mtu = 0
82110
} else {
83-
// Parse IP address and create AddressSpecSpec entry
84-
ipPrefix, err := address.IPPrefixFrom(oneContext[ifaceName+"_IP"], oneContext[ifaceName+"_MASK"])
111+
var mtu64 uint64
112+
113+
mtu64, err = strconv.ParseUint(oneContext[ifaceName+"_MTU"], 10, 32)
114+
// check if any error happened
85115
if err != nil {
86-
return nil, fmt.Errorf("failed to parse IP address: %w", err)
116+
return nil, fmt.Errorf("failed to parse MTU: %w", err)
87117
}
88118

89-
networkConfig.Addresses = append(networkConfig.Addresses,
90-
network.AddressSpecSpec{
91-
Address: ipPrefix,
92-
LinkName: ifaceNameLower,
93-
Family: nethelpers.FamilyInet4,
94-
Scope: nethelpers.ScopeGlobal,
95-
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
96-
AnnounceWithARP: false,
97-
ConfigLayer: network.ConfigPlatform,
98-
},
99-
)
100-
101-
var mtu uint32
102-
103-
if oneContext[ifaceName+"_MTU"] == "" {
104-
mtu = 0
105-
} else {
106-
var mtu64 uint64
107-
108-
mtu64, err = strconv.ParseUint(oneContext[ifaceName+"_MTU"], 10, 32)
109-
// check if any error happened
110-
if err != nil {
111-
return nil, fmt.Errorf("failed to parse MTU: %w", err)
112-
}
119+
mtu = uint32(mtu64)
120+
}
113121

114-
mtu = uint32(mtu64)
122+
// Create LinkSpecSpec entry
123+
networkConfig.Links = append(networkConfig.Links,
124+
network.LinkSpecSpec{
125+
Name: ifaceNameLower,
126+
Logical: false,
127+
Up: true,
128+
MTU: mtu,
129+
Kind: "",
130+
Type: nethelpers.LinkEther,
131+
ParentName: "",
132+
ConfigLayer: network.ConfigPlatform,
133+
},
134+
)
135+
136+
if oneContext[ifaceName+"_GATEWAY"] != "" {
137+
// Parse gateway address and create RouteSpecSpec entry
138+
gateway, err := netip.ParseAddr(oneContext[ifaceName+"_GATEWAY"])
139+
if err != nil {
140+
return nil, fmt.Errorf("failed to parse gateway ip: %w", err)
115141
}
116142

117-
// Create LinkSpecSpec entry
118-
networkConfig.Links = append(networkConfig.Links,
119-
network.LinkSpecSpec{
120-
Name: ifaceNameLower,
121-
Logical: false,
122-
Up: true,
123-
MTU: mtu,
124-
Kind: "",
125-
Type: nethelpers.LinkEther,
126-
ParentName: "",
127-
ConfigLayer: network.ConfigPlatform,
128-
},
129-
)
130-
131-
if oneContext[ifaceName+"_GATEWAY"] != "" {
132-
// Parse gateway address and create RouteSpecSpec entry
133-
gateway, err := netip.ParseAddr(oneContext[ifaceName+"_GATEWAY"])
134-
if err != nil {
135-
return nil, fmt.Errorf("failed to parse gateway ip: %w", err)
136-
}
137-
138-
route := network.RouteSpecSpec{
139-
ConfigLayer: network.ConfigPlatform,
140-
Gateway: gateway,
141-
OutLinkName: ifaceNameLower,
142-
Table: nethelpers.TableMain,
143-
Protocol: nethelpers.ProtocolStatic,
144-
Type: nethelpers.TypeUnicast,
145-
Family: nethelpers.FamilyInet4,
146-
Priority: network.DefaultRouteMetric,
147-
}
148-
149-
route.Normalize()
150-
151-
networkConfig.Routes = append(networkConfig.Routes, route)
143+
route := network.RouteSpecSpec{
144+
ConfigLayer: network.ConfigPlatform,
145+
Gateway: gateway,
146+
OutLinkName: ifaceNameLower,
147+
Table: nethelpers.TableMain,
148+
Protocol: nethelpers.ProtocolStatic,
149+
Type: nethelpers.TypeUnicast,
150+
Family: nethelpers.FamilyInet4,
151+
Priority: network.DefaultRouteMetric,
152152
}
153153

154-
// Parse DNS servers
155-
dnsServers := strings.Fields(oneContext[ifaceName+"_DNS"])
154+
route.Normalize()
155+
156+
networkConfig.Routes = append(networkConfig.Routes, route)
157+
}
156158

157-
var dnsIPs []netip.Addr
159+
// Parse DNS servers
160+
dnsServers := strings.Fields(oneContext[ifaceName+"_DNS"])
158161

159-
for _, dnsServer := range dnsServers {
160-
ip, err := netip.ParseAddr(dnsServer)
161-
if err != nil {
162-
return nil, fmt.Errorf("failed to parse DNS server IP: %w", err)
163-
}
162+
var dnsIPs []netip.Addr
164163

165-
dnsIPs = append(dnsIPs, ip)
164+
for _, dnsServer := range dnsServers {
165+
ip, err := netip.ParseAddr(dnsServer)
166+
if err != nil {
167+
return nil, fmt.Errorf("failed to parse DNS server IP: %w", err)
166168
}
167169

168-
// Create ResolverSpecSpec entry with multiple DNS servers
169-
networkConfig.Resolvers = append(networkConfig.Resolvers,
170-
network.ResolverSpecSpec{
171-
DNSServers: dnsIPs,
172-
ConfigLayer: network.ConfigPlatform,
173-
},
174-
)
170+
dnsIPs = append(dnsIPs, ip)
175171
}
172+
173+
// Create ResolverSpecSpec entry with multiple DNS servers
174+
networkConfig.Resolvers = append(networkConfig.Resolvers,
175+
network.ResolverSpecSpec{
176+
DNSServers: dnsIPs,
177+
ConfigLayer: network.ConfigPlatform,
178+
},
179+
)
176180
}
177181
}
178182
}

internal/app/machined/pkg/runtime/v1alpha1/platform/opennebula/opennebula_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ var oneContextPlain []byte
2424
//go:embed testdata/expected.yaml
2525
var expectedNetworkConfig string
2626

27+
//go:embed testdata/metadata_no_network_flag.yaml
28+
var oneContextPlainNoNetworkFlag []byte
29+
30+
//go:embed testdata/expected_no_network_flag.yaml
31+
var expectedNetworkConfigNoNetworkFlag string
32+
2733
func TestParseMetadata(t *testing.T) {
2834
o := &opennebula.OpenNebula{}
2935
st := state.WrapCore(namespaced.NewState(inmem.Build))
@@ -37,3 +43,20 @@ func TestParseMetadata(t *testing.T) {
3743
t.Log(string(marshaled))
3844
assert.Equal(t, expectedNetworkConfig, string(marshaled))
3945
}
46+
47+
// TestParseMetadataNoNetworkFlag verifies that ETH*_ variables are processed
48+
// regardless of the NETWORK context variable value. NETWORK=YES is a
49+
// server-side OpenNebula directive and should not gate guest-side processing.
50+
func TestParseMetadataNoNetworkFlag(t *testing.T) {
51+
o := &opennebula.OpenNebula{}
52+
st := state.WrapCore(namespaced.NewState(inmem.Build))
53+
54+
networkConfig, err := o.ParseMetadata(st, oneContextPlainNoNetworkFlag)
55+
require.NoError(t, err)
56+
57+
marshaled, err := yaml.Marshal(networkConfig)
58+
require.NoError(t, err)
59+
60+
t.Log(string(marshaled))
61+
assert.Equal(t, expectedNetworkConfigNoNetworkFlag, string(marshaled))
62+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
addresses:
2+
- address: 192.168.1.92/24
3+
linkName: eth0
4+
family: inet4
5+
scope: global
6+
flags: permanent
7+
layer: platform
8+
links:
9+
- name: eth0
10+
logical: false
11+
up: true
12+
mtu: 0
13+
kind: ""
14+
type: ether
15+
layer: platform
16+
routes:
17+
- family: inet4
18+
dst: ""
19+
src: ""
20+
gateway: 192.168.1.1
21+
outLinkName: eth0
22+
table: main
23+
priority: 1024
24+
scope: global
25+
type: unicast
26+
flags: ""
27+
protocol: static
28+
layer: platform
29+
hostnames:
30+
- hostname: code-server
31+
domainname: ""
32+
layer: platform
33+
resolvers:
34+
- dnsServers:
35+
- 192.168.1.1
36+
- 8.8.8.8
37+
- 1.1.1.1
38+
layer: platform
39+
timeServers: []
40+
operators: []
41+
externalIPs: []
42+
metadata:
43+
platform: opennebula
44+
hostname: code-server
45+
instanceId: "14"
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Context variables generated by OpenNebula with NETWORK=NO.
2+
# ETH*_ variables are manually specified (e.g. for ETHER-type address ranges
3+
# where NETWORK=YES would cause the server to overwrite them with empty values).
4+
DISK_ID = "1"
5+
ETH0_DNS = "192.168.1.1 8.8.8.8 1.1.1.1"
6+
ETH0_EXTERNAL = ""
7+
ETH0_GATEWAY = "192.168.1.1"
8+
ETH0_IP = "192.168.1.92"
9+
ETH0_IP6 = ""
10+
ETH0_IP6_GATEWAY = ""
11+
ETH0_IP6_METHOD = ""
12+
ETH0_IP6_METRIC = ""
13+
ETH0_IP6_PREFIX_LENGTH = ""
14+
ETH0_IP6_ULA = ""
15+
ETH0_MAC = "02:00:c0:a8:01:5c"
16+
ETH0_MASK = "255.255.255.0"
17+
ETH0_METHOD = ""
18+
ETH0_METRIC = ""
19+
ETH0_MTU = ""
20+
ETH0_NETWORK = "192.168.1.0"
21+
ETH0_SEARCH_DOMAIN = ""
22+
ETH0_VLAN_ID = "3"
23+
ETH0_VROUTER_IP = ""
24+
ETH0_VROUTER_IP6 = ""
25+
ETH0_VROUTER_MANAGEMENT = ""
26+
NETWORK = "NO"
27+
SSH_PUBLIC_KEY = ""
28+
TARGET = "hda"
29+
VMID = "14"
30+
NAME = "code-server"

0 commit comments

Comments
 (0)