Skip to content

Commit a737b7a

Browse files
committed
Set external_http_url on the node when BMC is IPv6
When the BMC address is IPv6, we need to set the `external_http_url` value on the node so that the node can access the provisioning image.
1 parent edd9287 commit a737b7a

4 files changed

Lines changed: 156 additions & 0 deletions

File tree

docs/configuration.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ but overflows could happen in case of slow provisioners and / or higher number o
4444
concurrent reconciles. For such reasons, it is highly recommended to keep
4545
BMO_CONCURRENCY value lower than the requested PROVISIONING_LIMIT. Default is 20.
4646

47+
`IRONIC_EXTERNAL_URL_V6` -- This is the URL where Ironic will find the image for
48+
nodes that use IPv6. In dual stack environments, this can be used to tell Ironic which IP
49+
version it should set on the BMC.
50+
4751
Kustomization Configuration
4852
---------------------------
4953

pkg/provisioner/ironic/factory.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package ironic
33
import (
44
"errors"
55
"fmt"
6+
"net/url"
67
"os"
78
"strconv"
89
"strings"
@@ -153,6 +154,17 @@ func loadConfigFromEnv(havePreprovImgBuilder bool) (ironicConfig, error) {
153154
c.liveISOForcePersistentBootDevice = forcePersistentBootDevice
154155
}
155156

157+
c.externalURL = os.Getenv("IRONIC_EXTERNAL_URL_V6")
158+
159+
// Let's see if externalURL looks like a URL
160+
if c.externalURL != "" {
161+
_, externalURLParseErr := url.Parse(c.externalURL)
162+
163+
if externalURLParseErr != nil {
164+
return c, externalURLParseErr
165+
}
166+
}
167+
156168
return c, nil
157169
}
158170

pkg/provisioner/ironic/ironic.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ironic
22

