1
0
mirror of https://github.com/openshift/installer.git synced 2026-02-06 00:48:45 +01:00
Files
installer/pkg/tfvars/openstack/openstack.go
2024-06-18 10:39:56 +02:00

285 lines
13 KiB
Go

// Package openstack contains OpenStack-specific Terraform-variable logic.
package openstack
import (
"context"
"encoding/json"
"fmt"
"github.com/gophercloud/gophercloud/v2"
"github.com/gophercloud/gophercloud/v2/openstack"
"github.com/gophercloud/gophercloud/v2/openstack/identity/v3/tokens"
configv1 "github.com/openshift/api/config/v1"
machinev1alpha1 "github.com/openshift/api/machine/v1alpha1"
"github.com/openshift/installer/pkg/asset/installconfig"
"github.com/openshift/installer/pkg/asset/machines"
"github.com/openshift/installer/pkg/rhcos"
types_openstack "github.com/openshift/installer/pkg/types/openstack"
openstackdefaults "github.com/openshift/installer/pkg/types/openstack/defaults"
)
// TFVars generates OpenStack-specific Terraform variables.
func TFVars(
ctx context.Context,
installConfig *installconfig.InstallConfig,
mastersAsset *machines.Master,
workersAsset *machines.Worker,
baseImage string,
clusterID *installconfig.ClusterID,
bootstrapIgn string,
) ([]byte, error) {
var (
cloud = installConfig.Config.Platform.OpenStack.Cloud
mastermpool = installConfig.Config.ControlPlane.Platform.OpenStack
defaultmpool = installConfig.Config.OpenStack.DefaultMachinePlatform
)
conn, err := openstackdefaults.NewServiceClient(ctx, "network", openstackdefaults.DefaultClientOpts(cloud))
if err != nil {
return nil, fmt.Errorf("failed to build an OpenStack service client: %w", err)
}
var masterSpecs []*machinev1alpha1.OpenstackProviderSpec
{
masters, err := mastersAsset.Machines()
if err != nil {
return nil, err
}
for _, master := range masters {
masterSpecs = append(masterSpecs, master.Spec.ProviderSpec.Value.Object.(*machinev1alpha1.OpenstackProviderSpec))
}
}
var workerSpecs []*machinev1alpha1.OpenstackProviderSpec
{
workers, err := workersAsset.MachineSets()
if err != nil {
return nil, err
}
for _, worker := range workers {
workerSpecs = append(workerSpecs, worker.Spec.Template.Spec.ProviderSpec.Value.Object.(*machinev1alpha1.OpenstackProviderSpec))
}
}
var workermpool *types_openstack.MachinePool
if len(installConfig.Config.Compute) > 0 {
// Only considering the first Compute machinepool here, because
// the current Installer implementation allows for one only.
//
// This validation code[1] errors if the pool is not named
// "worker", and also errors in case of duplicate names,
// factually rendering impossible to have two machinepools in
// the install-config YAML array.
//
// [1]: https://github.com/openshift/installer/blob/252facf5e6e1238ee60b5f78607214e8691a3eab/pkg/types/validation/installconfig.go#L404-L410
if len(installConfig.Config.Compute) > 1 {
panic("Multiple machine-pools are currently not supported by the OpenShift installer on OpenStack platform")
}
workermpool = installConfig.Config.Compute[0].Platform.OpenStack
}
var userManagedLoadBalancer bool
if lb := installConfig.Config.Platform.OpenStack.LoadBalancer; lb != nil && lb.Type == configv1.LoadBalancerTypeUserManaged {
userManagedLoadBalancer = true
}
// computeAvailabilityZones is a slice where each index targets a master.
computeAvailabilityZones := make([]string, len(masterSpecs))
for i := range computeAvailabilityZones {
computeAvailabilityZones[i] = masterSpecs[i].AvailabilityZone
}
// storageAvailabilityZones is a slice where each index targets a master.
storageAvailabilityZones := make([]string, len(masterSpecs))
for i := range storageAvailabilityZones {
if masterSpecs[i].RootVolume != nil {
storageAvailabilityZones[i] = masterSpecs[i].RootVolume.Zone
}
}
// storageVolumeTypes is a slice where each index targets a master.
storageVolumeTypes := make([]string, len(masterSpecs))
for i := range storageVolumeTypes {
if masterSpecs[i].RootVolume != nil {
storageVolumeTypes[i] = masterSpecs[i].RootVolume.VolumeType
}
}
// If baseImage is a URL, the corresponding image will be uploaded to
// Glance in the PreTerraform hook of the Cluster asset.
imageName, _ := rhcos.GenerateOpenStackImageName(baseImage, clusterID.InfraID)
serviceCatalog, err := getServiceCatalog(ctx, cloud)
if err != nil {
return nil, fmt.Errorf("could not retrieve service catalog: %w", err)
}
octaviaSupport, err := isOctaviaSupported(serviceCatalog)
if err != nil {
return nil, err
}
var rootVolumeSize int
if rootVolume := masterSpecs[0].RootVolume; rootVolume != nil {
rootVolumeSize = rootVolume.Size
}
masterServerGroupPolicy := GetServerGroupPolicy(mastermpool, defaultmpool)
masterServerGroupName := masterSpecs[0].ServerGroupName
if masterSpecs[0].ServerGroupID != "" {
return nil, fmt.Errorf("the field ServerGroupID is not implemented in the Installer. Please use ServerGroupName for automatic creation of the Control Plane server group")
}
workerServerGroupPolicy := GetServerGroupPolicy(workermpool, defaultmpool)
var workerServerGroupNames []string
{
for _, workerConfig := range workerSpecs {
workerServerGroupNames = append(workerServerGroupNames, workerConfig.ServerGroupName)
if workerConfig.ServerGroupID != "" {
return nil, fmt.Errorf("the field ServerGroupID is not implemented in the Installer. Please use ServerGroupName for automatic creation of the Compute server group")
}
}
}
var additionalNetworkIDs []string
if mastermpool != nil {
additionalNetworkIDs = mastermpool.AdditionalNetworkIDs
}
// defaultMachinesPort carries the machine subnets and the network.
var defaultMachinesPort *terraformPort
if controlPlanePort := installConfig.Config.Platform.OpenStack.ControlPlanePort; controlPlanePort != nil {
port, err := portTargetToTerraformPort(ctx, conn, *controlPlanePort)
if err != nil {
return nil, fmt.Errorf("failed to resolve portTarget :%w", err)
}
defaultMachinesPort = &port
}
// machinesPorts defines the primary port for a master. A nil value
// signals Terraform to fill in the blank with the network it creates.
// Each slice index targets a master.
machinesPorts := make([]*terraformPort, len(masterSpecs))
// additionalPorts translates non-control-plane
// `failureDomain.portTarget` information in Terraform-understandable
// syntax. Each slice index targets a master.
additionalPorts := make([][]terraformPort, len(masterSpecs))
for i := range masterSpecs {
// Assign a slice to each master's index, no matter what.
// Terraform expects each master to get an array, empty or otherwise.
additionalPorts[i] = []terraformPort{}
machinesPorts[i] = defaultMachinesPort
}
var additionalSecurityGroupIDs []string
if mastermpool != nil {
additionalSecurityGroupIDs = mastermpool.AdditionalSecurityGroupIDs
}
return json.MarshalIndent(struct {
BaseImageName string `json:"openstack_base_image_name,omitempty"`
ExternalNetwork string `json:"openstack_external_network,omitempty"`
Cloud string `json:"openstack_credentials_cloud,omitempty"`
FlavorName string `json:"openstack_master_flavor_name,omitempty"`
APIFloatingIP string `json:"openstack_api_floating_ip,omitempty"`
IngressFloatingIP string `json:"openstack_ingress_floating_ip,omitempty"`
APIVIPs []string `json:"openstack_api_int_ips,omitempty"`
IngressVIPs []string `json:"openstack_ingress_ips,omitempty"`
OctaviaSupport bool `json:"openstack_octavia_support,omitempty"`
RootVolumeSize int `json:"openstack_master_root_volume_size,omitempty"`
BootstrapShim string `json:"openstack_bootstrap_shim_ignition,omitempty"`
ExternalDNS []string `json:"openstack_external_dns,omitempty"`
MasterServerGroupName string `json:"openstack_master_server_group_name,omitempty"`
MasterServerGroupPolicy types_openstack.ServerGroupPolicy `json:"openstack_master_server_group_policy"`
WorkerServerGroupNames []string `json:"openstack_worker_server_group_names,omitempty"`
WorkerServerGroupPolicy types_openstack.ServerGroupPolicy `json:"openstack_worker_server_group_policy"`
AdditionalNetworkIDs []string `json:"openstack_additional_network_ids,omitempty"`
AdditionalPorts [][]terraformPort `json:"openstack_additional_ports"`
AdditionalSecurityGroupIDs []string `json:"openstack_master_extra_sg_ids,omitempty"`
DefaultMachinesPort *terraformPort `json:"openstack_default_machines_port,omitempty"`
MachinesPorts []*terraformPort `json:"openstack_machines_ports"`
MasterAvailabilityZones []string `json:"openstack_master_availability_zones,omitempty"`
MasterRootVolumeAvailabilityZones []string `json:"openstack_master_root_volume_availability_zones,omitempty"`
MasterRootVolumeTypes []string `json:"openstack_master_root_volume_types,omitempty"`
UserManagedLoadBalancer bool `json:"openstack_user_managed_load_balancer"`
}{
BaseImageName: imageName,
ExternalNetwork: installConfig.Config.Platform.OpenStack.ExternalNetwork,
Cloud: cloud,
FlavorName: masterSpecs[0].Flavor,
APIFloatingIP: installConfig.Config.Platform.OpenStack.APIFloatingIP,
IngressFloatingIP: installConfig.Config.Platform.OpenStack.IngressFloatingIP,
APIVIPs: installConfig.Config.Platform.OpenStack.APIVIPs,
IngressVIPs: installConfig.Config.Platform.OpenStack.IngressVIPs,
OctaviaSupport: octaviaSupport,
RootVolumeSize: rootVolumeSize,
BootstrapShim: bootstrapIgn,
ExternalDNS: installConfig.Config.Platform.OpenStack.ExternalDNS,
MasterServerGroupName: masterServerGroupName,
MasterServerGroupPolicy: masterServerGroupPolicy,
WorkerServerGroupNames: workerServerGroupNames,
WorkerServerGroupPolicy: workerServerGroupPolicy,
AdditionalNetworkIDs: additionalNetworkIDs,
AdditionalPorts: additionalPorts,
AdditionalSecurityGroupIDs: additionalSecurityGroupIDs,
DefaultMachinesPort: defaultMachinesPort,
MachinesPorts: machinesPorts,
MasterAvailabilityZones: computeAvailabilityZones,
MasterRootVolumeAvailabilityZones: storageAvailabilityZones,
MasterRootVolumeTypes: storageVolumeTypes,
UserManagedLoadBalancer: userManagedLoadBalancer,
}, "", " ")
}
// getServiceCatalog fetches OpenStack service catalog with service endpoints
func getServiceCatalog(ctx context.Context, cloud string) (*tokens.ServiceCatalog, error) {
conn, err := openstackdefaults.NewServiceClient(ctx, "identity", openstackdefaults.DefaultClientOpts(cloud))
if err != nil {
return nil, err
}
authResult := conn.GetAuthResult()
auth, ok := authResult.(tokens.CreateResult)
if !ok {
return nil, fmt.Errorf("unable to extract service catalog")
}
serviceCatalog, err := auth.ExtractServiceCatalog()
if err != nil {
return nil, err
}
return serviceCatalog, nil
}
func isOctaviaSupported(serviceCatalog *tokens.ServiceCatalog) (bool, error) {
_, err := openstack.V3EndpointURL(serviceCatalog, gophercloud.EndpointOpts{
Type: "load-balancer",
Name: "octavia",
Availability: gophercloud.AvailabilityPublic,
})
if err != nil {
if _, ok := err.(*gophercloud.ErrEndpointNotFound); ok {
return false, nil
}
return false, err
}
return true, nil
}
// GetServerGroupPolicy returns the server group policy set in the given machine-pool, or in the default one, or falls back to soft-anti-affinity.
func GetServerGroupPolicy(machinePool, defaultMachinePool *types_openstack.MachinePool) types_openstack.ServerGroupPolicy {
if machinePool != nil && machinePool.ServerGroupPolicy.IsSet() {
return machinePool.ServerGroupPolicy
}
if defaultMachinePool != nil && defaultMachinePool.ServerGroupPolicy.IsSet() {
return defaultMachinePool.ServerGroupPolicy
}
return types_openstack.SGPolicySoftAntiAffinity
}