diff --git a/pkg/asset/machines/machineconfig/ipv6.go b/pkg/asset/machines/machineconfig/ipv6.go new file mode 100644 index 00000000000..7b8e1a43333 --- /dev/null +++ b/pkg/asset/machines/machineconfig/ipv6.go @@ -0,0 +1,42 @@ +package machineconfig + +import ( + "fmt" + + igntypes "github.com/coreos/ignition/v2/config/v3_2/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/openshift/installer/pkg/asset/ignition" + mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1" +) + +// ForDualStackAddresses creates the MachineConfig to tell kernel to configure the IP addresses with DHCP and DHCPV6. +func ForDualStackAddresses(role string) (*mcfgv1.MachineConfig, error) { + ignConfig := igntypes.Config{ + Ignition: igntypes.Ignition{ + Version: igntypes.MaxVersion.String(), + }, + } + + rawExt, err := ignition.ConvertToRawExtension(ignConfig) + if err != nil { + return nil, err + } + + return &mcfgv1.MachineConfig{ + TypeMeta: metav1.TypeMeta{ + APIVersion: mcfgv1.SchemeGroupVersion.String(), + Kind: "MachineConfig", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("99-dual-stack-%s", role), + Labels: map[string]string{ + "machineconfiguration.openshift.io/role": role, + }, + }, + Spec: mcfgv1.MachineConfigSpec{ + Config: rawExt, + KernelArguments: []string{"ip=dhcp,dhcp6"}, + }, + }, nil +} diff --git a/pkg/asset/machines/master.go b/pkg/asset/machines/master.go index 8249b3d395f..bd13a38ef49 100644 --- a/pkg/asset/machines/master.go +++ b/pkg/asset/machines/master.go @@ -542,6 +542,17 @@ func (m *Master) Generate(dependencies asset.Parents) error { } machineConfigs = append(machineConfigs, ignMultipath) } + // The maximum number of networks supported on ServiceNetwork is two, one IPv4 and one IPv6 network. + // The cluster-network-operator handles the validation of this field. + // Reference: https://github.com/openshift/cluster-network-operator/blob/fc3e0e25b4cfa43e14122bdcdd6d7f2585017d75/pkg/network/cluster_config.go#L45-L52 + if ic.Platform.Name() == openstacktypes.Name && len(installConfig.Config.ServiceNetwork) == 2 { + // Only configure kernel args for dual-stack clusters. + ignIPv6, err := machineconfig.ForDualStackAddresses("master") + if err != nil { + return errors.Wrap(err, "failed to create ignition to configure IPv6 for master machines") + } + machineConfigs = append(machineConfigs, ignIPv6) + } m.MachineConfigFiles, err = machineconfig.Manifests(machineConfigs, "master", directory) if err != nil { diff --git a/pkg/asset/machines/worker.go b/pkg/asset/machines/worker.go index 2cf4288034a..00b9bc6b69c 100644 --- a/pkg/asset/machines/worker.go +++ b/pkg/asset/machines/worker.go @@ -295,6 +295,17 @@ func (w *Worker) Generate(dependencies asset.Parents) error { } machineConfigs = append(machineConfigs, ignMultipath) } + // The maximum number of networks supported on ServiceNetwork is two, one IPv4 and one IPv6 network. + // The cluster-network-operator handles the validation of this field. + // Reference: https://github.com/openshift/cluster-network-operator/blob/fc3e0e25b4cfa43e14122bdcdd6d7f2585017d75/pkg/network/cluster_config.go#L45-L52 + if ic.Platform.Name() == openstacktypes.Name && len(installConfig.Config.ServiceNetwork) == 2 { + // Only configure kernel args for dual-stack clusters. + ignIPv6, err := machineconfig.ForDualStackAddresses("worker") + if err != nil { + return errors.Wrap(err, "failed to create ignition to configure IPv6 for worker machines") + } + machineConfigs = append(machineConfigs, ignIPv6) + } switch ic.Platform.Name() { case alibabacloudtypes.Name: diff --git a/scripts/openstack/manifest-tests/dual-stack/install-config.yaml b/scripts/openstack/manifest-tests/dual-stack/install-config.yaml new file mode 100644 index 00000000000..16b72b80ff3 --- /dev/null +++ b/scripts/openstack/manifest-tests/dual-stack/install-config.yaml @@ -0,0 +1,46 @@ +apiVersion: v1 +baseDomain: shiftstack.example.com +featureSet: TechPreviewNoUpgrade +controlPlane: + hyperthreading: Enabled + architecture: amd64 + name: master + platform: + openstack: + type: ${COMPUTE_FLAVOR} + replicas: 3 +compute: +- name: worker + platform: + openstack: + type: ${COMPUTE_FLAVOR} + replicas: 3 +metadata: + name: manifests1 +networking: + machineNetwork: + - cidr: "192.168.25.0/24" + - cidr: "fd2e:6f44:5dd8:c956::/64" + clusterNetwork: + - cidr: 10.128.0.0/14 + hostPrefix: 23 + - cidr: fd01::/48 + hostPrefix: 64 + serviceNetwork: + - 172.30.0.0/16 + - fd02::/112 +platform: + openstack: + cloud: ${OS_CLOUD} + computeFlavor: ${COMPUTE_FLAVOR} # deprecated in 4.7 + ingressVIPs: ['192.168.25.79', 'fd2e:6f44:5dd8:c956:f816:3eff:fef1:1bad'] + apiVIPs: ['192.168.25.199', 'fd2e:6f44:5dd8:c956:f816:3eff:fe78:cf36'] + controlPlanePort: + fixedIPs: + - subnet: + name: external-subnet-v6 + - subnet: + name: external-subnet + network: + name: external +pullSecret: ${PULL_SECRET} diff --git a/scripts/openstack/manifest-tests/dual-stack/test_machine-config.py b/scripts/openstack/manifest-tests/dual-stack/test_machine-config.py new file mode 100755 index 00000000000..30ac20a3016 --- /dev/null +++ b/scripts/openstack/manifest-tests/dual-stack/test_machine-config.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import unittest +import xmlrunner + +import os +import sys +import glob +import yaml + +ASSETS_DIR = "" + +class GenerateMachineConfig(unittest.TestCase): + def setUp(self): + self.machine_configs = [] + for machine_config_path in glob.glob( + f'{ASSETS_DIR}/openshift/99_openshift-machineconfig_99-dual-stack-*.yaml' + ): + with open(machine_config_path) as f: + self.machine_configs.append(yaml.load(f, Loader=yaml.FullLoader)) + + def test_kernel_args(self): + """Assert there are machine configs configuring the kernel args for masters and workers""" + for machine_config in self.machine_configs: + kernel_args = machine_config["spec"]["kernelArguments"] + self.assertIn("ip=dhcp,dhcp6", kernel_args) + + +if __name__ == '__main__': + ASSETS_DIR = sys.argv.pop() + with open(os.environ.get('JUNIT_FILE', '/dev/null'), 'wb') as output: + unittest.main(testRunner=xmlrunner.XMLTestRunner(output=output), failfast=False, buffer=False, catchbreak=False, verbosity=2)