Remove ovirt/cloudstack/photon cloud providers

Change-Id: Iddb80bdc2a9d75d444b8a3cfe0b142acb78e9097
This commit is contained in:
Davanum Srinivas 2019-06-10 12:50:53 -04:00
parent 3a50c00692
commit e0821ca3dd
No known key found for this signature in database
GPG Key ID: 80D83A796103BF59
34 changed files with 0 additions and 4526 deletions

View File

@ -100,7 +100,6 @@ go_library(
"//pkg/volume/iscsi:go_default_library",
"//pkg/volume/local:go_default_library",
"//pkg/volume/nfs:go_default_library",
"//pkg/volume/photon_pd:go_default_library",
"//pkg/volume/portworx:go_default_library",
"//pkg/volume/quobyte:go_default_library",
"//pkg/volume/rbd:go_default_library",

View File

@ -45,7 +45,6 @@ import (
"k8s.io/kubernetes/pkg/volume/iscsi"
"k8s.io/kubernetes/pkg/volume/local"
"k8s.io/kubernetes/pkg/volume/nfs"
"k8s.io/kubernetes/pkg/volume/photon_pd"
"k8s.io/kubernetes/pkg/volume/portworx"
"k8s.io/kubernetes/pkg/volume/quobyte"
"k8s.io/kubernetes/pkg/volume/rbd"
@ -73,7 +72,6 @@ func ProbeAttachableVolumePlugins() []volume.VolumePlugin {
allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, azure_dd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, photon_pd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, fc.ProbeVolumePlugins()...)
@ -105,7 +103,6 @@ func ProbeExpandableVolumePlugins(config persistentvolumeconfig.VolumeConfigurat
allPlugins = append(allPlugins, rbd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, azure_dd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, azure_file.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, photon_pd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, storageos.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, fc.ProbeVolumePlugins()...)
@ -164,7 +161,6 @@ func ProbeControllerVolumePlugins(cloud cloudprovider.Interface, config persiste
allPlugins = append(allPlugins, cinder.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, azure_dd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, photon_pd.ProbeVolumePlugins()...)
if utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) {
allPlugins = append(allPlugins, csi.ProbeVolumePlugins()...)

View File

@ -95,12 +95,9 @@
"k8s.io/kubernetes/pkg/cloudprovider/providers",
"k8s.io/kubernetes/pkg/cloudprovider/providers/aws",
"k8s.io/kubernetes/pkg/cloudprovider/providers/azure",
"k8s.io/kubernetes/pkg/cloudprovider/providers/cloudstack",
"k8s.io/kubernetes/pkg/cloudprovider/providers/fake",
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce",
"k8s.io/kubernetes/pkg/cloudprovider/providers/openstack",
"k8s.io/kubernetes/pkg/cloudprovider/providers/ovirt",
"k8s.io/kubernetes/pkg/cloudprovider/providers/photon",
"k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere",
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
]

View File

@ -99,7 +99,6 @@ go_library(
"//pkg/volume/iscsi:go_default_library",
"//pkg/volume/local:go_default_library",
"//pkg/volume/nfs:go_default_library",
"//pkg/volume/photon_pd:go_default_library",
"//pkg/volume/portworx:go_default_library",
"//pkg/volume/projected:go_default_library",
"//pkg/volume/quobyte:go_default_library",

View File

@ -44,7 +44,6 @@ import (
"k8s.io/kubernetes/pkg/volume/iscsi"
"k8s.io/kubernetes/pkg/volume/local"
"k8s.io/kubernetes/pkg/volume/nfs"
"k8s.io/kubernetes/pkg/volume/photon_pd"
"k8s.io/kubernetes/pkg/volume/portworx"
"k8s.io/kubernetes/pkg/volume/projected"
"k8s.io/kubernetes/pkg/volume/quobyte"
@ -90,7 +89,6 @@ func ProbeVolumePlugins() []volume.VolumePlugin {
allPlugins = append(allPlugins, configmap.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, azure_dd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, photon_pd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, projected.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, portworx.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, scaleio.ProbeVolumePlugins()...)

View File

@ -51,7 +51,6 @@ pkg/apis/scheduling/v1beta1
pkg/apis/storage
pkg/apis/storage/v1
pkg/apis/storage/v1beta1
pkg/cloudprovider/providers/photon
pkg/controller
pkg/controller/apis/config/v1alpha1
pkg/controller/certificates
@ -315,7 +314,6 @@ pkg/volume/csi/csiv0
pkg/volume/csi/fake
pkg/volume/git_repo
pkg/volume/iscsi
pkg/volume/photon_pd
pkg/volume/rbd
pkg/volume/scaleio
pkg/volume/storageos

View File

@ -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"],
)

View File

@ -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"],
)

View File

@ -1,6 +0,0 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- ngtuna
- sebgoa
- svanharmelen

View File

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

View File

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

View File

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

View File

@ -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")
}
}

View File

@ -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")
}

View File

@ -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),
)
}

View File

@ -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),
)
}

View File

@ -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"],
)

View File

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

View File

@ -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)
}
}

View File

@ -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"],
)

View File

@ -1,6 +0,0 @@
# See the OWNERS docs at https://go.k8s.io/owners
maintainers:
- luomiao
- kerneltime
- abrarshivani

View File

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

View File

@ -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)
}
}

View File

@ -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"

View File

