Remove ovirt/cloudstack/photon cloud providers
Change-Id: Iddb80bdc2a9d75d444b8a3cfe0b142acb78e9097
This commit is contained in:
@@ -13,10 +13,7 @@ go_library(
|
||||
"//cmd/kubelet/app:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
"//pkg/cloudprovider/providers/cloudstack:go_default_library",
|
||||
"//pkg/cloudprovider/providers/openstack:go_default_library",
|
||||
"//pkg/cloudprovider/providers/ovirt:go_default_library",
|
||||
"//pkg/cloudprovider/providers/photon:go_default_library",
|
||||
"//staging/src/k8s.io/legacy-cloud-providers/aws:go_default_library",
|
||||
"//staging/src/k8s.io/legacy-cloud-providers/azure:go_default_library",
|
||||
"//staging/src/k8s.io/legacy-cloud-providers/gce:go_default_library",
|
||||
@@ -35,10 +32,7 @@ filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/cloudprovider/providers/cloudstack:all-srcs",
|
||||
"//pkg/cloudprovider/providers/openstack:all-srcs",
|
||||
"//pkg/cloudprovider/providers/ovirt:all-srcs",
|
||||
"//pkg/cloudprovider/providers/photon:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
@@ -1,88 +0,0 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"cloudstack.go",
|
||||
"cloudstack_instances.go",
|
||||
"cloudstack_loadbalancer.go",
|
||||
"metadata.go",
|
||||
"metadata_linux.go",
|
||||
"metadata_other.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/cloudstack",
|
||||
deps = [
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//staging/src/k8s.io/cloud-provider:go_default_library",
|
||||
"//vendor/github.com/d2g/dhcp4:go_default_library",
|
||||
"//vendor/github.com/kardianos/osext:go_default_library",
|
||||
"//vendor/github.com/xanzy/go-cloudstack/cloudstack:go_default_library",
|
||||
"//vendor/gopkg.in/gcfg.v1:go_default_library",
|
||||
"//vendor/k8s.io/klog:go_default_library",
|
||||
] + select({
|
||||
"@io_bazel_rules_go//go/platform:android": [
|
||||
"//vendor/github.com/d2g/dhcp4client:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:darwin": [
|
||||
"//vendor/github.com/d2g/dhcp4client:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:dragonfly": [
|
||||
"//vendor/github.com/d2g/dhcp4client:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:freebsd": [
|
||||
"//vendor/github.com/d2g/dhcp4client:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:linux": [
|
||||
"//vendor/github.com/d2g/dhcp4client:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:nacl": [
|
||||
"//vendor/github.com/d2g/dhcp4client:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:netbsd": [
|
||||
"//vendor/github.com/d2g/dhcp4client:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:openbsd": [
|
||||
"//vendor/github.com/d2g/dhcp4client:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:plan9": [
|
||||
"//vendor/github.com/d2g/dhcp4client:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:solaris": [
|
||||
"//vendor/github.com/d2g/dhcp4client:go_default_library",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:windows": [
|
||||
"//vendor/github.com/d2g/dhcp4client:go_default_library",
|
||||
],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["cloudstack_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
@@ -1,6 +0,0 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- ngtuna
|
||||
- sebgoa
|
||||
- svanharmelen
|
@@ -1,265 +0,0 @@
|
||||
/*
|
||||
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
|
||||
}
|
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
"github.com/xanzy/go-cloudstack/cloudstack"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
cloudprovider "k8s.io/cloud-provider"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
// NodeAddresses returns the addresses of the specified instance.
|
||||
func (cs *CSCloud) NodeAddresses(ctx context.Context, name types.NodeName) ([]v1.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) ([]v1.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) ([]v1.NodeAddress, error) {
|
||||
if len(instance.Nic) == 0 {
|
||||
return nil, errors.New("instance does not have an internal IP")
|
||||
}
|
||||
|
||||
addresses := []v1.NodeAddress{
|
||||
{Type: v1.NodeInternalIP, Address: instance.Nic[0].Ipaddress},
|
||||
}
|
||||
|
||||
if instance.Hostname != "" {
|
||||
addresses = append(addresses, v1.NodeAddress{Type: v1.NodeHostName, Address: instance.Hostname})
|
||||
}
|
||||
|
||||
if instance.Publicip != "" {
|
||||
addresses = append(addresses, v1.NodeAddress{Type: v1.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 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(
|
||||
providerID,
|
||||
cloudstack.WithProject(cs.projectID),
|
||||
)
|
||||
if err != nil {
|
||||
if count == 0 {
|
||||
return "", cloudprovider.InstanceNotFound
|
||||
}
|
||||
return "", fmt.Errorf("error retrieving instance type: %v", err)
|
||||
}
|
||||
|
||||
return 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(
|
||||
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
|
||||
}
|
@@ -1,549 +0,0 @@
|
||||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/xanzy/go-cloudstack/cloudstack"
|
||||
"k8s.io/klog"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
cloudprovider "k8s.io/cloud-provider"
|
||||
)
|
||||
|
||||
type loadBalancer struct {
|
||||
*cloudstack.CloudStackClient
|
||||
|
||||
name string
|
||||
algorithm string
|
||||
hostIDs []string
|
||||
ipAddr string
|
||||
ipAddrID string
|
||||
networkID string
|
||||
projectID string
|
||||
rules map[string]*cloudstack.LoadBalancerRule
|
||||
}
|
||||
|
||||
// GetLoadBalancer returns whether the specified load balancer exists, and if so, what its status is.
|
||||
func (cs *CSCloud) GetLoadBalancer(ctx context.Context, clusterName string, service *v1.Service) (*v1.LoadBalancerStatus, bool, error) {
|
||||
klog.V(4).Infof("GetLoadBalancer(%v, %v, %v)", clusterName, service.Namespace, service.Name)
|
||||
|
||||
// Get the load balancer details and existing rules.
|
||||
lb, err := cs.getLoadBalancer(service)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// If we don't have any rules, the load balancer does not exist.
|
||||
if len(lb.rules) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
klog.V(4).Infof("Found a load balancer associated with IP %v", lb.ipAddr)
|
||||
|
||||
status := &v1.LoadBalancerStatus{}
|
||||
status.Ingress = append(status.Ingress, v1.LoadBalancerIngress{IP: lb.ipAddr})
|
||||
|
||||
return status, true, nil
|
||||
}
|
||||
|
||||
// EnsureLoadBalancer creates a new load balancer, or updates the existing one. Returns the status of the balancer.
|
||||
func (cs *CSCloud) EnsureLoadBalancer(ctx context.Context, clusterName string, service *v1.Service, nodes []*v1.Node) (status *v1.LoadBalancerStatus, err error) {
|
||||
klog.V(4).Infof("EnsureLoadBalancer(%v, %v, %v, %v, %v, %v)", clusterName, service.Namespace, service.Name, service.Spec.LoadBalancerIP, service.Spec.Ports, nodes)
|
||||
|
||||
if len(service.Spec.Ports) == 0 {
|
||||
return nil, fmt.Errorf("requested load balancer with no ports")
|
||||
}
|
||||
|
||||
// Get the load balancer details and existing rules.
|
||||
lb, err := cs.getLoadBalancer(service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set the load balancer algorithm.
|
||||
switch service.Spec.SessionAffinity {
|
||||
case v1.ServiceAffinityNone:
|
||||
lb.algorithm = "roundrobin"
|
||||
case v1.ServiceAffinityClientIP:
|
||||
lb.algorithm = "source"
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported load balancer affinity: %v", service.Spec.SessionAffinity)
|
||||
}
|
||||
|
||||
// Verify that all the hosts belong to the same network, and retrieve their ID's.
|
||||
lb.hostIDs, lb.networkID, err = cs.verifyHosts(nodes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !lb.hasLoadBalancerIP() {
|
||||
// Create or retrieve the load balancer IP.
|
||||
if err := lb.getLoadBalancerIP(service.Spec.LoadBalancerIP); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if lb.ipAddr != "" && lb.ipAddr != service.Spec.LoadBalancerIP {
|
||||
defer func(lb *loadBalancer) {
|
||||
if err != nil {
|
||||
if err := lb.releaseLoadBalancerIP(); err != nil {
|
||||
klog.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
}(lb)
|
||||
}
|
||||
}
|
||||
|
||||
klog.V(4).Infof("Load balancer %v is associated with IP %v", lb.name, lb.ipAddr)
|
||||
|
||||
for _, port := range service.Spec.Ports {
|
||||
// All ports have their own load balancer rule, so add the port to lbName to keep the names unique.
|
||||
lbRuleName := fmt.Sprintf("%s-%d", lb.name, port.Port)
|
||||
|
||||
// If the load balancer rule exists and is up-to-date, we move on to the next rule.
|
||||
exists, needsUpdate, err := lb.checkLoadBalancerRule(lbRuleName, port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exists && !needsUpdate {
|
||||
klog.V(4).Infof("Load balancer rule %v is up-to-date", lbRuleName)
|
||||
// Delete the rule from the map, to prevent it being deleted.
|
||||
delete(lb.rules, lbRuleName)
|
||||
continue
|
||||
}
|
||||
|
||||
if needsUpdate {
|
||||
klog.V(4).Infof("Updating load balancer rule: %v", lbRuleName)
|
||||
if err := lb.updateLoadBalancerRule(lbRuleName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Delete the rule from the map, to prevent it being deleted.
|
||||
delete(lb.rules, lbRuleName)
|
||||
continue
|
||||
}
|
||||
|
||||
klog.V(4).Infof("Creating load balancer rule: %v", lbRuleName)
|
||||
lbRule, err := lb.createLoadBalancerRule(lbRuleName, port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
klog.V(4).Infof("Assigning hosts (%v) to load balancer rule: %v", lb.hostIDs, lbRuleName)
|
||||
if err = lb.assignHostsToRule(lbRule, lb.hostIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Cleanup any rules that are now still in the rules map, as they are no longer needed.
|
||||
for _, lbRule := range lb.rules {
|
||||
klog.V(4).Infof("Deleting obsolete load balancer rule: %v", lbRule.Name)
|
||||
if err := lb.deleteLoadBalancerRule(lbRule); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
status = &v1.LoadBalancerStatus{}
|
||||
status.Ingress = []v1.LoadBalancerIngress{{IP: lb.ipAddr}}
|
||||
|
||||
return status, nil
|
||||
}
|
||||
|
||||
// UpdateLoadBalancer updates hosts under the specified load balancer.
|
||||
func (cs *CSCloud) UpdateLoadBalancer(ctx context.Context, clusterName string, service *v1.Service, nodes []*v1.Node) error {
|
||||
klog.V(4).Infof("UpdateLoadBalancer(%v, %v, %v, %v)", clusterName, service.Namespace, service.Name, nodes)
|
||||
|
||||
// Get the load balancer details and existing rules.
|
||||
lb, err := cs.getLoadBalancer(service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify that all the hosts belong to the same network, and retrieve their ID's.
|
||||
lb.hostIDs, _, err = cs.verifyHosts(nodes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, lbRule := range lb.rules {
|
||||
p := lb.LoadBalancer.NewListLoadBalancerRuleInstancesParams(lbRule.Id)
|
||||
|
||||
// Retrieve all VMs currently associated to this load balancer rule.
|
||||
l, err := lb.LoadBalancer.ListLoadBalancerRuleInstances(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving associated instances: %v", err)
|
||||
}
|
||||
|
||||
assign, remove := symmetricDifference(lb.hostIDs, l.LoadBalancerRuleInstances)
|
||||
|
||||
if len(assign) > 0 {
|
||||
klog.V(4).Infof("Assigning new hosts (%v) to load balancer rule: %v", assign, lbRule.Name)
|
||||
if err := lb.assignHostsToRule(lbRule, assign); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(remove) > 0 {
|
||||
klog.V(4).Infof("Removing old hosts (%v) from load balancer rule: %v", assign, lbRule.Name)
|
||||
if err := lb.removeHostsFromRule(lbRule, remove); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnsureLoadBalancerDeleted deletes the specified load balancer if it exists, returning
|
||||
// nil if the load balancer specified either didn't exist or was successfully deleted.
|
||||
func (cs *CSCloud) EnsureLoadBalancerDeleted(ctx context.Context, clusterName string, service *v1.Service) error {
|
||||
klog.V(4).Infof("EnsureLoadBalancerDeleted(%v, %v, %v)", clusterName, service.Namespace, service.Name)
|
||||
|
||||
// Get the load balancer details and existing rules.
|
||||
lb, err := cs.getLoadBalancer(service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, lbRule := range lb.rules {
|
||||
klog.V(4).Infof("Deleting load balancer rule: %v", lbRule.Name)
|
||||
if err := lb.deleteLoadBalancerRule(lbRule); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if lb.ipAddr != "" && lb.ipAddr != service.Spec.LoadBalancerIP {
|
||||
klog.V(4).Infof("Releasing load balancer IP: %v", lb.ipAddr)
|
||||
if err := lb.releaseLoadBalancerIP(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLoadBalancerName retrieves the name of the LoadBalancer.
|
||||
func (cs *CSCloud) GetLoadBalancerName(ctx context.Context, clusterName string, service *v1.Service) string {
|
||||
return cloudprovider.DefaultLoadBalancerName(service)
|
||||
}
|
||||
|
||||
// getLoadBalancer retrieves the IP address and ID and all the existing rules it can find.
|
||||
func (cs *CSCloud) getLoadBalancer(service *v1.Service) (*loadBalancer, error) {
|
||||
lb := &loadBalancer{
|
||||
CloudStackClient: cs.client,
|
||||
name: cs.GetLoadBalancerName(context.TODO(), "", service),
|
||||
projectID: cs.projectID,
|
||||
rules: make(map[string]*cloudstack.LoadBalancerRule),
|
||||
}
|
||||
|
||||
p := cs.client.LoadBalancer.NewListLoadBalancerRulesParams()
|
||||
p.SetKeyword(lb.name)
|
||||
p.SetListall(true)
|
||||
|
||||
if cs.projectID != "" {
|
||||
p.SetProjectid(cs.projectID)
|
||||
}
|
||||
|
||||
l, err := cs.client.LoadBalancer.ListLoadBalancerRules(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving load balancer rules: %v", err)
|
||||
}
|
||||
|
||||
for _, lbRule := range l.LoadBalancerRules {
|
||||
lb.rules[lbRule.Name] = lbRule
|
||||
|
||||
if lb.ipAddr != "" && lb.ipAddr != lbRule.Publicip {
|
||||
klog.Warningf("Load balancer for service %v/%v has rules associated with different IP's: %v, %v", service.Namespace, service.Name, lb.ipAddr, lbRule.Publicip)
|
||||
}
|
||||
|
||||
lb.ipAddr = lbRule.Publicip
|
||||
lb.ipAddrID = lbRule.Publicipid
|
||||
}
|
||||
|
||||
klog.V(4).Infof("Load balancer %v contains %d rule(s)", lb.name, len(lb.rules))
|
||||
|
||||
return lb, nil
|
||||
}
|
||||
|
||||
// verifyHosts verifies if all hosts belong to the same network, and returns the host ID's and network ID.
|
||||
func (cs *CSCloud) verifyHosts(nodes []*v1.Node) ([]string, string, error) {
|
||||
hostNames := map[string]bool{}
|
||||
for _, node := range nodes {
|
||||
hostNames[node.Name] = true
|
||||
}
|
||||
|
||||
p := cs.client.VirtualMachine.NewListVirtualMachinesParams()
|
||||
p.SetListall(true)
|
||||
|
||||
if cs.projectID != "" {
|
||||
p.SetProjectid(cs.projectID)
|
||||
}
|
||||
|
||||
l, err := cs.client.VirtualMachine.ListVirtualMachines(p)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("error retrieving list of hosts: %v", err)
|
||||
}
|
||||
|
||||
var hostIDs []string
|
||||
var networkID string
|
||||
|
||||
// Check if the virtual machine is in the hosts slice, then add the corresponding ID.
|
||||
for _, vm := range l.VirtualMachines {
|
||||
if hostNames[vm.Name] {
|
||||
if networkID != "" && networkID != vm.Nic[0].Networkid {
|
||||
return nil, "", fmt.Errorf("found hosts that belong to different networks")
|
||||
}
|
||||
|
||||
networkID = vm.Nic[0].Networkid
|
||||
hostIDs = append(hostIDs, vm.Id)
|
||||
}
|
||||
}
|
||||
|
||||
return hostIDs, networkID, nil
|
||||
}
|
||||
|
||||
// hasLoadBalancerIP returns true if we have a load balancer address and ID.
|
||||
func (lb *loadBalancer) hasLoadBalancerIP() bool {
|
||||
return lb.ipAddr != "" && lb.ipAddrID != ""
|
||||
}
|
||||
|
||||
// getLoadBalancerIP retieves an existing IP or associates a new IP.
|
||||
func (lb *loadBalancer) getLoadBalancerIP(loadBalancerIP string) error {
|
||||
if loadBalancerIP != "" {
|
||||
return lb.getPublicIPAddress(loadBalancerIP)
|
||||
}
|
||||
|
||||
return lb.associatePublicIPAddress()
|
||||
}
|
||||
|
||||
// getPublicIPAddressID retrieves the ID of the given IP, and sets the address and it's ID.
|
||||
func (lb *loadBalancer) getPublicIPAddress(loadBalancerIP string) error {
|
||||
klog.V(4).Infof("Retrieve load balancer IP details: %v", loadBalancerIP)
|
||||
|
||||
p := lb.Address.NewListPublicIpAddressesParams()
|
||||
p.SetIpaddress(loadBalancerIP)
|
||||
p.SetListall(true)
|
||||
|
||||
if lb.projectID != "" {
|
||||
p.SetProjectid(lb.projectID)
|
||||
}
|
||||
|
||||
l, err := lb.Address.ListPublicIpAddresses(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error retrieving IP address: %v", err)
|
||||
}
|
||||
|
||||
if l.Count != 1 {
|
||||
return fmt.Errorf("could not find IP address %v", loadBalancerIP)
|
||||
}
|
||||
|
||||
lb.ipAddr = l.PublicIpAddresses[0].Ipaddress
|
||||
lb.ipAddrID = l.PublicIpAddresses[0].Id
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// associatePublicIPAddress associates a new IP and sets the address and it's ID.
|
||||
func (lb *loadBalancer) associatePublicIPAddress() error {
|
||||
klog.V(4).Infof("Allocate new IP for load balancer: %v", lb.name)
|
||||
// If a network belongs to a VPC, the IP address needs to be associated with
|
||||
// the VPC instead of with the network.
|
||||
network, count, err := lb.Network.GetNetworkByID(lb.networkID, cloudstack.WithProject(lb.projectID))
|
||||
if err != nil {
|
||||
if count == 0 {
|
||||
return fmt.Errorf("could not find network %v", lb.networkID)
|
||||
}
|
||||
return fmt.Errorf("error retrieving network: %v", err)
|
||||
}
|
||||
|
||||
p := lb.Address.NewAssociateIpAddressParams()
|
||||
|
||||
if network.Vpcid != "" {
|
||||
p.SetVpcid(network.Vpcid)
|
||||
} else {
|
||||
p.SetNetworkid(lb.networkID)
|
||||
}
|
||||
|
||||
if lb.projectID != "" {
|
||||
p.SetProjectid(lb.projectID)
|
||||
}
|
||||
|
||||
// Associate a new IP address
|
||||
r, err := lb.Address.AssociateIpAddress(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error associating new IP address: %v", err)
|
||||
}
|
||||
|
||||
lb.ipAddr = r.Ipaddress
|
||||
lb.ipAddrID = r.Id
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// releasePublicIPAddress releases an associated IP.
|
||||
func (lb *loadBalancer) releaseLoadBalancerIP() error {
|
||||
p := lb.Address.NewDisassociateIpAddressParams(lb.ipAddrID)
|
||||
|
||||
if _, err := lb.Address.DisassociateIpAddress(p); err != nil {
|
||||
return fmt.Errorf("error releasing load balancer IP %v: %v", lb.ipAddr, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkLoadBalancerRule checks if the rule already exists and if it does, if it can be updated. If
|
||||
// it does exist but cannot be updated, it will delete the existing rule so it can be created again.
|
||||
func (lb *loadBalancer) checkLoadBalancerRule(lbRuleName string, port v1.ServicePort) (bool, bool, error) {
|
||||
lbRule, ok := lb.rules[lbRuleName]
|
||||
if !ok {
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
// Check if any of the values we cannot update (those that require a new load balancer rule) are changed.
|
||||
if lbRule.Publicip == lb.ipAddr && lbRule.Privateport == strconv.Itoa(int(port.NodePort)) && lbRule.Publicport == strconv.Itoa(int(port.Port)) {
|
||||
return true, lbRule.Algorithm != lb.algorithm, nil
|
||||
}
|
||||
|
||||
// Delete the load balancer rule so we can create a new one using the new values.
|
||||
if err := lb.deleteLoadBalancerRule(lbRule); err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
// updateLoadBalancerRule updates a load balancer rule.
|
||||
func (lb *loadBalancer) updateLoadBalancerRule(lbRuleName string) error {
|
||||
lbRule := lb.rules[lbRuleName]
|
||||
|
||||
p := lb.LoadBalancer.NewUpdateLoadBalancerRuleParams(lbRule.Id)
|
||||
p.SetAlgorithm(lb.algorithm)
|
||||
|
||||
_, err := lb.LoadBalancer.UpdateLoadBalancerRule(p)
|
||||
return err
|
||||
}
|
||||
|
||||
// createLoadBalancerRule creates a new load balancer rule and returns it's ID.
|
||||
func (lb *loadBalancer) createLoadBalancerRule(lbRuleName string, port v1.ServicePort) (*cloudstack.LoadBalancerRule, error) {
|
||||
p := lb.LoadBalancer.NewCreateLoadBalancerRuleParams(
|
||||
lb.algorithm,
|
||||
lbRuleName,
|
||||
int(port.NodePort),
|
||||
int(port.Port),
|
||||
)
|
||||
|
||||
p.SetNetworkid(lb.networkID)
|
||||
p.SetPublicipid(lb.ipAddrID)
|
||||
|
||||
switch port.Protocol {
|
||||
case v1.ProtocolTCP:
|
||||
p.SetProtocol("TCP")
|
||||
case v1.ProtocolUDP:
|
||||
p.SetProtocol("UDP")
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported load balancer protocol: %v", port.Protocol)
|
||||
}
|
||||
|
||||
// Do not create corresponding firewall rule.
|
||||
p.SetOpenfirewall(false)
|
||||
|
||||
// Create a new load balancer rule.
|
||||
r, err := lb.LoadBalancer.CreateLoadBalancerRule(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating load balancer rule %v: %v", lbRuleName, err)
|
||||
}
|
||||
|
||||
lbRule := &cloudstack.LoadBalancerRule{
|
||||
Id: r.Id,
|
||||
Algorithm: r.Algorithm,
|
||||
Cidrlist: r.Cidrlist,
|
||||
Name: r.Name,
|
||||
Networkid: r.Networkid,
|
||||
Privateport: r.Privateport,
|
||||
Publicport: r.Publicport,
|
||||
Publicip: r.Publicip,
|
||||
Publicipid: r.Publicipid,
|
||||
}
|
||||
|
||||
return lbRule, nil
|
||||
}
|
||||
|
||||
// deleteLoadBalancerRule deletes a load balancer rule.
|
||||
func (lb *loadBalancer) deleteLoadBalancerRule(lbRule *cloudstack.LoadBalancerRule) error {
|
||||
p := lb.LoadBalancer.NewDeleteLoadBalancerRuleParams(lbRule.Id)
|
||||
|
||||
if _, err := lb.LoadBalancer.DeleteLoadBalancerRule(p); err != nil {
|
||||
return fmt.Errorf("error deleting load balancer rule %v: %v", lbRule.Name, err)
|
||||
}
|
||||
|
||||
// Delete the rule from the map as it no longer exists
|
||||
delete(lb.rules, lbRule.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// assignHostsToRule assigns hosts to a load balancer rule.
|
||||
func (lb *loadBalancer) assignHostsToRule(lbRule *cloudstack.LoadBalancerRule, hostIDs []string) error {
|
||||
p := lb.LoadBalancer.NewAssignToLoadBalancerRuleParams(lbRule.Id)
|
||||
p.SetVirtualmachineids(hostIDs)
|
||||
|
||||
if _, err := lb.LoadBalancer.AssignToLoadBalancerRule(p); err != nil {
|
||||
return fmt.Errorf("error assigning hosts to load balancer rule %v: %v", lbRule.Name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeHostsFromRule removes hosts from a load balancer rule.
|
||||
func (lb *loadBalancer) removeHostsFromRule(lbRule *cloudstack.LoadBalancerRule, hostIDs []string) error {
|
||||
p := lb.LoadBalancer.NewRemoveFromLoadBalancerRuleParams(lbRule.Id)
|
||||
p.SetVirtualmachineids(hostIDs)
|
||||
|
||||
if _, err := lb.LoadBalancer.RemoveFromLoadBalancerRule(p); err != nil {
|
||||
return fmt.Errorf("error removing hosts from load balancer rule %v: %v", lbRule.Name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// symmetricDifference returns the symmetric difference between the old (existing) and new (wanted) host ID's.
|
||||
func symmetricDifference(hostIDs []string, lbInstances []*cloudstack.VirtualMachine) ([]string, []string) {
|
||||
new := make(map[string]bool)
|
||||
for _, hostID := range hostIDs {
|
||||
new[hostID] = true
|
||||
}
|
||||
|
||||
var remove []string
|
||||
for _, instance := range lbInstances {
|
||||
if new[instance.Id] {
|
||||
delete(new, instance.Id)
|
||||
continue
|
||||
}
|
||||
|
||||
remove = append(remove, instance.Id)
|
||||
}
|
||||
|
||||
var assign []string
|
||||
for hostID := range new {
|
||||
assign = append(assign, hostID)
|
||||
}
|
||||
|
||||
return assign, remove
|
||||
}
|
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
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"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const testClusterName = "testCluster"
|
||||
|
||||
func TestReadConfig(t *testing.T) {
|
||||
_, err := readConfig(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Should not return an error when no config is provided: %v", err)
|
||||
}
|
||||
|
||||
cfg, err := readConfig(strings.NewReader(`
|
||||
[Global]
|
||||
api-url = https://cloudstack.url
|
||||
api-key = a-valid-api-key
|
||||
secret-key = a-valid-secret-key
|
||||
ssl-no-verify = true
|
||||
project-id = a-valid-project-id
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatalf("Should succeed when a valid config is provided: %v", err)
|
||||
}
|
||||
|
||||
if cfg.Global.APIURL != "https://cloudstack.url" {
|
||||
t.Errorf("incorrect api-url: %s", cfg.Global.APIURL)
|
||||
}
|
||||
if cfg.Global.APIKey != "a-valid-api-key" {
|
||||
t.Errorf("incorrect api-key: %s", cfg.Global.APIKey)
|
||||
}
|
||||
if cfg.Global.SecretKey != "a-valid-secret-key" {
|
||||
t.Errorf("incorrect secret-key: %s", cfg.Global.SecretKey)
|
||||
}
|
||||
if !cfg.Global.SSLNoVerify {
|
||||
t.Errorf("incorrect ssl-no-verify: %t", cfg.Global.SSLNoVerify)
|
||||
}
|
||||
}
|
||||
|
||||
// This allows acceptance testing against an existing CloudStack environment.
|
||||
func configFromEnv() (*CSConfig, bool) {
|
||||
cfg := &CSConfig{}
|
||||
|
||||
cfg.Global.APIURL = os.Getenv("CS_API_URL")
|
||||
cfg.Global.APIKey = os.Getenv("CS_API_KEY")
|
||||
cfg.Global.SecretKey = os.Getenv("CS_SECRET_KEY")
|
||||
cfg.Global.ProjectID = os.Getenv("CS_PROJECT_ID")
|
||||
|
||||
// It is save to ignore the error here. If the input cannot be parsed SSLNoVerify
|
||||
// will still be a bool with its zero value (false) which is the expected default.
|
||||
cfg.Global.SSLNoVerify, _ = strconv.ParseBool(os.Getenv("CS_SSL_NO_VERIFY"))
|
||||
|
||||
// Check if we have the minimum required info to be able to connect to CloudStack.
|
||||
ok := cfg.Global.APIURL != "" && cfg.Global.APIKey != "" && cfg.Global.SecretKey != ""
|
||||
|
||||
return cfg, ok
|
||||
}
|
||||
|
||||
func TestNewCSCloud(t *testing.T) {
|
||||
cfg, ok := configFromEnv()
|
||||
if !ok {
|
||||
t.Skipf("No config found in environment")
|
||||
}
|
||||
|
||||
_, err := newCSCloud(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct/authenticate CloudStack: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadBalancer(t *testing.T) {
|
||||
cfg, ok := configFromEnv()
|
||||
if !ok {
|
||||
t.Skipf("No config found in environment")
|
||||
}
|
||||
|
||||
cs, err := newCSCloud(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to construct/authenticate CloudStack: %v", err)
|
||||
}
|
||||
|
||||
lb, ok := cs.LoadBalancer()
|
||||
if !ok {
|
||||
t.Fatalf("LoadBalancer() returned false")
|
||||
}
|
||||
|
||||
_, exists, err := lb.GetLoadBalancer(context.TODO(), testClusterName, &v1.Service{ObjectMeta: metav1.ObjectMeta{Name: "noexist"}})
|
||||
if err != nil {
|
||||
t.Fatalf("GetLoadBalancer(\"noexist\") returned error: %s", err)
|
||||
}
|
||||
if exists {
|
||||
t.Fatalf("GetLoadBalancer(\"noexist\") returned exists")
|
||||
}
|
||||
}
|
@@ -1,226 +0,0 @@
|
||||
/*
|
||||
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/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/d2g/dhcp4"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
cloudprovider "k8s.io/cloud-provider"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
var _ cloudprovider.Instances = (*metadata)(nil)
|
||||
var _ cloudprovider.Zones = (*metadata)(nil)
|
||||
|
||||
type metadata struct {
|
||||
dhcpServer string
|
||||
zone string
|
||||
}
|
||||
|
||||
type metadataType string
|
||||
|
||||
const (
|
||||
metadataTypeHostname metadataType = "local-hostname"
|
||||
metadataTypeExternalIP metadataType = "public-ipv4"
|
||||
metadataTypeInternalIP metadataType = "local-ipv4"
|
||||
metadataTypeInstanceID metadataType = "instance-id"
|
||||
metadataTypeInstanceType metadataType = "service-offering"
|
||||
metadataTypeZone metadataType = "availability-zone"
|
||||
)
|
||||
|
||||
// NodeAddresses returns the addresses of the specified instance.
|
||||
func (m *metadata) NodeAddresses(ctx context.Context, name types.NodeName) ([]v1.NodeAddress, error) {
|
||||
externalIP, err := m.get(metadataTypeExternalIP)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get external IP: %v", err)
|
||||
}
|
||||
|
||||
internalIP, err := m.get(metadataTypeInternalIP)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get internal IP: %v", err)
|
||||
}
|
||||
|
||||
addresses := []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: externalIP},
|
||||
{Type: v1.NodeInternalIP, Address: internalIP},
|
||||
}
|
||||
|
||||
hostname, err := m.get(metadataTypeHostname)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get hostname: %v", err)
|
||||
}
|
||||
if hostname != "" {
|
||||
addresses = append(addresses, v1.NodeAddress{Type: v1.NodeHostName, Address: hostname})
|
||||
}
|
||||
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
// NodeAddressesByProviderID returns the addresses of the specified instance.
|
||||
func (m *metadata) NodeAddressesByProviderID(ctx context.Context, providerID string) ([]v1.NodeAddress, error) {
|
||||
return nil, errors.New("NodeAddressesByProviderID not implemented")
|
||||
}
|
||||
|
||||
// InstanceID returns the cloud provider ID of the specified instance.
|
||||
func (m *metadata) InstanceID(ctx context.Context, name types.NodeName) (string, error) {
|
||||
instanceID, err := m.get(metadataTypeInstanceID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not get instance ID: %v", err)
|
||||
}
|
||||
|
||||
zone, err := m.get(metadataTypeZone)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not get zone: %v", err)
|
||||
}
|
||||
|
||||
return "/" + zone + "/" + instanceID, nil
|
||||
}
|
||||
|
||||
// InstanceType returns the type of the specified instance.
|
||||
func (m *metadata) InstanceType(ctx context.Context, name types.NodeName) (string, error) {
|
||||
instanceType, err := m.get(metadataTypeInstanceType)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not get instance type: %v", err)
|
||||
}
|
||||
|
||||
return instanceType, nil
|
||||
}
|
||||
|
||||
// InstanceTypeByProviderID returns the type of the specified instance.
|
||||
func (m *metadata) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) {
|
||||
return "", errors.New("InstanceTypeByProviderID not implemented")
|
||||
}
|
||||
|
||||
// AddSSHKeyToAllInstances is currently not implemented.
|
||||
func (m *metadata) 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 (m *metadata) CurrentNodeName(ctx context.Context, hostname string) (types.NodeName, error) {
|
||||
return types.NodeName(hostname), nil
|
||||
}
|
||||
|
||||
// InstanceExistsByProviderID returns if the instance still exists.
|
||||
func (m *metadata) InstanceExistsByProviderID(ctx context.Context, providerID string) (bool, error) {
|
||||
return false, errors.New("InstanceExistsByProviderID not implemented")
|
||||
}
|
||||
|
||||
// InstanceShutdownByProviderID returns if the instance is shutdown.
|
||||
func (m *metadata) InstanceShutdownByProviderID(ctx context.Context, providerID string) (bool, error) {
|
||||
return false, cloudprovider.NotImplemented
|
||||
}
|
||||
|
||||
// GetZone returns the Zone containing the region that the program is running in.
|
||||
func (m *metadata) GetZone(ctx context.Context) (cloudprovider.Zone, error) {
|
||||
zone := cloudprovider.Zone{}
|
||||
|
||||
if m.zone == "" {
|
||||
zoneName, err := m.get(metadataTypeZone)
|
||||
if err != nil {
|
||||
return zone, fmt.Errorf("could not get zone: %v", err)
|
||||
}
|
||||
|
||||
m.zone = zoneName
|
||||
}
|
||||
|
||||
klog.V(2).Infof("Current zone is %v", zone)
|
||||
zone.FailureDomain = m.zone
|
||||
zone.Region = m.zone
|
||||
|
||||
return zone, nil
|
||||
}
|
||||
|
||||
// GetZoneByProviderID returns the Zone, found by using the provider ID.
|
||||
func (m *metadata) GetZoneByProviderID(ctx context.Context, providerID string) (cloudprovider.Zone, error) {
|
||||
return cloudprovider.Zone{}, errors.New("GetZoneByProviderID not implemented")
|
||||
}
|
||||
|
||||
// GetZoneByNodeName returns the Zone, found by using the node name.
|
||||
func (m *metadata) GetZoneByNodeName(ctx context.Context, nodeName types.NodeName) (cloudprovider.Zone, error) {
|
||||
return cloudprovider.Zone{}, errors.New("GetZoneByNodeName not implemented")
|
||||
}
|
||||
|
||||
func (m *metadata) get(mdType metadataType) (string, error) {
|
||||
url := fmt.Sprintf("http://%s/latest/meta-data/%s", m.dhcpServer, mdType)
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading metadata: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("unexpected HTTP status: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading response body: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func findDHCPServer() (string, error) {
|
||||
nics, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not get interfaces: %v", err)
|
||||
}
|
||||
|
||||
for _, nic := range nics {
|
||||
if nic.Flags&net.FlagUp == 1 && nic.Flags&net.FlagLoopback == 0 && nic.Flags&net.FlagPointToPoint == 0 {
|
||||
addrs, err := nic.Addrs()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading IP addresses from interface %v: %v", nic.Name, err)
|
||||
}
|
||||
|
||||
if addrs != nil {
|
||||
client, err := newDHCPClient(&nic)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error creating new DHCP client: %v", err)
|
||||
}
|
||||
|
||||
discoverPacket, err := client.SendDiscoverPacket()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error sending DHCP discover package: %v", err)
|
||||
}
|
||||
|
||||
offerPacket, err := client.GetOffer(&discoverPacket)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error receiving DHCP offer package: %v", err)
|
||||
}
|
||||
|
||||
offerPacketOptions := offerPacket.ParseOptions()
|
||||
|
||||
if ipaddr, ok := offerPacketOptions[dhcp4.OptionServerIdentifier]; ok {
|
||||
return net.IP(ipaddr).String(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("no server found")
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
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 (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/d2g/dhcp4client"
|
||||
)
|
||||
|
||||
func newDHCPClient(nic *net.Interface) (*dhcp4client.Client, error) {
|
||||
pktsock, err := dhcp4client.NewPacketSock(nic.Index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dhcp4client.New(
|
||||
dhcp4client.HardwareAddr(nic.HardwareAddr),
|
||||
dhcp4client.Timeout(2*time.Second),
|
||||
dhcp4client.Broadcast(false),
|
||||
dhcp4client.Connection(pktsock),
|
||||
)
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
// +build !linux
|
||||
|
||||
/*
|
||||
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 (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/d2g/dhcp4client"
|
||||
)
|
||||
|
||||
func newDHCPClient(nic *net.Interface) (*dhcp4client.Client, error) {
|
||||
inetsock, err := dhcp4client.NewInetSock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dhcp4client.New(
|
||||
dhcp4client.HardwareAddr(nic.HardwareAddr),
|
||||
dhcp4client.Timeout(2*time.Second),
|
||||
dhcp4client.Broadcast(false),
|
||||
dhcp4client.Connection(inetsock),
|
||||
)
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["ovirt.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/ovirt",
|
||||
deps = [
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//staging/src/k8s.io/cloud-provider:go_default_library",
|
||||
"//vendor/gopkg.in/gcfg.v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["ovirt_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = ["//staging/src/k8s.io/cloud-provider:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
@@ -1,329 +0,0 @@
|
||||
/*
|
||||
Copyright 2014 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 ovirt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/gcfg.v1"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
cloudprovider "k8s.io/cloud-provider"
|
||||
)
|
||||
|
||||
// ProviderName is the name of this cloud provider.
|
||||
const ProviderName = "ovirt"
|
||||
|
||||
// Instance specifies UUID, name and IP address of the instance.
|
||||
type Instance struct {
|
||||
UUID string
|
||||
Name string
|
||||
IPAddress string
|
||||
}
|
||||
|
||||
// InstanceMap provides the map of Ovirt instances.
|
||||
type InstanceMap map[string]Instance
|
||||
|
||||
var _ cloudprovider.Interface = (*Cloud)(nil)
|
||||
var _ cloudprovider.Instances = (*Cloud)(nil)
|
||||
|
||||
// Cloud is an implementation of the cloud provider interface for Ovirt.
|
||||
type Cloud struct {
|
||||
VmsRequest *url.URL
|
||||
HostsRequest *url.URL
|
||||
}
|
||||
|
||||
// APIConfig wraps the api settings for the Ovirt.
|
||||
type APIConfig struct {
|
||||
Connection struct {
|
||||
APIEntry string `gcfg:"uri"`
|
||||
Username string `gcfg:"username"`
|
||||
Password string `gcfg:"password"`
|
||||
}
|
||||
Filters struct {
|
||||
VmsQuery string `gcfg:"vms"`
|
||||
}
|
||||
}
|
||||
|
||||
// XMLVMAddress is an implementation for the Ovirt instance IP address in xml.
|
||||
type XMLVMAddress struct {
|
||||
Address string `xml:"address,attr"`
|
||||
}
|
||||
|
||||
// XMLVMInfo is an implementation for the Ovirt instance details in xml.
|
||||
type XMLVMInfo struct {
|
||||
UUID string `xml:"id,attr"`
|
||||
Name string `xml:"name"`
|
||||
Hostname string `xml:"guest_info>fqdn"`
|
||||
Addresses []XMLVMAddress `xml:"guest_info>ips>ip"`
|
||||
State string `xml:"status>state"`
|
||||
}
|
||||
|
||||
// XMLVmsList is an implementation to provide the list of Ovirt instances.
|
||||
type XMLVmsList struct {
|
||||
XMLName xml.Name `xml:"vms"`
|
||||
VM []XMLVMInfo `xml:"vm"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloudprovider.RegisterCloudProvider(ProviderName,
|
||||
func(config io.Reader) (cloudprovider.Interface, error) {
|
||||
return newOVirtCloud(config)
|
||||
})
|
||||
}
|
||||
|
||||
func newOVirtCloud(config io.Reader) (*Cloud, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("missing configuration file for ovirt cloud provider")
|
||||
}
|
||||
|
||||
oVirtConfig := APIConfig{}
|
||||
|
||||
/* defaults */
|
||||
oVirtConfig.Connection.Username = "admin@internal"
|
||||
|
||||
if err := gcfg.ReadInto(&oVirtConfig, config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if oVirtConfig.Connection.APIEntry == "" {
|
||||
return nil, fmt.Errorf("missing ovirt uri in cloud provider configuration")
|
||||
}
|
||||
|
||||
request, err := url.Parse(oVirtConfig.Connection.APIEntry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request.Path = path.Join(request.Path, "vms")
|
||||
request.User = url.UserPassword(oVirtConfig.Connection.Username, oVirtConfig.Connection.Password)
|
||||
request.RawQuery = url.Values{"search": {oVirtConfig.Filters.VmsQuery}}.Encode()
|
||||
|
||||
return &Cloud{VmsRequest: request}, nil
|
||||
}
|
||||
|
||||
// Initialize passes a Kubernetes clientBuilder interface to the cloud provider
|
||||
func (v *Cloud) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, stop <-chan struct{}) {
|
||||
}
|
||||
|
||||
// Clusters returns the list of clusters.
|
||||
func (v *Cloud) Clusters() (cloudprovider.Clusters, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// ProviderName returns the cloud provider ID.
|
||||
func (v *Cloud) ProviderName() string {
|
||||
return ProviderName
|
||||
}
|
||||
|
||||
// HasClusterID returns true if the cluster has a clusterID
|
||||
func (v *Cloud) HasClusterID() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// LoadBalancer returns an implementation of LoadBalancer for oVirt cloud
|
||||
func (v *Cloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Instances returns an implementation of Instances for oVirt cloud
|
||||
func (v *Cloud) Instances() (cloudprovider.Instances, bool) {
|
||||
return v, true
|
||||
}
|
||||
|
||||
// Zones returns an implementation of Zones for oVirt cloud
|
||||
func (v *Cloud) Zones() (cloudprovider.Zones, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Routes returns an implementation of Routes for oVirt cloud
|
||||
func (v *Cloud) Routes() (cloudprovider.Routes, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// NodeAddresses returns the NodeAddresses of the instance with the specified nodeName.
|
||||
func (v *Cloud) NodeAddresses(ctx context.Context, nodeName types.NodeName) ([]v1.NodeAddress, error) {
|
||||
name := mapNodeNameToInstanceName(nodeName)
|
||||
instance, err := v.fetchInstance(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var address net.IP
|
||||
|
||||
if instance.IPAddress != "" {
|
||||
address = net.ParseIP(instance.IPAddress)
|
||||
if address == nil {
|
||||
return nil, fmt.Errorf("couldn't parse address: %s", instance.IPAddress)
|
||||
}
|
||||
} else {
|
||||
resolved, err := net.LookupIP(name)
|
||||
if err != nil || len(resolved) < 1 {
|
||||
return nil, fmt.Errorf("couldn't lookup address: %s", name)
|
||||
}
|
||||
address = resolved[0]
|
||||
}
|
||||
|
||||
return []v1.NodeAddress{
|
||||
{Type: v1.NodeInternalIP, Address: address.String()},
|
||||
{Type: v1.NodeExternalIP, Address: address.String()},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NodeAddressesByProviderID returns the node addresses of an instances with the specified unique providerID
|
||||
// This method will not be called from the node that is requesting this ID. i.e. metadata service
|
||||
// and other local methods cannot be used here
|
||||
func (v *Cloud) NodeAddressesByProviderID(ctx context.Context, providerID string) ([]v1.NodeAddress, error) {
|
||||
return []v1.NodeAddress{}, cloudprovider.NotImplemented
|
||||
}
|
||||
|
||||
// mapNodeNameToInstanceName maps from a k8s NodeName to an ovirt instance name (the hostname)
|
||||
// This is a simple string cast
|
||||
func mapNodeNameToInstanceName(nodeName types.NodeName) string {
|
||||
return string(nodeName)
|
||||
}
|
||||
|
||||
// InstanceExistsByProviderID returns true if the instance with the given provider id still exists and is running.
|
||||
// If false is returned with no error, the instance will be immediately deleted by the cloud controller manager.
|
||||
func (v *Cloud) InstanceExistsByProviderID(ctx context.Context, providerID string) (bool, error) {
|
||||
return false, cloudprovider.NotImplemented
|
||||
}
|
||||
|
||||
// InstanceShutdownByProviderID returns true if the instance is in safe state to detach volumes
|
||||
func (v *Cloud) InstanceShutdownByProviderID(ctx context.Context, providerID string) (bool, error) {
|
||||
return false, cloudprovider.NotImplemented
|
||||
}
|
||||
|
||||
// InstanceID returns the cloud provider ID of the node with the specified NodeName.
|
||||
func (v *Cloud) InstanceID(ctx context.Context, nodeName types.NodeName) (string, error) {
|
||||
name := mapNodeNameToInstanceName(nodeName)
|
||||
instance, err := v.fetchInstance(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// TODO: define a way to identify the provider instance to complete
|
||||
// the format <provider_instance_id>/<instance_id>.
|
||||
return "/" + instance.UUID, err
|
||||
}
|
||||
|
||||
// InstanceTypeByProviderID returns the cloudprovider instance type of the node with the specified unique providerID
|
||||
// This method will not be called from the node that is requesting this ID. i.e. metadata service
|
||||
// and other local methods cannot be used here
|
||||
func (v *Cloud) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) {
|
||||
return "", cloudprovider.NotImplemented
|
||||
}
|
||||
|
||||
// InstanceType returns the type of the specified instance.
|
||||
func (v *Cloud) InstanceType(ctx context.Context, name types.NodeName) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func getInstancesFromXML(body io.Reader) (InstanceMap, error) {
|
||||
if body == nil {
|
||||
return nil, fmt.Errorf("ovirt rest-api response body is missing")
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadAll(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vmlist := XMLVmsList{}
|
||||
|
||||
if err := xml.Unmarshal(content, &vmlist); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instances := make(InstanceMap)
|
||||
|
||||
for _, vm := range vmlist.VM {
|
||||
// Always return only vms that are up and running
|
||||
if vm.Hostname != "" && strings.ToLower(vm.State) == "up" {
|
||||
address := ""
|
||||
if len(vm.Addresses) > 0 {
|
||||
address = vm.Addresses[0].Address
|
||||
}
|
||||
|
||||
instances[vm.Hostname] = Instance{
|
||||
UUID: vm.UUID,
|
||||
Name: vm.Name,
|
||||
IPAddress: address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return instances, nil
|
||||
}
|
||||
|
||||
func (v *Cloud) fetchAllInstances() (InstanceMap, error) {
|
||||
response, err := http.Get(v.VmsRequest.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
|
||||
return getInstancesFromXML(response.Body)
|
||||
}
|
||||
|
||||
func (v *Cloud) fetchInstance(name string) (*Instance, error) {
|
||||
allInstances, err := v.fetchAllInstances()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instance, found := allInstances[name]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("cannot find instance: %s", name)
|
||||
}
|
||||
|
||||
return &instance, nil
|
||||
}
|
||||
|
||||
// ListSortedNames returns the list of sorted Ovirt instances name.
|
||||
func (m *InstanceMap) ListSortedNames() []string {
|
||||
var names []string
|
||||
|
||||
for k := range *m {
|
||||
names = append(names, k)
|
||||
}
|
||||
|
||||
sort.Strings(names)
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
// CurrentNodeName is implementation of Instances.CurrentNodeName.
|
||||
func (v *Cloud) CurrentNodeName(ctx context.Context, hostname string) (types.NodeName, error) {
|
||||
return types.NodeName(hostname), nil
|
||||
}
|
||||
|
||||
// AddSSHKeyToAllInstances is currently not implemented.
|
||||
func (v *Cloud) AddSSHKeyToAllInstances(ctx context.Context, user string, keyData []byte) error {
|
||||
return cloudprovider.NotImplemented
|
||||
}
|
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
Copyright 2014 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 ovirt
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
cloudprovider "k8s.io/cloud-provider"
|
||||
)
|
||||
|
||||
func TestOVirtCloudConfiguration(t *testing.T) {
|
||||
config1 := (io.Reader)(nil)
|
||||
|
||||
_, err1 := cloudprovider.GetCloudProvider("ovirt", config1)
|
||||
if err1 == nil {
|
||||
t.Fatalf("An error is expected when the configuration is missing")
|
||||
}
|
||||
|
||||
config2 := strings.NewReader("")
|
||||
|
||||
_, err2 := cloudprovider.GetCloudProvider("ovirt", config2)
|
||||
if err2 == nil {
|
||||
t.Fatalf("An error is expected when the configuration is empty")
|
||||
}
|
||||
|
||||
config3 := strings.NewReader(`
|
||||
[connection]
|
||||
`)
|
||||
|
||||
_, err3 := cloudprovider.GetCloudProvider("ovirt", config3)
|
||||
if err3 == nil {
|
||||
t.Fatalf("An error is expected when the uri is missing")
|
||||
}
|
||||
|
||||
config4 := strings.NewReader(`
|
||||
[connection]
|
||||
uri = https://localhost:8443/ovirt-engine/api
|
||||
`)
|
||||
|
||||
_, err4 := cloudprovider.GetCloudProvider("ovirt", config4)
|
||||
if err4 != nil {
|
||||
t.Fatalf("Unexpected error creating the provider: %s", err4)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOVirtCloudXmlParsing(t *testing.T) {
|
||||
body1 := (io.Reader)(nil)
|
||||
|
||||
_, err1 := getInstancesFromXML(body1)
|
||||
if err1 == nil {
|
||||
t.Fatalf("An error is expected when body is missing")
|
||||
}
|
||||
|
||||
body2 := strings.NewReader("")
|
||||
|
||||
_, err2 := getInstancesFromXML(body2)
|
||||
if err2 == nil {
|
||||
t.Fatalf("An error is expected when body is empty")
|
||||
}
|
||||
|
||||
body3 := strings.NewReader(`
|
||||
<vms>
|
||||
<vm></vm>
|
||||
</vms>
|
||||
`)
|
||||
|
||||
instances3, err3 := getInstancesFromXML(body3)
|
||||
if err3 != nil {
|
||||
t.Fatalf("Unexpected error listing instances: %s", err3)
|
||||
}
|
||||
if len(instances3) > 0 {
|
||||
t.Fatalf("Unexpected number of instance(s): %d", len(instances3))
|
||||
}
|
||||
|
||||
body4 := strings.NewReader(`
|
||||
<vms>
|
||||
<vm>
|
||||
<status><state>Up</state></status>
|
||||
<guest_info><fqdn>host1</fqdn></guest_info>
|
||||
</vm>
|
||||
<vm>
|
||||
<!-- empty -->
|
||||
</vm>
|
||||
<vm>
|
||||
<status><state>Up</state></status>
|
||||
</vm>
|
||||
<vm>
|
||||
<status><state>Down</state></status>
|
||||
<guest_info><fqdn>host2</fqdn></guest_info>
|
||||
</vm>
|
||||
<vm>
|
||||
<status><state>Up</state></status>
|
||||
<guest_info><fqdn>host3</fqdn></guest_info>
|
||||
</vm>
|
||||
</vms>
|
||||
`)
|
||||
|
||||
instances4, err4 := getInstancesFromXML(body4)
|
||||
if err4 != nil {
|
||||
t.Fatalf("Unexpected error listing instances: %s", err4)
|
||||
}
|
||||
if len(instances4) != 2 {
|
||||
t.Fatalf("Unexpected number of instance(s): %d", len(instances4))
|
||||
}
|
||||
|
||||
names := instances4.ListSortedNames()
|
||||
if names[0] != "host1" || names[1] != "host3" {
|
||||
t.Fatalf("Unexpected instance(s): %s", instances4)
|
||||
}
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["photon.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/cloudprovider/providers/photon",
|
||||
deps = [
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//staging/src/k8s.io/cloud-provider:go_default_library",
|
||||
"//staging/src/k8s.io/cloud-provider/node/helpers:go_default_library",
|
||||
"//vendor/github.com/vmware/photon-controller-go-sdk/photon:go_default_library",
|
||||
"//vendor/gopkg.in/gcfg.v1:go_default_library",
|
||||
"//vendor/k8s.io/klog:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["photon_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/rand:go_default_library",
|
||||
"//staging/src/k8s.io/cloud-provider:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
@@ -1,6 +0,0 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
maintainers:
|
||||
- luomiao
|
||||
- kerneltime
|
||||
- abrarshivani
|
@@ -1,734 +0,0 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// This version of Photon cloud provider supports the disk interface
|
||||
// for Photon persistent disk volume plugin. LoadBalancer, Routes, and
|
||||
// Zones are currently not supported.
|
||||
// The use of Photon cloud provider requires to start kubelet, kube-apiserver,
|
||||
// and kube-controller-manager with config flag: '--cloud-provider=photon
|
||||
// --cloud-config=[path_to_config_file]'. When running multi-node kubernetes
|
||||
// using docker, the config file should be located inside /etc/kubernetes.
|
||||
package photon
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/photon-controller-go-sdk/photon"
|
||||
"gopkg.in/gcfg.v1"
|
||||
"k8s.io/api/core/v1"
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
cloudprovider "k8s.io/cloud-provider"
|
||||
nodehelpers "k8s.io/cloud-provider/node/helpers"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
const (
|
||||
ProviderName = "photon"
|
||||
DiskSpecKind = "persistent-disk"
|
||||
MAC_OUI_VC = "00:50:56"
|
||||
MAC_OUI_ESX = "00:0c:29"
|
||||
)
|
||||
|
||||
// overrideIP indicates if the hostname is overridden by IP address, such as when
|
||||
// running multi-node kubernetes using docker. In this case the user should set
|
||||
// overrideIP = true in cloud config file. Default value is false.
|
||||
var overrideIP = false
|
||||
|
||||
var _ cloudprovider.Interface = (*PCCloud)(nil)
|
||||
var _ cloudprovider.Instances = (*PCCloud)(nil)
|
||||
var _ cloudprovider.Zones = (*PCCloud)(nil)
|
||||
|
||||
// PCCloud is an implementation of the cloud provider interface for Photon Controller.
|
||||
type PCCloud struct {
|
||||
cfg *PCConfig
|
||||
// InstanceID of the server where this PCCloud object is instantiated.
|
||||
localInstanceID string
|
||||
// local $HOSTNAME
|
||||
localHostname string
|
||||
// hostname from K8S, could be overridden
|
||||
localK8sHostname string
|
||||
// Photon project ID. We assume that there is only one Photon Controller project
|
||||
// in the environment per current Photon Controller deployment methodology.
|
||||
projID string
|
||||
cloudprovider.Zone
|
||||
photonClient *photon.Client
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
type PCConfig struct {
|
||||
Global struct {
|
||||
// the Photon Controller endpoint IP address
|
||||
CloudTarget string `gcfg:"target"`
|
||||
// Photon Controller project name
|
||||
Project string `gcfg:"project"`
|
||||
// when kubelet is started with '--hostname-override=${IP_ADDRESS}', set to true;
|
||||
// otherwise, set to false.
|
||||
OverrideIP bool `gcfg:"overrideIP"`
|
||||
// VM ID for this node
|
||||
VMID string `gcfg:"vmID"`
|
||||
// Authentication enabled or not
|
||||
AuthEnabled bool `gcfg:"authentication"`
|
||||
}
|
||||
}
|
||||
|
||||
// Disks is interface for manipulation with PhotonController Persistent Disks.
|
||||
type Disks interface {
|
||||
// AttachDisk attaches given disk to given node. Current node
|
||||
// is used when nodeName is empty string.
|
||||
AttachDisk(ctx context.Context, pdID string, nodeName k8stypes.NodeName) error
|
||||
|
||||
// DetachDisk detaches given disk to given node. Current node
|
||||
// is used when nodeName is empty string.
|
||||
DetachDisk(ctx context.Context, pdID string, nodeName k8stypes.NodeName) error
|
||||
|
||||
// DiskIsAttached checks if a disk is attached to the given node.
|
||||
DiskIsAttached(ctx context.Context, pdID string, nodeName k8stypes.NodeName) (bool, error)
|
||||
|
||||
// DisksAreAttached is a batch function to check if a list of disks are attached
|
||||
// to the node with the specified NodeName.
|
||||
DisksAreAttached(ctx context.Context, pdIDs []string, nodeName k8stypes.NodeName) (map[string]bool, error)
|
||||
|
||||
// CreateDisk creates a new PD with given properties.
|
||||
CreateDisk(volumeOptions *VolumeOptions) (pdID string, err error)
|
||||
|
||||
// DeleteDisk deletes PD.
|
||||
DeleteDisk(pdID string) error
|
||||
}
|
||||
|
||||
// VolumeOptions specifies capacity, tags, name and flavorID for a volume.
|
||||
type VolumeOptions struct {
|
||||
CapacityGB int
|
||||
Tags map[string]string
|
||||
Name string
|
||||
Flavor string
|
||||
}
|
||||
|
||||
func readConfig(config io.Reader) (PCConfig, error) {
|
||||
if config == nil {
|
||||
err := fmt.Errorf("cloud provider config file is missing. Please restart kubelet with --cloud-provider=photon --cloud-config=[path_to_config_file]")
|
||||
return PCConfig{}, err
|
||||
}
|
||||
|
||||
var cfg PCConfig
|
||||
err := gcfg.ReadInto(&cfg, config)
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
func init() {
|
||||
cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) {
|
||||
cfg, err := readConfig(config)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: failed to read in cloud provider config file. Error[%v]", err)
|
||||
return nil, err
|
||||
}
|
||||
return newPCCloud(cfg)
|
||||
})
|
||||
}
|
||||
|
||||
// Retrieve the Photon VM ID from the Photon Controller endpoint based on the node name
|
||||
func getVMIDbyNodename(pc *PCCloud, nodeName string) (string, error) {
|
||||
photonClient, err := getPhotonClient(pc)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to get photon client for getVMIDbyNodename, error: [%v]", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
vmList, err := photonClient.Projects.GetVMs(pc.projID, nil)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to GetVMs from project %s with nodeName %s, error: [%v]", pc.projID, nodeName, err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, vm := range vmList.Items {
|
||||
if vm.Name == nodeName {
|
||||
return vm.ID, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("No matching started VM is found with name %s", nodeName)
|
||||
}
|
||||
|
||||
// Retrieve the Photon VM ID from the Photon Controller endpoint based on the IP address
|
||||
func getVMIDbyIP(pc *PCCloud, IPAddress string) (string, error) {
|
||||
photonClient, err := getPhotonClient(pc)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to get photon client for getVMIDbyNodename, error: [%v]", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
vmList, err := photonClient.Projects.GetVMs(pc.projID, nil)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to GetVMs for project %s. error: [%v]", pc.projID, err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, vm := range vmList.Items {
|
||||
task, err := photonClient.VMs.GetNetworks(vm.ID)
|
||||
if err != nil {
|
||||
klog.Warningf("Photon Cloud Provider: GetNetworks failed for vm.ID %s, error [%v]", vm.ID, err)
|
||||
} else {
|
||||
task, err = photonClient.Tasks.Wait(task.ID)
|
||||
if err != nil {
|
||||
klog.Warningf("Photon Cloud Provider: Wait task for GetNetworks failed for vm.ID %s, error [%v]", vm.ID, err)
|
||||
} else {
|
||||
networkConnections := task.ResourceProperties.(map[string]interface{})
|
||||
networks := networkConnections["networkConnections"].([]interface{})
|
||||
for _, nt := range networks {
|
||||
network := nt.(map[string]interface{})
|
||||
if val, ok := network["ipAddress"]; ok && val != nil {
|
||||
ipAddr := val.(string)
|
||||
if ipAddr == IPAddress {
|
||||
return vm.ID, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("No matching VM is found with IP %s", IPAddress)
|
||||
}
|
||||
|
||||
func getPhotonClient(pc *PCCloud) (*photon.Client, error) {
|
||||
var err error
|
||||
if len(pc.cfg.Global.CloudTarget) == 0 {
|
||||
return nil, fmt.Errorf("Photon Controller endpoint was not specified")
|
||||
}
|
||||
|
||||
options := &photon.ClientOptions{
|
||||
IgnoreCertificate: true,
|
||||
}
|
||||
|
||||
pc.photonClient = photon.NewClient(pc.cfg.Global.CloudTarget, options, pc.logger)
|
||||
if pc.cfg.Global.AuthEnabled == true {
|
||||
// work around before metadata is available
|
||||
file, err := os.Open("/etc/kubernetes/pc_login_info")
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Authentication is enabled but found no username/password at /etc/kubernetes/pc_login_info. Error[%v]", err)
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
scanner := bufio.NewScanner(file)
|
||||
if !scanner.Scan() {
|
||||
klog.Error("Photon Cloud Provider: Empty username inside /etc/kubernetes/pc_login_info.")
|
||||
return nil, fmt.Errorf("Failed to create authentication enabled client with invalid username")
|
||||
}
|
||||
username := scanner.Text()
|
||||
if !scanner.Scan() {
|
||||
klog.Error("Photon Cloud Provider: Empty password set inside /etc/kubernetes/pc_login_info.")
|
||||
return nil, fmt.Errorf("Failed to create authentication enabled client with invalid password")
|
||||
}
|
||||
password := scanner.Text()
|
||||
|
||||
tokenOptions, err := pc.photonClient.Auth.GetTokensByPassword(username, password)
|
||||
if err != nil {
|
||||
klog.Error("Photon Cloud Provider: failed to get tokens by password")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
options = &photon.ClientOptions{
|
||||
IgnoreCertificate: true,
|
||||
TokenOptions: &photon.TokenOptions{
|
||||
AccessToken: tokenOptions.AccessToken,
|
||||
},
|
||||
}
|
||||
pc.photonClient = photon.NewClient(pc.cfg.Global.CloudTarget, options, pc.logger)
|
||||
}
|
||||
|
||||
status, err := pc.photonClient.Status.Get()
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: new client creation failed. Error[%v]", err)
|
||||
return nil, err
|
||||
}
|
||||
klog.V(2).Infof("Photon Cloud Provider: Status of the new photon controller client: %v", status)
|
||||
|
||||
return pc.photonClient, nil
|
||||
}
|
||||
|
||||
func newPCCloud(cfg PCConfig) (*PCCloud, error) {
|
||||
projID := cfg.Global.Project
|
||||
vmID := cfg.Global.VMID
|
||||
|
||||
// Get local hostname
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: get hostname failed. Error[%v]", err)
|
||||
return nil, err
|
||||
}
|
||||
pc := PCCloud{
|
||||
cfg: &cfg,
|
||||
localInstanceID: vmID,
|
||||
localHostname: hostname,
|
||||
localK8sHostname: "",
|
||||
projID: projID,
|
||||
}
|
||||
|
||||
overrideIP = cfg.Global.OverrideIP
|
||||
|
||||
return &pc, nil
|
||||
}
|
||||
|
||||
// Initialize passes a Kubernetes clientBuilder interface to the cloud provider
|
||||
func (pc *PCCloud) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, stop <-chan struct{}) {
|
||||
}
|
||||
|
||||
// Instances returns an implementation of Instances for Photon Controller.
|
||||
func (pc *PCCloud) Instances() (cloudprovider.Instances, bool) {
|
||||
return pc, true
|
||||
}
|
||||
|
||||
// List is an implementation of Instances.List.
|
||||
func (pc *PCCloud) List(filter string) ([]k8stypes.NodeName, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// NodeAddresses is an implementation of Instances.NodeAddresses.
|
||||
func (pc *PCCloud) NodeAddresses(ctx context.Context, nodeName k8stypes.NodeName) ([]v1.NodeAddress, error) {
|
||||
nodeAddrs := []v1.NodeAddress{}
|
||||
name := string(nodeName)
|
||||
|
||||
if name == pc.localK8sHostname {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: net.Interfaces() failed for NodeAddresses. Error[%v]", err)
|
||||
return nodeAddrs, err
|
||||
}
|
||||
|
||||
for _, i := range ifaces {
|
||||
addrs, err := i.Addrs()
|
||||
if err != nil {
|
||||
klog.Warningf("Photon Cloud Provider: Failed to extract addresses for NodeAddresses. Error[%v]", err)
|
||||
} else {
|
||||
for _, addr := range addrs {
|
||||
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||
if ipnet.IP.To4() != nil {
|
||||
// Filter external IP by MAC address OUIs from vCenter and from ESX
|
||||
if strings.HasPrefix(i.HardwareAddr.String(), MAC_OUI_VC) ||
|
||||
strings.HasPrefix(i.HardwareAddr.String(), MAC_OUI_ESX) {
|
||||
nodehelpers.AddToNodeAddresses(&nodeAddrs,
|
||||
v1.NodeAddress{
|
||||
Type: v1.NodeExternalIP,
|
||||
Address: ipnet.IP.String(),
|
||||
},
|
||||
)
|
||||
} else {
|
||||
nodehelpers.AddToNodeAddresses(&nodeAddrs,
|
||||
v1.NodeAddress{
|
||||
Type: v1.NodeInternalIP,
|
||||
Address: ipnet.IP.String(),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodeAddrs, nil
|
||||
}
|
||||
|
||||
// Inquiring IP addresses from photon controller endpoint only for a node other than this node.
|
||||
// This is assumed to be done by master only.
|
||||
vmID, err := getInstanceID(pc, name)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: getInstanceID failed for NodeAddresses. Error[%v]", err)
|
||||
return nodeAddrs, err
|
||||
}
|
||||
|
||||
photonClient, err := getPhotonClient(pc)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to get photon client for NodeAddresses, error: [%v]", err)
|
||||
return nodeAddrs, err
|
||||
}
|
||||
|
||||
// Retrieve the Photon VM's IP addresses from the Photon Controller endpoint based on the VM ID
|
||||
vmList, err := photonClient.Projects.GetVMs(pc.projID, nil)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to GetVMs for project %s. Error[%v]", pc.projID, err)
|
||||
return nodeAddrs, err
|
||||
}
|
||||
|
||||
for _, vm := range vmList.Items {
|
||||
if vm.ID == vmID {
|
||||
task, err := photonClient.VMs.GetNetworks(vm.ID)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: GetNetworks failed for node %s with vm.ID %s. Error[%v]", name, vm.ID, err)
|
||||
return nodeAddrs, err
|
||||
} else {
|
||||
task, err = photonClient.Tasks.Wait(task.ID)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Wait task for GetNetworks failed for node %s with vm.ID %s. Error[%v]", name, vm.ID, err)
|
||||
return nodeAddrs, err
|
||||
} else {
|
||||
networkConnections := task.ResourceProperties.(map[string]interface{})
|
||||
networks := networkConnections["networkConnections"].([]interface{})
|
||||
for _, nt := range networks {
|
||||
ipAddr := "-"
|
||||
macAddr := "-"
|
||||
network := nt.(map[string]interface{})
|
||||
if val, ok := network["ipAddress"]; ok && val != nil {
|
||||
ipAddr = val.(string)
|
||||
}
|
||||
if val, ok := network["macAddress"]; ok && val != nil {
|
||||
macAddr = val.(string)
|
||||
}
|
||||
if ipAddr != "-" {
|
||||
if strings.HasPrefix(macAddr, MAC_OUI_VC) ||
|
||||
strings.HasPrefix(macAddr, MAC_OUI_ESX) {
|
||||
nodehelpers.AddToNodeAddresses(&nodeAddrs,
|
||||
v1.NodeAddress{
|
||||
Type: v1.NodeExternalIP,
|
||||
Address: ipAddr,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
nodehelpers.AddToNodeAddresses(&nodeAddrs,
|
||||
v1.NodeAddress{
|
||||
Type: v1.NodeInternalIP,
|
||||
Address: ipAddr,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodeAddrs, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
klog.Errorf("Failed to find the node %s from Photon Controller endpoint", name)
|
||||
return nodeAddrs, fmt.Errorf("Failed to find the node %s from Photon Controller endpoint", name)
|
||||
}
|
||||
|
||||
// NodeAddressesByProviderID returns the node addresses of an instances with the specified unique providerID
|
||||
// This method will not be called from the node that is requesting this ID. i.e. metadata service
|
||||
// and other local methods cannot be used here
|
||||
func (pc *PCCloud) NodeAddressesByProviderID(ctx context.Context, providerID string) ([]v1.NodeAddress, error) {
|
||||
return []v1.NodeAddress{}, cloudprovider.NotImplemented
|
||||
}
|
||||
|
||||
func (pc *PCCloud) AddSSHKeyToAllInstances(ctx context.Context, user string, keyData []byte) error {
|
||||
return cloudprovider.NotImplemented
|
||||
}
|
||||
|
||||
func (pc *PCCloud) CurrentNodeName(ctx context.Context, hostname string) (k8stypes.NodeName, error) {
|
||||
pc.localK8sHostname = hostname
|
||||
return k8stypes.NodeName(hostname), nil
|
||||
}
|
||||
|
||||
func getInstanceID(pc *PCCloud, name string) (string, error) {
|
||||
var vmID string
|
||||
var err error
|
||||
|
||||
if overrideIP == true {
|
||||
vmID, err = getVMIDbyIP(pc, name)
|
||||
} else {
|
||||
vmID, err = getVMIDbyNodename(pc, name)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if vmID == "" {
|
||||
err = cloudprovider.InstanceNotFound
|
||||
}
|
||||
|
||||
return vmID, err
|
||||
}
|
||||
|
||||
// InstanceExistsByProviderID returns true if the instance with the given provider id still exists and is running.
|
||||
// If false is returned with no error, the instance will be immediately deleted by the cloud controller manager.
|
||||
func (pc *PCCloud) InstanceExistsByProviderID(ctx context.Context, providerID string) (bool, error) {
|
||||
return false, cloudprovider.NotImplemented
|
||||
}
|
||||
|
||||
// InstanceShutdownByProviderID returns true if the instance is in safe state to detach volumes
|
||||
func (pc *PCCloud) InstanceShutdownByProviderID(ctx context.Context, providerID string) (bool, error) {
|
||||
return false, cloudprovider.NotImplemented
|
||||
}
|
||||
|
||||
// InstanceID returns the cloud provider ID of the specified instance.
|
||||
func (pc *PCCloud) InstanceID(ctx context.Context, nodeName k8stypes.NodeName) (string, error) {
|
||||
name := string(nodeName)
|
||||
if name == pc.localK8sHostname {
|
||||
return pc.localInstanceID, nil
|
||||
}
|
||||
// We assume only master need to get InstanceID of a node other than itself
|
||||
id, err := getInstanceID(pc, name)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: getInstanceID failed for InstanceID. Error[%v]", err)
|
||||
}
|
||||
return id, err
|
||||
|
||||
}
|
||||
|
||||
// InstanceTypeByProviderID returns the cloudprovider instance type of the node with the specified unique providerID
|
||||
// This method will not be called from the node that is requesting this ID. i.e. metadata service
|
||||
// and other local methods cannot be used here
|
||||
func (pc *PCCloud) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) {
|
||||
return "", cloudprovider.NotImplemented
|
||||
}
|
||||
|
||||
func (pc *PCCloud) InstanceType(ctx context.Context, nodeName k8stypes.NodeName) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (pc *PCCloud) Clusters() (cloudprovider.Clusters, bool) {
|
||||
return nil, true
|
||||
}
|
||||
|
||||
// ProviderName returns the cloud provider ID.
|
||||
func (pc *PCCloud) ProviderName() string {
|
||||
return ProviderName
|
||||
}
|
||||
|
||||
// LoadBalancer returns an implementation of LoadBalancer for Photon Controller.
|
||||
func (pc *PCCloud) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Zones returns an implementation of Zones for Photon Controller.
|
||||
func (pc *PCCloud) Zones() (cloudprovider.Zones, bool) {
|
||||
return pc, true
|
||||
}
|
||||
|
||||
func (pc *PCCloud) GetZone(ctx context.Context) (cloudprovider.Zone, error) {
|
||||
return pc.Zone, nil
|
||||
}
|
||||
|
||||
// GetZoneByProviderID implements Zones.GetZoneByProviderID
|
||||
// This is particularly useful in external cloud providers where the kubelet
|
||||
// does not initialize node data.
|
||||
func (pc *PCCloud) GetZoneByProviderID(ctx context.Context, providerID string) (cloudprovider.Zone, error) {
|
||||
return cloudprovider.Zone{}, errors.New("GetZoneByProviderID not implemented")
|
||||
}
|
||||
|
||||
// GetZoneByNodeName implements Zones.GetZoneByNodeName
|
||||
// This is particularly useful in external cloud providers where the kubelet
|
||||
// does not initialize node data.
|
||||
func (pc *PCCloud) GetZoneByNodeName(ctx context.Context, nodeName k8stypes.NodeName) (cloudprovider.Zone, error) {
|
||||
return cloudprovider.Zone{}, errors.New("GetZoneByNodeName not imeplemented")
|
||||
}
|
||||
|
||||
// Routes returns a false since the interface is not supported for photon controller.
|
||||
func (pc *PCCloud) Routes() (cloudprovider.Routes, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// HasClusterID returns true if the cluster has a clusterID
|
||||
func (pc *PCCloud) HasClusterID() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// AttachDisk attaches given virtual disk volume to the compute running kubelet.
|
||||
func (pc *PCCloud) AttachDisk(ctx context.Context, pdID string, nodeName k8stypes.NodeName) error {
|
||||
photonClient, err := getPhotonClient(pc)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to get photon client for AttachDisk, error: [%v]", err)
|
||||
return err
|
||||
}
|
||||
|
||||
operation := &photon.VmDiskOperation{
|
||||
DiskID: pdID,
|
||||
}
|
||||
|
||||
vmID, err := pc.InstanceID(ctx, nodeName)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: pc.InstanceID failed for AttachDisk. Error[%v]", err)
|
||||
return err
|
||||
}
|
||||
|
||||
task, err := photonClient.VMs.AttachDisk(vmID, operation)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to attach disk with pdID %s. Error[%v]", pdID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = photonClient.Tasks.Wait(task.ID)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to wait for task to attach disk with pdID %s. Error[%v]", pdID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Detaches given virtual disk volume from the compute running kubelet.
|
||||
func (pc *PCCloud) DetachDisk(ctx context.Context, pdID string, nodeName k8stypes.NodeName) error {
|
||||
photonClient, err := getPhotonClient(pc)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to get photon client for DetachDisk, error: [%v]", err)
|
||||
return err
|
||||
}
|
||||
|
||||
operation := &photon.VmDiskOperation{
|
||||
DiskID: pdID,
|
||||
}
|
||||
|
||||
vmID, err := pc.InstanceID(ctx, nodeName)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: pc.InstanceID failed for DetachDisk. Error[%v]", err)
|
||||
return err
|
||||
}
|
||||
|
||||
task, err := photonClient.VMs.DetachDisk(vmID, operation)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to detach disk with pdID %s. Error[%v]", pdID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = photonClient.Tasks.Wait(task.ID)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to wait for task to detach disk with pdID %s. Error[%v]", pdID, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DiskIsAttached returns if disk is attached to the VM using controllers supported by the plugin.
|
||||
func (pc *PCCloud) DiskIsAttached(ctx context.Context, pdID string, nodeName k8stypes.NodeName) (bool, error) {
|
||||
photonClient, err := getPhotonClient(pc)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to get photon client for DiskIsAttached, error: [%v]", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
disk, err := photonClient.Disks.Get(pdID)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to Get disk with pdID %s. Error[%v]", pdID, err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
vmID, err := pc.InstanceID(ctx, nodeName)
|
||||
if err == cloudprovider.InstanceNotFound {
|
||||
klog.Infof("Instance %q does not exist, disk %s will be detached automatically.", nodeName, pdID)
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: pc.InstanceID failed for DiskIsAttached. Error[%v]", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, vm := range disk.VMs {
|
||||
if vm == vmID {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// DisksAreAttached returns if disks are attached to the VM using controllers supported by the plugin.
|
||||
func (pc *PCCloud) DisksAreAttached(ctx context.Context, pdIDs []string, nodeName k8stypes.NodeName) (map[string]bool, error) {
|
||||
attached := make(map[string]bool)
|
||||
photonClient, err := getPhotonClient(pc)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to get photon client for DisksAreAttached, error: [%v]", err)
|
||||
return attached, err
|
||||
}
|
||||
|
||||
for _, pdID := range pdIDs {
|
||||
attached[pdID] = false
|
||||
}
|
||||
|
||||
vmID, err := pc.InstanceID(ctx, nodeName)
|
||||
if err == cloudprovider.InstanceNotFound {
|
||||
klog.Infof("Instance %q does not exist, its disks will be detached automatically.", nodeName)
|
||||
// make all the disks as detached.
|
||||
return attached, nil
|
||||
}
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: pc.InstanceID failed for DiskIsAttached. Error[%v]", err)
|
||||
return attached, err
|
||||
}
|
||||
|
||||
for _, pdID := range pdIDs {
|
||||
disk, err := photonClient.Disks.Get(pdID)
|
||||
if err != nil {
|
||||
klog.Warningf("Photon Cloud Provider: failed to get VMs for persistent disk %s, err [%v]", pdID, err)
|
||||
} else {
|
||||
for _, vm := range disk.VMs {
|
||||
if vm == vmID {
|
||||
attached[pdID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attached, nil
|
||||
}
|
||||
|
||||
// Create a volume of given size (in GB).
|
||||
func (pc *PCCloud) CreateDisk(volumeOptions *VolumeOptions) (pdID string, err error) {
|
||||
photonClient, err := getPhotonClient(pc)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to get photon client for CreateDisk, error: [%v]", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
diskSpec := photon.DiskCreateSpec{}
|
||||
diskSpec.Name = volumeOptions.Name
|
||||
diskSpec.Flavor = volumeOptions.Flavor
|
||||
diskSpec.CapacityGB = volumeOptions.CapacityGB
|
||||
diskSpec.Kind = DiskSpecKind
|
||||
|
||||
task, err := photonClient.Projects.CreateDisk(pc.projID, &diskSpec)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to CreateDisk. Error[%v]", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
waitTask, err := photonClient.Tasks.Wait(task.ID)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to wait for task to CreateDisk. Error[%v]", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return waitTask.Entity.ID, nil
|
||||
}
|
||||
|
||||
// DeleteDisk deletes a volume given volume name.
|
||||
func (pc *PCCloud) DeleteDisk(pdID string) error {
|
||||
photonClient, err := getPhotonClient(pc)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to get photon client for DeleteDisk, error: [%v]", err)
|
||||
return err
|
||||
}
|
||||
|
||||
task, err := photonClient.Disks.Delete(pdID)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to DeleteDisk. Error[%v]", err)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = photonClient.Tasks.Wait(task.ID)
|
||||
if err != nil {
|
||||
klog.Errorf("Photon Cloud Provider: Failed to wait for task to DeleteDisk. Error[%v]", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
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 photon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
cloudprovider "k8s.io/cloud-provider"
|
||||
)
|
||||
|
||||
func configFromEnv() (TestVM string, TestFlavor string, cfg PCConfig, ok bool) {
|
||||
var AuthEnabled bool
|
||||
var OverrideIP bool
|
||||
var err error
|
||||
cfg.Global.CloudTarget = os.Getenv("PHOTON_TARGET")
|
||||
cfg.Global.Project = os.Getenv("PHOTON_PROJECT")
|
||||
cfg.Global.VMID = os.Getenv("PHOTON_VMID")
|
||||
if os.Getenv("PHOTON_AUTH_ENABLED") != "" {
|
||||
AuthEnabled, err = strconv.ParseBool(os.Getenv("PHOTON_AUTH_ENABLED"))
|
||||
} else {
|
||||
AuthEnabled = false
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cfg.Global.AuthEnabled = AuthEnabled
|
||||
if os.Getenv("PHOTON_OVERRIDE_IP") != "" {
|
||||
OverrideIP, err = strconv.ParseBool(os.Getenv("PHOTON_OVERRIDE_IP"))
|
||||
} else {
|
||||
OverrideIP = false
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cfg.Global.OverrideIP = OverrideIP
|
||||
|
||||
TestVM = os.Getenv("PHOTON_TEST_VM")
|
||||
if os.Getenv("PHOTON_TEST_FLAVOR") != "" {
|
||||
TestFlavor = os.Getenv("PHOTON_TEST_FLAVOR")
|
||||
} else {
|
||||
TestFlavor = ""
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ok = (cfg.Global.CloudTarget != "" &&
|
||||
cfg.Global.Project != "" &&
|
||||
cfg.Global.VMID != "" &&
|
||||
TestVM != "")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func TestReadConfig(t *testing.T) {
|
||||
_, err := readConfig(nil)
|
||||
if err == nil {
|
||||
t.Errorf("Should fail when no config is provided: %s", err)
|
||||
}
|
||||
|
||||
cfg, err := readConfig(strings.NewReader(`
|
||||
[Global]
|
||||
target = 0.0.0.0
|
||||
project = project
|
||||
overrideIP = true
|
||||
vmID = vmid
|
||||
authentication = false
|
||||
`))
|
||||
if err != nil {
|
||||
t.Fatalf("Should succeed when a valid config is provided: %s", err)
|
||||
}
|
||||
|
||||
if cfg.Global.CloudTarget != "0.0.0.0" {
|
||||
t.Errorf("incorrect photon target ip: %s", cfg.Global.CloudTarget)
|
||||
}
|
||||
|
||||
if cfg.Global.Project != "project" {
|
||||
t.Errorf("incorrect project: %s", cfg.Global.Project)
|
||||
}
|
||||
|
||||
if cfg.Global.VMID != "vmid" {
|
||||
t.Errorf("incorrect vmid: %s", cfg.Global.VMID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPCCloud(t *testing.T) {
|
||||
_, _, cfg, ok := configFromEnv()
|
||||
if !ok {
|
||||
t.Skipf("No config found in environment")
|
||||
}
|
||||
|
||||
_, err := newPCCloud(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new Photon client: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstances(t *testing.T) {
|
||||
testVM, _, cfg, ok := configFromEnv()
|
||||
if !ok {
|
||||
t.Skipf("No config found in environment")
|
||||
}
|
||||
NodeName := types.NodeName(testVM)
|
||||
|
||||
pc, err := newPCCloud(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new Photon client: %s", err)
|
||||
}
|
||||
|
||||
i, ok := pc.Instances()
|
||||
if !ok {
|
||||
t.Fatalf("Instances() returned false")
|
||||
}
|
||||
|
||||
nonExistingVM := types.NodeName(rand.String(15))
|
||||
instanceId, err := i.InstanceID(context.TODO(), NodeName)
|
||||
if err != nil {
|
||||
t.Fatalf("Instances.InstanceID(%s) failed: %s", testVM, err)
|
||||
}
|
||||
t.Logf("Found InstanceID(%s) = %s\n", testVM, instanceId)
|
||||
|
||||
_, err = i.InstanceID(context.TODO(), nonExistingVM)
|
||||
if err == cloudprovider.InstanceNotFound {
|
||||
t.Logf("VM %s was not found as expected\n", nonExistingVM)
|
||||
} else if err == nil {
|
||||
t.Fatalf("Instances.InstanceID did not fail as expected, VM %s was found", nonExistingVM)
|
||||
} else {
|
||||
t.Fatalf("Instances.InstanceID did not fail as expected, err: %v", err)
|
||||
}
|
||||
|
||||
addrs, err := i.NodeAddresses(context.TODO(), NodeName)
|
||||
if err != nil {
|
||||
t.Fatalf("Instances.NodeAddresses(%s) failed: %s", testVM, err)
|
||||
}
|
||||
t.Logf("Found NodeAddresses(%s) = %s\n", testVM, addrs)
|
||||
}
|
||||
|
||||
func TestVolumes(t *testing.T) {
|
||||
testVM, testFlavor, cfg, ok := configFromEnv()
|
||||
if !ok {
|
||||
t.Skipf("No config found in environment")
|
||||
}
|
||||
|
||||
pc, err := newPCCloud(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create new Photon client: %s", err)
|
||||
}
|
||||
|
||||
NodeName := types.NodeName(testVM)
|
||||
|
||||
volumeOptions := &VolumeOptions{
|
||||
CapacityGB: 2,
|
||||
Tags: nil,
|
||||
Name: "kubernetes-test-volume-" + rand.String(10),
|
||||
Flavor: testFlavor}
|
||||
|
||||
pdID, err := pc.CreateDisk(volumeOptions)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot create a Photon persistent disk: %v", err)
|
||||
}
|
||||
|
||||
err = pc.AttachDisk(context.TODO(), pdID, NodeName)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot attach persistent disk(%s) to VM(%s): %v", pdID, testVM, err)
|
||||
}
|
||||
|
||||
_, err = pc.DiskIsAttached(context.TODO(), pdID, NodeName)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot attach persistent disk(%s) to VM(%s): %v", pdID, testVM, err)
|
||||
}
|
||||
|
||||
err = pc.DetachDisk(context.TODO(), pdID, NodeName)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot detach persisten disk(%s) from VM(%s): %v", pdID, testVM, err)
|
||||
}
|
||||
|
||||
err = pc.DeleteDisk(pdID)
|
||||
if err != nil {
|
||||
t.Fatalf("Cannot delete persisten disk(%s): %v", pdID, err)
|
||||
}
|
||||
}
|
@@ -18,10 +18,7 @@ package cloudprovider
|
||||
|
||||
import (
|
||||
// Cloud providers
|
||||
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/cloudstack"
|
||||
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/openstack"
|
||||
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/ovirt"
|
||||
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/photon"
|
||||
_ "k8s.io/legacy-cloud-providers/aws"
|
||||
_ "k8s.io/legacy-cloud-providers/azure"
|
||||
_ "k8s.io/legacy-cloud-providers/gce"
|
||||
|
Reference in New Issue
Block a user