33
import (
44
"fmt"
5+
"net"
56
"strings"
67
"time"
78

@@ -75,6 +76,7 @@ type ironicConfig struct {
7576
deployISOURL string
7677
liveISOForcePersistentBootDevice string
7778
maxBusyHosts int
79+
externalURL string
7880
}
7981

8082
// Provisioner implements the provisioning.Provisioner interface
@@ -352,6 +354,7 @@ func (p *ironicProvisioner) ValidateManagementAccess(data provisioner.Management
352354
}
353355

354356
driverInfo := bmcAccess.DriverInfo(p.bmcCreds)
357+
driverInfo = setExternalURL(p, driverInfo)
355358
deployImageInfo := setDeployImage(driverInfo, p.config, bmcAccess, data.PreprovisioningImage)
356359

357360
// If we have not found a node yet, we need to create one
@@ -580,6 +583,48 @@ func (p *ironicProvisioner) PreprovisioningImageFormats() ([]metal3v1alpha1.Imag
580583
return formats, nil
581584
}
582585

586+
func setExternalURL(p *ironicProvisioner, driverInfo map[string]interface{}) map[string]interface{} {
587+
if _, ok := driverInfo["external_http_url"]; ok {
588+
driverInfo["external_http_url"] = nil
589+
}
590+
591+
if p.config.externalURL == "" {
592+
return driverInfo
593+
}
594+
595+
parsedURL, err := bmc.GetParsedURL(p.bmcAddress)
596+
if err != nil {
597+
p.log.Info("Failed to parse BMC address", "bmcAddress", p.bmcAddress, "err", err)
598+
return driverInfo
599+
}
600+
601+
ip := net.ParseIP(parsedURL.Hostname())
602+
if ip == nil {
603+
// Maybe it's a hostname?
604+
ips, err := net.LookupIP(p.bmcAddress)
605+
if err != nil {
606+
p.log.Info("Failed to look up the IP address for BMC hostname", "hostname", p.bmcAddress)
607+
return driverInfo
608+
}
609+
610+
if len(ips) == 0 {
611+
p.log.Info("Zero IP addresses for BMC hostname", "hostname", p.bmcAddress)
612+
return driverInfo
613+
}
614+
615+
ip = ips[0]
616+
}
617+
618+
// In the case of IPv4, we don't have to do anything.
619+
if ip.To4() != nil {
620+
return driverInfo
621+
}
622+
623+
driverInfo["external_http_url"] = p.config.externalURL
624+
625+
return driverInfo
626+
}
627+
583628
func setDeployImage(driverInfo map[string]interface{}, config ironicConfig, accessDetails bmc.AccessDetails, hostImage *provisioner.PreprovisioningImage) optionsData {
584629
deployImageInfo := optionsData{
585630
deployKernelKey: nil,

pkg/provisioner/ironic/validatemanagementaccess_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,3 +1148,98 @@ func TestSetDeployImage(t *testing.T) {
11481148
})
11491149
}
11501150
}
1151+
1152+
func TestSetExternalURL(t *testing.T) {
1153+
host := makeHost()
1154+
host.Spec.BMC.Address = "redfish-virtualmedia://[fe80::fc33:62ff:fe83:8a76]:6233"
1155+
1156+
ironic := testserver.NewIronic(t).Ready().
1157+
Node(nodes.Node{
1158+
Name: host.Namespace + nameSeparator + host.Name,
1159+
UUID: host.Status.Provisioning.ID,
1160+
}).NodeUpdate(nodes.Node{
1161+
UUID: host.Status.Provisioning.ID,
1162+
})
1163+
1164+
ironic.Start()
1165+
defer ironic.Stop()
1166+
1167+
auth := clients.AuthConfig{Type: clients.NoAuth}
1168+
prov, err := newProvisionerWithSettings(host, bmc.Credentials{}, nil,
1169+
ironic.Endpoint(), auth, testserver.NewInspector(t).Endpoint(), auth,
1170+
)
1171+
1172+
if err != nil {
1173+
t.Fatalf("could not create provisioner: %s", err)
1174+
}
1175+
1176+
prov.config.externalURL = "XXX"
1177+
1178+
driverInfo := make(map[string]interface{}, 0)
1179+
updatedDriverInfo := setExternalURL(prov, driverInfo)
1180+
1181+
assert.Equal(t, "XXX", updatedDriverInfo["external_http_url"])
1182+
}
1183+
1184+
func TestSetExternalURLIPv4(t *testing.T) {
1185+
host := makeHost()
1186+
host.Spec.BMC.Address = "redfish-virtualmedia://1.1.1.1:1111"
1187+
1188+
ironic := testserver.NewIronic(t).Ready().
1189+
Node(nodes.Node{
1190+
Name: host.Namespace + nameSeparator + host.Name,
1191+
UUID: host.Status.Provisioning.ID,
1192+
}).NodeUpdate(nodes.Node{
1193+
UUID: host.Status.Provisioning.ID,
1194+
})
1195+
1196+
ironic.Start()
1197+
defer ironic.Stop()
1198+
1199+
auth := clients.AuthConfig{Type: clients.NoAuth}
1200+
prov, err := newProvisionerWithSettings(host, bmc.Credentials{}, nil,
1201+
ironic.Endpoint(), auth, testserver.NewInspector(t).Endpoint(), auth,
1202+
)
1203+
1204+
if err != nil {
1205+
t.Fatalf("could not create provisioner: %s", err)
1206+
}
1207+
1208+
prov.config.externalURL = "XXX"
1209+
1210+
driverInfo := make(map[string]interface{}, 0)
1211+
updatedDriverInfo := setExternalURL(prov, driverInfo)
1212+
1213+
assert.Equal(t, nil, updatedDriverInfo["external_http_url"])
1214+
}
1215+
1216+
func TestSetExternalURLRemoving(t *testing.T) {
1217+
host := makeHost()
1218+
host.Spec.BMC.Address = "redfish-virtualmedia://1.1.1.1:1111"
1219+
1220+
ironic := testserver.NewIronic(t).Ready().
1221+
Node(nodes.Node{
1222+
Name: host.Namespace + nameSeparator + host.Name,
1223+
UUID: host.Status.Provisioning.ID,
1224+
}).NodeUpdate(nodes.Node{
1225+
UUID: host.Status.Provisioning.ID,
1226+
})
1227+
1228+
ironic.Start()
1229+
defer ironic.Stop()
1230+
1231+
auth := clients.AuthConfig{Type: clients.NoAuth}
1232+
prov, err := newProvisionerWithSettings(host, bmc.Credentials{}, nil,
1233+
ironic.Endpoint(), auth, testserver.NewInspector(t).Endpoint(), auth,
1234+
)
1235+
1236+
if err != nil {
1237+
t.Fatalf("could not create provisioner: %s", err)
1238+
}
1239+
1240+
driverInfo := make(map[string]interface{}, 0)
1241+
driverInfo["external_http_url"] = "non-empty"
1242+
updatedDriverInfo := setExternalURL(prov, driverInfo)
1243+
1244+
assert.Equal(t, nil, updatedDriverInfo["external_http_url"])
1245+
}

0 commit comments

Comments
 (0)