@ -94,7 +94,6 @@ filegroup(
"//pkg/volume/iscsi:all-srcs",
"//pkg/volume/local:all-srcs",
"//pkg/volume/nfs:all-srcs",
"//pkg/volume/photon_pd:all-srcs",
"//pkg/volume/portworx:all-srcs",
"//pkg/volume/projected:all-srcs",
"//pkg/volume/quobyte:all-srcs",

View File

@ -1,63 +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 = [
"attacher.go",
"photon_pd.go",
"photon_util.go",
],
importpath = "k8s.io/kubernetes/pkg/volume/photon_pd",
deps = [
"//pkg/cloudprovider/providers/photon:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/util:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/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/volume/helpers:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/utils/strings:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"attacher_test.go",
"photon_pd_test.go",
],
embed = [":go_default_library"],
deps = [
"//pkg/cloudprovider/providers/photon:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/testing:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/client-go/util/testing:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,6 +0,0 @@
# See the OWNERS docs at https://go.k8s.io/owners
maintainers:
- luomiao
- kerneltime
- abrarshivani

View File

@ -1,317 +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_pd
import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"time"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/cloudprovider/providers/photon"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
volumeutil "k8s.io/kubernetes/pkg/volume/util"
)
type photonPersistentDiskAttacher struct {
host volume.VolumeHost
photonDisks photon.Disks
}
var _ volume.Attacher = &photonPersistentDiskAttacher{}
var _ volume.DeviceMounter = &photonPersistentDiskAttacher{}
var _ volume.AttachableVolumePlugin = &photonPersistentDiskPlugin{}
var _ volume.DeviceMountableVolumePlugin = &photonPersistentDiskPlugin{}
func (plugin *photonPersistentDiskPlugin) NewAttacher() (volume.Attacher, error) {
photonCloud, err := getCloudProvider(plugin.host.GetCloudProvider())
if err != nil {
klog.Errorf("Photon Controller attacher: NewAttacher failed to get cloud provider")
return nil, err
}
return &photonPersistentDiskAttacher{
host: plugin.host,
photonDisks: photonCloud,
}, nil
}
func (plugin *photonPersistentDiskPlugin) NewDeviceMounter() (volume.DeviceMounter, error) {
return plugin.NewAttacher()
}
// Attaches the volume specified by the given spec to the given host.
// On success, returns the device path where the device was attached on the
// node.
// Callers are responsible for retryinging on failure.
// Callers are responsible for thread safety between concurrent attach and
// detach operations.
func (attacher *photonPersistentDiskAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) {
hostName := string(nodeName)
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
klog.Errorf("Photon Controller attacher: Attach failed to get volume source")
return "", err
}
attached, err := attacher.photonDisks.DiskIsAttached(context.TODO(), volumeSource.PdID, nodeName)
if err != nil {
klog.Warningf("Photon Controller: couldn't check if disk is Attached for host %s, will try attach disk: %+v", hostName, err)
attached = false
}
if !attached {
klog.V(4).Infof("Photon Controller: Attach disk called for host %s", hostName)
err = attacher.photonDisks.AttachDisk(context.TODO(), volumeSource.PdID, nodeName)
if err != nil {
klog.Errorf("Error attaching volume %q to node %q: %+v", volumeSource.PdID, nodeName, err)
return "", err
}
}
PdidWithNoHypens := strings.Replace(volumeSource.PdID, "-", "", -1)
return filepath.Join(diskByIDPath, diskPhotonPrefix+PdidWithNoHypens), nil
}
func (attacher *photonPersistentDiskAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) {
volumesAttachedCheck := make(map[*volume.Spec]bool)
volumeSpecMap := make(map[string]*volume.Spec)
pdIDList := []string{}
for _, spec := range specs {
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
klog.Errorf("Error getting volume (%q) source : %v", spec.Name(), err)
continue
}
pdIDList = append(pdIDList, volumeSource.PdID)
volumesAttachedCheck[spec] = true
volumeSpecMap[volumeSource.PdID] = spec
}
attachedResult, err := attacher.photonDisks.DisksAreAttached(context.TODO(), pdIDList, nodeName)
if err != nil {
klog.Errorf(
"Error checking if volumes (%v) are attached to current node (%q). err=%v",
pdIDList, nodeName, err)
return volumesAttachedCheck, err
}
for pdID, attached := range attachedResult {
if !attached {
spec := volumeSpecMap[pdID]
volumesAttachedCheck[spec] = false
klog.V(2).Infof("VolumesAreAttached: check volume %q (specName: %q) is no longer attached", pdID, spec.Name())
}
}
return volumesAttachedCheck, nil
}
func (attacher *photonPersistentDiskAttacher) WaitForAttach(spec *volume.Spec, devicePath string, _ *v1.Pod, timeout time.Duration) (string, error) {
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
klog.Errorf("Photon Controller attacher: WaitForAttach failed to get volume source")
return "", err
}
if devicePath == "" {
return "", fmt.Errorf("WaitForAttach failed for PD %s: devicePath is empty.", volumeSource.PdID)
}
// scan scsi path to discover the new disk
scsiHostScan()
ticker := time.NewTicker(checkSleepDuration)
defer ticker.Stop()
timer := time.NewTimer(timeout)
defer timer.Stop()
for {
select {
case <-ticker.C:
klog.V(4).Infof("Checking PD %s is attached", volumeSource.PdID)
checkPath, err := verifyDevicePath(devicePath)
if err != nil {
// Log error, if any, and continue checking periodically. See issue #11321
klog.Warningf("Photon Controller attacher: WaitForAttach with devicePath %s Checking PD %s Error verify path", devicePath, volumeSource.PdID)
} else if checkPath != "" {
// A device path has successfully been created for the VMDK
klog.V(4).Infof("Successfully found attached PD %s.", volumeSource.PdID)
// map path with spec.Name()
volName := spec.Name()
realPath, _ := filepath.EvalSymlinks(devicePath)
deviceName := path.Base(realPath)
volNameToDeviceName[volName] = deviceName
return devicePath, nil
}
case <-timer.C:
return "", fmt.Errorf("Could not find attached PD %s. Timeout waiting for mount paths to be created.", volumeSource.PdID)
}
}
}
// GetDeviceMountPath returns a path where the device should
// point which should be bind mounted for individual volumes.
func (attacher *photonPersistentDiskAttacher) GetDeviceMountPath(spec *volume.Spec) (string, error) {
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
klog.Errorf("Photon Controller attacher: GetDeviceMountPath failed to get volume source")
return "", err
}
return makeGlobalPDPath(attacher.host, volumeSource.PdID), nil
}
// GetMountDeviceRefs finds all other references to the device referenced
// by deviceMountPath; returns a list of paths.
func (plugin *photonPersistentDiskPlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) {
mounter := plugin.host.GetMounter(plugin.GetPluginName())
return mounter.GetMountRefs(deviceMountPath)
}
// MountDevice mounts device to global mount point.
func (attacher *photonPersistentDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error {
mounter := attacher.host.GetMounter(photonPersistentDiskPluginName)
notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
if err != nil {
if os.IsNotExist(err) {
if err := os.MkdirAll(deviceMountPath, 0750); err != nil {
klog.Errorf("Failed to create directory at %#v. err: %s", deviceMountPath, err)
return err
}
notMnt = true
} else {
return err
}
}
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
klog.Errorf("Photon Controller attacher: MountDevice failed to get volume source. err: %s", err)
return err
}
options := []string{}
if notMnt {
diskMounter := volumeutil.NewSafeFormatAndMountFromHost(photonPersistentDiskPluginName, attacher.host)
mountOptions := volumeutil.MountOptionFromSpec(spec)
err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, mountOptions)
if err != nil {
os.Remove(deviceMountPath)
return err
}
klog.V(4).Infof("formatting spec %v devicePath %v deviceMountPath %v fs %v with options %+v", spec.Name(), devicePath, deviceMountPath, volumeSource.FSType, options)
}
return nil
}
type photonPersistentDiskDetacher struct {
mounter mount.Interface
photonDisks photon.Disks
}
var _ volume.Detacher = &photonPersistentDiskDetacher{}
var _ volume.DeviceUnmounter = &photonPersistentDiskDetacher{}
func (plugin *photonPersistentDiskPlugin) NewDetacher() (volume.Detacher, error) {
photonCloud, err := getCloudProvider(plugin.host.GetCloudProvider())
if err != nil {
klog.Errorf("Photon Controller attacher: NewDetacher failed to get cloud provider. err: %s", err)
return nil, err
}
return &photonPersistentDiskDetacher{
mounter: plugin.host.GetMounter(plugin.GetPluginName()),
photonDisks: photonCloud,
}, nil
}
func (plugin *photonPersistentDiskPlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) {
return plugin.NewDetacher()
}
// Detach the given device from the given host.
func (detacher *photonPersistentDiskDetacher) Detach(volumeName string, nodeName types.NodeName) error {
hostName := string(nodeName)
pdID := volumeName
attached, err := detacher.photonDisks.DiskIsAttached(context.TODO(), pdID, nodeName)
if err != nil {
// Log error and continue with detach
klog.Errorf(
"Error checking if persistent disk (%q) is already attached to current node (%q). Will continue and try detach anyway. err=%v",
pdID, hostName, err)
}
if err == nil && !attached {
// Volume is already detached from node.
klog.V(4).Infof("detach operation was successful. persistent disk %q is already detached from node %q.", pdID, hostName)
return nil
}
if err := detacher.photonDisks.DetachDisk(context.TODO(), pdID, nodeName); err != nil {
klog.Errorf("Error detaching volume %q: %v", pdID, err)
return err
}
return nil
}
func (detacher *photonPersistentDiskDetacher) WaitForDetach(devicePath string, timeout time.Duration) error {
ticker := time.NewTicker(checkSleepDuration)
defer ticker.Stop()
timer := time.NewTimer(timeout)
defer timer.Stop()
for {
select {
case <-ticker.C:
klog.V(4).Infof("Checking device %q is detached.", devicePath)
if pathExists, err := mount.PathExists(devicePath); err != nil {
return fmt.Errorf("Error checking if device path exists: %v", err)
} else if !pathExists {
return nil
}
case <-timer.C:
return fmt.Errorf("Timeout reached; Device %v is still attached", devicePath)
}
}
}
func (detacher *photonPersistentDiskDetacher) UnmountDevice(deviceMountPath string) error {
return mount.CleanupMountPoint(deviceMountPath, detacher.mounter, false)
}
func (plugin *photonPersistentDiskPlugin) CanAttach(spec *volume.Spec) (bool, error) {
return true, nil
}
func (plugin *photonPersistentDiskPlugin) CanDeviceMount(spec *volume.Spec) (bool, error) {
return true, nil
}

