Skip to content

Commit 21ec248

Browse files
Merge pull request #900 from dtantsur/configdrive
Fix configdrive handling
2 parents acc871e + 2070dfa commit 21ec248

File tree

2 files changed

+201
-49
lines changed

2 files changed

+201
-49
lines changed
+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package ironic
2+
3+
import (
4+
"testing"
5+
6+
"github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes"
7+
"github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection"
8+
"github.com/stretchr/testify/assert"
9+
10+
"github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1"
11+
"github.com/metal3-io/baremetal-operator/pkg/bmc"
12+
"github.com/metal3-io/baremetal-operator/pkg/provisioner"
13+
"github.com/metal3-io/baremetal-operator/pkg/provisioner/fixture"
14+
"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/clients"
15+
"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/testserver"
16+
)
17+
18+
func TestEmpty(t *testing.T) {
19+
nodeUUID := "33ce8659-7400-4c68-9535-d10766f07a58"
20+
21+
cases := []struct {
22+
name string
23+
hostData provisioner.HostConfigData
24+
expected nodes.ConfigDrive
25+
}{
26+
{
27+
name: "empty",
28+
hostData: fixture.NewHostConfigData("", "", ""),
29+
expected: nodes.ConfigDrive{},
30+
},
31+
{
32+
name: "everything",
33+
hostData: fixture.NewHostConfigData("testUserData", "test: NetworkData", "test: Meta"),
34+
expected: nodes.ConfigDrive{
35+
MetaData: map[string]interface{}{
36+
"local-hostname": "myhost",
37+
"local_hostname": "myhost",
38+
"metal3-name": "myhost",
39+
"metal3-namespace": "myns",
40+
"name": "myhost",
41+
"test": "Meta",
42+
},
43+
NetworkData: map[string]interface{}{
44+
"test": "NetworkData",
45+
},
46+
UserData: "testUserData",
47+
},
48+
},
49+
{
50+
name: "only network data",
51+
hostData: fixture.NewHostConfigData("", "test: NetworkData", ""),
52+
expected: nodes.ConfigDrive{
53+
MetaData: map[string]interface{}{
54+
"local-hostname": "myhost",
55+
"local_hostname": "myhost",
56+
"metal3-name": "myhost",
57+
"metal3-namespace": "myns",
58+
"name": "myhost",
59+
},
60+
NetworkData: map[string]interface{}{
61+
"test": "NetworkData",
62+
},
63+
},
64+
},
65+
{
66+
name: "only user data",
67+
hostData: fixture.NewHostConfigData("testUserData", "", ""),
68+
expected: nodes.ConfigDrive{
69+
MetaData: map[string]interface{}{
70+
"local-hostname": "myhost",
71+
"local_hostname": "myhost",
72+
"metal3-name": "myhost",
73+
"metal3-namespace": "myns",
74+
"name": "myhost",
75+
},
76+
UserData: "testUserData",
77+
},
78+
},
79+
{
80+
name: "only meta data",
81+
hostData: fixture.NewHostConfigData("", "", "test: Meta"),
82+
expected: nodes.ConfigDrive{
83+
MetaData: map[string]interface{}{
84+
"local-hostname": "myhost",
85+
"local_hostname": "myhost",
86+
"metal3-name": "myhost",
87+
"metal3-namespace": "myns",
88+
"name": "myhost",
89+
"test": "Meta",
90+
},
91+
},
92+
},
93+
}
94+
95+
for _, tc := range cases {
96+
t.Run(tc.name, func(t *testing.T) {
97+
ironic := testserver.NewIronic(t).Ready().Node(nodes.Node{
98+
ProvisionState: string(nodes.Active),
99+
UUID: nodeUUID,
100+
})
101+
ironic.Start()
102+
defer ironic.Stop()
103+
104+
inspector := testserver.NewInspector(t).Ready().WithIntrospection(nodeUUID, introspection.Introspection{
105+
Finished: false,
106+
})
107+
inspector.Start()
108+
defer inspector.Stop()
109+
110+
host := makeHost()
111+
host.Status.Provisioning.ID = nodeUUID
112+
publisher := func(reason, message string) {}
113+
auth := clients.AuthConfig{Type: clients.NoAuth}
114+
prov, err := newProvisionerWithSettings(host, bmc.Credentials{}, publisher,
115+
ironic.Endpoint(), auth, inspector.Endpoint(), auth,
116+
)
117+
if err != nil {
118+
t.Fatalf("could not create provisioner: %s", err)
119+
}
120+
121+
result, err := prov.getConfigDrive(provisioner.ProvisionData{
122+
HostConfig: tc.hostData,
123+
BootMode: v1alpha1.DefaultBootMode,
124+
})
125+
126+
if len(tc.expected.MetaData) > 0 {
127+
tc.expected.MetaData["uuid"] = string(prov.objectMeta.UID)
128+
}
129+
130+
assert.Equal(t, tc.expected, result)
131+
assert.NoError(t, err)
132+
})
133+
}
134+
}

pkg/provisioner/ironic/ironic.go

+67-49
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,59 @@ func (p *ironicProvisioner) Prepare(data provisioner.PrepareData, unprepared boo
12521252
return
12531253
}
12541254

