github.com/openshift/installer@v1.4.17/pkg/asset/machines/openstack/openstackmachines.go (about)

     1  // Package openstack generates Machine objects for openstack.
     2  package openstack
     3  
     4  import (
     5  	"fmt"
     6  
     7  	v1 "k8s.io/api/core/v1"
     8  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
     9  	"k8s.io/utils/ptr"
    10  	capo "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1"
    11  	capi "sigs.k8s.io/cluster-api/api/v1beta1"
    12  
    13  	configv1 "github.com/openshift/api/config/v1"
    14  	machinev1 "github.com/openshift/api/machine/v1"
    15  	"github.com/openshift/installer/pkg/asset"
    16  	"github.com/openshift/installer/pkg/asset/manifests/capiutils"
    17  	"github.com/openshift/installer/pkg/types"
    18  	"github.com/openshift/installer/pkg/types/openstack"
    19  )
    20  
    21  // GenerateMachines returns manifests and runtime objects to provision the control plane (including bootstrap, if applicable) nodes using CAPI.
    22  func GenerateMachines(clusterID string, config *types.InstallConfig, pool *types.MachinePool, osImage, role string) ([]*asset.RuntimeFile, error) {
    23  	if configPlatform := config.Platform.Name(); configPlatform != openstack.Name {
    24  		return nil, fmt.Errorf("non-OpenStack configuration: %q", configPlatform)
    25  	}
    26  	if poolPlatform := pool.Platform.Name(); poolPlatform != openstack.Name {
    27  		return nil, fmt.Errorf("non-OpenStack machine-pool: %q", poolPlatform)
    28  	}
    29  
    30  	mpool := pool.Platform.OpenStack
    31  
    32  	total := int64(1)
    33  	if role == "master" && pool.Replicas != nil {
    34  		total = *pool.Replicas
    35  	}
    36  
    37  	var result []*asset.RuntimeFile
    38  	failureDomains := failureDomainsFromSpec(*mpool)
    39  	for idx := int64(0); idx < total; idx++ {
    40  		failureDomain := failureDomains[uint(idx)%uint(len(failureDomains))]
    41  		machineSpec, err := generateMachineSpec(
    42  			clusterID,
    43  			config.Platform.OpenStack,
    44  			mpool,
    45  			osImage,
    46  			role,
    47  			failureDomain,
    48  		)
    49  		if err != nil {
    50  			return nil, err
    51  		}
    52  
    53  		machineName := fmt.Sprintf("%s-%s-%d", clusterID, pool.Name, idx)
    54  		machineLabels := map[string]string{
    55  			"cluster.x-k8s.io/control-plane": "",
    56  		}
    57  		if role == "bootstrap" {
    58  			machineName = capiutils.GenerateBoostrapMachineName(clusterID)
    59  			machineLabels = map[string]string{
    60  				"cluster.x-k8s.io/control-plane": "",
    61  				"install.openshift.io/bootstrap": "",
    62  			}
    63  		}
    64  		openStackMachine := &capo.OpenStackMachine{
    65  			ObjectMeta: metav1.ObjectMeta{
    66  				Name:   machineName,
    67  				Labels: machineLabels,
    68  			},
    69  			Spec: *machineSpec,
    70  		}
    71  		openStackMachine.SetGroupVersionKind(capo.GroupVersion.WithKind("OpenStackMachine"))
    72  
    73  		result = append(result, &asset.RuntimeFile{
    74  			File:   asset.File{Filename: fmt.Sprintf("10_inframachine_%s.yaml", openStackMachine.Name)},
    75  			Object: openStackMachine,
    76  		})
    77  
    78  		// The instanceSpec used to create the server uses the failureDomain from CAPI Machine
    79  		// defined bellow. This field must match a Key on FailureDomains stored in the cluster.
    80  		// https://github.com/kubernetes-sigs/cluster-api-provider-openstack/blob/main/controllers/openstackmachine_controller.go#L472
    81  		// TODO (maysa): test this
    82  		machine := &capi.Machine{
    83  			ObjectMeta: metav1.ObjectMeta{
    84  				Name: openStackMachine.Name,
    85  				Labels: map[string]string{
    86  					"cluster.x-k8s.io/control-plane": "",
    87  				},
    88  			},
    89  			Spec: capi.MachineSpec{
    90  				ClusterName: clusterID,
    91  				Bootstrap: capi.Bootstrap{
    92  					DataSecretName: ptr.To(fmt.Sprintf("%s-%s", clusterID, role)),
    93  				},
    94  				InfrastructureRef: v1.ObjectReference{
    95  					APIVersion: capo.GroupVersion.String(),
    96  					Kind:       "OpenStackMachine",
    97  					Name:       openStackMachine.Name,
    98  				},
    99  				FailureDomain: &failureDomain.AvailabilityZone,
   100  			},
   101  		}
   102  		machine.SetGroupVersionKind(capi.GroupVersion.WithKind("Machine"))
   103  
   104  		result = append(result, &asset.RuntimeFile{
   105  			File:   asset.File{Filename: fmt.Sprintf("10_machine_%s.yaml", machine.Name)},
   106  			Object: machine,
   107  		})
   108  	}
   109  	return result, nil
   110  }
   111  
   112  func generateMachineSpec(clusterID string, platform *openstack.Platform, mpool *openstack.MachinePool, osImage string, role string, failureDomain machinev1.OpenStackFailureDomain) (*capo.OpenStackMachineSpec, error) {
   113  	port := capo.PortOpts{}
   114  
   115  	addressPairs := populateAllowedAddressPairs(platform)
   116  
   117  	if platform.ControlPlanePort != nil {
   118  		if networkID := platform.ControlPlanePort.Network.ID; networkID != "" {
   119  			port.Network = &capo.NetworkParam{ID: &networkID}
   120  		} else if networkName := platform.ControlPlanePort.Network.Name; networkName != "" {
   121  			port.Network = &capo.NetworkParam{Filter: &capo.NetworkFilter{Name: networkName}}
   122  		}
   123  
   124  		var fixedIPs []capo.FixedIP
   125  		for _, fixedIP := range platform.ControlPlanePort.FixedIPs {
   126  			if subnetID := fixedIP.Subnet.ID; subnetID != "" {
   127  				fixedIPs = append(fixedIPs, capo.FixedIP{Subnet: &capo.SubnetParam{ID: &subnetID}})
   128  			} else {
   129  				fixedIPs = append(fixedIPs, capo.FixedIP{Subnet: &capo.SubnetParam{Filter: &capo.SubnetFilter{Name: fixedIP.Subnet.Name}}})
   130  			}
   131  		}
   132  		port.FixedIPs = fixedIPs
   133  		if len(addressPairs) > 0 {
   134  			port.AllowedAddressPairs = addressPairs
   135  		}
   136  	} else {
   137  		port = capo.PortOpts{
   138  			FixedIPs: []capo.FixedIP{
   139  				{
   140  					Subnet: &capo.SubnetParam{
   141  						Filter: &capo.SubnetFilter{
   142  							// NOTE(mandre) the format of the subnet name changes when letting CAPI create it.
   143  							// So solely rely on tags for now.
   144  							FilterByNeutronTags: capo.FilterByNeutronTags{
   145  								TagsAny: []capo.NeutronTag{capo.NeutronTag("openshiftClusterID=" + clusterID)},
   146  							},
   147  						},
   148  					},
   149  				},
   150  			},
   151  		}
   152  		if len(addressPairs) > 0 {
   153  			port.AllowedAddressPairs = addressPairs
   154  		}
   155  	}
   156  
   157  	additionalPorts := make([]capo.PortOpts, 0, len(mpool.AdditionalNetworkIDs))
   158  	for i := range mpool.AdditionalNetworkIDs {
   159  		additionalPorts = append(additionalPorts, capo.PortOpts{
   160  			Network: &capo.NetworkParam{ID: &mpool.AdditionalNetworkIDs[i]},
   161  		})
   162  	}
   163  
   164  	securityGroups := []capo.SecurityGroupParam{
   165  		{
   166  			// Bootstrap and Master share the same security group
   167  			Filter: &capo.SecurityGroupFilter{Name: fmt.Sprintf("%s-master", clusterID)},
   168  		},
   169  	}
   170  
   171  	for i := range mpool.AdditionalSecurityGroupIDs {
   172  		securityGroups = append(securityGroups, capo.SecurityGroupParam{ID: &mpool.AdditionalSecurityGroupIDs[i]})
   173  	}
   174  
   175  	spec := capo.OpenStackMachineSpec{
   176  		Flavor: mpool.FlavorName,
   177  		IdentityRef: &capo.OpenStackIdentityReference{
   178  			Name:      clusterID + "-cloud-config",
   179  			CloudName: CloudName,
   180  		},
   181  		Image:          capo.ImageParam{Filter: &capo.ImageFilter{Name: &osImage}},
   182  		Ports:          append([]capo.PortOpts{port}, additionalPorts...),
   183  		SecurityGroups: securityGroups,
   184  		ServerMetadata: []capo.ServerMetadata{
   185  			{
   186  				Key:   "Name",
   187  				Value: fmt.Sprintf("%s-%s", clusterID, role),
   188  			},
   189  			{
   190  				Key:   "openshiftClusterID",
   191  				Value: clusterID,
   192  			},
   193  		},
   194  		Trunk: false,
   195  		Tags: []string{
   196  			fmt.Sprintf("openshiftClusterID=%s", clusterID),
   197  		},
   198  	}
   199  
   200  	if role != "bootstrap" {
   201  		spec.ServerGroup = &capo.ServerGroupParam{Filter: &capo.ServerGroupFilter{Name: ptr.To(clusterID + "-" + role)}}
   202  	}
   203  
   204  	if mpool.RootVolume != nil {
   205  		spec.RootVolume = &capo.RootVolume{
   206  			SizeGiB:           mpool.RootVolume.Size,
   207  			BlockDeviceVolume: capo.BlockDeviceVolume{Type: failureDomain.RootVolume.VolumeType},
   208  		}
   209  		if failureDomain.RootVolume.AvailabilityZone != "" {
   210  			spec.RootVolume.BlockDeviceVolume.AvailabilityZone = &capo.VolumeAvailabilityZone{
   211  				From: capo.VolumeAZFromName,
   212  				Name: ptr.To(capo.VolumeAZName(failureDomain.RootVolume.AvailabilityZone)),
   213  			}
   214  		}
   215  	}
   216  
   217  	return &spec, nil
   218  }
   219  
   220  func populateAllowedAddressPairs(platform *openstack.Platform) []capo.AddressPair {
   221  	if lb := platform.LoadBalancer; lb != nil && lb.Type == configv1.LoadBalancerTypeUserManaged {
   222  		return nil
   223  	}
   224  	addressPairs := []capo.AddressPair{}
   225  	for _, apiVIP := range platform.APIVIPs {
   226  		addressPairs = append(addressPairs, capo.AddressPair{IPAddress: apiVIP})
   227  	}
   228  	for _, ingressVIP := range platform.IngressVIPs {
   229  		addressPairs = append(addressPairs, capo.AddressPair{IPAddress: ingressVIP})
   230  	}
   231  	return addressPairs
   232  }