View File

@ -1,330 +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_pd
import (
"context"
"errors"
"testing"
"k8s.io/api/core/v1"
"k8s.io/kubernetes/pkg/cloudprovider/providers/photon"
"k8s.io/kubernetes/pkg/volume"
volumetest "k8s.io/kubernetes/pkg/volume/testing"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog"
)
func TestGetDeviceName_Volume(t *testing.T) {
plugin := newPlugin()
name := "my-photon-volume"
spec := createVolSpec(name, false)
deviceName, err := plugin.GetVolumeName(spec)
if err != nil {
t.Errorf("GetDeviceName error: %v", err)
}
if deviceName != name {
t.Errorf("GetDeviceName error: expected %s, got %s", name, deviceName)
}
}
func TestGetDeviceName_PersistentVolume(t *testing.T) {
plugin := newPlugin()
name := "my-photon-pv"
spec := createPVSpec(name, true)
deviceName, err := plugin.GetVolumeName(spec)
if err != nil {
t.Errorf("GetDeviceName error: %v", err)
}
if deviceName != name {
t.Errorf("GetDeviceName error: expected %s, got %s", name, deviceName)
}
}
// One testcase for TestAttachDetach table test below
type testcase struct {
name string
// For fake Photon cloud provider:
attach attachCall
detach detachCall
diskIsAttached diskIsAttachedCall
t *testing.T
// Actual test to run
test func(test *testcase) (string, error)
// Expected return of the test
expectedDevice string
expectedError error
}
func TestAttachDetach(t *testing.T) {
diskName := "000-000-000"
nodeName := types.NodeName("instance")
readOnly := false
spec := createVolSpec(diskName, readOnly)
detachError := errors.New("Fake detach error")
diskCheckError := errors.New("Fake DiskIsAttached error")
tests := []testcase{
// Successful Attach call
{
name: "Attach_Positive",
diskIsAttached: diskIsAttachedCall{diskName, nodeName, false, diskCheckError},
attach: attachCall{diskName, nodeName, nil},
test: func(testcase *testcase) (string, error) {
attacher := newAttacher(testcase)
return attacher.Attach(spec, nodeName)
},
expectedDevice: "/dev/disk/by-id/wwn-0x000000000",
},
// Disk is already attached
{
name: "Attach_Positive_AlreadyAttached",
diskIsAttached: diskIsAttachedCall{diskName, nodeName, false, diskCheckError},
attach: attachCall{diskName, nodeName, nil},
test: func(testcase *testcase) (string, error) {
attacher := newAttacher(testcase)
return attacher.Attach(spec, nodeName)
},
expectedDevice: "/dev/disk/by-id/wwn-0x000000000",
},
// Detach succeeds
{
name: "Detach_Positive",
diskIsAttached: diskIsAttachedCall{diskName, nodeName, true, nil},
detach: detachCall{diskName, nodeName, nil},
test: func(testcase *testcase) (string, error) {
detacher := newDetacher(testcase)
return "", detacher.Detach(diskName, nodeName)
},
},
// Disk is already detached
{
name: "Detach_Positive_AlreadyDetached",
diskIsAttached: diskIsAttachedCall{diskName, nodeName, false, nil},
test: func(testcase *testcase) (string, error) {
detacher := newDetacher(testcase)
return "", detacher.Detach(diskName, nodeName)
},
},
// Detach succeeds when DiskIsAttached fails
{
name: "Detach_Positive_CheckFails",
diskIsAttached: diskIsAttachedCall{diskName, nodeName, false, diskCheckError},
detach: detachCall{diskName, nodeName, nil},
test: func(testcase *testcase) (string, error) {
detacher := newDetacher(testcase)
return "", detacher.Detach(diskName, nodeName)
},
},
// Detach fails
{
name: "Detach_Negative",
diskIsAttached: diskIsAttachedCall{diskName, nodeName, false, diskCheckError},
detach: detachCall{diskName, nodeName, detachError},
test: func(testcase *testcase) (string, error) {
detacher := newDetacher(testcase)
return "", detacher.Detach(diskName, nodeName)
},
expectedError: detachError,
},
}
for _, testcase := range tests {
testcase.t = t
device, err := testcase.test(&testcase)
if err != testcase.expectedError {
t.Errorf("%s failed: expected err=%q, got %q", testcase.name, testcase.expectedError.Error(), err.Error())
}
if device != testcase.expectedDevice {
t.Errorf("%s failed: expected device=%q, got %q", testcase.name, testcase.expectedDevice, device)
}
t.Logf("Test %q succeeded", testcase.name)
}
}
// newPlugin creates a new gcePersistentDiskPlugin with fake cloud, NewAttacher
// and NewDetacher won't work.
func newPlugin() *photonPersistentDiskPlugin {
host := volumetest.NewFakeVolumeHost("/tmp", nil, nil)
plugins := ProbeVolumePlugins()
plugin := plugins[0]
plugin.Init(host)
return plugin.(*photonPersistentDiskPlugin)
}
func newAttacher(testcase *testcase) *photonPersistentDiskAttacher {
return &photonPersistentDiskAttacher{
host: nil,
photonDisks: testcase,
}
}
func newDetacher(testcase *testcase) *photonPersistentDiskDetacher {
return &photonPersistentDiskDetacher{
photonDisks: testcase,
}
}
func createVolSpec(name string, readOnly bool) *volume.Spec {
return &volume.Spec{
Volume: &v1.Volume{
VolumeSource: v1.VolumeSource{
PhotonPersistentDisk: &v1.PhotonPersistentDiskVolumeSource{
PdID: name,
},
},
},
}
}
func createPVSpec(name string, readOnly bool) *volume.Spec {
return &volume.Spec{
PersistentVolume: &v1.PersistentVolume{
Spec: v1.PersistentVolumeSpec{
PersistentVolumeSource: v1.PersistentVolumeSource{
PhotonPersistentDisk: &v1.PhotonPersistentDiskVolumeSource{
PdID: name,
},
},
},
},
}
}
// Fake PhotonPD implementation
type attachCall struct {
diskName string
nodeName types.NodeName
ret error
}
type detachCall struct {
diskName string
nodeName types.NodeName
ret error
}
type diskIsAttachedCall struct {
diskName string
nodeName types.NodeName
isAttached bool
ret error
}
func (testcase *testcase) AttachDisk(ctx context.Context, diskName string, nodeName types.NodeName) error {
expected := &testcase.attach
if expected.diskName == "" && expected.nodeName == "" {
// testcase.attach looks uninitialized, test did not expect to call
// AttachDisk
testcase.t.Errorf("Unexpected AttachDisk call!")
return errors.New("Unexpected AttachDisk call!")
}
if expected.diskName != diskName {
testcase.t.Errorf("Unexpected AttachDisk call: expected diskName %s, got %s", expected.diskName, diskName)
return errors.New("Unexpected AttachDisk call: wrong diskName")
}
if expected.nodeName != nodeName {
testcase.t.Errorf("Unexpected AttachDisk call: expected nodeName %s, got %s", expected.nodeName, nodeName)
return errors.New("Unexpected AttachDisk call: wrong nodeName")
}
klog.V(4).Infof("AttachDisk call: %s, %s, returning %v", diskName, nodeName, expected.ret)
return expected.ret
}
func (testcase *testcase) DetachDisk(ctx context.Context, diskName string, nodeName types.NodeName) error {
expected := &testcase.detach
if expected.diskName == "" && expected.nodeName == "" {
// testcase.detach looks uninitialized, test did not expect to call
// DetachDisk
testcase.t.Errorf("Unexpected DetachDisk call!")
return errors.New("Unexpected DetachDisk call!")
}
if expected.diskName != diskName {
testcase.t.Errorf("Unexpected DetachDisk call: expected diskName %s, got %s", expected.diskName, diskName)
return errors.New("Unexpected DetachDisk call: wrong diskName")
}
if expected.nodeName != nodeName {
testcase.t.Errorf("Unexpected DetachDisk call: expected nodeName %s, got %s", expected.nodeName, nodeName)
return errors.New("Unexpected DetachDisk call: wrong nodeName")
}
klog.V(4).Infof("DetachDisk call: %s, %s, returning %v", diskName, nodeName, expected.ret)
return expected.ret
}
func (testcase *testcase) DiskIsAttached(ctx context.Context, diskName string, nodeName types.NodeName) (bool, error) {
expected := &testcase.diskIsAttached
if expected.diskName == "" && expected.nodeName == "" {
// testcase.diskIsAttached looks uninitialized, test did not expect to
// call DiskIsAttached
testcase.t.Errorf("Unexpected DiskIsAttached call!")
return false, errors.New("Unexpected DiskIsAttached call!")
}
if expected.diskName != diskName {
testcase.t.Errorf("Unexpected DiskIsAttached call: expected diskName %s, got %s", expected.diskName, diskName)
return false, errors.New("Unexpected DiskIsAttached call: wrong diskName")
}
if expected.nodeName != nodeName {
testcase.t.Errorf("Unexpected DiskIsAttached call: expected nodeName %s, got %s", expected.nodeName, nodeName)
return false, errors.New("Unexpected DiskIsAttached call: wrong nodeName")
}
klog.V(4).Infof("DiskIsAttached call: %s, %s, returning %v, %v", diskName, nodeName, expected.isAttached, expected.ret)
return expected.isAttached, expected.ret
}
func (testcase *testcase) DisksAreAttached(ctx context.Context, diskNames []string, nodeName types.NodeName) (map[string]bool, error) {
return nil, errors.New("Not implemented")
}
func (testcase *testcase) CreateDisk(volumeOptions *photon.VolumeOptions) (volumeName string, err error) {
return "", errors.New("Not implemented")
}
func (testcase *testcase) DeleteDisk(volumeName string) error {
return errors.New("Not implemented")
}
func (testcase *testcase) GetVolumeLabels(volumeName string) (map[string]string, error) {
return map[string]string{}, errors.New("Not implemented")
}
func (testcase *testcase) GetDiskPath(volumeName string) (string, error) {
return "", errors.New("Not implemented")
}

