Commit b22ccc67 authored by Miao Luo's avatar Miao Luo

Support persistent volume on Photon Controller platform

1. Enable Photon Controller as cloud provider 2. Support Photon persistent disk as volume source/persistent volume source
parent c41c24fb
......@@ -1452,6 +1452,10 @@
"azureDisk": {
"$ref": "v1.AzureDiskVolumeSource",
"description": "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod."
},
"photonPersistentDisk": {
"$ref": "v1.PhotonPersistentDiskVolumeSource",
"description": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine"
}
}
},
......@@ -2085,6 +2089,23 @@
"id": "v1.AzureDataDiskCachingMode",
"properties": {}
},
"v1.PhotonPersistentDiskVolumeSource": {
"id": "v1.PhotonPersistentDiskVolumeSource",
"description": "Represents a Photon Controller persistent disk resource.",
"required": [
"pdID"
],
"properties": {
"pdID": {
"type": "string",
"description": "ID that identifies Photon Controller persistent disk"
},
"fsType": {
"type": "string",
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified."
}
}
},
"v1.Container": {
"id": "v1.Container",
"description": "A single application container that you want to run within a pod.",
......
......@@ -1457,6 +1457,10 @@
"azureDisk": {
"$ref": "v1.AzureDiskVolumeSource",
"description": "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod."
},
"photonPersistentDisk": {
"$ref": "v1.PhotonPersistentDiskVolumeSource",
"description": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine"
}
}
},
......@@ -2090,6 +2094,23 @@
"id": "v1.AzureDataDiskCachingMode",
"properties": {}
},
"v1.PhotonPersistentDiskVolumeSource": {
"id": "v1.PhotonPersistentDiskVolumeSource",
"description": "Represents a Photon Controller persistent disk resource.",
"required": [
"pdID"
],
"properties": {
"pdID": {
"type": "string",
"description": "ID that identifies Photon Controller persistent disk"
},
"fsType": {
"type": "string",
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified."
}
}
},
"v1.Container": {
"id": "v1.Container",
"description": "A single application container that you want to run within a pod.",
......
......@@ -8224,6 +8224,10 @@
"azureDisk": {
"$ref": "v1.AzureDiskVolumeSource",
"description": "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod."
},
"photonPersistentDisk": {
"$ref": "v1.PhotonPersistentDiskVolumeSource",
"description": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine"
}
}
},
......@@ -8857,6 +8861,23 @@
"id": "v1.AzureDataDiskCachingMode",
"properties": {}
},
"v1.PhotonPersistentDiskVolumeSource": {
"id": "v1.PhotonPersistentDiskVolumeSource",
"description": "Represents a Photon Controller persistent disk resource.",
"required": [
"pdID"
],
"properties": {
"pdID": {
"type": "string",
"description": "ID that identifies Photon Controller persistent disk"
},
"fsType": {
"type": "string",
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified."
}
}
},
"v1.Container": {
"id": "v1.Container",
"description": "A single application container that you want to run within a pod.",
......
......@@ -17654,6 +17654,10 @@
"$ref": "v1.AzureDiskVolumeSource",
"description": "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod."
},
"photonPersistentDisk": {
"$ref": "v1.PhotonPersistentDiskVolumeSource",
"description": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine"
},
"accessModes": {
"type": "array",
"items": {
......@@ -18104,6 +18108,23 @@
"id": "v1.AzureDataDiskCachingMode",
"properties": {}
},
"v1.PhotonPersistentDiskVolumeSource": {
"id": "v1.PhotonPersistentDiskVolumeSource",
"description": "Represents a Photon Controller persistent disk resource.",
"required": [
"pdID"
],
"properties": {
"pdID": {
"type": "string",
"description": "ID that identifies Photon Controller persistent disk"
},
"fsType": {
"type": "string",
"description": "Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified."
}
}
},
"v1.PersistentVolumeStatus": {
"id": "v1.PersistentVolumeStatus",
"description": "PersistentVolumeStatus is the current status of a persistent volume.",
......@@ -18362,6 +18383,10 @@
"azureDisk": {
"$ref": "v1.AzureDiskVolumeSource",
"description": "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod."
},
"photonPersistentDisk": {
"$ref": "v1.PhotonPersistentDiskVolumeSource",
"description": "PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine"
}
}
},
......
......@@ -38,6 +38,7 @@ go_library(
"//pkg/cloudprovider/providers/azure:go_default_library",
"//pkg/cloudprovider/providers/gce:go_default_library",
"//pkg/cloudprovider/providers/openstack:go_default_library",
"//pkg/cloudprovider/providers/photon:go_default_library",
"//pkg/cloudprovider/providers/vsphere:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/controller/certificates:go_default_library",
......@@ -83,6 +84,7 @@ go_library(
"//pkg/volume/glusterfs:go_default_library",
"//pkg/volume/host_path:go_default_library",
"//pkg/volume/nfs:go_default_library",
"//pkg/volume/photon_pd:go_default_library",
"//pkg/volume/quobyte:go_default_library",
"//pkg/volume/rbd:go_default_library",
"//pkg/volume/vsphere_volume:go_default_library",
......
......@@ -34,6 +34,7 @@ import (
"k8s.io/kubernetes/pkg/cloudprovider/providers/azure"
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
"k8s.io/kubernetes/pkg/cloudprovider/providers/openstack"
"k8s.io/kubernetes/pkg/cloudprovider/providers/photon"
"k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere"
utilconfig "k8s.io/kubernetes/pkg/util/config"
"k8s.io/kubernetes/pkg/util/io"
......@@ -47,6 +48,7 @@ import (
"k8s.io/kubernetes/pkg/volume/glusterfs"
"k8s.io/kubernetes/pkg/volume/host_path"
"k8s.io/kubernetes/pkg/volume/nfs"
"k8s.io/kubernetes/pkg/volume/photon_pd"
"k8s.io/kubernetes/pkg/volume/quobyte"
"k8s.io/kubernetes/pkg/volume/rbd"
"k8s.io/kubernetes/pkg/volume/vsphere_volume"
......@@ -67,6 +69,7 @@ func ProbeAttachableVolumePlugins(config componentconfig.VolumeConfiguration) []
allPlugins = append(allPlugins, flexvolume.ProbeVolumePlugins(config.FlexVolumePluginDir)...)
allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, azure_dd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, photon_pd.ProbeVolumePlugins()...)
return allPlugins
}
......@@ -124,6 +127,8 @@ func ProbeControllerVolumePlugins(cloud cloudprovider.Interface, config componen
allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...)
case azure.CloudProviderName == cloud.ProviderName():
allPlugins = append(allPlugins, azure_dd.ProbeVolumePlugins()...)
case photon.ProviderName == cloud.ProviderName():
allPlugins = append(allPlugins, photon_pd.ProbeVolumePlugins()...)
}
}
......@@ -154,6 +159,8 @@ func NewAlphaVolumeProvisioner(cloud cloudprovider.Interface, config componentco
return getProvisionablePluginFromVolumePlugins(vsphere_volume.ProbeVolumePlugins())
case cloud != nil && azure.CloudProviderName == cloud.ProviderName():
return getProvisionablePluginFromVolumePlugins(azure_dd.ProbeVolumePlugins())
case cloud != nil && photon.ProviderName == cloud.ProviderName():
return getProvisionablePluginFromVolumePlugins(photon_pd.ProbeVolumePlugins())
}
return nil, nil
}
......
......@@ -93,6 +93,7 @@ go_library(
"//pkg/volume/host_path:go_default_library",
"//pkg/volume/iscsi:go_default_library",
"//pkg/volume/nfs:go_default_library",
"//pkg/volume/photon_pd:go_default_library",
"//pkg/volume/quobyte:go_default_library",
"//pkg/volume/rbd:go_default_library",
"//pkg/volume/secret:go_default_library",
......
......@@ -45,6 +45,7 @@ import (
"k8s.io/kubernetes/pkg/volume/host_path"
"k8s.io/kubernetes/pkg/volume/iscsi"
"k8s.io/kubernetes/pkg/volume/nfs"
"k8s.io/kubernetes/pkg/volume/photon_pd"
"k8s.io/kubernetes/pkg/volume/quobyte"
"k8s.io/kubernetes/pkg/volume/rbd"
"k8s.io/kubernetes/pkg/volume/secret"
......@@ -86,6 +87,7 @@ func ProbeVolumePlugins(pluginDir string) []volume.VolumePlugin {
allPlugins = append(allPlugins, configmap.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, azure_dd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, photon_pd.ProbeVolumePlugins()...)
return allPlugins
}
......
......@@ -289,6 +289,8 @@ type VolumeSource struct {
// AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.
// +optional
AzureDisk *AzureDiskVolumeSource `json:"azureDisk,omitempty"`
// PhotonPersistentDisk represents a Photon Controller persistent disk attached and mounted on kubelets host machine
PhotonPersistentDisk *PhotonPersistentDiskVolumeSource `json:"photonPersistentDisk,omitempty"`
}
// Similar to VolumeSource but meant for the administrator who creates PVs.
......@@ -349,6 +351,8 @@ type PersistentVolumeSource struct {
// AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.
// +optional
AzureDisk *AzureDiskVolumeSource `json:"azureDisk,omitempty"`
// PhotonPersistentDisk represents a Photon Controller persistent disk attached and mounted on kubelets host machine
PhotonPersistentDisk *PhotonPersistentDiskVolumeSource `json:"photonPersistentDisk,omitempty"`
}
type PersistentVolumeClaimVolumeSource struct {
......@@ -936,6 +940,16 @@ type VsphereVirtualDiskVolumeSource struct {
FSType string `json:"fsType,omitempty"`
}
// Represents a Photon Controller persistent disk resource.
type PhotonPersistentDiskVolumeSource struct {
// ID that identifies Photon Controller persistent disk
PdID string `json:"pdID"`
// Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
FSType string `json:"fsType,omitempty"`
}
type AzureDataDiskCachingMode string
const (
......
......@@ -322,6 +322,8 @@ type VolumeSource struct {
// AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.
// +optional
AzureDisk *AzureDiskVolumeSource `json:"azureDisk,omitempty" protobuf:"bytes,22,opt,name=azureDisk"`
// PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine
PhotonPersistentDisk *PhotonPersistentDiskVolumeSource `json:"photonPersistentDisk,omitempty" protobuf:"bytes,23,opt,name=photonPersistentDisk"`
}
// PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace.
......@@ -405,6 +407,8 @@ type PersistentVolumeSource struct {
// AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.
// +optional
AzureDisk *AzureDiskVolumeSource `json:"azureDisk,omitempty" protobuf:"bytes,16,opt,name=azureDisk"`
// PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine
PhotonPersistentDisk *PhotonPersistentDiskVolumeSource `json:"photonPersistentDisk,omitempty" protobuf:"bytes,17,opt,name=photonPersistentDisk"`
}
// +genclient=true
......@@ -1023,6 +1027,17 @@ type VsphereVirtualDiskVolumeSource struct {
// +optional
FSType string `json:"fsType,omitempty" protobuf:"bytes,2,opt,name=fsType"`
}
// Represents a Photon Controller persistent disk resource.
type PhotonPersistentDiskVolumeSource struct {
// ID that identifies Photon Controller persistent disk
PdID string `json:"pdID" protobuf:"bytes,1,opt,name=pdID"`
// Filesystem type to mount.
// Must be a filesystem type supported by the host operating system.
// Ex. "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified.
FSType string `json:"fsType,omitempty" protobuf:"bytes,2,opt,name=fsType"`
}
type AzureDataDiskCachingMode string
const (
......
......@@ -665,6 +665,14 @@ func validateVolumeSource(source *api.VolumeSource, fldPath *field.Path) field.E
allErrs = append(allErrs, validateVsphereVolumeSource(source.VsphereVolume, fldPath.Child("vsphereVolume"))...)
}
}
if source.PhotonPersistentDisk != nil {
if numVolumes > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("photonPersistentDisk"), "may not specify more than 1 volume type"))
} else {
numVolumes++
allErrs = append(allErrs, validatePhotonPersistentDiskVolumeSource(source.PhotonPersistentDisk, fldPath.Child("photonPersistentDisk"))...)
}
}
if source.AzureDisk != nil {
numVolumes++
allErrs = append(allErrs, validateAzureDisk(source.AzureDisk, fldPath.Child("azureDisk"))...)
......@@ -1008,6 +1016,14 @@ func validateVsphereVolumeSource(cd *api.VsphereVirtualDiskVolumeSource, fldPath
return allErrs
}
func validatePhotonPersistentDiskVolumeSource(cd *api.PhotonPersistentDiskVolumeSource, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(cd.PdID) == 0 {
allErrs = append(allErrs, field.Required(fldPath.Child("pdID"), ""))
}
return allErrs
}
// ValidatePersistentVolumeName checks that a name is appropriate for a
// PersistentVolumeName object.
var ValidatePersistentVolumeName = NameIsDNSSubdomain
......@@ -1159,6 +1175,14 @@ func ValidatePersistentVolume(pv *api.PersistentVolume) field.ErrorList {
allErrs = append(allErrs, validateVsphereVolumeSource(pv.Spec.VsphereVolume, specPath.Child("vsphereVolume"))...)
}
}
if pv.Spec.PhotonPersistentDisk != nil {
if numVolumes > 0 {
allErrs = append(allErrs, field.Forbidden(specPath.Child("photonPersistentDisk"), "may not specify more than 1 volume type"))
} else {
numVolumes++
allErrs = append(allErrs, validatePhotonPersistentDiskVolumeSource(pv.Spec.PhotonPersistentDisk, specPath.Child("photonPersistentDisk"))...)
}
}
if pv.Spec.AzureDisk != nil {
numVolumes++
allErrs = append(allErrs, validateAzureDisk(pv.Spec.AzureDisk, specPath.Child("azureDisk"))...)
......
......@@ -898,6 +898,7 @@ var (
VsphereVolume FSType = "vsphereVolume"
Quobyte FSType = "quobyte"
AzureDisk FSType = "azureDisk"
PhotonPersistentDisk FSType = "photonPersistentDisk"
All FSType = "*"
)
......
......@@ -22,6 +22,7 @@ go_library(
"//pkg/cloudprovider/providers/mesos:go_default_library",
"//pkg/cloudprovider/providers/openstack:go_default_library",
"//pkg/cloudprovider/providers/ovirt:go_default_library",
"//pkg/cloudprovider/providers/photon:go_default_library",
"//pkg/cloudprovider/providers/rackspace:go_default_library",
"//pkg/cloudprovider/providers/vsphere:go_default_library",
],
......
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = ["photon.go"],
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/cloudprovider:go_default_library",
"//pkg/types:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:github.com/vmware/photon-controller-go-sdk/photon",
"//vendor:gopkg.in/gcfg.v1",
],
)
go_test(
name = "go_default_test",
srcs = ["photon_test.go"],
library = "go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/cloudprovider:go_default_library",
"//pkg/types:go_default_library",
"//pkg/util/rand:go_default_library",
],
)
maintainers:
- luomiao
- kerneltime
- abrarshivani
/*
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 (
"log"
"os"
"strconv"
"strings"
"testing"
"k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/rand"
)
func configFromEnv() (TestVM string, TestFlavor string, cfg PCConfig, ok bool) {
var IgnoreCertificate bool
var OverrideIP bool
var err error
cfg.Global.CloudTarget = os.Getenv("PHOTON_TARGET")
cfg.Global.Tenant = os.Getenv("PHOTON_TENANT")
cfg.Global.Project = os.Getenv("PHOTON_PROJECT")
if os.Getenv("PHOTON_IGNORE_CERTIFICATE") != "" {
IgnoreCertificate, err = strconv.ParseBool(os.Getenv("PHOTON_IGNORE_CERTIFICATE"))
} else {
IgnoreCertificate = false
}
if err != nil {
log.Fatal(err)
}
cfg.Global.IgnoreCertificate = IgnoreCertificate
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.Tenant != "" &&
cfg.Global.Project != "" &&
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
ignoreCertificate = true
tenant = tenant
project = project
overrideIP = 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.Tenant != "tenant" {
t.Errorf("incorrect tenant: %s", cfg.Global.Tenant)
}
if cfg.Global.Project != "project" {
t.Errorf("incorrect project: %s", cfg.Global.Project)
}
}
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")
}
externalId, err := i.ExternalID(NodeName)
if err != nil {
t.Fatalf("Instances.ExternalID(%s) failed: %s", testVM, err)
}
t.Logf("Found ExternalID(%s) = %s\n", testVM, externalId)
nonExistingVM := types.NodeName(rand.String(15))
externalId, err = i.ExternalID(nonExistingVM)
if err == cloudprovider.InstanceNotFound {
t.Logf("VM %s was not found as expected\n", nonExistingVM)
} else if err == nil {
t.Fatalf("Instances.ExternalID did not fail as expected, VM %s was found", nonExistingVM)
} else {
t.Fatalf("Instances.ExternalID did not fail as expected, err: %v", err)
}
instanceId, err := i.InstanceID(NodeName)
if err != nil {
t.Fatalf("Instances.InstanceID(%s) failed: %s", testVM, err)
}
t.Logf("Found InstanceID(%s) = %s\n", testVM, instanceId)
instanceId, err = i.InstanceID(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(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(pdID, NodeName)
if err != nil {
t.Fatalf("Cannot attach persistent disk(%s) to VM(%s): %v", pdID, testVM, err)
}
_, err = pc.DiskIsAttached(pdID, NodeName)
if err != nil {
t.Fatalf("Cannot attach persistent disk(%s) to VM(%s): %v", pdID, testVM, err)
}
err = pc.DetachDisk(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)
}
}
......@@ -25,6 +25,7 @@ import (
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/mesos"
_ "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/rackspace"
_ "k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere"
)
......@@ -572,6 +572,8 @@ func describeVolumes(volumes []api.Volume, out io.Writer, space string) {
printVsphereVolumeSource(volume.VolumeSource.VsphereVolume, out)
case volume.VolumeSource.Cinder != nil:
printCinderVolumeSource(volume.VolumeSource.Cinder, out)
case volume.VolumeSource.PhotonPersistentDisk != nil:
printPhotonPersistentDiskVolumeSource(volume.VolumeSource.PhotonPersistentDisk, out)
default:
fmt.Fprintf(out, " <unknown>\n")
}
......@@ -706,6 +708,14 @@ func printVsphereVolumeSource(vsphere *api.VsphereVirtualDiskVolumeSource, out i
" FSType:\t%v\n",
vsphere.VolumePath, vsphere.FSType)
}
func printPhotonPersistentDiskVolumeSource(photon *api.PhotonPersistentDiskVolumeSource, out io.Writer) {
fmt.Fprintf(out, " Type:\tPhotonPersistentDisk (a Persistent Disk resource in photon platform)\n"+
" PdID:\t%v\n"+
" FSType:\t%v\n",
photon.PdID, photon.FSType)
}
func printCinderVolumeSource(cinder *api.CinderVolumeSource, out io.Writer) {
fmt.Fprintf(out, " Type:\tCinder (a Persistent Disk resource in OpenStack)\n"+
" VolumeID:\t%v\n"+
......@@ -772,6 +782,8 @@ func (d *PersistentVolumeDescriber) Describe(namespace, name string, describerSe
printCinderVolumeSource(pv.Spec.Cinder, out)
case pv.Spec.AzureDisk != nil:
printAzureDiskVolumeSource(pv.Spec.AzureDisk, out)
case pv.Spec.PhotonPersistentDisk != nil:
printPhotonPersistentDiskVolumeSource(pv.Spec.PhotonPersistentDisk, out)
}
if events != nil {
......
......@@ -60,7 +60,8 @@ func GetAllFSTypesAsSet() sets.String {
string(extensions.ConfigMap),
string(extensions.VsphereVolume),
string(extensions.Quobyte),
string(extensions.AzureDisk))
string(extensions.AzureDisk),
string(extensions.PhotonPersistentDisk))
return fstypes
}
......@@ -111,6 +112,8 @@ func GetVolumeFSType(v api.Volume) (extensions.FSType, error) {
return extensions.Quobyte, nil
case v.AzureDisk != nil:
return extensions.AzureDisk, nil
case v.PhotonPersistentDisk != nil:
return extensions.PhotonPersistentDisk, nil
}
return "", fmt.Errorf("unknown volume type for volume: %#v", v)
......
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
"go_test",
"cgo_library",
)
go_library(
name = "go_default_library",
srcs = [
"attacher.go",
"photon_pd.go",
"photon_util.go",
],
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/resource:go_default_library",
"//pkg/cloudprovider:go_default_library",
"//pkg/cloudprovider/providers/photon:go_default_library",
"//pkg/types:go_default_library",
"//pkg/util/exec:go_default_library",
"//pkg/util/keymutex:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/util/strings:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/util:go_default_library",
"//vendor:github.com/golang/glog",
],
)
go_test(
name = "go_default_test",
srcs = [
"attacher_test.go",
"photon_pd_test.go",
],
library = "go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/cloudprovider/providers/photon:go_default_library",
"//pkg/types:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/util/testing:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/testing:go_default_library",
"//vendor:github.com/golang/glog",
],
)
maintainers:
- luomiao
- kerneltime
- abrarshivani
/*
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"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/mount"
utiltesting "k8s.io/kubernetes/pkg/util/testing"
"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(), 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: &api.Volume{VolumeSource: api.VolumeSource{PhotonPersistentDisk: &api.PhotonPersistentDiskVolumeSource{}}}}) {
t.Errorf("Expected true")
}
if !plug.CanSupport(&volume.Spec{PersistentVolume: &api.PersistentVolume{Spec: api.PersistentVolumeSpec{PersistentVolumeSource: api.PersistentVolumeSource{PhotonPersistentDisk: &api.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(), 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 !contains(plug.GetAccessModes(), api.ReadWriteOnce) {
t.Errorf("Expected to support AccessModeTypes: %s", api.ReadWriteOnce)
}
if contains(plug.GetAccessModes(), api.ReadOnlyMany) {
t.Errorf("Expected not to support AccessModeTypes: %s", api.ReadOnlyMany)
}
}
func contains(modes []api.PersistentVolumeAccessMode, mode api.PersistentVolumeAccessMode) bool {
for _, m := range modes {
if m == mode {
return true
}
}
return false
}
type fakePDManager struct {
}
func (fake *fakePDManager) CreateVolume(c *photonPersistentDiskProvisioner) (pdID string, volumeSizeGB int, err error) {
return "test-photon-pd-id", 10, 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(), 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 := &api.Volume{
Name: "vol1",
VolumeSource: api.VolumeSource{
PhotonPersistentDisk: &api.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 := path.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(nil); 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)
}
}
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("SetUp() failed: %v", err)
}
// Test Provisioner
options := volume.VolumeOptions{
PVC: volumetest.CreateTestPVC("10Gi", []api.PersistentVolumeAccessMode{api.ReadWriteOnce}),
PersistentVolumeReclaimPolicy: api.PersistentVolumeReclaimDelete,
}
provisioner, err := plug.(*photonPersistentDiskPlugin).newProvisionerInternal(options, &fakePDManager{})
persistentSpec, err := provisioner.Provision()
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[api.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{})
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(), 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 := &api.Volume{
Name: "vol1",
VolumeSource: api.VolumeSource{
PhotonPersistentDisk: &api.PhotonPersistentDiskVolumeSource{
PdID: "pdid",
FSType: "ext4",
},
},
}
mounter, err := plug.(*photonPersistentDiskPlugin).newMounterInternal(volume.NewSpecFromVolume(spec), types.UID("poduid"), &fakePDManager{}, &mount.FakeMounter{})
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 _, ok := unmounter.(volume.Mounter); ok {
t.Errorf("Volume Unmounter can be type-assert to Mounter")
}
}
/*
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"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/cloudprovider/providers/photon"
"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")
var volNameToDeviceName = make(map[string]string)
type PhotonDiskUtil struct{}
func logError(msg string, err error) error {
s := "Photon Controller utility: " + msg + ". Error [" + err.Error() + "]"
glog.Errorf(s)
return fmt.Errorf(s)
}
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)
glog.Errorf("scsiHostScan scan for %s", name)
}
}
}
func verifyDevicePath(path string) (string, error) {
if pathExists, err := volumeutil.PathExists(path); err != nil {
return "", fmt.Errorf("Error checking if path exists: %v", err)
} else if pathExists {
return path, nil
}
glog.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, err error) {
cloud, err := getCloudProvider(p.plugin.host.GetCloudProvider())
if err != nil {
return "", 0, logError("CreateVolume failed to get cloud provider", err)
}
capacity := p.options.PVC.Spec.Resources.Requests[api.ResourceName(api.ResourceStorage)]
volSizeBytes := capacity.Value()
// PhotonController works with GB, convert to GB with rounding up
volSizeGB := int(volume.RoundUpSize(volSizeBytes, 1024*1024*1024))
name := volume.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
default:
return "", 0, logError("invalid option "+parameter+" for volume plugin "+p.plugin.GetPluginName(), err)
}
}
pdID, err = cloud.CreateDisk(volumeOptions)
if err != nil {
return "", 0, logError("failed to CreateDisk", err)
}
glog.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 {
return logError("DeleteVolume failed to get cloud provider", err)
}
if err = cloud.DeleteDisk(pd.pdID); err != nil {
return logError("failed to DeleteDisk for pdID "+pd.pdID, err)
}
glog.V(4).Infof("Successfully deleted PhotonController persistent disk %s", pd.pdID)
return nil
}
func getCloudProvider(cloud cloudprovider.Interface) (*photon.PCCloud, error) {
if cloud == nil {
return nil, logError("Cloud provider not initialized properly", nil)
}
pcc := cloud.(*photon.PCCloud)
if pcc == nil {
return nil, logError("Invalid cloud provider: expected Photon Controller", nil)
}
return pcc, nil
}
......@@ -573,6 +573,7 @@ k8s.io/kubernetes/pkg/cloudprovider/providers/gce,yifan-gu,1
k8s.io/kubernetes/pkg/cloudprovider/providers/mesos,mml,1
k8s.io/kubernetes/pkg/cloudprovider/providers/openstack,Q-Lee,1
k8s.io/kubernetes/pkg/cloudprovider/providers/ovirt,girishkalele,1
k8s.io/kubernetes/pkg/cloudprovider/providers/photon,luomiao,0
k8s.io/kubernetes/pkg/cloudprovider/providers/rackspace,caesarxuchao,1
k8s.io/kubernetes/pkg/cloudprovider/providers/vsphere,apelisse,1
k8s.io/kubernetes/pkg/controller,mikedanese,1
......@@ -845,6 +846,7 @@ k8s.io/kubernetes/pkg/volume/glusterfs,timstclair,1
k8s.io/kubernetes/pkg/volume/host_path,jbeda,1
k8s.io/kubernetes/pkg/volume/iscsi,cjcullen,1
k8s.io/kubernetes/pkg/volume/nfs,justinsb,1
k8s.io/kubernetes/pkg/volume/photon_pd,luomiao,0
k8s.io/kubernetes/pkg/volume/quobyte,yujuhong,1
k8s.io/kubernetes/pkg/volume/rbd,piosz,1
k8s.io/kubernetes/pkg/volume/secret,rmmh,1
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment