Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 7 additions & 97 deletions cloudstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,17 @@
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"
const ProviderName = "cloudstack"

// CSConfig wraps the config for the CloudStack cloud provider.
type CSConfig struct {
Expand Down Expand Up @@ -113,13 +109,13 @@ func (cs *CSCloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) {

// 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
return nil, false
}

// InstancesV2 is an implementation for instances and should only be implemented by external cloud providers.
// Implementing InstancesV2 is behaviorally identical to Instances but is optimized to significantly reduce
// API calls to the cloud provider when registering and syncing nodes. Implementation of this interface will
// disable calls to the Zones interface. Also returns true if the interface is supported, false otherwise.
func (cs *CSCloud) InstancesV2() (cloudprovider.InstancesV2, bool) {
if cs.client == nil {
return nil, false
Expand All @@ -130,30 +126,16 @@ func (cs *CSCloud) InstancesV2() (cloudprovider.InstancesV2, bool) {

// 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
return nil, false
}

// 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
}

Expand All @@ -166,75 +148,3 @@ func (cs *CSCloud) ProviderName() string {
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
}
185 changes: 59 additions & 126 deletions cloudstack_instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,49 +23,13 @@ import (
"context"
"errors"
"fmt"
"regexp"

"github.com/apache/cloudstack-go/v2/cloudstack"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
cloudprovider "k8s.io/cloud-provider"
"k8s.io/klog/v2"
)

var labelInvalidCharsRegex *regexp.Regexp = regexp.MustCompile(`([^A-Za-z0-9][^-A-Za-z0-9_.]*)?[^A-Za-z0-9]`)

// NodeAddresses returns the addresses of the specified instance.
func (cs *CSCloud) NodeAddresses(ctx context.Context, name types.NodeName) ([]corev1.NodeAddress, error) {
instance, count, err := cs.client.VirtualMachine.GetVirtualMachineByName(
string(name),
cloudstack.WithProject(cs.projectID),
)
if err != nil {
if count == 0 {
return nil, cloudprovider.InstanceNotFound
}
return nil, fmt.Errorf("error retrieving node addresses: %v", err)
}

return cs.nodeAddresses(instance)
}

// NodeAddressesByProviderID returns the addresses of the specified instance.
func (cs *CSCloud) NodeAddressesByProviderID(ctx context.Context, providerID string) ([]corev1.NodeAddress, error) {
instance, count, err := cs.client.VirtualMachine.GetVirtualMachineByID(
providerID,
cloudstack.WithProject(cs.projectID),
)
if err != nil {
if count == 0 {
return nil, cloudprovider.InstanceNotFound
}
return nil, fmt.Errorf("error retrieving node addresses: %v", err)
}

return cs.nodeAddresses(instance)
}

func (cs *CSCloud) nodeAddresses(instance *cloudstack.VirtualMachine) ([]corev1.NodeAddress, error) {
if len(instance.Nic) == 0 {
return nil, errors.New("instance does not have an internal IP")
Expand All @@ -81,130 +45,99 @@ func (cs *CSCloud) nodeAddresses(instance *cloudstack.VirtualMachine) ([]corev1.

if instance.Publicip != "" {
addresses = append(addresses, corev1.NodeAddress{Type: corev1.NodeExternalIP, Address: instance.Publicip})
} else {
// Since there is no sane way to determine the external IP if the host isn't
// using static NAT, we will just fire a log message and omit the external IP.
klog.V(4).Infof("Could not determine the public IP of host %v (%v)", instance.Name, instance.Id)
}

return addresses, nil
}

// InstanceID returns the cloud provider ID of the specified instance.
func (cs *CSCloud) InstanceID(ctx context.Context, name types.NodeName) (string, error) {
instance, count, err := cs.client.VirtualMachine.GetVirtualMachineByName(
string(name),
cloudstack.WithProject(cs.projectID),
)
if err != nil {
if count == 0 {
return "", cloudprovider.InstanceNotFound
}
return "", fmt.Errorf("error retrieving instance ID: %v", err)
}
func (cs *CSCloud) InstanceExists(ctx context.Context, node *corev1.Node) (bool, error) {
_, err := cs.getInstance(ctx, node)

return instance.Id, nil
}
if err == cloudprovider.InstanceNotFound {
klog.V(5).Infof("instance not found for node: %s", node.Name)
return false, nil
}

// InstanceType returns the type of the specified instance.
func (cs *CSCloud) InstanceType(ctx context.Context, name types.NodeName) (string, error) {
instance, count, err := cs.client.VirtualMachine.GetVirtualMachineByName(
string(name),
cloudstack.WithProject(cs.projectID),
)
if err != nil {
if count == 0 {
return "", cloudprovider.InstanceNotFound
}
return "", fmt.Errorf("error retrieving instance type: %v", err)
return false, err
}

return labelInvalidCharsRegex.ReplaceAllString(instance.Serviceofferingname, ``), nil
return true, nil
}

// InstanceTypeByProviderID returns the type of the specified instance.
func (cs *CSCloud) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) {
instance, count, err := cs.client.VirtualMachine.GetVirtualMachineByID(
providerID,
cloudstack.WithProject(cs.projectID),
)
func (cs *CSCloud) InstanceShutdown(ctx context.Context, node *corev1.Node) (bool, error) {
instance, err := cs.getInstance(ctx, node)
if err != nil {
if count == 0 {
return "", cloudprovider.InstanceNotFound
}
return "", fmt.Errorf("error retrieving instance type: %v", err)
return false, err
}

return labelInvalidCharsRegex.ReplaceAllString(instance.Serviceofferingname, ``), nil
return instance != nil && instance.State == "Stopped", nil
}

// AddSSHKeyToAllInstances is currently not implemented.
func (cs *CSCloud) AddSSHKeyToAllInstances(ctx context.Context, user string, keyData []byte) error {
return cloudprovider.NotImplemented
}

// CurrentNodeName returns the name of the node we are currently running on.
func (cs *CSCloud) CurrentNodeName(ctx context.Context, hostname string) (types.NodeName, error) {
return types.NodeName(hostname), nil
}

// InstanceExistsByProviderID returns if the instance still exists.
func (cs *CSCloud) InstanceExistsByProviderID(ctx context.Context, providerID string) (bool, error) {
_, count, err := cs.client.VirtualMachine.GetVirtualMachineByID(
providerID,
cloudstack.WithProject(cs.projectID),
)
func (cs *CSCloud) InstanceMetadata(ctx context.Context, node *corev1.Node) (*cloudprovider.InstanceMetadata, error) {
instance, err := cs.getInstance(ctx, node)
if err != nil {
if count == 0 {
return false, nil
}
return false, fmt.Errorf("error retrieving instance: %v", err)
return nil, err
}

return true, nil
}

// InstanceShutdownByProviderID returns true if the instance is in safe state to detach volumes
func (cs *CSCloud) InstanceShutdownByProviderID(ctx context.Context, providerID string) (bool, error) {
return false, cloudprovider.NotImplemented
}

func (cs *CSCloud) InstanceExists(ctx context.Context, node *corev1.Node) (bool, error) {
nodeName := types.NodeName(node.Name)
providerID, err := cs.InstanceID(ctx, nodeName)
addresses, err := cs.nodeAddresses(instance)
if err != nil {
return false, err
return nil, err
}

return cs.InstanceExistsByProviderID(ctx, providerID)
return &cloudprovider.InstanceMetadata{
ProviderID: getInstanceProviderID(instance),
InstanceType: sanitizeLabel(instance.Serviceofferingname),
NodeAddresses: addresses,
Zone: sanitizeLabel(instance.Zonename),
Region: "",
}, nil
}

func (cs *CSCloud) InstanceShutdown(ctx context.Context, node *corev1.Node) (bool, error) {
return false, cloudprovider.NotImplemented
func getInstanceProviderID(instance *cloudstack.VirtualMachine) string {
// TODO: implement region
return fmt.Sprintf("%s:///%s", ProviderName, instance.Id)
}

func (cs *CSCloud) InstanceMetadata(ctx context.Context, node *corev1.Node) (*cloudprovider.InstanceMetadata, error) {

instanceType, err := cs.InstanceType(ctx, types.NodeName(node.Name))
if err != nil {
return nil, err
func (cs *CSCloud) getInstance(ctx context.Context, node *corev1.Node) (*cloudstack.VirtualMachine, error) {
if node.Spec.ProviderID == "" {
var err error
klog.V(4).Infof("looking for node by node name %v", node.Name)
instance, count, err := cs.client.VirtualMachine.GetVirtualMachineByName(
node.Name,
cloudstack.WithProject(cs.projectID),
)
if err != nil {
if count == 0 {
return nil, cloudprovider.InstanceNotFound
}
if count > 1 {
return nil, fmt.Errorf("getInstance: multiple instances found")
}
return nil, fmt.Errorf("getInstance: error retrieving instance by name: %v", err)
}
return instance, nil
}

addresses, err := cs.NodeAddresses(ctx, types.NodeName(node.Name))
klog.V(4).Infof("looking for node by provider ID %v", node.Spec.ProviderID)
id, _, err := instanceIDFromProviderID(node.Spec.ProviderID)
if err != nil {
return nil, err
}

zone, err := cs.GetZone(ctx)
instance, count, err := cs.client.VirtualMachine.GetVirtualMachineByID(
id,
cloudstack.WithProject(cs.projectID),
)
if err != nil {
return nil, err
if count == 0 {
return nil, cloudprovider.InstanceNotFound
}
if count > 1 {
return nil, fmt.Errorf("getInstance: multiple instances found")
}
return nil, fmt.Errorf("error retrieving instance by provider ID: %v", err)
}

return &cloudprovider.InstanceMetadata{
ProviderID: cs.ProviderName(),
InstanceType: instanceType,
NodeAddresses: addresses,
Zone: cs.zone,
Region: zone.Region,
}, nil
return instance, nil
}
Loading