View File

@ -1,409 +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_pd
import (
"fmt"
"os"
"path/filepath"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util"
utilstrings "k8s.io/utils/strings"
)
// This is the primary entrypoint for volume plugins.
func ProbeVolumePlugins() []volume.VolumePlugin {
return []volume.VolumePlugin{&photonPersistentDiskPlugin{}}
}
type photonPersistentDiskPlugin struct {
host volume.VolumeHost
}
var _ volume.VolumePlugin = &photonPersistentDiskPlugin{}
var _ volume.PersistentVolumePlugin = &photonPersistentDiskPlugin{}
var _ volume.DeletableVolumePlugin = &photonPersistentDiskPlugin{}
var _ volume.ProvisionableVolumePlugin = &photonPersistentDiskPlugin{}
const (
photonPersistentDiskPluginName = "kubernetes.io/photon-pd"
)
func (plugin *photonPersistentDiskPlugin) Init(host volume.VolumeHost) error {
plugin.host = host
return nil
}
func (plugin *photonPersistentDiskPlugin) GetPluginName() string {
return photonPersistentDiskPluginName
}
func (plugin *photonPersistentDiskPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
volumeSource, _, err := getVolumeSource(spec)
if err != nil {
klog.Errorf("Photon volume plugin: GetVolumeName failed to get volume source")
return "", err
}
return volumeSource.PdID, nil
}
func (plugin *photonPersistentDiskPlugin) CanSupport(spec *volume.Spec) bool {
return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.PhotonPersistentDisk != nil) ||
(spec.Volume != nil && spec.Volume.PhotonPersistentDisk != nil)
}
func (plugin *photonPersistentDiskPlugin) IsMigratedToCSI() bool {
return false
}
func (plugin *photonPersistentDiskPlugin) RequiresRemount() bool {
return false
}
func (plugin *photonPersistentDiskPlugin) SupportsMountOption() bool {
return true
}
func (plugin *photonPersistentDiskPlugin) SupportsBulkVolumeVerification() bool {
return false
}
func (plugin *photonPersistentDiskPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
return plugin.newMounterInternal(spec, pod.UID, &PhotonDiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName()))
}
func (plugin *photonPersistentDiskPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
return plugin.newUnmounterInternal(volName, podUID, &PhotonDiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName()))
}
func (plugin *photonPersistentDiskPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager pdManager, mounter mount.Interface) (volume.Mounter, error) {
vvol, _, err := getVolumeSource(spec)
if err != nil {
klog.Errorf("Photon volume plugin: newMounterInternal failed to get volume source")
return nil, err
}
pdID := vvol.PdID
fsType := vvol.FSType
return &photonPersistentDiskMounter{
photonPersistentDisk: &photonPersistentDisk{
podUID: podUID,
volName: spec.Name(),
pdID: pdID,
manager: manager,
mounter: mounter,
plugin: plugin,
},
fsType: fsType,
diskMounter: util.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host),
mountOption: util.MountOptionFromSpec(spec),
}, nil
}
func (plugin *photonPersistentDiskPlugin) newUnmounterInternal(volName string, podUID types.UID, manager pdManager, mounter mount.Interface) (volume.Unmounter, error) {
return &photonPersistentDiskUnmounter{
&photonPersistentDisk{
podUID: podUID,
volName: volName,
manager: manager,
mounter: mounter,
plugin: plugin,
}}, nil
}
func (plugin *photonPersistentDiskPlugin) ConstructVolumeSpec(volumeSpecName, mountPath string) (*volume.Spec, error) {
mounter := plugin.host.GetMounter(plugin.GetPluginName())
pluginMntDir := util.GetPluginMountDir(plugin.host, plugin.GetPluginName())
pdID, err := mounter.GetDeviceNameFromMount(mountPath, pluginMntDir)
if err != nil {
return nil, err
}
photonPersistentDisk := &v1.Volume{
Name: volumeSpecName,
VolumeSource: v1.VolumeSource{
PhotonPersistentDisk: &v1.PhotonPersistentDiskVolumeSource{
PdID: pdID,
},
},
}
return volume.NewSpecFromVolume(photonPersistentDisk), nil
}
// Abstract interface to disk operations.
type pdManager interface {
// Creates a volume
CreateVolume(provisioner *photonPersistentDiskProvisioner) (pdID string, volumeSizeGB int, fstype string, err error)
// Deletes a volume
DeleteVolume(deleter *photonPersistentDiskDeleter) error
}
// photonPersistentDisk volumes are disk resources are attached to the kubelet's host machine and exposed to the pod.
type photonPersistentDisk struct {
volName string
podUID types.UID
// Unique identifier of the volume, used to find the disk resource in the provider.
pdID string
// Filesystem type, optional.
fsType string
// Utility interface that provides API calls to the provider to attach/detach disks.
manager pdManager
// Mounter interface that provides system calls to mount the global path to the pod local path.
mounter mount.Interface
plugin *photonPersistentDiskPlugin
volume.MetricsNil
}
var _ volume.Mounter = &photonPersistentDiskMounter{}
type photonPersistentDiskMounter struct {
*photonPersistentDisk
fsType string
diskMounter *mount.SafeFormatAndMount
mountOption []string
}
func (b *photonPersistentDiskMounter) GetAttributes() volume.Attributes {
return volume.Attributes{
SupportsSELinux: true,
}
}
// Checks prior to mount operations to verify that the required components (binaries, etc.)
// to mount the volume are available on the underlying node.
// If not, it returns an error
func (b *photonPersistentDiskMounter) CanMount() error {
return nil
}
// SetUp attaches the disk and bind mounts to the volume path.
func (b *photonPersistentDiskMounter) SetUp(mounterArgs volume.MounterArgs) error {
return b.SetUpAt(b.GetPath(), mounterArgs)
}
// SetUp attaches the disk and bind mounts to the volume path.
func (b *photonPersistentDiskMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
klog.V(4).Infof("Photon Persistent Disk setup %s to %s", b.pdID, dir)
// TODO: handle failed mounts here.
notmnt, err := b.mounter.IsLikelyNotMountPoint(dir)
if err != nil && !os.IsNotExist(err) {
klog.Errorf("cannot validate mount point: %s %v", dir, err)
return err
}
if !notmnt {
return nil
}
if err := os.MkdirAll(dir, 0750); err != nil {
klog.Errorf("mkdir failed on disk %s (%v)", dir, err)
return err
}
options := []string{"bind"}
// Perform a bind mount to the full path to allow duplicate mounts of the same PD.
globalPDPath := makeGlobalPDPath(b.plugin.host, b.pdID)
klog.V(4).Infof("attempting to mount %s", dir)
mountOptions := util.JoinMountOptions(options, b.mountOption)
err = b.mounter.Mount(globalPDPath, dir, "", mountOptions)
if err != nil {
notmnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
if mntErr != nil {
klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
return err
}
if !notmnt {
if mntErr = b.mounter.Unmount(dir); mntErr != nil {
klog.Errorf("Failed to unmount: %v", mntErr)
return err
}
notmnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
if mntErr != nil {
klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
return err
}
if !notmnt {
klog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", b.GetPath())
return err
}
}
os.Remove(dir)
klog.Errorf("Mount of disk %s failed: %v", dir, err)
return err
}
return nil
}
var _ volume.Unmounter = &photonPersistentDiskUnmounter{}
type photonPersistentDiskUnmounter struct {
*photonPersistentDisk
}
// Unmounts the bind mount, and detaches the disk only if the PD
// resource was the last reference to that disk on the kubelet.
func (c *photonPersistentDiskUnmounter) TearDown() error {
err := c.TearDownAt(c.GetPath())
if err != nil {
return err
}
removeFromScsiSubsystem(c.volName)
return nil
}
// Unmounts the bind mount, and detaches the disk only if the PD
// resource was the last reference to that disk on the kubelet.
func (c *photonPersistentDiskUnmounter) TearDownAt(dir string) error {
return mount.CleanupMountPoint(dir, c.mounter, false)
}
func makeGlobalPDPath(host volume.VolumeHost, devName string) string {
return filepath.Join(host.GetPluginDir(photonPersistentDiskPluginName), util.MountsInGlobalPDPath, devName)
}
func (ppd *photonPersistentDisk) GetPath() string {
name := photonPersistentDiskPluginName
return ppd.plugin.host.GetPodVolumeDir(ppd.podUID, utilstrings.EscapeQualifiedName(name), ppd.volName)
}
// TODO: supporting more access mode for PhotonController persistent disk
func (plugin *photonPersistentDiskPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
return []v1.PersistentVolumeAccessMode{
v1.ReadWriteOnce,
}
}
type photonPersistentDiskDeleter struct {
*photonPersistentDisk
}
var _ volume.Deleter = &photonPersistentDiskDeleter{}
func (plugin *photonPersistentDiskPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) {
return plugin.newDeleterInternal(spec, &PhotonDiskUtil{})
}
func (plugin *photonPersistentDiskPlugin) newDeleterInternal(spec *volume.Spec, manager pdManager) (volume.Deleter, error) {
if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.PhotonPersistentDisk == nil {
return nil, fmt.Errorf("spec.PersistentVolumeSource.PhotonPersistentDisk is nil")
}
return &photonPersistentDiskDeleter{
&photonPersistentDisk{
volName: spec.Name(),
pdID: spec.PersistentVolume.Spec.PhotonPersistentDisk.PdID,
manager: manager,
plugin: plugin,
}}, nil
}
func (r *photonPersistentDiskDeleter) Delete() error {
return r.manager.DeleteVolume(r)
}
type photonPersistentDiskProvisioner struct {
*photonPersistentDisk
options volume.VolumeOptions
}
var _ volume.Provisioner = &photonPersistentDiskProvisioner{}
func (plugin *photonPersistentDiskPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
return plugin.newProvisionerInternal(options, &PhotonDiskUtil{})
}
func (plugin *photonPersistentDiskPlugin) newProvisionerInternal(options volume.VolumeOptions, manager pdManager) (volume.Provisioner, error) {
return &photonPersistentDiskProvisioner{
photonPersistentDisk: &photonPersistentDisk{
manager: manager,
plugin: plugin,
},
options: options,
}, nil
}
func (p *photonPersistentDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
if !util.AccessModesContainedInAll(p.plugin.GetAccessModes(), p.options.PVC.Spec.AccessModes) {
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", p.options.PVC.Spec.AccessModes, p.plugin.GetAccessModes())
}
if util.CheckPersistentVolumeClaimModeBlock(p.options.PVC) {
return nil, fmt.Errorf("%s does not support block volume provisioning", p.plugin.GetPluginName())
}
pdID, sizeGB, fstype, err := p.manager.CreateVolume(p)
if err != nil {
return nil, err
}
if fstype == "" {
fstype = "ext4"
}
pv := &v1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: p.options.PVName,
Labels: map[string]string{},
Annotations: map[string]string{
util.VolumeDynamicallyCreatedByKey: "photon-volume-dynamic-provisioner",
},
},
Spec: v1.PersistentVolumeSpec{
PersistentVolumeReclaimPolicy: p.options.PersistentVolumeReclaimPolicy,
AccessModes: p.options.PVC.Spec.AccessModes,
Capacity: v1.ResourceList{
v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGB)),
},
PersistentVolumeSource: v1.PersistentVolumeSource{
PhotonPersistentDisk: &v1.PhotonPersistentDiskVolumeSource{
PdID: pdID,
FSType: fstype,
},
},
MountOptions: p.options.MountOptions,
},
}
if len(p.options.PVC.Spec.AccessModes) == 0 {
pv.Spec.AccessModes = p.plugin.GetAccessModes()
}
return pv, nil
}
func getVolumeSource(
spec *volume.Spec) (*v1.PhotonPersistentDiskVolumeSource, bool, error) {
if spec.Volume != nil && spec.Volume.PhotonPersistentDisk != nil {
return spec.Volume.PhotonPersistentDisk, spec.ReadOnly, nil
} else if spec.PersistentVolume != nil &&
spec.PersistentVolume.Spec.PhotonPersistentDisk != nil {
return spec.PersistentVolume.Spec.PhotonPersistentDisk, spec.ReadOnly, nil
}
return nil, false, fmt.Errorf("Spec does not reference a Photon Controller persistent disk type")
}

