| /* | |
| * 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" | |
| "regexp" | |
| "strings" | |
| "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( | |
| cs.getInstanceIDFromProviderID(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") | |
| } | |
| addresses := []corev1.NodeAddress{ | |
| {Type: corev1.NodeInternalIP, Address: instance.Nic[0].Ipaddress}, | |
| } | |
| if instance.Hostname != "" { | |
| addresses = append(addresses, corev1.NodeAddress{Type: corev1.NodeHostName, Address: instance.Hostname}) | |
| } | |
| 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) | |
| } | |
| return instance.Id, 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 labelInvalidCharsRegex.ReplaceAllString(instance.Serviceofferingname, ``), 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( | |
| cs.getInstanceIDFromProviderID(providerID), | |
| cloudstack.WithProject(cs.projectID), | |
| ) | |
| if err != nil { | |
| if count == 0 { | |
| return "", cloudprovider.InstanceNotFound | |
| } | |
| return "", fmt.Errorf("error retrieving instance type: %v", err) | |
| } | |
| return labelInvalidCharsRegex.ReplaceAllString(instance.Serviceofferingname, ``), 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( | |
| cs.getInstanceIDFromProviderID(providerID), | |
| cloudstack.WithProject(cs.projectID), | |
| ) | |
| if err != nil { | |
| if count == 0 { | |
| return false, nil | |
| } | |
| return false, fmt.Errorf("error retrieving instance: %v", 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) | |
| if err != nil { | |
| return false, err | |
| } | |
| return cs.InstanceExistsByProviderID(ctx, providerID) | |
| } | |
| func (cs *CSCloud) InstanceShutdown(ctx context.Context, node *corev1.Node) (bool, error) { | |
| return false, cloudprovider.NotImplemented | |
| } | |
| func (cs *CSCloud) InstanceMetadata(ctx context.Context, node *corev1.Node) (*cloudprovider.InstanceMetadata, error) { | |
| instanceID, err := cs.InstanceID(ctx, types.NodeName(node.Name)) | |
| if err != nil { | |
| return nil, err | |
| } | |
| instanceType, err := cs.InstanceType(ctx, types.NodeName(node.Name)) | |
| if err != nil { | |
| return nil, err | |
| } | |
| addresses, err := cs.NodeAddresses(ctx, types.NodeName(node.Name)) | |
| if err != nil { | |
| return nil, err | |
| } | |
| zone, err := cs.GetZoneByNodeName(ctx, types.NodeName(node.Name)) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return &cloudprovider.InstanceMetadata{ | |
| ProviderID: cs.getProviderIDFromInstanceID(instanceID), | |
| InstanceType: instanceType, | |
| NodeAddresses: addresses, | |
| Zone: zone.FailureDomain, | |
| Region: zone.Region, | |
| }, nil | |
| } | |
| func (cs *CSCloud) getProviderIDFromInstanceID(instanceID string) string { | |
| return fmt.Sprintf("%s://%s", cs.ProviderName(), instanceID) | |
| } | |
| func (cs *CSCloud) getInstanceIDFromProviderID(providerID string) string { | |
| parts := strings.Split(providerID, "://") | |
| if len(parts) == 1 { | |
| return providerID | |
| } | |
| return parts[1] | |
| } |