diff --git a/pkg/asset/agent/image/ignition.go b/pkg/asset/agent/image/ignition.go index 601d9732257..b3a843dfd94 100644 --- a/pkg/asset/agent/image/ignition.go +++ b/pkg/asset/agent/image/ignition.go @@ -120,6 +120,10 @@ func (a *Ignition) Generate(dependencies asset.Parents) error { }, } + if len(agentManifests.NMStateConfigs) == 0 { + return errors.New("at least one NMState configuration must be provided") + } + nodeZeroIP, err := retrieveRendezvousIP(agentConfigAsset.Config, agentManifests.NMStateConfigs) if err != nil { return err diff --git a/pkg/asset/agent/image/ignition_test.go b/pkg/asset/agent/image/ignition_test.go index b5dae24af12..651bafa08a0 100644 --- a/pkg/asset/agent/image/ignition_test.go +++ b/pkg/asset/agent/image/ignition_test.go @@ -12,6 +12,7 @@ import ( hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1" "github.com/openshift/assisted-service/api/v1beta1" + aiv1beta1 "github.com/openshift/assisted-service/api/v1beta1" "github.com/openshift/assisted-service/models" hivev1 "github.com/openshift/hive/apis/hive/v1" "github.com/openshift/installer/pkg/asset" @@ -390,6 +391,18 @@ func buildIgnitionAssetDefaultDependencies() []asset.Asset { }, }, }, + NMStateConfigs: []*aiv1beta1.NMStateConfig{ + { + Spec: aiv1beta1.NMStateConfigSpec{ + Interfaces: []*aiv1beta1.Interface{ + { + Name: "eth0", + MacAddress: "00:01:02:03:04:05", + }, + }, + }, + }, + }, }, &agentconfig.AgentConfig{ Config: &agent.Config{ diff --git a/pkg/asset/agent/manifests/nmstateconfig.go b/pkg/asset/agent/manifests/nmstateconfig.go index ff2d602ca9b..2d4f9b22dce 100644 --- a/pkg/asset/agent/manifests/nmstateconfig.go +++ b/pkg/asset/agent/manifests/nmstateconfig.go @@ -222,31 +222,41 @@ func (n *NMStateConfig) validateNMStateLabels() field.ErrorList { return allErrs } +func getFirstIP(nmStateConfig *nmStateConfig) string { + for _, intf := range nmStateConfig.Interfaces { + for _, addr4 := range intf.IPV4.Address { + if addr4.IP != "" { + return addr4.IP + } + } + for _, addr6 := range intf.IPV6.Address { + if addr6.IP != "" { + return addr6.IP + } + } + } + return "" +} + // GetNodeZeroIP retrieves the first IP from the user provided NMStateConfigs to set as the node0 IP func GetNodeZeroIP(nmStateConfigs []*aiv1beta1.NMStateConfig) (string, error) { - var nmStateConfig nmStateConfig - // Use entry for first host - err := yaml.Unmarshal(nmStateConfigs[0].Spec.NetConfig.Raw, &nmStateConfig) - if err != nil { - return "", fmt.Errorf("error unmarshalling nodeZero nmStateConfig: %v", err) - } + for i := range nmStateConfigs { + var nmStateConfig nmStateConfig + err := yaml.Unmarshal(nmStateConfigs[i].Spec.NetConfig.Raw, &nmStateConfig) + if err != nil { + return "", fmt.Errorf("error unmarshalling NMStateConfig: %v", err) + } + if nodeZeroIP := getFirstIP(&nmStateConfig); nodeZeroIP != "" { + if net.ParseIP(nodeZeroIP) == nil { + return "", fmt.Errorf("could not parse static IP: %s", nodeZeroIP) + } - var nodeZeroIP string - if nmStateConfig.Interfaces == nil { - return "", fmt.Errorf("invalid NMStateConfig yaml, no valid interfaces set") - } + return nodeZeroIP, nil + } - if nmStateConfig.Interfaces[0].IPV4.Address != nil { - nodeZeroIP = nmStateConfig.Interfaces[0].IPV4.Address[0].IP - } - if nmStateConfig.Interfaces[0].IPV6.Address != nil { - nodeZeroIP = nmStateConfig.Interfaces[0].IPV6.Address[0].IP - } - if net.ParseIP(nodeZeroIP) == nil { - return "", fmt.Errorf("could not parse nodeZeroIP: %s", nodeZeroIP) } - return nodeZeroIP, nil + return "", fmt.Errorf("invalid NMStateConfig yaml, no interface IPs set") } // GetNMIgnitionFiles returns the list of NetworkManager configuration files diff --git a/pkg/asset/agent/manifests/nmstateconfig_test.go b/pkg/asset/agent/manifests/nmstateconfig_test.go index 96d6217e635..f81f3d5f7e7 100644 --- a/pkg/asset/agent/manifests/nmstateconfig_test.go +++ b/pkg/asset/agent/manifests/nmstateconfig_test.go @@ -287,7 +287,7 @@ spec: - name: "eth0" macAddress: "52:54:01:bb:bb:b1"`, requiresNmstatectl: true, - expectedError: "staticNetwork configuration is not valid.*", + expectedError: "staticNetwork configuration is not valid", }, { @@ -314,7 +314,7 @@ spec: - name: "eth0" macAddress: "52:54:01:aa:aa:a1"`, requiresNmstatectl: true, - expectedError: "staticNetwork configuration is not valid.*", + expectedError: "staticNetwork configuration is not valid", }, { @@ -383,7 +383,7 @@ spec: found, err := asset.Load(fileFetcher) assert.Equal(t, tc.expectedFound, found, "unexpected found value returned from Load") if tc.expectedError != "" { - assert.Regexp(t, tc.expectedError, err.Error()) + assert.ErrorContains(t, err, tc.expectedError) } else { assert.NoError(t, err) } @@ -409,3 +409,169 @@ spec: }) } } + +func TestGetNodeZeroIP(t *testing.T) { + cases := []struct { + name string + expectedIP string + expectedError string + configs []string + }{ + { + name: "no interfaces", + expectedError: "no interface IPs set", + }, + { + name: "first interface", + expectedIP: "192.168.122.21", + configs: []string{ + ` +interfaces: + - name: eth0 + type: ethernet + ipv4: + address: + - ip: 192.168.122.21 + - name: eth1 + type: ethernet + ipv4: + address: + - ip: 192.168.122.22 +`, + }, + }, + { + name: "second interface", + expectedIP: "192.168.122.22", + configs: []string{ + ` +interfaces: + - name: eth0 + type: ethernet + - name: eth1 + type: ethernet + ipv4: + address: + - ip: 192.168.122.22 +`, + }, + }, + { + name: "second host", + expectedIP: "192.168.122.22", + configs: []string{ + ` +interfaces: + - name: eth0 + type: ethernet + - name: eth1 + type: ethernet +`, + ` +interfaces: + - name: eth0 + type: ethernet + - name: eth1 + type: ethernet + ipv4: + address: + - ip: 192.168.122.22 +`, + }, + }, + { + name: "ipv4 first", + expectedIP: "192.168.122.22", + configs: []string{ + ` +interfaces: + - name: eth0 + type: ethernet + ipv6: + address: + - ip: "2001:0db8::0001" + ipv4: + address: + - ip: 192.168.122.22 +`, + }, + }, + { + name: "ipv6 host first", + expectedIP: "2001:0db8::0001", + configs: []string{ + ` +interfaces: + - name: eth0 + type: ethernet + ipv6: + address: + - ip: "2001:0db8::0001" +`, + ` +interfaces: + - name: eth0 + type: ethernet + ipv4: + address: + - ip: 192.168.122.31 +`, + }, + }, + { + name: "ipv6 first", + expectedIP: "2001:0db8::0001", + configs: []string{ + ` +interfaces: + - name: eth0 + type: ethernet + ipv6: + address: + - ip: "2001:0db8::0001" + - name: eth1 + type: ethernet + ipv4: + address: + - ip: 192.168.122.22 +`, + }, + }, + { + name: "ipv6", + expectedIP: "2001:0db8::0001", + configs: []string{ + ` +interfaces: + - name: eth0 + type: ethernet + ipv6: + address: + - ip: "2001:0db8::0001" +`, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + var configs []*aiv1beta1.NMStateConfig + for _, hostRaw := range tc.configs { + configs = append(configs, &aiv1beta1.NMStateConfig{ + Spec: aiv1beta1.NMStateConfigSpec{ + NetConfig: aiv1beta1.NetConfig{ + Raw: aiv1beta1.RawNetConfig(hostRaw), + }, + }, + }) + } + + ip, err := GetNodeZeroIP(configs) + if tc.expectedError == "" { + assert.NoError(t, err) + assert.Equal(t, tc.expectedIP, ip) + } else { + assert.ErrorContains(t, err, tc.expectedError) + } + }) + } +}