View File

@ -1,235 +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 photon_pd
import (
"fmt"
"os"
"path/filepath"
"testing"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
utiltesting "k8s.io/client-go/util/testing"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
volumetest "k8s.io/kubernetes/pkg/volume/testing"
)
func TestCanSupport(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("photonpdTest")
if err != nil {
t.Fatalf("can't make a temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plug, err := plugMgr.FindPluginByName("kubernetes.io/photon-pd")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if plug.GetPluginName() != "kubernetes.io/photon-pd" {
t.Errorf("Wrong name: %s", plug.GetPluginName())
}
if !plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{PhotonPersistentDisk: &v1.PhotonPersistentDiskVolumeSource{}}}}) {
t.Errorf("Expected true")
}
if !plug.CanSupport(&volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{PersistentVolumeSource: v1.PersistentVolumeSource{PhotonPersistentDisk: &v1.PhotonPersistentDiskVolumeSource{}}}}}) {
t.Errorf("Expected true")
}
}
func TestGetAccessModes(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("photonpdTest")
if err != nil {
t.Fatalf("can't make a temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/photon-pd")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) {
t.Errorf("Expected to support AccessModeTypes: %s", v1.ReadWriteOnce)
}
if volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) {
t.Errorf("Expected not to support AccessModeTypes: %s", v1.ReadOnlyMany)
}
}
type fakePDManager struct {
}
func (fake *fakePDManager) CreateVolume(c *photonPersistentDiskProvisioner) (pdID string, volumeSizeGB int, fstype string, err error) {
return "test-photon-pd-id", 10, "ext4", nil
}
func (fake *fakePDManager) DeleteVolume(cd *photonPersistentDiskDeleter) error {
if cd.pdID != "test-photon-pd-id" {
return fmt.Errorf("Deleter got unexpected volume name: %s", cd.pdID)
}
return nil
}
func TestPlugin(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("photonpdTest")
if err != nil {
t.Fatalf("can't make a temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plug, err := plugMgr.FindPluginByName("kubernetes.io/photon-pd")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
spec := &v1.Volume{
Name: "vol1",
VolumeSource: v1.VolumeSource{
PhotonPersistentDisk: &v1.PhotonPersistentDiskVolumeSource{
PdID: "pdid",
FSType: "ext4",
},
},
}
fakeManager := &fakePDManager{}
fakeMounter := &mount.FakeMounter{}
mounter, err := plug.(*photonPersistentDiskPlugin).newMounterInternal(volume.NewSpecFromVolume(spec), types.UID("poduid"), fakeManager, fakeMounter)
if err != nil {
t.Errorf("Failed to make a new Mounter: %v", err)
}
if mounter == nil {
t.Errorf("Got a nil Mounter")
}
volPath := filepath.Join(tmpDir, "pods/poduid/volumes/kubernetes.io~photon-pd/vol1")
path := mounter.GetPath()
if path != volPath {
t.Errorf("Got unexpected path: %s", path)
}
if err := mounter.SetUp(volume.MounterArgs{}); err != nil {
t.Errorf("Expected success, got: %v", err)
}
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
t.Errorf("SetUp() failed, volume path not created: %s", path)
} else {
t.Errorf("SetUp() failed: %v", err)
}
}
fakeManager = &fakePDManager{}
unmounter, err := plug.(*photonPersistentDiskPlugin).newUnmounterInternal("vol1", types.UID("poduid"), fakeManager, fakeMounter)
if err != nil {
t.Errorf("Failed to make a new Unmounter: %v", err)
}
if unmounter == nil {
t.Errorf("Got a nil Unmounter")
}
if err := unmounter.TearDown(); err != nil {
t.Errorf("Expected success, got: %v", err)
}
if _, err := os.Stat(path); err == nil {
t.Errorf("TearDown() failed, volume path still exists: %s", path)
} else if !os.IsNotExist(err) {
t.Errorf("TearDown() failed: %v", err)
}
// Test Provisioner
options := volume.VolumeOptions{
PVC: volumetest.CreateTestPVC("10Gi", []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}),
PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimDelete,
}
provisioner, err := plug.(*photonPersistentDiskPlugin).newProvisionerInternal(options, &fakePDManager{})
if err != nil {
t.Fatalf("Error creating new provisioner:%v", err)
}
persistentSpec, err := provisioner.Provision(nil, nil)
if err != nil {
t.Errorf("Provision() failed: %v", err)
}
if persistentSpec.Spec.PersistentVolumeSource.PhotonPersistentDisk.PdID != "test-photon-pd-id" {
t.Errorf("Provision() returned unexpected persistent disk ID: %s", persistentSpec.Spec.PersistentVolumeSource.PhotonPersistentDisk.PdID)
}
cap := persistentSpec.Spec.Capacity[v1.ResourceStorage]
size := cap.Value()
if size != 10*1024*1024*1024 {
t.Errorf("Provision() returned unexpected volume size: %v", size)
}
// Test Deleter
volSpec := &volume.Spec{
PersistentVolume: persistentSpec,
}
deleter, err := plug.(*photonPersistentDiskPlugin).newDeleterInternal(volSpec, &fakePDManager{})
if err != nil {
t.Fatalf("Error creating new deleter:%v", err)
}
err = deleter.Delete()
if err != nil {
t.Errorf("Deleter() failed: %v", err)
}
}
func TestMounterAndUnmounterTypeAssert(t *testing.T) {
tmpDir, err := utiltesting.MkTmpdir("photonpdTest")
if err != nil {
t.Fatalf("can't make a temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
plugMgr := volume.VolumePluginMgr{}
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(tmpDir, nil, nil))
plug, err := plugMgr.FindPluginByName("kubernetes.io/photon-pd")
if err != nil {
t.Errorf("Can't find the plugin by name")
}
spec := &v1.Volume{
Name: "vol1",
VolumeSource: v1.VolumeSource{
PhotonPersistentDisk: &v1.PhotonPersistentDiskVolumeSource{
PdID: "pdid",
FSType: "ext4",
},
},
}
mounter, err := plug.(*photonPersistentDiskPlugin).newMounterInternal(volume.NewSpecFromVolume(spec), types.UID("poduid"), &fakePDManager{}, &mount.FakeMounter{})
if err != nil {
t.Fatalf("Error creating new mounter:%v", err)
}
if _, ok := mounter.(volume.Unmounter); ok {
t.Errorf("Volume Mounter can be type-assert to Unmounter")
}
unmounter, err := plug.(*photonPersistentDiskPlugin).newUnmounterInternal("vol1", types.UID("poduid"), &fakePDManager{}, &mount.FakeMounter{})
if err != nil {
t.Fatalf("Error creating new unmounter:%v", err)
}
if _, ok := unmounter.(volume.Mounter); ok {
t.Errorf("Volume Unmounter can be type-assert to Mounter")
}
}

View File

@ -1,157 +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_pd
import (
"errors"
"fmt"
"io/ioutil"
"strings"
"time"
"k8s.io/api/core/v1"
cloudprovider "k8s.io/cloud-provider"
volumehelpers "k8s.io/cloud-provider/volume/helpers"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/cloudprovider/providers/photon"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
volumeutil "k8s.io/kubernetes/pkg/volume/util"
)
const (
maxRetries = 10
checkSleepDuration = time.Second
diskByIDPath = "/dev/disk/by-id/"
diskPhotonPrefix = "wwn-0x"
)
var ErrProbeVolume = errors.New("Error scanning attached volumes")
// volNameToDeviceName is a mapping between spec.Name from detacher
// and the device name inside scsi path. Once pvscsi controller is
// supported, this won't be needed.
var volNameToDeviceName = make(map[string]string)
type PhotonDiskUtil struct{}
func removeFromScsiSubsystem(volName string) {
// TODO: if using pvscsi controller, this won't be needed
deviceName := volNameToDeviceName[volName]
fileName := "/sys/block/" + deviceName + "/device/delete"
data := []byte("1")
ioutil.WriteFile(fileName, data, 0666)
}
func scsiHostScan() {
// TODO: if using pvscsi controller, this won't be needed
scsi_path := "/sys/class/scsi_host/"
if dirs, err := ioutil.ReadDir(scsi_path); err == nil {
for _, f := range dirs {
name := scsi_path + f.Name() + "/scan"
data := []byte("- - -")
ioutil.WriteFile(name, data, 0666)
klog.Errorf("scsiHostScan scan for %s", name)
}
}
}
func verifyDevicePath(path string) (string, error) {
if pathExists, err := mount.PathExists(path); err != nil {
return "", fmt.Errorf("Error checking if path exists: %v", err)
} else if pathExists {
return path, nil
}
klog.V(4).Infof("verifyDevicePath: path not exists yet")
return "", nil
}
// CreateVolume creates a PhotonController persistent disk.
func (util *PhotonDiskUtil) CreateVolume(p *photonPersistentDiskProvisioner) (pdID string, capacityGB int, fstype string, err error) {
cloud, err := getCloudProvider(p.plugin.host.GetCloudProvider())
if err != nil {
klog.Errorf("Photon Controller Util: CreateVolume failed to get cloud provider. Error [%v]", err)
return "", 0, "", err
}
capacity := p.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
// PhotonController works with GiB, convert to GiB with rounding up
volSizeGB, err := volumehelpers.RoundUpToGiBInt(capacity)
if err != nil {
return "", 0, "", err
}
name := volumeutil.GenerateVolumeName(p.options.ClusterName, p.options.PVName, 255)
volumeOptions := &photon.VolumeOptions{
CapacityGB: volSizeGB,
Tags: *p.options.CloudTags,
Name: name,
}
for parameter, value := range p.options.Parameters {
switch strings.ToLower(parameter) {
case "flavor":
volumeOptions.Flavor = value
case volume.VolumeParameterFSType:
fstype = value
klog.V(4).Infof("Photon Controller Util: Setting fstype to %s", fstype)
default:
klog.Errorf("Photon Controller Util: invalid option %s for volume plugin %s.", parameter, p.plugin.GetPluginName())
return "", 0, "", fmt.Errorf("Photon Controller Util: invalid option %s for volume plugin %s.", parameter, p.plugin.GetPluginName())
}
}
pdID, err = cloud.CreateDisk(volumeOptions)
if err != nil {
klog.Errorf("Photon Controller Util: failed to CreateDisk. Error [%v]", err)
return "", 0, "", err
}
klog.V(4).Infof("Successfully created Photon Controller persistent disk %s", name)
return pdID, volSizeGB, "", nil
}
// DeleteVolume deletes a vSphere volume.
func (util *PhotonDiskUtil) DeleteVolume(pd *photonPersistentDiskDeleter) error {
cloud, err := getCloudProvider(pd.plugin.host.GetCloudProvider())
if err != nil {
klog.Errorf("Photon Controller Util: DeleteVolume failed to get cloud provider. Error [%v]", err)
return err
}
if err = cloud.DeleteDisk(pd.pdID); err != nil {
klog.Errorf("Photon Controller Util: failed to DeleteDisk for pdID %s. Error [%v]", pd.pdID, err)
return err
}
klog.V(4).Infof("Successfully deleted PhotonController persistent disk %s", pd.pdID)
return nil
}
func getCloudProvider(cloud cloudprovider.Interface) (*photon.PCCloud, error) {
if cloud == nil {
klog.Errorf("Photon Controller Util: Cloud provider not initialized properly")
return nil, fmt.Errorf("Photon Controller Util: Cloud provider not initialized properly")
}
pcc := cloud.(*photon.PCCloud)
if pcc == nil {
klog.Errorf("Invalid cloud provider: expected Photon Controller")
return nil, fmt.Errorf("Invalid cloud provider: expected Photon Controller")
}
return pcc, nil
}

View File

@ -42,11 +42,8 @@ var (
}{
{"aws", false, "The AWS provider is deprecated and will be removed in a future release"},
{"azure", false, "The Azure provider is deprecated and will be removed in a future release"},
{"cloudstack", false, "The CloudStack Controller project is no longer maintained."},
{"gce", false, "The GCE provider is deprecated and will be removed in a future release"},
{"openstack", true, "https://github.com/kubernetes/cloud-provider-openstack"},
{"ovirt", false, "The ovirt Controller project is no longer maintained."},
{"photon", false, "The Photon Controller project is no longer maintained."},
{"vsphere", false, "The vSphere provider is deprecated and will be removed in a future release"},
}
)

