123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- /*
- Copyright 2016 The Kubernetes Authors.
- Licensed 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"
- "path/filepath"
- "github.com/kardianos/osext"
- "github.com/xanzy/go-cloudstack/cloudstack"
- "gopkg.in/gcfg.v1"
- "k8s.io/apimachinery/pkg/types"
- cloudprovider "k8s.io/cloud-provider"
- "k8s.io/klog"
- )
- // ProviderName is the name of this cloud provider.
- const ProviderName = "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
- metadata *metadata
- 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,
- }
- exe, err := osext.Executable()
- if err != nil {
- return nil, fmt.Errorf("cloud not find the service executable: %v", err)
- }
- // When running the kubelet service it's fine to not specify a config file (or only a
- // partial config file) as all needed info can be retrieved anonymously using metadata.
- if filepath.Base(exe) == "kubelet" || filepath.Base(exe) == "kubelet.exe" {
- // In CloudStack your metadata is always served by the DHCP server.
- dhcpServer, err := findDHCPServer()
- if err == nil {
- klog.V(4).Infof("Found metadata server: %v", dhcpServer)
- cs.metadata = &metadata{dhcpServer: dhcpServer, zone: cs.zone}
- } else {
- klog.Errorf("Error searching metadata server: %v", err)
- }
- }
- 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 {
- if cs.metadata != nil {
- klog.V(2).Infof("No API URL, key and secret are provided, so only using metadata!")
- } else {
- return nil, errors.New("no cloud provider config given")
- }
- }
- return cs, nil
- }
- var _ cloudprovider.Interface = (*CSCloud)(nil)
- var _ cloudprovider.Instances = (*CSCloud)(nil)
- var _ cloudprovider.LoadBalancer = (*CSCloud)(nil)
- var _ cloudprovider.Zones = (*CSCloud)(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.metadata != nil {
- return cs.metadata, true
- }
- 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.metadata != nil {
- return cs.metadata, true
- }
- 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
- }
- 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
- }
- 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
- }
|