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