View File

@ -583,12 +583,8 @@ k8s.io/kubernetes/pkg/client/tests,Q-Lee,1,
k8s.io/kubernetes/pkg/client/unversioned,justinsb,1,
k8s.io/kubernetes/pkg/cloudprovider/providers/aws,eparis,1,
k8s.io/kubernetes/pkg/cloudprovider/providers/azure,saad-ali,1,
k8s.io/kubernetes/pkg/cloudprovider/providers/cloudstack,roberthbailey,1,
k8s.io/kubernetes/pkg/cloudprovider/providers/gce,yifan-gu,1,
k8s.io/kubernetes/pkg/cloudprovider/providers/mesos,mml,1,
k8s.io/kubernetes/pkg/cloudprovider/providers/openstack,Q-Lee,1,
k8s.io/kubernetes/pkg/cloudprovider/providers/ovirt,dchen1107,1,
k8s.io/kubernetes/pkg/cloudprovider/providers/photon,luomiao,0,
k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere,apelisse,1,
k8s.io/kubernetes/pkg/controller,mikedanese,1,
k8s.io/kubernetes/pkg/controller/bootstrap,mikedanese,0,
@ -821,7 +817,6 @@ k8s.io/kubernetes/pkg/volume/glusterfs,tallclair,1,
k8s.io/kubernetes/pkg/volume/hostpath,jbeda,1,
k8s.io/kubernetes/pkg/volume/iscsi,cjcullen,1,
k8s.io/kubernetes/pkg/volume/nfs,justinsb,1,
k8s.io/kubernetes/pkg/volume/photon_pd,luomiao,0,
k8s.io/kubernetes/pkg/volume/projected,kevin-wangzefeng,1,
k8s.io/kubernetes/pkg/volume/quobyte,yujuhong,1,
k8s.io/kubernetes/pkg/volume/rbd,piosz,1,