1255+
func (p *ironicProvisioner) getConfigDrive(data provisioner.ProvisionData) (configDrive nodes.ConfigDrive, err error) {
1256+
// Retrieve instance specific user data (cloud-init, ignition, etc).
1257+
userData, err := data.HostConfig.UserData()
1258+
if err != nil {
1259+
return configDrive, errors.Wrap(err, "could not retrieve user data")
1260+
}
1261+
if userData != "" {
1262+
configDrive.UserData = userData
1263+
}
1264+
1265+
// Retrieve OpenStack network_data. Default value is empty.
1266+
networkDataRaw, err := data.HostConfig.NetworkData()
1267+
if err != nil {
1268+
return configDrive, errors.Wrap(err, "could not retrieve network data")
1269+
}
1270+
if networkDataRaw != "" {
1271+
var networkData map[string]interface{}
1272+
if err = yaml.Unmarshal([]byte(networkDataRaw), &networkData); err != nil {
1273+
return configDrive, errors.Wrap(err, "failed to unmarshal network_data.json from secret")
1274+
}
1275+
configDrive.NetworkData = networkData
1276+
}
1277+
1278+
// Retrieve meta data with fallback to defaults from provisioner.
1279+
metaData := map[string]interface{}{
1280+
"uuid": string(p.objectMeta.UID),
1281+
"metal3-namespace": p.objectMeta.Namespace,
1282+
"metal3-name": p.objectMeta.Name,
1283+
"local-hostname": p.objectMeta.Name,
1284+
"local_hostname": p.objectMeta.Name,
1285+
"name": p.objectMeta.Name,
1286+
}
1287+
metaDataRaw, err := data.HostConfig.MetaData()
1288+
if err != nil {
1289+
return configDrive, errors.Wrap(err, "could not retrieve metadata")
1290+
}
1291+
if metaDataRaw != "" {
1292+
if err = yaml.Unmarshal([]byte(metaDataRaw), &metaData); err != nil {
1293+
return configDrive, errors.Wrap(err, "failed to unmarshal metadata from secret")
1294+
}
1295+
}
1296+
1297+
// Set metaData if any field is populated by a user.
1298+
if metaDataRaw != "" || networkDataRaw != "" || userData != "" {
1299+
configDrive.MetaData = metaData
1300+
p.log.Info("triggering provisioning with config drive")
1301+
} else {
1302+
p.log.Info("triggering provisioning without config drive")
1303+
}
1304+
1305+
return
1306+
}
1307+
12551308
// Provision writes the image from the host spec to the host. It may
12561309
// be called multiple times, and should return true for its dirty flag
12571310
// until the deprovisioning operation is completed.
@@ -1290,8 +1343,18 @@ func (p *ironicProvisioner) Provision(data provisioner.ProvisionData) (result pr
12901343
return provResult, err
12911344
}
12921345

1293-
return p.changeNodeProvisionState(ironicNode,
1294-
nodes.ProvisionStateOpts{Target: nodes.TargetActive})
1346+
configDrive, err := p.getConfigDrive(data)
1347+
if err != nil {
1348+
return transientError(err)
1349+
}
1350+
1351+
return p.changeNodeProvisionState(
1352+
ironicNode,
1353+
nodes.ProvisionStateOpts{
1354+
Target: nodes.TargetActive,
1355+
ConfigDrive: configDrive,
1356+
},
1357+
)
12951358

12961359
case nodes.Manageable:
12971360
return p.changeNodeProvisionState(ironicNode,
@@ -1316,54 +1379,9 @@ func (p *ironicProvisioner) Provision(data provisioner.ProvisionData) (result pr
13161379
// setting the state to "active".
13171380
p.log.Info("making host active")
13181381

1319-
// Retrieve cloud-init user data
1320-
userData, err := data.HostConfig.UserData()
1321-
if err != nil {
1322-
return transientError(errors.Wrap(err, "could not retrieve user data"))
1323-
}
1324-
1325-
// Retrieve cloud-init network_data.json. Default value is empty
1326-
networkDataRaw, err := data.HostConfig.NetworkData()
1327-
if err != nil {
1328-
return transientError(errors.Wrap(err, "could not retrieve network data"))
1329-
}
1330-
var networkData map[string]interface{}
1331-
if err = yaml.Unmarshal([]byte(networkDataRaw), &networkData); err != nil {
1332-
return transientError(errors.Wrap(err, "failed to unmarshal network_data.json from secret"))
1333-
}
1334-
1335-
// Retrieve cloud-init meta_data.json with falback to default
1336-
metaData := map[string]interface{}{
1337-
"uuid": string(p.objectMeta.UID),
1338-
"metal3-namespace": p.objectMeta.Namespace,
1339-
"metal3-name": p.objectMeta.Name,
1340-
"local-hostname": p.objectMeta.Name,
1341-
"local_hostname": p.objectMeta.Name,
1342-
"name": p.objectMeta.Name,
1343-
}
1344-
metaDataRaw, err := data.HostConfig.MetaData()
1382+
configDrive, err := p.getConfigDrive(data)
13451383
if err != nil {
1346-
return transientError(errors.Wrap(err, "could not retrieve metadata"))
1347-
}
1348-
if metaDataRaw != "" {
1349-
if err = yaml.Unmarshal([]byte(metaDataRaw), &metaData); err != nil {
1350-
return transientError(errors.Wrap(err, "failed to unmarshal metadata from secret"))
1351-
}
1352-
}
1353-
1354-
var configDrive nodes.ConfigDrive
1355-
if userData != "" {
1356-
configDrive = nodes.ConfigDrive{
1357-
UserData: userData,
1358-
MetaData: metaData,
1359-
NetworkData: networkData,
1360-
}
1361-
if err != nil {
1362-
return transientError(errors.Wrap(err, "failed to build config drive"))
1363-
}
1364-
p.log.Info("triggering provisioning with config drive")
1365-
} else {
1366-
p.log.Info("triggering provisioning without config drive")
1384+
return transientError(err)
13671385
}
13681386

13691387
return p.changeNodeProvisionState(

0 commit comments

Comments
 (0)