cloudstack.go (162 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package cloudstack import ( "context" "errors" "fmt" "io" "os" "github.com/apache/cloudstack-go/v2/cloudstack" "gopkg.in/gcfg.v1" "k8s.io/apimachinery/pkg/types" cloudprovider "k8s.io/cloud-provider" "k8s.io/klog/v2" ) // ProviderName is the name of this cloud provider. const ProviderName = "external-cloudstack" // CSConfig wraps the config for the CloudStack cloud provider. type CSConfig struct { Global struct { APIURL string `gcfg:"api-url"` APIKey string `gcfg:"api-key"` SecretKey string `gcfg:"secret-key"` SSLNoVerify bool `gcfg:"ssl-no-verify"` ProjectID string `gcfg:"project-id"` Zone string `gcfg:"zone"` } } // CSCloud is an implementation of Interface for CloudStack. type CSCloud struct { client *cloudstack.CloudStackClient projectID string // If non-"", all resources will be created within this project zone string } func init() { cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) { cfg, err := readConfig(config) if err != nil { return nil, err } return newCSCloud(cfg) }) } func readConfig(config io.Reader) (*CSConfig, error) { cfg := &CSConfig{} if config == nil { return cfg, nil } if err := gcfg.ReadInto(cfg, config); err != nil { return nil, fmt.Errorf("could not parse cloud provider config: %v", err) } return cfg, nil } // newCSCloud creates a new instance of CSCloud. func newCSCloud(cfg *CSConfig) (*CSCloud, error) { cs := &CSCloud{ projectID: cfg.Global.ProjectID, zone: cfg.Global.Zone, } if cfg.Global.APIURL != "" && cfg.Global.APIKey != "" && cfg.Global.SecretKey != "" { cs.client = cloudstack.NewAsyncClient(cfg.Global.APIURL, cfg.Global.APIKey, cfg.Global.SecretKey, !cfg.Global.SSLNoVerify) } if cs.client == nil { return nil, errors.New("no cloud provider config given") } return cs, nil } // Initialize passes a Kubernetes clientBuilder interface to the cloud provider func (cs *CSCloud) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, stop <-chan struct{}) { } // LoadBalancer returns an implementation of LoadBalancer for CloudStack. func (cs *CSCloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) { if cs.client == nil { return nil, false } return cs, true } // Instances returns an implementation of Instances for CloudStack. func (cs *CSCloud) Instances() (cloudprovider.Instances, bool) { if cs.client == nil { return nil, false } return cs, true } func (cs *CSCloud) InstancesV2() (cloudprovider.InstancesV2, bool) { if cs.client == nil { return nil, false } return cs, true } // Zones returns an implementation of Zones for CloudStack. func (cs *CSCloud) Zones() (cloudprovider.Zones, bool) { if cs.client == nil { return nil, false } return cs, true } // Clusters returns an implementation of Clusters for CloudStack. func (cs *CSCloud) Clusters() (cloudprovider.Clusters, bool) { if cs.client == nil { return nil, false } klog.Warning("This cloud provider doesn't support clusters") return nil, false } // Routes returns an implementation of Routes for CloudStack. func (cs *CSCloud) Routes() (cloudprovider.Routes, bool) { if cs.client == nil { return nil, false } klog.Warning("This cloud provider doesn't support routes") return nil, false } // ProviderName returns the cloud provider ID. func (cs *CSCloud) ProviderName() string { return ProviderName } // HasClusterID returns true if the cluster has a clusterID func (cs *CSCloud) HasClusterID() bool { return true } // GetZone returns the Zone containing the region that the program is running in. func (cs *CSCloud) GetZone(ctx context.Context) (cloudprovider.Zone, error) { zone := cloudprovider.Zone{} if cs.zone == "" { hostname, err := os.Hostname() if err != nil { return zone, fmt.Errorf("failed to get hostname for retrieving the zone: %v", err) } instance, count, err := cs.client.VirtualMachine.GetVirtualMachineByName(hostname) if err != nil { if count == 0 { return zone, fmt.Errorf("could not find instance for retrieving the zone: %v", err) } return zone, fmt.Errorf("error getting instance for retrieving the zone: %v", err) } cs.zone = instance.Zonename } klog.V(2).Infof("Current zone is %v", cs.zone) zone.FailureDomain = cs.zone zone.Region = cs.zone return zone, nil } // GetZoneByProviderID returns the Zone, found by using the provider ID. func (cs *CSCloud) GetZoneByProviderID(ctx context.Context, providerID string) (cloudprovider.Zone, error) { zone := cloudprovider.Zone{} instance, count, err := cs.client.VirtualMachine.GetVirtualMachineByID( providerID, cloudstack.WithProject(cs.projectID), ) if err != nil { if count == 0 { return zone, fmt.Errorf("could not find node by ID: %v", providerID) } return zone, fmt.Errorf("error retrieving zone: %v", err) } klog.V(2).Infof("Current zone is %v", cs.zone) zone.FailureDomain = instance.Zonename zone.Region = instance.Zonename return zone, nil } // GetZoneByNodeName returns the Zone, found by using the node name. func (cs *CSCloud) GetZoneByNodeName(ctx context.Context, nodeName types.NodeName) (cloudprovider.Zone, error) { zone := cloudprovider.Zone{} instance, count, err := cs.client.VirtualMachine.GetVirtualMachineByName( string(nodeName), cloudstack.WithProject(cs.projectID), ) if err != nil { if count == 0 { return zone, fmt.Errorf("could not find node: %v", nodeName) } return zone, fmt.Errorf("error retrieving zone: %v", err) } klog.V(2).Infof("Current zone is %v", cs.zone) zone.FailureDomain = instance.Zonename zone.Region = instance.Zonename return zone, nil }