1 name owner auto-assigned sig
583 k8s.io/kubernetes/pkg/client/unversioned justinsb 1
584 k8s.io/kubernetes/pkg/cloudprovider/providers/aws eparis 1
585 k8s.io/kubernetes/pkg/cloudprovider/providers/azure saad-ali 1
k8s.io/kubernetes/pkg/cloudprovider/providers/cloudstack roberthbailey 1
586 k8s.io/kubernetes/pkg/cloudprovider/providers/gce yifan-gu 1
k8s.io/kubernetes/pkg/cloudprovider/providers/mesos mml 1
587 k8s.io/kubernetes/pkg/cloudprovider/providers/openstack Q-Lee 1
k8s.io/kubernetes/pkg/cloudprovider/providers/ovirt dchen1107 1
k8s.io/kubernetes/pkg/cloudprovider/providers/photon luomiao 0
588 k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere apelisse 1
589 k8s.io/kubernetes/pkg/controller mikedanese 1
590 k8s.io/kubernetes/pkg/controller/bootstrap mikedanese 0
817 k8s.io/kubernetes/pkg/volume/hostpath jbeda 1
818 k8s.io/kubernetes/pkg/volume/iscsi cjcullen 1
819 k8s.io/kubernetes/pkg/volume/nfs justinsb 1
k8s.io/kubernetes/pkg/volume/photon_pd luomiao 0
820 k8s.io/kubernetes/pkg/volume/projected kevin-wangzefeng 1
821 k8s.io/kubernetes/pkg/volume/quobyte yujuhong 1
822 k8s.io/kubernetes/pkg/volume/rbd piosz 1