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 }