Remove AWS legacy cloud provider + EBS in-tree storage plugin
Signed-off-by: torredil <torredil@amazon.com>
This commit is contained in:
parent
30df862563
commit
6aebda9b1e
26
LICENSES/vendor/github.com/stretchr/objx/LICENSE
generated
vendored
26
LICENSES/vendor/github.com/stretchr/objx/LICENSE
generated
vendored
@ -1,26 +0,0 @@
|
||||
= vendor/github.com/stretchr/objx licensed under: =
|
||||
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2014 Stretchr, Inc.
|
||||
Copyright (c) 2017-2018 objx contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
= vendor/github.com/stretchr/objx/LICENSE d023fd31d3ca39ec61eec65a91732735
|
@ -1,11 +0,0 @@
|
||||
apiVersion: storage.k8s.io/v1
|
||||
kind: StorageClass
|
||||
metadata:
|
||||
name: gp2
|
||||
annotations:
|
||||
storageclass.kubernetes.io/is-default-class: "true"
|
||||
labels:
|
||||
addonmanager.kubernetes.io/mode: EnsureExists
|
||||
provisioner: kubernetes.io/aws-ebs
|
||||
parameters:
|
||||
type: gp2
|
@ -25,7 +25,6 @@ package main
|
||||
import (
|
||||
// NOTE: Importing all in-tree cloud-providers is not required when
|
||||
// implementing an out-of-tree cloud-provider.
|
||||
_ "k8s.io/legacy-cloud-providers/aws"
|
||||
_ "k8s.io/legacy-cloud-providers/azure"
|
||||
_ "k8s.io/legacy-cloud-providers/gce"
|
||||
_ "k8s.io/legacy-cloud-providers/vsphere"
|
||||
|
@ -25,7 +25,6 @@ import (
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/awsebs"
|
||||
"k8s.io/kubernetes/pkg/volume/azure_file"
|
||||
"k8s.io/kubernetes/pkg/volume/azuredd"
|
||||
"k8s.io/kubernetes/pkg/volume/csimigration"
|
||||
@ -63,7 +62,6 @@ type pluginInfo struct {
|
||||
|
||||
func appendAttachableLegacyProviderVolumes(allPlugins []volume.VolumePlugin, featureGate featuregate.FeatureGate) ([]volume.VolumePlugin, error) {
|
||||
pluginMigrationStatus := make(map[string]pluginInfo)
|
||||
pluginMigrationStatus[plugins.AWSEBSInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAWS, pluginUnregisterFeature: features.InTreePluginAWSUnregister, pluginProbeFunction: awsebs.ProbeVolumePlugins}
|
||||
pluginMigrationStatus[plugins.GCEPDInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationGCE, pluginUnregisterFeature: features.InTreePluginGCEUnregister, pluginProbeFunction: gcepd.ProbeVolumePlugins}
|
||||
pluginMigrationStatus[plugins.AzureDiskInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAzureDisk, pluginUnregisterFeature: features.InTreePluginAzureDiskUnregister, pluginProbeFunction: azuredd.ProbeVolumePlugins}
|
||||
pluginMigrationStatus[plugins.VSphereInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationvSphere, pluginUnregisterFeature: features.InTreePluginvSphereUnregister, pluginProbeFunction: vsphere_volume.ProbeVolumePlugins}
|
||||
|
@ -30,7 +30,6 @@ import (
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/awsebs"
|
||||
"k8s.io/kubernetes/pkg/volume/azure_file"
|
||||
"k8s.io/kubernetes/pkg/volume/azuredd"
|
||||
"k8s.io/kubernetes/pkg/volume/csimigration"
|
||||
@ -69,7 +68,6 @@ type pluginInfo struct {
|
||||
|
||||
func appendLegacyProviderVolumes(allPlugins []volume.VolumePlugin, featureGate featuregate.FeatureGate) ([]volume.VolumePlugin, error) {
|
||||
pluginMigrationStatus := make(map[string]pluginInfo)
|
||||
pluginMigrationStatus[plugins.AWSEBSInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAWS, pluginUnregisterFeature: features.InTreePluginAWSUnregister, pluginProbeFunction: awsebs.ProbeVolumePlugins}
|
||||
pluginMigrationStatus[plugins.GCEPDInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationGCE, pluginUnregisterFeature: features.InTreePluginGCEUnregister, pluginProbeFunction: gcepd.ProbeVolumePlugins}
|
||||
pluginMigrationStatus[plugins.AzureDiskInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAzureDisk, pluginUnregisterFeature: features.InTreePluginAzureDiskUnregister, pluginProbeFunction: azuredd.ProbeVolumePlugins}
|
||||
pluginMigrationStatus[plugins.AzureFileInTreePluginName] = pluginInfo{pluginMigrationFeature: features.CSIMigrationAzureFile, pluginUnregisterFeature: features.InTreePluginAzureFileUnregister, pluginProbeFunction: azure_file.ProbeVolumePlugins}
|
||||
|
1
go.mod
1
go.mod
@ -213,7 +213,6 @@ require (
|
||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||
github.com/soheilhy/cmux v0.1.5 // indirect
|
||||
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 // indirect
|
||||
github.com/vishvananda/netns v0.0.2 // indirect
|
||||
|
@ -21,7 +21,6 @@ package cloudprovider
|
||||
|
||||
import (
|
||||
// Cloud providers
|
||||
_ "k8s.io/legacy-cloud-providers/aws"
|
||||
_ "k8s.io/legacy-cloud-providers/azure"
|
||||
_ "k8s.io/legacy-cloud-providers/gce"
|
||||
_ "k8s.io/legacy-cloud-providers/vsphere"
|
||||
|
@ -38,7 +38,6 @@ import (
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
controllervolumetesting "k8s.io/kubernetes/pkg/controller/volume/attachdetach/testing"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/awsebs"
|
||||
"k8s.io/kubernetes/pkg/volume/csimigration"
|
||||
"k8s.io/kubernetes/pkg/volume/util"
|
||||
"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
|
||||
@ -107,7 +106,6 @@ func TestSyncHandler(t *testing.T) {
|
||||
informerFactory.Core().V1().PersistentVolumeClaims().Informer().GetIndexer().Add(pvc)
|
||||
}
|
||||
allPlugins := []volume.VolumePlugin{}
|
||||
allPlugins = append(allPlugins, awsebs.ProbeVolumePlugins()...)
|
||||
translator := csitrans.New()
|
||||
expc, err := NewExpandController(fakeKubeClient, pvcInformer, pvInformer, nil, allPlugins, translator, csimigration.NewPluginManager(translator, utilfeature.DefaultFeatureGate), nil)
|
||||
if err != nil {
|
||||
|
@ -108,14 +108,6 @@ const (
|
||||
// Allow the usage of options to fine-tune the cpumanager policies.
|
||||
CPUManagerPolicyOptions featuregate.Feature = "CPUManagerPolicyOptions"
|
||||
|
||||
// owner: @leakingtapan
|
||||
// alpha: v1.14
|
||||
// beta: v1.17
|
||||
// GA: v1.25
|
||||
//
|
||||
// Enables the AWS EBS in-tree driver to AWS EBS CSI Driver migration feature.
|
||||
CSIMigrationAWS featuregate.Feature = "CSIMigrationAWS"
|
||||
|
||||
// owner: @andyzhangx
|
||||
// alpha: v1.15
|
||||
// beta: v1.19
|
||||
@ -885,8 +877,6 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
||||
|
||||
CPUManagerPolicyOptions: {Default: true, PreRelease: featuregate.Beta},
|
||||
|
||||
CSIMigrationAWS: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.27
|
||||
|
||||
CSIMigrationAzureDisk: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.26
|
||||
|
||||
CSIMigrationAzureFile: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.28
|
||||
|
@ -22,7 +22,6 @@ package cadvisor
|
||||
import (
|
||||
// Register cloud info providers.
|
||||
// TODO(#68522): Remove this in 1.20+ once the cAdvisor endpoints are removed.
|
||||
_ "github.com/google/cadvisor/utils/cloudinfo/aws"
|
||||
_ "github.com/google/cadvisor/utils/cloudinfo/azure"
|
||||
_ "github.com/google/cadvisor/utils/cloudinfo/gce"
|
||||
)
|
||||
|
@ -92,7 +92,6 @@ import (
|
||||
schedulerframework "k8s.io/kubernetes/pkg/scheduler/framework"
|
||||
"k8s.io/kubernetes/pkg/util/oom"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/awsebs"
|
||||
"k8s.io/kubernetes/pkg/volume/azuredd"
|
||||
"k8s.io/kubernetes/pkg/volume/gcepd"
|
||||
_ "k8s.io/kubernetes/pkg/volume/hostpath"
|
||||
@ -368,7 +367,6 @@ func newTestKubeletWithImageList(
|
||||
if initFakeVolumePlugin {
|
||||
allPlugins = append(allPlugins, plug)
|
||||
} else {
|
||||
allPlugins = append(allPlugins, awsebs.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, gcepd.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, azuredd.ProbeVolumePlugins()...)
|
||||
}
|
||||
|
@ -40,9 +40,7 @@ func isCSIMigrationOn(csiNode *storagev1.CSINode, pluginName string) bool {
|
||||
// along with the plugin-specific one
|
||||
switch pluginName {
|
||||
case csilibplugins.AWSEBSInTreePluginName:
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationAWS) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
case csilibplugins.PortworxVolumePluginName:
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationPortworx) {
|
||||
return false
|
||||
|
@ -1086,7 +1086,7 @@ func (a byPVCSize) Less(i, j int) bool {
|
||||
func isCSIMigrationOnForPlugin(pluginName string) bool {
|
||||
switch pluginName {
|
||||
case csiplugins.AWSEBSInTreePluginName:
|
||||
return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationAWS)
|
||||
return true
|
||||
case csiplugins.GCEPDInTreePluginName:
|
||||
return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationGCE)
|
||||
case csiplugins.AzureDiskInTreePluginName:
|
||||
|
@ -1,13 +0,0 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- gnufied
|
||||
- jingxu97
|
||||
- jsafrane
|
||||
- justinsb
|
||||
reviewers:
|
||||
- gnufied
|
||||
- jingxu97
|
||||
- justinsb
|
||||
- jsafrane
|
||||
- saad-ali
|
@ -1,312 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
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 awsebs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/mount-utils"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
volumeutil "k8s.io/kubernetes/pkg/volume/util"
|
||||
"k8s.io/legacy-cloud-providers/aws"
|
||||
)
|
||||
|
||||
type awsElasticBlockStoreAttacher struct {
|
||||
host volume.VolumeHost
|
||||
awsVolumes aws.Volumes
|
||||
}
|
||||
|
||||
var _ volume.Attacher = &awsElasticBlockStoreAttacher{}
|
||||
|
||||
var _ volume.DeviceMounter = &awsElasticBlockStoreAttacher{}
|
||||
|
||||
var _ volume.AttachableVolumePlugin = &awsElasticBlockStorePlugin{}
|
||||
|
||||
var _ volume.DeviceMountableVolumePlugin = &awsElasticBlockStorePlugin{}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) NewAttacher() (volume.Attacher, error) {
|
||||
awsCloud, err := getCloudProvider(plugin.host.GetCloudProvider())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &awsElasticBlockStoreAttacher{
|
||||
host: plugin.host,
|
||||
awsVolumes: awsCloud,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) NewDeviceMounter() (volume.DeviceMounter, error) {
|
||||
return plugin.NewAttacher()
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) {
|
||||
mounter := plugin.host.GetMounter(plugin.GetPluginName())
|
||||
return mounter.GetMountRefs(deviceMountPath)
|
||||
}
|
||||
|
||||
func (attacher *awsElasticBlockStoreAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) {
|
||||
volumeSource, _, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
volumeID := aws.KubernetesVolumeID(volumeSource.VolumeID)
|
||||
|
||||
// awsCloud.AttachDisk checks if disk is already attached to node and
|
||||
// succeeds in that case, so no need to do that separately.
|
||||
devicePath, err := attacher.awsVolumes.AttachDisk(volumeID, nodeName)
|
||||
if err != nil {
|
||||
klog.Errorf("Error attaching volume %q to node %q: %+v", volumeID, nodeName, err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return devicePath, nil
|
||||
}
|
||||
|
||||
func (attacher *awsElasticBlockStoreAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) {
|
||||
|
||||
klog.Warningf("Attacher.VolumesAreAttached called for node %q - Please use BulkVerifyVolumes for AWS", nodeName)
|
||||
volumeNodeMap := map[types.NodeName][]*volume.Spec{
|
||||
nodeName: specs,
|
||||
}
|
||||
nodeVolumesResult := make(map[*volume.Spec]bool)
|
||||
nodesVerificationMap, err := attacher.BulkVerifyVolumes(volumeNodeMap)
|
||||
if err != nil {
|
||||
klog.Errorf("Attacher.VolumesAreAttached - error checking volumes for node %q with %v", nodeName, err)
|
||||
return nodeVolumesResult, err
|
||||
}
|
||||
|
||||
if result, ok := nodesVerificationMap[nodeName]; ok {
|
||||
return result, nil
|
||||
}
|
||||
return nodeVolumesResult, nil
|
||||
}
|
||||
|
||||
func (attacher *awsElasticBlockStoreAttacher) BulkVerifyVolumes(volumesByNode map[types.NodeName][]*volume.Spec) (map[types.NodeName]map[*volume.Spec]bool, error) {
|
||||
volumesAttachedCheck := make(map[types.NodeName]map[*volume.Spec]bool)
|
||||
diskNamesByNode := make(map[types.NodeName][]aws.KubernetesVolumeID)
|
||||
volumeSpecMap := make(map[aws.KubernetesVolumeID]*volume.Spec)
|
||||
|
||||
for nodeName, volumeSpecs := range volumesByNode {
|
||||
for _, volumeSpec := range volumeSpecs {
|
||||
volumeSource, _, err := getVolumeSource(volumeSpec)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("Error getting volume (%q) source : %v", volumeSpec.Name(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
name := aws.KubernetesVolumeID(volumeSource.VolumeID)
|
||||
diskNamesByNode[nodeName] = append(diskNamesByNode[nodeName], name)
|
||||
|
||||
nodeDisk, nodeDiskExists := volumesAttachedCheck[nodeName]
|
||||
|
||||
if !nodeDiskExists {
|
||||
nodeDisk = make(map[*volume.Spec]bool)
|
||||
}
|
||||
nodeDisk[volumeSpec] = true
|
||||
volumeSpecMap[name] = volumeSpec
|
||||
volumesAttachedCheck[nodeName] = nodeDisk
|
||||
}
|
||||
}
|
||||
attachedResult, err := attacher.awsVolumes.DisksAreAttached(diskNamesByNode)
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("Error checking if volumes are attached to nodes err = %v", err)
|
||||
return volumesAttachedCheck, err
|
||||
}
|
||||
|
||||
for nodeName, nodeDisks := range attachedResult {
|
||||
for diskName, attached := range nodeDisks {
|
||||
if !attached {
|
||||
spec := volumeSpecMap[diskName]
|
||||
setNodeDisk(volumesAttachedCheck, spec, nodeName, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return volumesAttachedCheck, nil
|
||||
}
|
||||
|
||||
func (attacher *awsElasticBlockStoreAttacher) WaitForAttach(spec *volume.Spec, devicePath string, _ *v1.Pod, timeout time.Duration) (string, error) {
|
||||
volumeSource, _, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
volumeID := volumeSource.VolumeID
|
||||
partition := ""
|
||||
if volumeSource.Partition != 0 {
|
||||
partition = strconv.Itoa(int(volumeSource.Partition))
|
||||
}
|
||||
|
||||
if devicePath == "" {
|
||||
return "", fmt.Errorf("waitForAttach failed for AWS Volume %q: devicePath is empty", volumeID)
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(checkSleepDuration)
|
||||
defer ticker.Stop()
|
||||
timer := time.NewTimer(timeout)
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
klog.V(5).Infof("Checking AWS Volume %q is attached at devicePath %q.", volumeID, devicePath)
|
||||
path, err := attacher.getDevicePath(volumeSource.VolumeID, partition, devicePath)
|
||||
if err != nil {
|
||||
// Log error, if any, and continue checking periodically. See issue #11321
|
||||
klog.Errorf("Error verifying AWS Volume (%q) is attached at devicePath %q: %v", volumeID, devicePath, err)
|
||||
} else if path != "" {
|
||||
// A device path has successfully been created for the PD
|
||||
klog.Infof("Successfully found attached AWS Volume %q at path %q.", volumeID, path)
|
||||
return path, nil
|
||||
}
|
||||
case <-timer.C:
|
||||
return "", fmt.Errorf("could not find attached AWS Volume %q. Timeout waiting for mount paths to be created", volumeID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (attacher *awsElasticBlockStoreAttacher) GetDeviceMountPath(
|
||||
spec *volume.Spec) (string, error) {
|
||||
volumeSource, _, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return makeGlobalPDPath(attacher.host, aws.KubernetesVolumeID(volumeSource.VolumeID)), nil
|
||||
}
|
||||
|
||||
// FIXME: this method can be further pruned.
|
||||
func (attacher *awsElasticBlockStoreAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error {
|
||||
mounter := attacher.host.GetMounter(awsElasticBlockStorePluginName)
|
||||
notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
dir := deviceMountPath
|
||||
if runtime.GOOS == "windows" {
|
||||
// On Windows, FormatAndMount will mklink (create a symbolic link) at deviceMountPath later, so don't create a
|
||||
// directory at deviceMountPath now. Otherwise mklink will error: "Cannot create a file when that file already exists".
|
||||
// Instead, create the parent of deviceMountPath. For example when deviceMountPath is:
|
||||
// C:\var\lib\kubelet\plugins\kubernetes.io\aws-ebs\mounts\aws\us-west-2b\vol-xxx
|
||||
// create us-west-2b. FormatAndMount will make vol-xxx a symlink to the drive (e.g. D:\)
|
||||
dir = filepath.Dir(deviceMountPath)
|
||||
}
|
||||
if err := os.MkdirAll(dir, 0750); err != nil {
|
||||
return fmt.Errorf("making dir %s failed with %s", dir, err)
|
||||
}
|
||||
notMnt = true
|
||||
}
|
||||
|
||||
volumeSource, readOnly, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var options []string
|
||||
if readOnly {
|
||||
options = append(options, "ro")
|
||||
}
|
||||
if notMnt {
|
||||
diskMounter := volumeutil.NewSafeFormatAndMountFromHost(awsElasticBlockStorePluginName, attacher.host)
|
||||
mountOptions := volumeutil.MountOptionFromSpec(spec, options...)
|
||||
err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, mountOptions)
|
||||
if err != nil {
|
||||
os.Remove(deviceMountPath)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type awsElasticBlockStoreDetacher struct {
|
||||
mounter mount.Interface
|
||||
awsVolumes aws.Volumes
|
||||
}
|
||||
|
||||
var _ volume.Detacher = &awsElasticBlockStoreDetacher{}
|
||||
|
||||
var _ volume.DeviceUnmounter = &awsElasticBlockStoreDetacher{}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) NewDetacher() (volume.Detacher, error) {
|
||||
awsCloud, err := getCloudProvider(plugin.host.GetCloudProvider())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &awsElasticBlockStoreDetacher{
|
||||
mounter: plugin.host.GetMounter(plugin.GetPluginName()),
|
||||
awsVolumes: awsCloud,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) {
|
||||
return plugin.NewDetacher()
|
||||
}
|
||||
|
||||
func (detacher *awsElasticBlockStoreDetacher) Detach(volumeName string, nodeName types.NodeName) error {
|
||||
volumeID := aws.KubernetesVolumeID(path.Base(volumeName))
|
||||
|
||||
if _, err := detacher.awsVolumes.DetachDisk(volumeID, nodeName); err != nil {
|
||||
klog.Errorf("Error detaching volumeID %q: %v", volumeID, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (detacher *awsElasticBlockStoreDetacher) UnmountDevice(deviceMountPath string) error {
|
||||
return mount.CleanupMountPoint(deviceMountPath, detacher.mounter, false)
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) CanAttach(spec *volume.Spec) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) CanDeviceMount(spec *volume.Spec) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func setNodeDisk(
|
||||
nodeDiskMap map[types.NodeName]map[*volume.Spec]bool,
|
||||
volumeSpec *volume.Spec,
|
||||
nodeName types.NodeName,
|
||||
check bool) {
|
||||
|
||||
volumeMap := nodeDiskMap[nodeName]
|
||||
if volumeMap == nil {
|
||||
volumeMap = make(map[*volume.Spec]bool)
|
||||
nodeDiskMap[nodeName] = volumeMap
|
||||
}
|
||||
volumeMap[volumeSpec] = check
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
//go:build !providerless && linux
|
||||
// +build !providerless,linux
|
||||
|
||||
/*
|
||||
Copyright 2019 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 awsebs
|
||||
|
||||
import (
|
||||
"k8s.io/legacy-cloud-providers/aws"
|
||||
)
|
||||
|
||||
func (attacher *awsElasticBlockStoreAttacher) getDevicePath(volumeID, partition, devicePath string) (string, error) {
|
||||
devicePaths := getDiskByIDPaths(aws.KubernetesVolumeID(volumeID), partition, devicePath)
|
||||
return verifyDevicePath(devicePaths)
|
||||
}
|
@ -1,293 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
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 awsebs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
||||
"k8s.io/legacy-cloud-providers/aws"
|
||||
)
|
||||
|
||||
func TestGetVolumeName_Volume(t *testing.T) {
|
||||
plugin := newPlugin(t)
|
||||
name := aws.KubernetesVolumeID("my-aws-volume")
|
||||
spec := createVolSpec(name, false)
|
||||
|
||||
volumeName, err := plugin.GetVolumeName(spec)
|
||||
if err != nil {
|
||||
t.Errorf("GetVolumeName error: %v", err)
|
||||
}
|
||||
if volumeName != string(name) {
|
||||
t.Errorf("GetVolumeName error: expected %s, got %s", name, volumeName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVolumeName_PersistentVolume(t *testing.T) {
|
||||
plugin := newPlugin(t)
|
||||
name := aws.KubernetesVolumeID("my-aws-pv")
|
||||
spec := createPVSpec(name, true)
|
||||
|
||||
volumeName, err := plugin.GetVolumeName(spec)
|
||||
if err != nil {
|
||||
t.Errorf("GetVolumeName error: %v", err)
|
||||
}
|
||||
if volumeName != string(name) {
|
||||
t.Errorf("GetVolumeName error: expected %s, got %s", name, volumeName)
|
||||
}
|
||||
}
|
||||
|
||||
// One testcase for TestAttachDetach table test below
|
||||
type testcase struct {
|
||||
name string
|
||||
// For fake AWS:
|
||||
attach attachCall
|
||||
detach detachCall
|
||||
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 := aws.KubernetesVolumeID("disk")
|
||||
nodeName := types.NodeName("instance")
|
||||
spec := createVolSpec(diskName, false)
|
||||
attachError := errors.New("fake attach error")
|
||||
detachError := errors.New("fake detach error")
|
||||
tests := []testcase{
|
||||
// Successful Attach call
|
||||
{
|
||||
name: "Attach_Positive",
|
||||
attach: attachCall{diskName, nodeName, "/dev/sda", nil},
|
||||
test: func(testcase *testcase) (string, error) {
|
||||
attacher := newAttacher(testcase)
|
||||
return attacher.Attach(spec, nodeName)
|
||||
},
|
||||
expectedDevice: "/dev/sda",
|
||||
},
|
||||
|
||||
// Attach call fails
|
||||
{
|
||||
name: "Attach_Negative",
|
||||
attach: attachCall{diskName, nodeName, "", attachError},
|
||||
test: func(testcase *testcase) (string, error) {
|
||||
attacher := newAttacher(testcase)
|
||||
return attacher.Attach(spec, nodeName)
|
||||
},
|
||||
expectedError: attachError,
|
||||
},
|
||||
|
||||
// Detach succeeds
|
||||
{
|
||||
name: "Detach_Positive",
|
||||
detach: detachCall{diskName, nodeName, "/dev/sda", nil},
|
||||
test: func(testcase *testcase) (string, error) {
|
||||
detacher := newDetacher(testcase)
|
||||
mountPath := "/mnt/" + string(diskName)
|
||||
return "", detacher.Detach(mountPath, nodeName)
|
||||
},
|
||||
},
|
||||
// Detach fails
|
||||
{
|
||||
name: "Detach_Negative",
|
||||
detach: detachCall{diskName, nodeName, "", detachError},
|
||||
test: func(testcase *testcase) (string, error) {
|
||||
detacher := newDetacher(testcase)
|
||||
mountPath := "/mnt/" + string(diskName)
|
||||
return "", detacher.Detach(mountPath, nodeName)
|
||||
},
|
||||
expectedError: detachError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range tests {
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
testcase.t = t
|
||||
device, err := testcase.test(&testcase)
|
||||
if err != testcase.expectedError {
|
||||
t.Errorf("failed: expected err=%q, got %q", testcase.expectedError.Error(), err.Error())
|
||||
}
|
||||
if device != testcase.expectedDevice {
|
||||
t.Errorf("failed: expected device=%q, got %q", testcase.expectedDevice, device)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// newPlugin creates a new gcePersistentDiskPlugin with fake cloud, NewAttacher
|
||||
// and NewDetacher won't work.
|
||||
func newPlugin(t *testing.T) *awsElasticBlockStorePlugin {
|
||||
host := volumetest.NewFakeVolumeHost(t, os.TempDir(), nil, nil)
|
||||
plugins := ProbeVolumePlugins()
|
||||
plugin := plugins[0]
|
||||
plugin.Init(host)
|
||||
return plugin.(*awsElasticBlockStorePlugin)
|
||||
}
|
||||
|
||||
func newAttacher(testcase *testcase) *awsElasticBlockStoreAttacher {
|
||||
return &awsElasticBlockStoreAttacher{
|
||||
host: nil,
|
||||
awsVolumes: testcase,
|
||||
}
|
||||
}
|
||||
|
||||
func newDetacher(testcase *testcase) *awsElasticBlockStoreDetacher {
|
||||
return &awsElasticBlockStoreDetacher{
|
||||
awsVolumes: testcase,
|
||||
}
|
||||
}
|
||||
|
||||
func createVolSpec(name aws.KubernetesVolumeID, readOnly bool) *volume.Spec {
|
||||
return &volume.Spec{
|
||||
Volume: &v1.Volume{
|
||||
VolumeSource: v1.VolumeSource{
|
||||
AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: string(name),
|
||||
ReadOnly: readOnly,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createPVSpec(name aws.KubernetesVolumeID, readOnly bool) *volume.Spec {
|
||||
return &volume.Spec{
|
||||
PersistentVolume: &v1.PersistentVolume{
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: string(name),
|
||||
ReadOnly: readOnly,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Fake AWS implementation
|
||||
|
||||
type attachCall struct {
|
||||
diskName aws.KubernetesVolumeID
|
||||
nodeName types.NodeName
|
||||
retDeviceName string
|
||||
ret error
|
||||
}
|
||||
|
||||
type detachCall struct {
|
||||
diskName aws.KubernetesVolumeID
|
||||
nodeName types.NodeName
|
||||
retDeviceName string
|
||||
ret error
|
||||
}
|
||||
|
||||
func (testcase *testcase) AttachDisk(diskName aws.KubernetesVolumeID, nodeName types.NodeName) (string, 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 %q, %v", diskName, nodeName, expected.retDeviceName, expected.ret)
|
||||
|
||||
return expected.retDeviceName, expected.ret
|
||||
}
|
||||
|
||||
func (testcase *testcase) DetachDisk(diskName aws.KubernetesVolumeID, nodeName types.NodeName) (string, 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 %q, %v", diskName, nodeName, expected.retDeviceName, expected.ret)
|
||||
|
||||
return expected.retDeviceName, expected.ret
|
||||
}
|
||||
|
||||
func (testcase *testcase) DiskIsAttached(diskName aws.KubernetesVolumeID, nodeName types.NodeName) (bool, error) {
|
||||
// DetachDisk no longer relies on DiskIsAttached api call
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (testcase *testcase) DisksAreAttached(nodeDisks map[types.NodeName][]aws.KubernetesVolumeID) (map[types.NodeName]map[aws.KubernetesVolumeID]bool, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (testcase *testcase) CreateDisk(volumeOptions *aws.VolumeOptions) (volumeName aws.KubernetesVolumeID, err error) {
|
||||
return "", errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (testcase *testcase) DeleteDisk(volumeName aws.KubernetesVolumeID) (bool, error) {
|
||||
return false, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (testcase *testcase) GetVolumeLabels(volumeName aws.KubernetesVolumeID) (map[string]string, error) {
|
||||
return map[string]string{}, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (testcase *testcase) GetDiskPath(volumeName aws.KubernetesVolumeID) (string, error) {
|
||||
return "", errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (testcase *testcase) ResizeDisk(
|
||||
volumeName aws.KubernetesVolumeID,
|
||||
oldSize resource.Quantity,
|
||||
newSize resource.Quantity) (resource.Quantity, error) {
|
||||
return oldSize, errors.New("not implemented")
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
//go:build !providerless && !linux && !windows
|
||||
// +build !providerless,!linux,!windows
|
||||
|
||||
/*
|
||||
Copyright 2019 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 awsebs
|
||||
|
||||
import "errors"
|
||||
|
||||
func (attacher *awsElasticBlockStoreAttacher) getDevicePath(volumeID, partition, devicePath string) (string, error) {
|
||||
return "", errors.New("unsupported")
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
//go:build !providerless && windows
|
||||
// +build !providerless,windows
|
||||
|
||||
/*
|
||||
Copyright 2019 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 awsebs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ebsnvme-id is present on AWS-provided Windows Server AMIs
|
||||
// https://docs.aws.amazon.com/AWSEC2/latest/WindowsGuide/nvme-ebs-volumes.html#identify-nvme-ebs-device
|
||||
const ebsnvmeID = `C:\ProgramData\Amazon\Tools\ebsnvme-id.exe`
|
||||
|
||||
func (attacher *awsElasticBlockStoreAttacher) getDevicePath(volumeID, partition, devicePath string) (string, error) {
|
||||
return attacher.getDiskNumber(volumeID)
|
||||
}
|
||||
|
||||
// getDiskNumber gets the Windows disk number for a given volume ID. The disk number is needed for mounting.
|
||||
// TODO This only works for Nitro-based instances
|
||||
// TODO fallback to Get-Disk
|
||||
func (attacher *awsElasticBlockStoreAttacher) getDiskNumber(volumeID string) (string, error) {
|
||||
// Split the ID from zone: aws://us-west-2b/vol-06d0909eb358b05f9
|
||||
split := strings.Split(volumeID, "/")
|
||||
volumeID = split[len(split)-1]
|
||||
|
||||
exec := attacher.host.GetExec(awsElasticBlockStorePluginName)
|
||||
output, err := exec.Command(ebsnvmeID).CombinedOutput()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error calling ebsnvme-id.exe: %v", err)
|
||||
}
|
||||
// ebsnvme-id.exe will output a list of disks in this format:
|
||||
// ```
|
||||
// Disk Number: 1
|
||||
// Volume ID: vol-06d0909eb358b05f9
|
||||
// Device Name: /dev/xvdch
|
||||
// ```
|
||||
// Don't try to match devicePath against "Device Name" not only because volume ID is sufficient,
|
||||
// but because devicePath may change between Linux & Windows formats between WaitForAttach calls.
|
||||
// The first attach and mount, WaitForAttach gets devicePath as the Linux format /dev/xvdch. Then
|
||||
// WaitForAttach returns the disk number as the "right" devicePath and that is persisted to ASW.
|
||||
// In subsequent mounts of the same disk, WaitForAttach gets devicePath as the Windows format it
|
||||
// returned the first time.
|
||||
diskRe := regexp.MustCompile(
|
||||
`Disk Number: (\d+)\s*` +
|
||||
`Volume ID: ` + volumeID + `\s*`)
|
||||
matches := diskRe.FindStringSubmatch(string(output))
|
||||
if len(matches) != 2 {
|
||||
return "", fmt.Errorf("disk not found in ebsnvme-id.exe output: %q", string(output))
|
||||
}
|
||||
return matches[1], nil
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
//go:build !providerless && windows
|
||||
// +build !providerless,windows
|
||||
|
||||
/*
|
||||
Copyright 2022 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 awsebs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
||||
"k8s.io/utils/exec"
|
||||
exectest "k8s.io/utils/exec/testing"
|
||||
)
|
||||
|
||||
func TestGetDevicePath(t *testing.T) {
|
||||
testCases := []struct {
|
||||
commandOutput string
|
||||
commandError error
|
||||
expectedOutput string
|
||||
expectedError bool
|
||||
expectedErrMsg string
|
||||
}{
|
||||
{
|
||||
commandOutput: "",
|
||||
commandError: errors.New("expected error."),
|
||||
expectedError: true,
|
||||
expectedErrMsg: "error calling ebsnvme-id.exe: expected error.",
|
||||
},
|
||||
{
|
||||
commandOutput: "foolish output.",
|
||||
expectedError: true,
|
||||
expectedErrMsg: `disk not found in ebsnvme-id.exe output: "foolish output."`,
|
||||
},
|
||||
{
|
||||
commandOutput: "Disk Number: 42\nVolume ID: vol-fake-id",
|
||||
expectedOutput: "42",
|
||||
},
|
||||
}
|
||||
|
||||
fakeHost := volumetest.NewFakeVolumeHost(t, os.TempDir(), nil, nil)
|
||||
fakeExec := fakeHost.GetExec("").(*exectest.FakeExec)
|
||||
|
||||
// This will enable fakeExec to "run" commands.
|
||||
fakeExec.DisableScripts = false
|
||||
attacher := &awsElasticBlockStoreAttacher{
|
||||
host: fakeHost,
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
fakeCmd := &exectest.FakeCmd{
|
||||
CombinedOutputScript: []exectest.FakeAction{
|
||||
func() ([]byte, []byte, error) {
|
||||
return []byte(tc.commandOutput), []byte(""), tc.commandError
|
||||
},
|
||||
},
|
||||
}
|
||||
fakeExec.CommandScript = []exectest.FakeCommandAction{
|
||||
func(cmd string, args ...string) exec.Cmd {
|
||||
return fakeCmd
|
||||
},
|
||||
}
|
||||
fakeExec.CommandCalls = 0
|
||||
|
||||
fakeVolID := "aws://us-west-2b/vol-fake-id"
|
||||
devPath, err := attacher.getDevicePath(fakeVolID, "fake-partition", "fake-device-path")
|
||||
if tc.expectedError {
|
||||
if err == nil || err.Error() != tc.expectedErrMsg {
|
||||
t.Errorf("expected error message `%s` but got `%v`", tc.expectedErrMsg, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedOutput, devPath)
|
||||
}
|
||||
}
|
@ -1,556 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
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 awsebs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/mount-utils"
|
||||
|
||||
v1 "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/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util"
|
||||
"k8s.io/legacy-cloud-providers/aws"
|
||||
utilstrings "k8s.io/utils/strings"
|
||||
)
|
||||
|
||||
// ProbeVolumePlugins is the primary entrypoint for volume plugins.
|
||||
func ProbeVolumePlugins() []volume.VolumePlugin {
|
||||
return []volume.VolumePlugin{&awsElasticBlockStorePlugin{nil}}
|
||||
}
|
||||
|
||||
type awsElasticBlockStorePlugin struct {
|
||||
host volume.VolumeHost
|
||||
}
|
||||
|
||||
var _ volume.VolumePlugin = &awsElasticBlockStorePlugin{}
|
||||
var _ volume.PersistentVolumePlugin = &awsElasticBlockStorePlugin{}
|
||||
var _ volume.DeletableVolumePlugin = &awsElasticBlockStorePlugin{}
|
||||
var _ volume.ProvisionableVolumePlugin = &awsElasticBlockStorePlugin{}
|
||||
|
||||
const (
|
||||
awsElasticBlockStorePluginName = "kubernetes.io/aws-ebs"
|
||||
awsURLNamePrefix = "aws://"
|
||||
)
|
||||
|
||||
func getPath(uid types.UID, volName string, host volume.VolumeHost) string {
|
||||
return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(awsElasticBlockStorePluginName), volName)
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) Init(host volume.VolumeHost) error {
|
||||
plugin.host = host
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) GetPluginName() string {
|
||||
return awsElasticBlockStorePluginName
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) GetVolumeName(spec *volume.Spec) (string, error) {
|
||||
volumeSource, _, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return volumeSource.VolumeID, nil
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) CanSupport(spec *volume.Spec) bool {
|
||||
return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.AWSElasticBlockStore != nil) ||
|
||||
(spec.Volume != nil && spec.Volume.AWSElasticBlockStore != nil)
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) RequiresRemount(spec *volume.Spec) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) SupportsMountOption() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) SupportsBulkVolumeVerification() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) SupportsSELinuxContextMount(spec *volume.Spec) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) GetVolumeLimits() (map[string]int64, error) {
|
||||
volumeLimits := map[string]int64{
|
||||
util.EBSVolumeLimitKey: util.DefaultMaxEBSVolumes,
|
||||
}
|
||||
cloud := plugin.host.GetCloudProvider()
|
||||
|
||||
// if we can't fetch cloudprovider we return an error
|
||||
// hoping external CCM or admin can set it. Returning
|
||||
// default values from here will mean, no one can
|
||||
// override them.
|
||||
if cloud == nil {
|
||||
return nil, fmt.Errorf("no cloudprovider present")
|
||||
}
|
||||
|
||||
if cloud.ProviderName() != aws.ProviderName {
|
||||
return nil, fmt.Errorf("expected aws cloud, found %s", cloud.ProviderName())
|
||||
}
|
||||
|
||||
instances, ok := cloud.Instances()
|
||||
if !ok {
|
||||
klog.V(3).Infof("Failed to get instances from cloud provider")
|
||||
return volumeLimits, nil
|
||||
}
|
||||
|
||||
instanceType, err := instances.InstanceType(context.TODO(), plugin.host.GetNodeName())
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to get instance type from AWS cloud provider")
|
||||
return volumeLimits, nil
|
||||
}
|
||||
|
||||
if ok, _ := regexp.MatchString(util.EBSNitroLimitRegex, instanceType); ok {
|
||||
volumeLimits[util.EBSVolumeLimitKey] = util.DefaultMaxEBSNitroVolumeLimit
|
||||
}
|
||||
|
||||
return volumeLimits, nil
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) VolumeLimitKey(spec *volume.Spec) string {
|
||||
return util.EBSVolumeLimitKey
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
|
||||
return []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
}
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
|
||||
// Inject real implementations here, test through the internal function.
|
||||
return plugin.newMounterInternal(spec, pod.UID, &AWSDiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName()))
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager ebsManager, mounter mount.Interface) (volume.Mounter, error) {
|
||||
// EBSs used directly in a pod have a ReadOnly flag set by the pod author.
|
||||
// EBSs used as a PersistentVolume gets the ReadOnly flag indirectly through the persistent-claim volume used to mount the PV
|
||||
ebs, readOnly, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volumeID := aws.KubernetesVolumeID(ebs.VolumeID)
|
||||
fsType := ebs.FSType
|
||||
partition := ""
|
||||
if ebs.Partition != 0 {
|
||||
partition = strconv.Itoa(int(ebs.Partition))
|
||||
}
|
||||
|
||||
return &awsElasticBlockStoreMounter{
|
||||
awsElasticBlockStore: &awsElasticBlockStore{
|
||||
podUID: podUID,
|
||||
volName: spec.Name(),
|
||||
volumeID: volumeID,
|
||||
partition: partition,
|
||||
manager: manager,
|
||||
mounter: mounter,
|
||||
plugin: plugin,
|
||||
MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, spec.Name(), plugin.host)),
|
||||
},
|
||||
fsType: fsType,
|
||||
readOnly: readOnly,
|
||||
diskMounter: util.NewSafeFormatAndMountFromHost(plugin.GetPluginName(), plugin.host),
|
||||
mountOptions: util.MountOptionFromSpec(spec),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
|
||||
// Inject real implementations here, test through the internal function.
|
||||
return plugin.newUnmounterInternal(volName, podUID, &AWSDiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName()))
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) newUnmounterInternal(volName string, podUID types.UID, manager ebsManager, mounter mount.Interface) (volume.Unmounter, error) {
|
||||
return &awsElasticBlockStoreUnmounter{&awsElasticBlockStore{
|
||||
podUID: podUID,
|
||||
volName: volName,
|
||||
manager: manager,
|
||||
mounter: mounter,
|
||||
plugin: plugin,
|
||||
MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volName, plugin.host)),
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) {
|
||||
return plugin.newDeleterInternal(spec, &AWSDiskUtil{})
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) newDeleterInternal(spec *volume.Spec, manager ebsManager) (volume.Deleter, error) {
|
||||
if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.AWSElasticBlockStore == nil {
|
||||
klog.Errorf("spec.PersistentVolumeSource.AWSElasticBlockStore is nil")
|
||||
return nil, fmt.Errorf("spec.PersistentVolumeSource.AWSElasticBlockStore is nil")
|
||||
}
|
||||
return &awsElasticBlockStoreDeleter{
|
||||
awsElasticBlockStore: &awsElasticBlockStore{
|
||||
volName: spec.Name(),
|
||||
volumeID: aws.KubernetesVolumeID(spec.PersistentVolume.Spec.AWSElasticBlockStore.VolumeID),
|
||||
manager: manager,
|
||||
plugin: plugin,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
|
||||
return plugin.newProvisionerInternal(options, &AWSDiskUtil{})
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) newProvisionerInternal(options volume.VolumeOptions, manager ebsManager) (volume.Provisioner, error) {
|
||||
return &awsElasticBlockStoreProvisioner{
|
||||
awsElasticBlockStore: &awsElasticBlockStore{
|
||||
manager: manager,
|
||||
plugin: plugin,
|
||||
},
|
||||
options: options,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getVolumeSource(
|
||||
spec *volume.Spec) (*v1.AWSElasticBlockStoreVolumeSource, bool, error) {
|
||||
if spec.Volume != nil && spec.Volume.AWSElasticBlockStore != nil {
|
||||
return spec.Volume.AWSElasticBlockStore, spec.Volume.AWSElasticBlockStore.ReadOnly, nil
|
||||
} else if spec.PersistentVolume != nil &&
|
||||
spec.PersistentVolume.Spec.AWSElasticBlockStore != nil {
|
||||
return spec.PersistentVolume.Spec.AWSElasticBlockStore, spec.ReadOnly, nil
|
||||
}
|
||||
|
||||
return nil, false, fmt.Errorf("Spec does not reference an AWS EBS volume type")
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) ConstructVolumeSpec(volName, mountPath string) (volume.ReconstructedVolume, error) {
|
||||
mounter := plugin.host.GetMounter(plugin.GetPluginName())
|
||||
kvh, ok := plugin.host.(volume.KubeletVolumeHost)
|
||||
if !ok {
|
||||
return volume.ReconstructedVolume{}, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface")
|
||||
}
|
||||
hu := kvh.GetHostUtil()
|
||||
pluginMntDir := util.GetPluginMountDir(plugin.host, plugin.GetPluginName())
|
||||
volumeID, err := hu.GetDeviceNameFromMount(mounter, mountPath, pluginMntDir)
|
||||
if err != nil {
|
||||
return volume.ReconstructedVolume{}, err
|
||||
}
|
||||
volumeID, err = formatVolumeID(volumeID)
|
||||
if err != nil {
|
||||
return volume.ReconstructedVolume{}, fmt.Errorf("failed to get AWS volume id from mount path %q: %v", mountPath, err)
|
||||
}
|
||||
|
||||
file := v1.PersistentVolumeFilesystem
|
||||
return volume.ReconstructedVolume{
|
||||
Spec: newAWSVolumeSpec(volName, volumeID, file),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) RequiresFSResize() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) ExpandVolumeDevice(
|
||||
spec *volume.Spec,
|
||||
newSize resource.Quantity,
|
||||
oldSize resource.Quantity) (resource.Quantity, error) {
|
||||
var awsVolume aws.Volumes
|
||||
|
||||
awsVolume, err := getCloudProvider(plugin.host.GetCloudProvider())
|
||||
|
||||
if err != nil {
|
||||
return oldSize, err
|
||||
}
|
||||
// we don't expect to receive this call for non PVs
|
||||
rawVolumeName := spec.PersistentVolume.Spec.AWSElasticBlockStore.VolumeID
|
||||
volumeID := aws.KubernetesVolumeID(rawVolumeName)
|
||||
|
||||
if volumeID == "" {
|
||||
return oldSize, fmt.Errorf("EBS.ExpandVolumeDevice Invalid volume id for %s", spec.Name())
|
||||
}
|
||||
return awsVolume.ResizeDisk(volumeID, oldSize, newSize)
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) {
|
||||
fsVolume, err := util.CheckVolumeModeFilesystem(resizeOptions.VolumeSpec)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error checking VolumeMode: %v", err)
|
||||
}
|
||||
// if volume is not a fs file system, there is nothing for us to do here.
|
||||
if !fsVolume {
|
||||
return true, nil
|
||||
}
|
||||
_, err = util.GenericResizeFS(plugin.host, plugin.GetPluginName(), resizeOptions.DevicePath, resizeOptions.DeviceMountPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
var _ volume.NodeExpandableVolumePlugin = &awsElasticBlockStorePlugin{}
|
||||
var _ volume.ExpandableVolumePlugin = &awsElasticBlockStorePlugin{}
|
||||
var _ volume.VolumePluginWithAttachLimits = &awsElasticBlockStorePlugin{}
|
||||
|
||||
// Abstract interface to PD operations.
|
||||
type ebsManager interface {
|
||||
CreateVolume(provisioner *awsElasticBlockStoreProvisioner, node *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (volumeID aws.KubernetesVolumeID, volumeSizeGB int, labels map[string]string, fstype string, err error)
|
||||
// Deletes a volume
|
||||
DeleteVolume(deleter *awsElasticBlockStoreDeleter) error
|
||||
}
|
||||
|
||||
// awsElasticBlockStore volumes are disk resources provided by Amazon Web Services
|
||||
// that are attached to the kubelet's host machine and exposed to the pod.
|
||||
type awsElasticBlockStore struct {
|
||||
volName string
|
||||
podUID types.UID
|
||||
// Unique id of the PD, used to find the disk resource in the provider.
|
||||
volumeID aws.KubernetesVolumeID
|
||||
// Specifies the partition to mount
|
||||
partition string
|
||||
// Utility interface that provides API calls to the provider to attach/detach disks.
|
||||
manager ebsManager
|
||||
// Mounter interface that provides system calls to mount the global path to the pod local path.
|
||||
mounter mount.Interface
|
||||
plugin *awsElasticBlockStorePlugin
|
||||
volume.MetricsProvider
|
||||
}
|
||||
|
||||
type awsElasticBlockStoreMounter struct {
|
||||
*awsElasticBlockStore
|
||||
// Filesystem type, optional.
|
||||
fsType string
|
||||
// Specifies whether the disk will be attached as read-only.
|
||||
readOnly bool
|
||||
// diskMounter provides the interface that is used to mount the actual block device.
|
||||
diskMounter *mount.SafeFormatAndMount
|
||||
mountOptions []string
|
||||
}
|
||||
|
||||
var _ volume.Mounter = &awsElasticBlockStoreMounter{}
|
||||
|
||||
func (b *awsElasticBlockStoreMounter) GetAttributes() volume.Attributes {
|
||||
return volume.Attributes{
|
||||
ReadOnly: b.readOnly,
|
||||
Managed: !b.readOnly,
|
||||
SELinuxRelabel: true,
|
||||
}
|
||||
}
|
||||
|
||||
// SetUp attaches the disk and bind mounts to the volume path.
|
||||
func (b *awsElasticBlockStoreMounter) SetUp(mounterArgs volume.MounterArgs) error {
|
||||
return b.SetUpAt(b.GetPath(), mounterArgs)
|
||||
}
|
||||
|
||||
// SetUpAt attaches the disk and bind mounts to the volume path.
|
||||
func (b *awsElasticBlockStoreMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
|
||||
// TODO: handle failed mounts here.
|
||||
notMnt, err := b.mounter.IsLikelyNotMountPoint(dir)
|
||||
klog.V(4).Infof("PersistentDisk set up: %s %v %v", dir, !notMnt, err)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
klog.Errorf("cannot validate mount point: %s %v", dir, err)
|
||||
return err
|
||||
}
|
||||
if !notMnt {
|
||||
return nil
|
||||
}
|
||||
|
||||
globalPDPath := makeGlobalPDPath(b.plugin.host, b.volumeID)
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
// On Windows, Mount will create the parent of dir and mklink (create a symbolic link) at dir later, so don't create a
|
||||
// directory at dir now. Otherwise mklink will error: "Cannot create a file when that file already exists".
|
||||
// Instead, do nothing. For example when dir is:
|
||||
// C:\var\lib\kubelet\pods\xxx\volumes\kubernetes.io~aws-ebs\pvc-xxx
|
||||
// do nothing. Mount will make pvc-xxx a symlink to the global mount path (e.g. C:\var\lib\kubelet\plugins\kubernetes.io\aws-ebs\mounts\aws\us-west-2b\vol-xxx)
|
||||
if err := os.MkdirAll(dir, 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Perform a bind mount to the full path to allow duplicate mounts of the same PD.
|
||||
options := []string{"bind"}
|
||||
if b.readOnly {
|
||||
options = append(options, "ro")
|
||||
}
|
||||
mountOptions := util.JoinMountOptions(options, b.mountOptions)
|
||||
err = b.mounter.MountSensitiveWithoutSystemd(globalPDPath, dir, "", mountOptions, nil)
|
||||
if err != nil {
|
||||
notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
|
||||
if mntErr != nil {
|
||||
klog.Errorf("IsLikelyNotMountPoint check failed for %s: %v", dir, mntErr)
|
||||
return err
|
||||
}
|
||||
if !notMnt {
|
||||
if mntErr = b.mounter.Unmount(dir); mntErr != nil {
|
||||
klog.Errorf("failed to unmount %s: %v", dir, mntErr)
|
||||
return err
|
||||
}
|
||||
notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
|
||||
if mntErr != nil {
|
||||
klog.Errorf("IsLikelyNotMountPoint check failed for %s: %v", dir, mntErr)
|
||||
return err
|
||||
}
|
||||
if !notMnt {
|
||||
// This is very odd, we don't expect it. We'll try again next sync loop.
|
||||
klog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", dir)
|
||||
return err
|
||||
}
|
||||
}
|
||||
os.Remove(dir)
|
||||
klog.Errorf("Mount of disk %s failed: %v", dir, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !b.readOnly {
|
||||
volume.SetVolumeOwnership(b, mounterArgs.FsGroup, mounterArgs.FSGroupChangePolicy, util.FSGroupCompleteHook(b.plugin, nil))
|
||||
}
|
||||
|
||||
klog.V(4).Infof("Successfully mounted %s", dir)
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeGlobalPDPath(host volume.VolumeHost, volumeID aws.KubernetesVolumeID) string {
|
||||
// Clean up the URI to be more fs-friendly
|
||||
name := string(volumeID)
|
||||
name = strings.Replace(name, "://", "/", -1)
|
||||
return filepath.Join(host.GetPluginDir(awsElasticBlockStorePluginName), util.MountsInGlobalPDPath, name)
|
||||
}
|
||||
|
||||
func (ebs *awsElasticBlockStore) GetPath() string {
|
||||
return getPath(ebs.podUID, ebs.volName, ebs.plugin.host)
|
||||
}
|
||||
|
||||
type awsElasticBlockStoreUnmounter struct {
|
||||
*awsElasticBlockStore
|
||||
}
|
||||
|
||||
var _ volume.Unmounter = &awsElasticBlockStoreUnmounter{}
|
||||
|
||||
// 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 *awsElasticBlockStoreUnmounter) TearDown() error {
|
||||
return c.TearDownAt(c.GetPath())
|
||||
}
|
||||
|
||||
// Unmounts the bind mount
|
||||
func (c *awsElasticBlockStoreUnmounter) TearDownAt(dir string) error {
|
||||
return mount.CleanupMountPoint(dir, c.mounter, false)
|
||||
}
|
||||
|
||||
type awsElasticBlockStoreDeleter struct {
|
||||
*awsElasticBlockStore
|
||||
}
|
||||
|
||||
var _ volume.Deleter = &awsElasticBlockStoreDeleter{}
|
||||
|
||||
func (d *awsElasticBlockStoreDeleter) GetPath() string {
|
||||
return getPath(d.podUID, d.volName, d.plugin.host)
|
||||
}
|
||||
|
||||
func (d *awsElasticBlockStoreDeleter) Delete() error {
|
||||
return d.manager.DeleteVolume(d)
|
||||
}
|
||||
|
||||
type awsElasticBlockStoreProvisioner struct {
|
||||
*awsElasticBlockStore
|
||||
options volume.VolumeOptions
|
||||
}
|
||||
|
||||
var _ volume.Provisioner = &awsElasticBlockStoreProvisioner{}
|
||||
|
||||
func (c *awsElasticBlockStoreProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
||||
if !util.ContainsAllAccessModes(c.plugin.GetAccessModes(), c.options.PVC.Spec.AccessModes) {
|
||||
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", c.options.PVC.Spec.AccessModes, c.plugin.GetAccessModes())
|
||||
}
|
||||
|
||||
volumeID, sizeGB, labels, fstype, err := c.manager.CreateVolume(c, selectedNode, allowedTopologies)
|
||||
if err != nil {
|
||||
klog.Errorf("Provision failed: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fstype == "" {
|
||||
fstype = "ext4"
|
||||
}
|
||||
|
||||
volumeMode := c.options.PVC.Spec.VolumeMode
|
||||
if volumeMode != nil && *volumeMode == v1.PersistentVolumeBlock {
|
||||
// Block volumes should not have any FSType
|
||||
fstype = ""
|
||||
}
|
||||
|
||||
pv := &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: c.options.PVName,
|
||||
Labels: map[string]string{},
|
||||
Annotations: map[string]string{
|
||||
util.VolumeDynamicallyCreatedByKey: "aws-ebs-dynamic-provisioner",
|
||||
},
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeReclaimPolicy: c.options.PersistentVolumeReclaimPolicy,
|
||||
AccessModes: c.options.PVC.Spec.AccessModes,
|
||||
Capacity: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGB)),
|
||||
},
|
||||
VolumeMode: volumeMode,
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: string(volumeID),
|
||||
FSType: fstype,
|
||||
Partition: 0,
|
||||
ReadOnly: false,
|
||||
},
|
||||
},
|
||||
MountOptions: c.options.MountOptions,
|
||||
},
|
||||
}
|
||||
|
||||
if len(c.options.PVC.Spec.AccessModes) == 0 {
|
||||
pv.Spec.AccessModes = c.plugin.GetAccessModes()
|
||||
}
|
||||
|
||||
requirements := make([]v1.NodeSelectorRequirement, 0, len(labels))
|
||||
if len(labels) != 0 {
|
||||
if pv.Labels == nil {
|
||||
pv.Labels = make(map[string]string, len(labels))
|
||||
}
|
||||
for k, v := range labels {
|
||||
pv.Labels[k] = v
|
||||
requirements = append(requirements, v1.NodeSelectorRequirement{Key: k, Operator: v1.NodeSelectorOpIn, Values: []string{v}})
|
||||
}
|
||||
}
|
||||
|
||||
pv.Spec.NodeAffinity = new(v1.VolumeNodeAffinity)
|
||||
pv.Spec.NodeAffinity.Required = new(v1.NodeSelector)
|
||||
pv.Spec.NodeAffinity.Required.NodeSelectorTerms = make([]v1.NodeSelectorTerm, 1)
|
||||
pv.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions = requirements
|
||||
|
||||
return pv, nil
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
Copyright 2018 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 awsebs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
goruntime "runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/mount-utils"
|
||||
utilstrings "k8s.io/utils/strings"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
|
||||
"k8s.io/legacy-cloud-providers/aws"
|
||||
)
|
||||
|
||||
var _ volume.BlockVolumePlugin = &awsElasticBlockStorePlugin{}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) ConstructBlockVolumeSpec(podUID types.UID, volumeName, mapPath string) (*volume.Spec, error) {
|
||||
pluginDir := plugin.host.GetVolumeDevicePluginDir(awsElasticBlockStorePluginName)
|
||||
blkutil := volumepathhandler.NewBlockVolumePathHandler()
|
||||
globalMapPathUUID, err := blkutil.FindGlobalMapPathUUIDFromPod(pluginDir, mapPath, podUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
klog.V(5).Infof("globalMapPathUUID: %s", globalMapPathUUID)
|
||||
|
||||
globalMapPath := filepath.Dir(globalMapPathUUID)
|
||||
if len(globalMapPath) <= 1 {
|
||||
return nil, fmt.Errorf("failed to get volume plugin information from globalMapPathUUID: %v", globalMapPathUUID)
|
||||
}
|
||||
|
||||
return plugin.getVolumeSpecFromGlobalMapPath(volumeName, globalMapPath)
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) getVolumeSpecFromGlobalMapPath(volumeName string, globalMapPath string) (*volume.Spec, error) {
|
||||
// Get volume spec information from globalMapPath
|
||||
// globalMapPath example:
|
||||
// plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumeID}
|
||||
// plugins/kubernetes.io/aws-ebs/volumeDevices/vol-XXXXXX
|
||||
pluginDir := plugin.host.GetVolumeDevicePluginDir(awsElasticBlockStorePluginName)
|
||||
if !strings.HasPrefix(globalMapPath, pluginDir) {
|
||||
return nil, fmt.Errorf("volume symlink %s is not in global plugin directory", globalMapPath)
|
||||
}
|
||||
fullVolumeID := strings.TrimPrefix(globalMapPath, pluginDir) // /vol-XXXXXX
|
||||
fullVolumeID = strings.TrimLeft(fullVolumeID, "/") // vol-XXXXXX
|
||||
// Windows paths have \\ instead.
|
||||
if goruntime.GOOS == "windows" {
|
||||
fullVolumeID = strings.TrimLeft(fullVolumeID, "\\") // vol-XXXXXX
|
||||
}
|
||||
vID, err := formatVolumeID(fullVolumeID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get AWS volume id from map path %q: %v", globalMapPath, err)
|
||||
}
|
||||
|
||||
block := v1.PersistentVolumeBlock
|
||||
return newAWSVolumeSpec(volumeName, vID, block), nil
|
||||
}
|
||||
|
||||
// NewBlockVolumeMapper creates a new volume.BlockVolumeMapper from an API specification.
|
||||
func (plugin *awsElasticBlockStorePlugin) NewBlockVolumeMapper(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.BlockVolumeMapper, error) {
|
||||
// If this is called via GenerateUnmapDeviceFunc(), pod is nil.
|
||||
// Pass empty string as dummy uid since uid isn't used in the case.
|
||||
var uid types.UID
|
||||
if pod != nil {
|
||||
uid = pod.UID
|
||||
}
|
||||
|
||||
return plugin.newBlockVolumeMapperInternal(spec, uid, &AWSDiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName()))
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) newBlockVolumeMapperInternal(spec *volume.Spec, podUID types.UID, manager ebsManager, mounter mount.Interface) (volume.BlockVolumeMapper, error) {
|
||||
ebs, readOnly, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volumeID := aws.KubernetesVolumeID(ebs.VolumeID)
|
||||
partition := ""
|
||||
if ebs.Partition != 0 {
|
||||
partition = strconv.Itoa(int(ebs.Partition))
|
||||
}
|
||||
|
||||
mapper := &awsElasticBlockStoreMapper{
|
||||
awsElasticBlockStore: &awsElasticBlockStore{
|
||||
podUID: podUID,
|
||||
volName: spec.Name(),
|
||||
volumeID: volumeID,
|
||||
partition: partition,
|
||||
manager: manager,
|
||||
mounter: mounter,
|
||||
plugin: plugin,
|
||||
},
|
||||
readOnly: readOnly,
|
||||
}
|
||||
|
||||
blockPath, err := mapper.GetGlobalMapPath(spec)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get device path: %v", err)
|
||||
}
|
||||
mapper.MetricsProvider = volume.NewMetricsBlock(filepath.Join(blockPath, string(podUID)))
|
||||
|
||||
return mapper, nil
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) NewBlockVolumeUnmapper(volName string, podUID types.UID) (volume.BlockVolumeUnmapper, error) {
|
||||
return plugin.newUnmapperInternal(volName, podUID, &AWSDiskUtil{}, plugin.host.GetMounter(plugin.GetPluginName()))
|
||||
}
|
||||
|
||||
func (plugin *awsElasticBlockStorePlugin) newUnmapperInternal(volName string, podUID types.UID, manager ebsManager, mounter mount.Interface) (volume.BlockVolumeUnmapper, error) {
|
||||
return &awsElasticBlockStoreUnmapper{
|
||||
awsElasticBlockStore: &awsElasticBlockStore{
|
||||
podUID: podUID,
|
||||
volName: volName,
|
||||
manager: manager,
|
||||
mounter: mounter,
|
||||
plugin: plugin,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
type awsElasticBlockStoreUnmapper struct {
|
||||
*awsElasticBlockStore
|
||||
}
|
||||
|
||||
var _ volume.BlockVolumeUnmapper = &awsElasticBlockStoreUnmapper{}
|
||||
|
||||
type awsElasticBlockStoreMapper struct {
|
||||
*awsElasticBlockStore
|
||||
readOnly bool
|
||||
}
|
||||
|
||||
var _ volume.BlockVolumeMapper = &awsElasticBlockStoreMapper{}
|
||||
|
||||
// GetGlobalMapPath returns global map path and error
|
||||
// path: plugins/kubernetes.io/{PluginName}/volumeDevices/volumeID
|
||||
//
|
||||
// plugins/kubernetes.io/aws-ebs/volumeDevices/vol-XXXXXX
|
||||
func (ebs *awsElasticBlockStore) GetGlobalMapPath(spec *volume.Spec) (string, error) {
|
||||
volumeSource, _, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(ebs.plugin.host.GetVolumeDevicePluginDir(awsElasticBlockStorePluginName), string(volumeSource.VolumeID)), nil
|
||||
}
|
||||
|
||||
// GetPodDeviceMapPath returns pod device map path and volume name
|
||||
// path: pods/{podUid}/volumeDevices/kubernetes.io~aws
|
||||
func (ebs *awsElasticBlockStore) GetPodDeviceMapPath() (string, string) {
|
||||
name := awsElasticBlockStorePluginName
|
||||
return ebs.plugin.host.GetPodVolumeDeviceDir(ebs.podUID, utilstrings.EscapeQualifiedName(name)), ebs.volName
|
||||
}
|
||||
|
||||
// SupportsMetrics returns true for awsElasticBlockStore as it initializes the
|
||||
// MetricsProvider.
|
||||
func (ebs *awsElasticBlockStore) SupportsMetrics() bool {
|
||||
return true
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
Copyright 2018 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 awsebs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
||||
)
|
||||
|
||||
const (
|
||||
testVolName = "vol-1234"
|
||||
testPVName = "pv1"
|
||||
testGlobalPath = "plugins/kubernetes.io/aws-ebs/volumeDevices/vol-1234"
|
||||
testPodPath = "pods/poduid/volumeDevices/kubernetes.io~aws-ebs"
|
||||
)
|
||||
|
||||
func TestGetVolumeSpecFromGlobalMapPath(t *testing.T) {
|
||||
// make our test path for fake GlobalMapPath
|
||||
// /tmp symbolized our pluginDir
|
||||
// /tmp/testGlobalPathXXXXX/plugins/kubernetes.io/gce-pd/volumeDevices/pdVol1
|
||||
tmpVDir, err := utiltesting.MkTmpdir("awsBlockTest")
|
||||
if err != nil {
|
||||
t.Fatalf("can't make a temp dir: %v", err)
|
||||
}
|
||||
//deferred clean up
|
||||
defer os.RemoveAll(tmpVDir)
|
||||
|
||||
expectedGlobalPath := filepath.Join(tmpVDir, testGlobalPath)
|
||||
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpVDir, nil, nil))
|
||||
plug, err := plugMgr.FindMapperPluginByName(awsElasticBlockStorePluginName)
|
||||
if err != nil {
|
||||
os.RemoveAll(tmpVDir)
|
||||
t.Fatalf("Can't find the plugin by name: %q", awsElasticBlockStorePluginName)
|
||||
}
|
||||
|
||||
//Bad Path
|
||||
badspec, err := plug.(*awsElasticBlockStorePlugin).getVolumeSpecFromGlobalMapPath("", "")
|
||||
if badspec != nil || err == nil {
|
||||
t.Fatalf("Expected not to get spec from GlobalMapPath but did")
|
||||
}
|
||||
|
||||
// Good Path
|
||||
spec, err := plug.(*awsElasticBlockStorePlugin).getVolumeSpecFromGlobalMapPath("myVolume", expectedGlobalPath)
|
||||
if spec == nil || err != nil {
|
||||
t.Fatalf("Failed to get spec from GlobalMapPath: %v", err)
|
||||
}
|
||||
if spec.PersistentVolume.Name != "myVolume" {
|
||||
t.Errorf("Invalid PV name from GlobalMapPath spec: %s", spec.PersistentVolume.Name)
|
||||
}
|
||||
if spec.PersistentVolume.Spec.AWSElasticBlockStore.VolumeID != testVolName {
|
||||
t.Errorf("Invalid volumeID from GlobalMapPath spec: %s", spec.PersistentVolume.Spec.AWSElasticBlockStore.VolumeID)
|
||||
}
|
||||
block := v1.PersistentVolumeBlock
|
||||
specMode := spec.PersistentVolume.Spec.VolumeMode
|
||||
if specMode == nil {
|
||||
t.Errorf("Invalid volumeMode from GlobalMapPath spec: %v - %v", specMode, block)
|
||||
}
|
||||
if *specMode != block {
|
||||
t.Errorf("Invalid volumeMode from GlobalMapPath spec: %v - %v", *specMode, block)
|
||||
}
|
||||
}
|
||||
|
||||
func getTestVolume(readOnly bool, isBlock bool) *volume.Spec {
|
||||
pv := &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: testPVName,
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: testVolName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if isBlock {
|
||||
blockMode := v1.PersistentVolumeBlock
|
||||
pv.Spec.VolumeMode = &blockMode
|
||||
}
|
||||
return volume.NewSpecFromPersistentVolume(pv, readOnly)
|
||||
}
|
||||
|
||||
func TestGetPodAndPluginMapPaths(t *testing.T) {
|
||||
tmpVDir, err := utiltesting.MkTmpdir("awsBlockTest")
|
||||
if err != nil {
|
||||
t.Fatalf("can't make a temp dir: %v", err)
|
||||
}
|
||||
//deferred clean up
|
||||
defer os.RemoveAll(tmpVDir)
|
||||
|
||||
expectedGlobalPath := filepath.Join(tmpVDir, testGlobalPath)
|
||||
expectedPodPath := filepath.Join(tmpVDir, testPodPath)
|
||||
|
||||
spec := getTestVolume(false, true /*isBlock*/)
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpVDir, nil, nil))
|
||||
plug, err := plugMgr.FindMapperPluginByName(awsElasticBlockStorePluginName)
|
||||
if err != nil {
|
||||
os.RemoveAll(tmpVDir)
|
||||
t.Fatalf("Can't find the plugin by name: %q", awsElasticBlockStorePluginName)
|
||||
}
|
||||
if plug.GetPluginName() != awsElasticBlockStorePluginName {
|
||||
t.Fatalf("Wrong name: %s", plug.GetPluginName())
|
||||
}
|
||||
pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}}
|
||||
mapper, err := plug.NewBlockVolumeMapper(spec, pod, volume.VolumeOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to make a new Mounter: %v", err)
|
||||
}
|
||||
if mapper == nil {
|
||||
t.Fatalf("Got a nil Mounter")
|
||||
}
|
||||
|
||||
//GetGlobalMapPath
|
||||
gMapPath, err := mapper.GetGlobalMapPath(spec)
|
||||
if err != nil || len(gMapPath) == 0 {
|
||||
t.Fatalf("Invalid path from GlobalMapPath spec: %s", spec.PersistentVolume.Spec.GCEPersistentDisk.PDName)
|
||||
}
|
||||
if gMapPath != expectedGlobalPath {
|
||||
t.Fatalf("Failed to get GlobalMapPath: %s %s", gMapPath, expectedGlobalPath)
|
||||
}
|
||||
|
||||
//GetPodDeviceMapPath
|
||||
gDevicePath, gVolName := mapper.GetPodDeviceMapPath()
|
||||
if gDevicePath != expectedPodPath {
|
||||
t.Errorf("Got unexpected pod path: %s, expected %s", gDevicePath, expectedPodPath)
|
||||
}
|
||||
if gVolName != testPVName {
|
||||
t.Errorf("Got unexpected volNamne: %s, expected %s", gVolName, testPVName)
|
||||
}
|
||||
}
|
@ -1,477 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
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 awsebs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
goruntime "runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/mount-utils"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
||||
"k8s.io/legacy-cloud-providers/aws"
|
||||
)
|
||||
|
||||
func TestCanSupport(t *testing.T) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("awsebsTest")
|
||||
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.NewFakeKubeletVolumeHost(t, tmpDir, nil, nil))
|
||||
|
||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/aws-ebs")
|
||||
if err != nil {
|
||||
t.Fatal("Can't find the plugin by name")
|
||||
}
|
||||
if plug.GetPluginName() != "kubernetes.io/aws-ebs" {
|
||||
t.Errorf("Wrong name: %s", plug.GetPluginName())
|
||||
}
|
||||
if !plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{}}}}) {
|
||||
t.Errorf("Expected true")
|
||||
}
|
||||
if !plug.CanSupport(&volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{PersistentVolumeSource: v1.PersistentVolumeSource{AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{}}}}}) {
|
||||
t.Errorf("Expected true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAccessModes(t *testing.T) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("awsebsTest")
|
||||
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.NewFakeKubeletVolumeHost(t, tmpDir, nil, nil))
|
||||
|
||||
plug, err := plugMgr.FindPersistentPluginByName("kubernetes.io/aws-ebs")
|
||||
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 {
|
||||
}
|
||||
|
||||
// TODO(jonesdl) To fully test this, we could create a loopback device
|
||||
// and mount that instead.
|
||||
func (fake *fakePDManager) CreateVolume(c *awsElasticBlockStoreProvisioner, node *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (volumeID aws.KubernetesVolumeID, volumeSizeGB int, labels map[string]string, fstype string, err error) {
|
||||
labels = make(map[string]string)
|
||||
labels["fakepdmanager"] = "yes"
|
||||
return "test-aws-volume-name", 100, labels, "", nil
|
||||
}
|
||||
|
||||
func (fake *fakePDManager) DeleteVolume(cd *awsElasticBlockStoreDeleter) error {
|
||||
if cd.volumeID != "test-aws-volume-name" {
|
||||
return fmt.Errorf("Deleter got unexpected volume name: %s", cd.volumeID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestPlugin(t *testing.T) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("awsebsTest")
|
||||
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.NewFakeKubeletVolumeHost(t, tmpDir, nil, nil))
|
||||
|
||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/aws-ebs")
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
spec := &v1.Volume{
|
||||
Name: "vol1",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "pd",
|
||||
FSType: "ext4",
|
||||
},
|
||||
},
|
||||
}
|
||||
fakeManager := &fakePDManager{}
|
||||
fakeMounter := mount.NewFakeMounter(nil)
|
||||
mounter, err := plug.(*awsElasticBlockStorePlugin).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~aws-ebs/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)
|
||||
}
|
||||
|
||||
// On Windows, Mount will create the parent of dir and mklink (create a symbolic link) at the volume path later,
|
||||
// so mounter.SetUp will not create the directory. Otherwise mklink will error: "Cannot create a file when that file already exists".
|
||||
if goruntime.GOOS != "windows" {
|
||||
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.(*awsElasticBlockStorePlugin).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("100Mi", []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}),
|
||||
PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimDelete,
|
||||
}
|
||||
provisioner, err := plug.(*awsElasticBlockStorePlugin).newProvisionerInternal(options, &fakePDManager{})
|
||||
if err != nil {
|
||||
t.Errorf("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.AWSElasticBlockStore.VolumeID != "test-aws-volume-name" {
|
||||
t.Errorf("Provision() returned unexpected volume ID: %s", persistentSpec.Spec.PersistentVolumeSource.AWSElasticBlockStore.VolumeID)
|
||||
}
|
||||
cap := persistentSpec.Spec.Capacity[v1.ResourceStorage]
|
||||
size := cap.Value()
|
||||
if size != 100*1024*1024*1024 {
|
||||
t.Errorf("Provision() returned unexpected volume size: %v", size)
|
||||
}
|
||||
|
||||
if persistentSpec.Labels["fakepdmanager"] != "yes" {
|
||||
t.Errorf("Provision() returned unexpected labels: %v", persistentSpec.Labels)
|
||||
}
|
||||
|
||||
// check nodeaffinity members
|
||||
if persistentSpec.Spec.NodeAffinity == nil {
|
||||
t.Errorf("Provision() returned unexpected nil NodeAffinity")
|
||||
}
|
||||
|
||||
if persistentSpec.Spec.NodeAffinity.Required == nil {
|
||||
t.Errorf("Provision() returned unexpected nil NodeAffinity.Required")
|
||||
}
|
||||
|
||||
n := len(persistentSpec.Spec.NodeAffinity.Required.NodeSelectorTerms)
|
||||
if n != 1 {
|
||||
t.Errorf("Provision() returned unexpected number of NodeSelectorTerms %d. Expected %d", n, 1)
|
||||
}
|
||||
|
||||
n = len(persistentSpec.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions)
|
||||
if n != 1 {
|
||||
t.Errorf("Provision() returned unexpected number of MatchExpressions %d. Expected %d", n, 1)
|
||||
}
|
||||
|
||||
req := persistentSpec.Spec.NodeAffinity.Required.NodeSelectorTerms[0].MatchExpressions[0]
|
||||
if req.Key != "fakepdmanager" {
|
||||
t.Errorf("Provision() returned unexpected requirement key in NodeAffinity %v", req.Key)
|
||||
}
|
||||
|
||||
if req.Operator != v1.NodeSelectorOpIn {
|
||||
t.Errorf("Provision() returned unexpected requirement operator in NodeAffinity %v", req.Operator)
|
||||
}
|
||||
|
||||
if len(req.Values) != 1 || req.Values[0] != "yes" {
|
||||
t.Errorf("Provision() returned unexpected requirement value in NodeAffinity %v", req.Values)
|
||||
}
|
||||
// Test Deleter
|
||||
volSpec := &volume.Spec{
|
||||
PersistentVolume: persistentSpec,
|
||||
}
|
||||
deleter, err := plug.(*awsElasticBlockStorePlugin).newDeleterInternal(volSpec, &fakePDManager{})
|
||||
if err != nil {
|
||||
t.Errorf("Error creating new deleter:%v", err)
|
||||
}
|
||||
err = deleter.Delete()
|
||||
if err != nil {
|
||||
t.Errorf("Deleter() failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPersistentClaimReadOnlyFlag(t *testing.T) {
|
||||
pv := &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pvA",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{},
|
||||
},
|
||||
ClaimRef: &v1.ObjectReference{
|
||||
Name: "claimA",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
claim := &v1.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "claimA",
|
||||
Namespace: "nsA",
|
||||
},
|
||||
Spec: v1.PersistentVolumeClaimSpec{
|
||||
VolumeName: "pvA",
|
||||
},
|
||||
Status: v1.PersistentVolumeClaimStatus{
|
||||
Phase: v1.ClaimBound,
|
||||
},
|
||||
}
|
||||
|
||||
clientset := fake.NewSimpleClientset(pv, claim)
|
||||
|
||||
tmpDir, err := utiltesting.MkTmpdir("awsebsTest")
|
||||
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.NewFakeKubeletVolumeHost(t, tmpDir, clientset, nil))
|
||||
plug, _ := plugMgr.FindPluginByName(awsElasticBlockStorePluginName)
|
||||
|
||||
// readOnly bool is supplied by persistent-claim volume source when its mounter creates other volumes
|
||||
spec := volume.NewSpecFromPersistentVolume(pv, true)
|
||||
pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}}
|
||||
mounter, _ := plug.NewMounter(spec, pod, volume.VolumeOptions{})
|
||||
|
||||
if !mounter.GetAttributes().ReadOnly {
|
||||
t.Errorf("Expected true for mounter.IsReadOnly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMounterAndUnmounterTypeAssert(t *testing.T) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("awsebsTest")
|
||||
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.NewFakeKubeletVolumeHost(t, tmpDir, nil, nil))
|
||||
|
||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/aws-ebs")
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
spec := &v1.Volume{
|
||||
Name: "vol1",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "pd",
|
||||
FSType: "ext4",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mounter, err := plug.(*awsElasticBlockStorePlugin).newMounterInternal(volume.NewSpecFromVolume(spec), types.UID("poduid"), &fakePDManager{}, mount.NewFakeMounter(nil))
|
||||
if err != nil {
|
||||
t.Errorf("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.(*awsElasticBlockStorePlugin).newUnmounterInternal("vol1", types.UID("poduid"), &fakePDManager{}, mount.NewFakeMounter(nil))
|
||||
if err != nil {
|
||||
t.Errorf("Error creating new unmounter:%v", err)
|
||||
}
|
||||
if _, ok := unmounter.(volume.Mounter); ok {
|
||||
t.Errorf("Volume Unmounter can be type-assert to Mounter")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMountOptions(t *testing.T) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("aws-ebs")
|
||||
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.NewFakeKubeletVolumeHost(t, tmpDir, nil, nil))
|
||||
|
||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/aws-ebs")
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
|
||||
pv := &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pvA",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{},
|
||||
},
|
||||
ClaimRef: &v1.ObjectReference{
|
||||
Name: "claimA",
|
||||
},
|
||||
MountOptions: []string{"_netdev"},
|
||||
},
|
||||
}
|
||||
|
||||
fakeManager := &fakePDManager{}
|
||||
fakeMounter := mount.NewFakeMounter(nil)
|
||||
|
||||
mounter, err := plug.(*awsElasticBlockStorePlugin).newMounterInternal(volume.NewSpecFromPersistentVolume(pv, false), 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")
|
||||
}
|
||||
|
||||
if err := mounter.SetUp(volume.MounterArgs{}); err != nil {
|
||||
t.Errorf("Expected success, got: %v", err)
|
||||
}
|
||||
mountOptions := fakeMounter.MountPoints[0].Opts
|
||||
expectedMountOptions := []string{"_netdev", "bind"}
|
||||
if !reflect.DeepEqual(mountOptions, expectedMountOptions) {
|
||||
t.Errorf("Expected mount options to be %v got %v", expectedMountOptions, mountOptions)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCandidateZone(t *testing.T) {
|
||||
const testZone = "my-zone-1a"
|
||||
|
||||
// TODO: add test case for immediate bind volume when we have a good way to mock Cloud outside cloudprovider
|
||||
tests := []struct {
|
||||
cloud *aws.Cloud
|
||||
node *v1.Node
|
||||
expectedZones sets.String
|
||||
}{
|
||||
{
|
||||
cloud: nil,
|
||||
node: &v1.Node{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
v1.LabelTopologyZone: testZone,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedZones: sets.NewString(),
|
||||
},
|
||||
{
|
||||
cloud: nil,
|
||||
node: &v1.Node{},
|
||||
expectedZones: sets.NewString(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
zones, err := getCandidateZones(test.cloud, test.node)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.expectedZones, zones)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatVolumeID(t *testing.T) {
|
||||
tests := []struct {
|
||||
volumeIDFromPath string
|
||||
expectedVolumeID string
|
||||
}{
|
||||
{
|
||||
"aws/vol-1234",
|
||||
"aws:///vol-1234",
|
||||
},
|
||||
{
|
||||
"aws:/vol-1234",
|
||||
"aws:///vol-1234",
|
||||
},
|
||||
{
|
||||
"aws/us-east-1/vol-1234",
|
||||
"aws://us-east-1/vol-1234",
|
||||
},
|
||||
{
|
||||
"aws:/us-east-1/vol-1234",
|
||||
"aws://us-east-1/vol-1234",
|
||||
},
|
||||
{
|
||||
"vol-1234",
|
||||
"vol-1234",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
volumeID, err := formatVolumeID(test.volumeIDFromPath)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.expectedVolumeID, volumeID, test.volumeIDFromPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsupportedVolumeHost(t *testing.T) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("awsebsTest")
|
||||
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(t, tmpDir, nil, nil))
|
||||
|
||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/aws-ebs")
|
||||
if err != nil {
|
||||
t.Fatal("Can't find the plugin by name")
|
||||
}
|
||||
|
||||
_, err = plug.ConstructVolumeSpec("", "")
|
||||
if err == nil {
|
||||
t.Errorf("Expected failure constructing volume spec with unsupported VolumeHost")
|
||||
}
|
||||
}
|
@ -1,342 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
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 awsebs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/mount-utils"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
cloudprovider "k8s.io/cloud-provider"
|
||||
volumehelpers "k8s.io/cloud-provider/volume/helpers"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
volumeutil "k8s.io/kubernetes/pkg/volume/util"
|
||||
"k8s.io/legacy-cloud-providers/aws"
|
||||
)
|
||||
|
||||
const (
|
||||
diskPartitionSuffix = ""
|
||||
nvmeDiskPartitionSuffix = "p"
|
||||
checkSleepDuration = time.Second
|
||||
)
|
||||
|
||||
// AWSDiskUtil provides operations for EBS volume.
|
||||
type AWSDiskUtil struct{}
|
||||
|
||||
// DeleteVolume deletes an AWS EBS volume.
|
||||
func (util *AWSDiskUtil) DeleteVolume(d *awsElasticBlockStoreDeleter) error {
|
||||
cloud, err := getCloudProvider(d.awsElasticBlockStore.plugin.host.GetCloudProvider())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
deleted, err := cloud.DeleteDisk(d.volumeID)
|
||||
if err != nil {
|
||||
// AWS cloud provider returns volume.deletedVolumeInUseError when
|
||||
// necessary, no handling needed here.
|
||||
klog.V(2).Infof("Error deleting EBS Disk volume %s: %v", d.volumeID, err)
|
||||
return err
|
||||
}
|
||||
if deleted {
|
||||
klog.V(2).Infof("Successfully deleted EBS Disk volume %s", d.volumeID)
|
||||
} else {
|
||||
klog.V(2).Infof("Successfully deleted EBS Disk volume %s (actually already deleted)", d.volumeID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateVolume creates an AWS EBS volume.
|
||||
// Returns: volumeID, volumeSizeGB, labels, fstype, error
|
||||
func (util *AWSDiskUtil) CreateVolume(c *awsElasticBlockStoreProvisioner, node *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (aws.KubernetesVolumeID, int, map[string]string, string, error) {
|
||||
cloud, err := getCloudProvider(c.awsElasticBlockStore.plugin.host.GetCloudProvider())
|
||||
if err != nil {
|
||||
return "", 0, nil, "", err
|
||||
}
|
||||
|
||||
// AWS volumes don't have Name field, store the name in Name tag
|
||||
var tags map[string]string
|
||||
if c.options.CloudTags == nil {
|
||||
tags = make(map[string]string)
|
||||
} else {
|
||||
tags = *c.options.CloudTags
|
||||
}
|
||||
tags["Name"] = volumeutil.GenerateVolumeName(c.options.ClusterName, c.options.PVName, 255) // AWS tags can have 255 characters
|
||||
|
||||
capacity := c.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
|
||||
|
||||
zonesWithNodes, err := getCandidateZones(cloud, node)
|
||||
if err != nil {
|
||||
return "", 0, nil, "", fmt.Errorf("error finding candidate zone for pvc: %v", err)
|
||||
}
|
||||
|
||||
volumeOptions, err := populateVolumeOptions(c.plugin.GetPluginName(), c.options.PVC.Name, capacity, tags, c.options.Parameters, node, allowedTopologies, zonesWithNodes)
|
||||
if err != nil {
|
||||
klog.V(2).Infof("Error populating EBS options: %v", err)
|
||||
return "", 0, nil, "", err
|
||||
}
|
||||
|
||||
// TODO: implement PVC.Selector parsing
|
||||
if c.options.PVC.Spec.Selector != nil {
|
||||
return "", 0, nil, "", fmt.Errorf("claim.Spec.Selector is not supported for dynamic provisioning on AWS")
|
||||
}
|
||||
|
||||
name, err := cloud.CreateDisk(volumeOptions)
|
||||
if err != nil {
|
||||
klog.V(2).Infof("Error creating EBS Disk volume: %v", err)
|
||||
return "", 0, nil, "", err
|
||||
}
|
||||
klog.V(2).Infof("Successfully created EBS Disk volume %s", name)
|
||||
|
||||
labels, err := cloud.GetVolumeLabels(name)
|
||||
if err != nil {
|
||||
// We don't really want to leak the volume here...
|
||||
klog.Errorf("Error building labels for new EBS volume %q: %v", name, err)
|
||||
}
|
||||
|
||||
fstype := ""
|
||||
for k, v := range c.options.Parameters {
|
||||
if strings.ToLower(k) == volume.VolumeParameterFSType {
|
||||
fstype = v
|
||||
}
|
||||
}
|
||||
|
||||
return name, volumeOptions.CapacityGB, labels, fstype, nil
|
||||
}
|
||||
|
||||
// getCandidateZones finds possible zones that a volume can be created in
|
||||
func getCandidateZones(cloud *aws.Cloud, selectedNode *v1.Node) (sets.String, error) {
|
||||
if selectedNode != nil {
|
||||
// For topology aware volume provisioning, node is already selected so we use the zone from
|
||||
// selected node directly instead of candidate zones.
|
||||
// We can assume the information is always available as node controller shall maintain it.
|
||||
return sets.NewString(), nil
|
||||
}
|
||||
|
||||
// For non-topology-aware volumes (those that binds immediately), we fall back to original logic to query
|
||||
// cloud provider for possible zones
|
||||
return cloud.GetCandidateZonesForDynamicVolume()
|
||||
}
|
||||
|
||||
// returns volumeOptions for EBS based on storageclass parameters and node configuration
|
||||
func populateVolumeOptions(pluginName, pvcName string, capacityGB resource.Quantity, tags map[string]string, storageParams map[string]string, node *v1.Node, allowedTopologies []v1.TopologySelectorTerm, zonesWithNodes sets.String) (*aws.VolumeOptions, error) {
|
||||
requestGiB, err := volumehelpers.RoundUpToGiBInt(capacityGB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volumeOptions := &aws.VolumeOptions{
|
||||
CapacityGB: requestGiB,
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
// Apply Parameters (case-insensitive). We leave validation of
|
||||
// the values to the cloud provider.
|
||||
zonePresent := false
|
||||
zonesPresent := false
|
||||
var zone string
|
||||
var zones sets.String
|
||||
for k, v := range storageParams {
|
||||
switch strings.ToLower(k) {
|
||||
case "type":
|
||||
volumeOptions.VolumeType = v
|
||||
case "zone":
|
||||
zonePresent = true
|
||||
zone = v
|
||||
case "zones":
|
||||
zonesPresent = true
|
||||
zones, err = volumehelpers.ZonesToSet(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing zones %s, must be strings separated by commas: %v", zones, err)
|
||||
}
|
||||
case "iopspergb":
|
||||
volumeOptions.IOPSPerGB, err = strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid iopsPerGB value %q: %v", v, err)
|
||||
}
|
||||
case "encrypted":
|
||||
volumeOptions.Encrypted, err = strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid encrypted boolean value %q, must be true or false: %v", v, err)
|
||||
}
|
||||
case "kmskeyid":
|
||||
volumeOptions.KmsKeyID = v
|
||||
case volume.VolumeParameterFSType:
|
||||
// Do nothing but don't make this fail
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid option %q for volume plugin %s", k, pluginName)
|
||||
}
|
||||
}
|
||||
|
||||
volumeOptions.AvailabilityZone, err = volumehelpers.SelectZoneForVolume(zonePresent, zonesPresent, zone, zones, zonesWithNodes, node, allowedTopologies, pvcName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return volumeOptions, nil
|
||||
}
|
||||
|
||||
// Returns the first path that exists, or empty string if none exist.
|
||||
func verifyDevicePath(devicePaths []string) (string, error) {
|
||||
for _, path := range devicePaths {
|
||||
if pathExists, err := mount.PathExists(path); err != nil {
|
||||
return "", fmt.Errorf("error checking if path exists: %v", err)
|
||||
} else if pathExists {
|
||||
return path, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Returns list of all paths for given EBS mount
|
||||
// This is more interesting on GCE (where we are able to identify volumes under /dev/disk-by-id)
|
||||
// Here it is mostly about applying the partition path
|
||||
func getDiskByIDPaths(volumeID aws.KubernetesVolumeID, partition string, devicePath string) []string {
|
||||
var devicePaths []string
|
||||
if devicePath != "" {
|
||||
devicePaths = append(devicePaths, devicePath)
|
||||
}
|
||||
|
||||
if partition != "" {
|
||||
for i, path := range devicePaths {
|
||||
devicePaths[i] = path + diskPartitionSuffix + partition
|
||||
}
|
||||
}
|
||||
|
||||
// We need to find NVME volumes, which are mounted on a "random" nvme path ("/dev/nvme0n1"),
|
||||
// and we have to get the volume id from the nvme interface
|
||||
awsVolumeID, err := volumeID.MapToAWSVolumeID()
|
||||
if err != nil {
|
||||
klog.Warningf("Error mapping volume %q to AWS volume: %v", volumeID, err)
|
||||
} else {
|
||||
// This is the magic name on which AWS presents NVME devices under /dev/disk/by-id/
|
||||
// For example, vol-0fab1d5e3f72a5e23 creates a symlink at /dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_vol0fab1d5e3f72a5e23
|
||||
nvmeName := "nvme-Amazon_Elastic_Block_Store_" + strings.Replace(string(awsVolumeID), "-", "", -1)
|
||||
nvmePath, err := findNvmeVolume(nvmeName)
|
||||
if err != nil {
|
||||
klog.Warningf("Error looking for nvme volume %q: %v", volumeID, err)
|
||||
} else if nvmePath != "" {
|
||||
if partition != "" {
|
||||
nvmePath = nvmePath + nvmeDiskPartitionSuffix + partition
|
||||
}
|
||||
devicePaths = append(devicePaths, nvmePath)
|
||||
}
|
||||
}
|
||||
|
||||
return devicePaths
|
||||
}
|
||||
|
||||
// Returns cloud provider
|
||||
func getCloudProvider(cloudProvider cloudprovider.Interface) (*aws.Cloud, error) {
|
||||
awsCloudProvider, ok := cloudProvider.(*aws.Cloud)
|
||||
if !ok || awsCloudProvider == nil {
|
||||
return nil, fmt.Errorf("failed to get AWS Cloud Provider. GetCloudProvider returned %v instead", cloudProvider)
|
||||
}
|
||||
|
||||
return awsCloudProvider, nil
|
||||
}
|
||||
|
||||
// findNvmeVolume looks for the nvme volume with the specified name
|
||||
// It follows the symlink (if it exists) and returns the absolute path to the device
|
||||
func findNvmeVolume(findName string) (device string, err error) {
|
||||
p := filepath.Join("/dev/disk/by-id/", findName)
|
||||
stat, err := os.Lstat(p)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
klog.V(6).Infof("nvme path not found %q", p)
|
||||
return "", nil
|
||||
}
|
||||
return "", fmt.Errorf("error getting stat of %q: %v", p, err)
|
||||
}
|
||||
|
||||
if stat.Mode()&os.ModeSymlink != os.ModeSymlink {
|
||||
klog.Warningf("nvme file %q found, but was not a symlink", p)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Find the target, resolving to an absolute path
|
||||
// For example, /dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_vol0fab1d5e3f72a5e23 -> ../../nvme2n1
|
||||
resolved, err := filepath.EvalSymlinks(p)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error reading target of symlink %q: %v", p, err)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(resolved, "/dev") {
|
||||
return "", fmt.Errorf("resolved symlink for %q was unexpected: %q", p, resolved)
|
||||
}
|
||||
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
func formatVolumeID(volumeID string) (string, error) {
|
||||
// This is a workaround to fix the issue in converting aws volume id from globalPDPath and globalMapPath
|
||||
// There are three formats for AWSEBSVolumeSource.VolumeID and they are stored on disk in paths like so:
|
||||
// VolumeID mountPath mapPath
|
||||
// aws:///vol-1234 aws/vol-1234 aws:/vol-1234
|
||||
// aws://us-east-1/vol-1234 aws/us-east-1/vol-1234 aws:/us-east-1/vol-1234
|
||||
// vol-1234 vol-1234 vol-1234
|
||||
// This code is for converting volume ids from paths back to AWS style VolumeIDs
|
||||
sourceName := volumeID
|
||||
if strings.HasPrefix(volumeID, "aws/") || strings.HasPrefix(volumeID, "aws:/") {
|
||||
names := strings.Split(volumeID, "/")
|
||||
length := len(names)
|
||||
if length < 2 || length > 3 {
|
||||
return "", fmt.Errorf("invalid volume name format %q", volumeID)
|
||||
}
|
||||
volName := names[length-1]
|
||||
if !strings.HasPrefix(volName, "vol-") {
|
||||
return "", fmt.Errorf("invalid volume name format for AWS volume (%q)", volName)
|
||||
}
|
||||
if length == 2 {
|
||||
sourceName = awsURLNamePrefix + "" + "/" + volName // empty zone label
|
||||
}
|
||||
if length == 3 {
|
||||
sourceName = awsURLNamePrefix + names[1] + "/" + volName // names[1] is the zone label
|
||||
}
|
||||
klog.V(4).Infof("Convert aws volume name from %q to %q ", volumeID, sourceName)
|
||||
}
|
||||
return sourceName, nil
|
||||
}
|
||||
|
||||
func newAWSVolumeSpec(volumeName, volumeID string, mode v1.PersistentVolumeMode) *volume.Spec {
|
||||
awsVolume := &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: volumeName,
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: volumeID,
|
||||
},
|
||||
},
|
||||
VolumeMode: &mode,
|
||||
},
|
||||
}
|
||||
return volume.NewSpecFromPersistentVolume(awsVolume, false)
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 awsebs contains the internal representation of AWS Elastic
|
||||
// Block Store volumes.
|
||||
package awsebs // import "k8s.io/kubernetes/pkg/volume/awsebs"
|
@ -220,7 +220,7 @@ func (p *csiPlugin) Init(host volume.VolumeHost) error {
|
||||
return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationGCE)
|
||||
},
|
||||
csitranslationplugins.AWSEBSInTreePluginName: func() bool {
|
||||
return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationAWS)
|
||||
return true
|
||||
},
|
||||
csitranslationplugins.CinderInTreePluginName: func() bool {
|
||||
return true
|
||||
|
@ -89,7 +89,7 @@ func (pm PluginManager) IsMigrationEnabledForPlugin(pluginName string) bool {
|
||||
|
||||
switch pluginName {
|
||||
case csilibplugins.AWSEBSInTreePluginName:
|
||||
return pm.featureGate.Enabled(features.CSIMigrationAWS)
|
||||
return true
|
||||
case csilibplugins.GCEPDInTreePluginName:
|
||||
return pm.featureGate.Enabled(features.CSIMigrationGCE)
|
||||
case csilibplugins.AzureFileInTreePluginName:
|
||||
|
@ -74,27 +74,6 @@ func TestIsMigratable(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "AWS EBS PV with CSIMigrationAWS enabled",
|
||||
pluginFeature: features.CSIMigrationAWS,
|
||||
pluginFeatureEnabled: true,
|
||||
isMigratable: true,
|
||||
csiMigrationEnabled: true,
|
||||
spec: &volume.Spec{
|
||||
PersistentVolume: &v1.PersistentVolume{
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "vol01",
|
||||
FSType: "ext3",
|
||||
Partition: 1,
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
csiTranslator := csitrans.New()
|
||||
for _, test := range testCases {
|
||||
@ -146,28 +125,6 @@ func TestMigrationFeatureFlagStatus(t *testing.T) {
|
||||
csiMigrationResult: true,
|
||||
csiMigrationCompleteResult: true,
|
||||
},
|
||||
{
|
||||
name: "aws-ebs migration flag enabled and migration-complete flag disabled with CSI migration flag enabled",
|
||||
pluginName: "kubernetes.io/aws-ebs",
|
||||
pluginFeature: features.CSIMigrationAWS,
|
||||
pluginFeatureEnabled: true,
|
||||
csiMigrationEnabled: true,
|
||||
inTreePluginUnregister: features.InTreePluginAWSUnregister,
|
||||
inTreePluginUnregisterEnabled: false,
|
||||
csiMigrationResult: true,
|
||||
csiMigrationCompleteResult: false,
|
||||
},
|
||||
{
|
||||
name: "aws-ebs migration flag enabled and migration-complete flag enabled with CSI migration flag enabled",
|
||||
pluginName: "kubernetes.io/aws-ebs",
|
||||
pluginFeature: features.CSIMigrationAWS,
|
||||
pluginFeatureEnabled: true,
|
||||
csiMigrationEnabled: true,
|
||||
inTreePluginUnregister: features.InTreePluginAWSUnregister,
|
||||
inTreePluginUnregisterEnabled: true,
|
||||
csiMigrationResult: true,
|
||||
csiMigrationCompleteResult: true,
|
||||
},
|
||||
{
|
||||
name: "vsphere-volume migration flag enabled and migration-complete flag disabled with CSI migration flag enabled",
|
||||
pluginName: "kubernetes.io/vsphere-volume",
|
||||
|
@ -18,6 +18,9 @@ package operationexecutor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
@ -25,8 +28,6 @@ import (
|
||||
"k8s.io/client-go/tools/record"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
@ -37,7 +38,6 @@ import (
|
||||
"k8s.io/component-base/metrics/testutil"
|
||||
"k8s.io/csi-translation-lib/plugins"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/awsebs"
|
||||
csitesting "k8s.io/kubernetes/pkg/volume/csi/testing"
|
||||
"k8s.io/kubernetes/pkg/volume/gcepd"
|
||||
volumetesting "k8s.io/kubernetes/pkg/volume/testing"
|
||||
@ -65,15 +65,6 @@ func TestOperationGenerator_GenerateUnmapVolumeFunc_PluginName(t *testing.T) {
|
||||
}},
|
||||
probVolumePlugins: gcepd.ProbeVolumePlugins(),
|
||||
},
|
||||
{
|
||||
name: "aws ebs plugin: csi migration disabled",
|
||||
pluginName: plugins.AWSEBSInTreePluginName,
|
||||
pvSpec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{},
|
||||
}},
|
||||
probVolumePlugins: awsebs.ProbeVolumePlugins(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
|
@ -57,7 +57,6 @@ type persistentVolumeLabel struct {
|
||||
|
||||
mutex sync.Mutex
|
||||
cloudConfig []byte
|
||||
awsPVLabeler cloudprovider.PVLabeler
|
||||
gcePVLabeler cloudprovider.PVLabeler
|
||||
azurePVLabeler cloudprovider.PVLabeler
|
||||
vspherePVLabeler cloudprovider.PVLabeler
|
||||
@ -72,7 +71,7 @@ var _ kubeapiserveradmission.WantsCloudConfig = &persistentVolumeLabel{}
|
||||
// As a side effect, the cloud provider may block invalid or non-existent volumes.
|
||||
func newPersistentVolumeLabel() *persistentVolumeLabel {
|
||||
// DEPRECATED: in a future release, we will use mutating admission webhooks to apply PV labels.
|
||||
// Once the mutating admission webhook is used for AWS, Azure, and GCE,
|
||||
// Once the mutating admission webhook is used for Azure, and GCE,
|
||||
// this admission controller will be removed.
|
||||
klog.Warning("PersistentVolumeLabel admission controller is deprecated. " +
|
||||
"Please remove this controller from your configuration files and scripts.")
|
||||
@ -200,12 +199,6 @@ func (l *persistentVolumeLabel) findVolumeLabels(volume *api.PersistentVolume) (
|
||||
|
||||
// Either missing labels or we don't trust the user provided correct values.
|
||||
switch {
|
||||
case volume.Spec.AWSElasticBlockStore != nil:
|
||||
labels, err := l.findAWSEBSLabels(volume)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error querying AWS EBS volume %s: %v", volume.Spec.AWSElasticBlockStore.VolumeID, err)
|
||||
}
|
||||
return labels, nil
|
||||
case volume.Spec.GCEPersistentDisk != nil:
|
||||
labels, err := l.findGCEPDLabels(volume)
|
||||
if err != nil {
|
||||
@ -229,54 +222,6 @@ func (l *persistentVolumeLabel) findVolumeLabels(volume *api.PersistentVolume) (
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (l *persistentVolumeLabel) findAWSEBSLabels(volume *api.PersistentVolume) (map[string]string, error) {
|
||||
// Ignore any volumes that are being provisioned
|
||||
if volume.Spec.AWSElasticBlockStore.VolumeID == cloudvolume.ProvisionedVolumeName {
|
||||
return nil, nil
|
||||
}
|
||||
pvlabler, err := l.getAWSPVLabeler()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pvlabler == nil {
|
||||
return nil, fmt.Errorf("unable to build AWS cloud provider for EBS")
|
||||
}
|
||||
|
||||
pv := &v1.PersistentVolume{}
|
||||
err = k8s_api_v1.Convert_core_PersistentVolume_To_v1_PersistentVolume(volume, pv, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert PersistentVolume to core/v1: %q", err)
|
||||
}
|
||||
|
||||
return pvlabler.GetLabelsForVolume(context.TODO(), pv)
|
||||
}
|
||||
|
||||
// getAWSPVLabeler returns the AWS implementation of PVLabeler
|
||||
func (l *persistentVolumeLabel) getAWSPVLabeler() (cloudprovider.PVLabeler, error) {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
if l.awsPVLabeler == nil {
|
||||
var cloudConfigReader io.Reader
|
||||
if len(l.cloudConfig) > 0 {
|
||||
cloudConfigReader = bytes.NewReader(l.cloudConfig)
|
||||
}
|
||||
|
||||
cloudProvider, err := cloudprovider.GetCloudProvider("aws", cloudConfigReader)
|
||||
if err != nil || cloudProvider == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
awsPVLabeler, ok := cloudProvider.(cloudprovider.PVLabeler)
|
||||
if !ok {
|
||||
return nil, errors.New("AWS cloud provider does not implement PV labeling")
|
||||
}
|
||||
|
||||
l.awsPVLabeler = awsPVLabeler
|
||||
}
|
||||
return l.awsPVLabeler, nil
|
||||
}
|
||||
|
||||
func (l *persistentVolumeLabel) findGCEPDLabels(volume *api.PersistentVolume) (map[string]string, error) {
|
||||
// Ignore any volumes that are being provisioned
|
||||
if volume.Spec.GCEPersistentDisk.PDName == cloudvolume.ProvisionedVolumeName {
|
||||
|
@ -97,26 +97,26 @@ func Test_PVLAdmission(t *testing.T) {
|
||||
handler: newPersistentVolumeLabel(),
|
||||
pvlabeler: mockVolumeFailure(errors.New("invalid volume")),
|
||||
preAdmissionPV: &api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "vSpherePV", Namespace: "myns"},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "123",
|
||||
VsphereVolume: &api.VsphereVirtualDiskVolumeSource{
|
||||
VolumePath: "123",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
postAdmissionPV: &api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "vSpherePV", Namespace: "myns"},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "123",
|
||||
VsphereVolume: &api.VsphereVirtualDiskVolumeSource{
|
||||
VolumePath: "123",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: apierrors.NewForbidden(schema.ParseGroupResource("persistentvolumes"), "awsebs", errors.New("error querying AWS EBS volume 123: invalid volume")),
|
||||
err: apierrors.NewForbidden(schema.ParseGroupResource("persistentvolumes"), "vSpherePV", errors.New("error querying vSphere Volume 123: invalid volume")),
|
||||
},
|
||||
{
|
||||
name: "cloud provider returns no labels",
|
||||
@ -170,69 +170,6 @@ func Test_PVLAdmission(t *testing.T) {
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "AWS EBS PV labeled correctly",
|
||||
handler: newPersistentVolumeLabel(),
|
||||
pvlabeler: mockVolumeLabels(map[string]string{
|
||||
"a": "1",
|
||||
"b": "2",
|
||||
v1.LabelTopologyZone: "1__2__3",
|
||||
}),
|
||||
preAdmissionPV: &api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "awsebs", Namespace: "myns"},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "123",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
postAdmissionPV: &api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "awsebs",
|
||||
Namespace: "myns",
|
||||
Labels: map[string]string{
|
||||
"a": "1",
|
||||
"b": "2",
|
||||
v1.LabelTopologyZone: "1__2__3",
|
||||
},
|
||||
},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "123",
|
||||
},
|
||||
},
|
||||
NodeAffinity: &api.VolumeNodeAffinity{
|
||||
Required: &api.NodeSelector{
|
||||
NodeSelectorTerms: []api.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []api.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "a",
|
||||
Operator: api.NodeSelectorOpIn,
|
||||
Values: []string{"1"},
|
||||
},
|
||||
{
|
||||
Key: "b",
|
||||
Operator: api.NodeSelectorOpIn,
|
||||
Values: []string{"2"},
|
||||
},
|
||||
{
|
||||
Key: v1.LabelTopologyZone,
|
||||
Operator: api.NodeSelectorOpIn,
|
||||
Values: []string{"1", "2", "3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "existing Beta labels from dynamic provisioning are not changed",
|
||||
handler: newPersistentVolumeLabel(),
|
||||
@ -378,7 +315,7 @@ func Test_PVLAdmission(t *testing.T) {
|
||||
}),
|
||||
preAdmissionPV: &api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "awsebs", Namespace: "myns",
|
||||
Name: "vSpherePV", Namespace: "myns",
|
||||
Labels: map[string]string{
|
||||
v1.LabelTopologyZone: "existingDomain",
|
||||
v1.LabelTopologyRegion: "existingRegion",
|
||||
@ -386,15 +323,15 @@ func Test_PVLAdmission(t *testing.T) {
|
||||
},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "123",
|
||||
VsphereVolume: &api.VsphereVirtualDiskVolumeSource{
|
||||
VolumePath: "123",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
postAdmissionPV: &api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "awsebs",
|
||||
Name: "vSpherePV",
|
||||
Namespace: "myns",
|
||||
Labels: map[string]string{
|
||||
v1.LabelTopologyZone: "domain1",
|
||||
@ -403,8 +340,8 @@ func Test_PVLAdmission(t *testing.T) {
|
||||
},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "123",
|
||||
VsphereVolume: &api.VsphereVirtualDiskVolumeSource{
|
||||
VolumePath: "123",
|
||||
},
|
||||
},
|
||||
NodeAffinity: &api.VolumeNodeAffinity{
|
||||
@ -561,150 +498,7 @@ func Test_PVLAdmission(t *testing.T) {
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "AWS EBS PV overrides user applied labels",
|
||||
handler: newPersistentVolumeLabel(),
|
||||
pvlabeler: mockVolumeLabels(map[string]string{
|
||||
"a": "1",
|
||||
"b": "2",
|
||||
v1.LabelTopologyZone: "1__2__3",
|
||||
}),
|
||||
preAdmissionPV: &api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "awsebs",
|
||||
Namespace: "myns",
|
||||
Labels: map[string]string{
|
||||
"a": "not1",
|
||||
},
|
||||
},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "123",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
postAdmissionPV: &api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "awsebs",
|
||||
Namespace: "myns",
|
||||
Labels: map[string]string{
|
||||
"a": "1",
|
||||
"b": "2",
|
||||
v1.LabelTopologyZone: "1__2__3",
|
||||
},
|
||||
},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "123",
|
||||
},
|
||||
},
|
||||
NodeAffinity: &api.VolumeNodeAffinity{
|
||||
Required: &api.NodeSelector{
|
||||
NodeSelectorTerms: []api.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []api.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "a",
|
||||
Operator: api.NodeSelectorOpIn,
|
||||
Values: []string{"1"},
|
||||
},
|
||||
{
|
||||
Key: "b",
|
||||
Operator: api.NodeSelectorOpIn,
|
||||
Values: []string{"2"},
|
||||
},
|
||||
{
|
||||
Key: v1.LabelTopologyZone,
|
||||
Operator: api.NodeSelectorOpIn,
|
||||
Values: []string{"1", "2", "3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "AWS EBS PV conflicting affinity rules left in-tact",
|
||||
handler: newPersistentVolumeLabel(),
|
||||
pvlabeler: mockVolumeLabels(map[string]string{
|
||||
"a": "1",
|
||||
"b": "2",
|
||||
"c": "3",
|
||||
}),
|
||||
preAdmissionPV: &api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "awsebs",
|
||||
Namespace: "myns",
|
||||
Labels: map[string]string{
|
||||
"c": "3",
|
||||
},
|
||||
},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "123",
|
||||
},
|
||||
},
|
||||
NodeAffinity: &api.VolumeNodeAffinity{
|
||||
Required: &api.NodeSelector{
|
||||
NodeSelectorTerms: []api.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []api.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "c",
|
||||
Operator: api.NodeSelectorOpIn,
|
||||
Values: []string{"3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
postAdmissionPV: &api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "awsebs",
|
||||
Namespace: "myns",
|
||||
Labels: map[string]string{
|
||||
"a": "1",
|
||||
"b": "2",
|
||||
"c": "3",
|
||||
},
|
||||
},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "123",
|
||||
},
|
||||
},
|
||||
NodeAffinity: &api.VolumeNodeAffinity{
|
||||
Required: &api.NodeSelector{
|
||||
NodeSelectorTerms: []api.NodeSelectorTerm{
|
||||
{
|
||||
MatchExpressions: []api.NodeSelectorRequirement{
|
||||
{
|
||||
Key: "c",
|
||||
Operator: api.NodeSelectorOpIn,
|
||||
Values: []string{"3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "AWS EBS PV non-conflicting affinity rules added",
|
||||
name: "vSphere PV non-conflicting affinity rules added",
|
||||
handler: newPersistentVolumeLabel(),
|
||||
pvlabeler: mockVolumeLabels(map[string]string{
|
||||
"d": "1",
|
||||
@ -713,7 +507,7 @@ func Test_PVLAdmission(t *testing.T) {
|
||||
}),
|
||||
preAdmissionPV: &api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "awsebs",
|
||||
Name: "vSpherePV",
|
||||
Namespace: "myns",
|
||||
Labels: map[string]string{
|
||||
"a": "1",
|
||||
@ -723,8 +517,8 @@ func Test_PVLAdmission(t *testing.T) {
|
||||
},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "123",
|
||||
VsphereVolume: &api.VsphereVirtualDiskVolumeSource{
|
||||
VolumePath: "123",
|
||||
},
|
||||
},
|
||||
NodeAffinity: &api.VolumeNodeAffinity{
|
||||
@ -756,7 +550,7 @@ func Test_PVLAdmission(t *testing.T) {
|
||||
},
|
||||
postAdmissionPV: &api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "awsebs",
|
||||
Name: "vSpherePV",
|
||||
Namespace: "myns",
|
||||
Labels: map[string]string{
|
||||
"a": "1",
|
||||
@ -769,8 +563,8 @@ func Test_PVLAdmission(t *testing.T) {
|
||||
},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "123",
|
||||
VsphereVolume: &api.VsphereVirtualDiskVolumeSource{
|
||||
VolumePath: "123",
|
||||
},
|
||||
},
|
||||
NodeAffinity: &api.VolumeNodeAffinity{
|
||||
@ -914,7 +708,6 @@ func Test_PVLAdmission(t *testing.T) {
|
||||
// provider does not reduce test coverage but it does simplify/clean up the tests here because
|
||||
// the provider is then decided based on the type of PV (EBS, GCEPD, Azure Disk, etc)
|
||||
func setPVLabeler(handler *persistentVolumeLabel, pvlabeler cloudprovider.PVLabeler) {
|
||||
handler.awsPVLabeler = pvlabeler
|
||||
handler.gcePVLabeler = pvlabeler
|
||||
handler.azurePVLabeler = pvlabeler
|
||||
handler.vspherePVLabeler = pvlabeler
|
||||
|
@ -1673,8 +1673,6 @@ rules:
|
||||
branch: master
|
||||
- repository: cloud-provider
|
||||
branch: master
|
||||
- repository: csi-translation-lib
|
||||
branch: master
|
||||
- repository: apiserver
|
||||
branch: master
|
||||
- repository: component-base
|
||||
|
@ -40,7 +40,6 @@ var (
|
||||
external bool
|
||||
detail string
|
||||
}{
|
||||
{"aws", false, "The AWS provider is deprecated and will be removed in a future release. Please use https://github.com/kubernetes/cloud-provider-aws"},
|
||||
{"azure", false, "The Azure provider is deprecated and will be removed in a future release. Please use https://github.com/kubernetes-sigs/cloud-provider-azure"},
|
||||
{"gce", false, "The GCE provider is deprecated and will be removed in a future release. Please use https://github.com/kubernetes/cloud-provider-gcp"},
|
||||
{"vsphere", false, "The vSphere provider is deprecated and will be removed in a future release. Please use https://github.com/kubernetes/cloud-provider-vsphere"},
|
||||
|
@ -1,18 +0,0 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
# We are no longer accepting features into k8s.io/legacy-cloud-providers.
|
||||
# Any kind/feature PRs must be approved by SIG Cloud Provider going forward.
|
||||
|
||||
emeritus_approvers:
|
||||
- justinsb
|
||||
- gnufied
|
||||
- jsafrane
|
||||
- micahhausler
|
||||
- m00nf1sh
|
||||
- zmerlynn
|
||||
- mcrute
|
||||
reviewers:
|
||||
- gnufied
|
||||
- jsafrane
|
||||
- justinsb
|
||||
- nckturner
|
||||
- m00nf1sh
|
File diff suppressed because it is too large
Load Diff
@ -1,62 +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 aws
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
)
|
||||
|
||||
const (
|
||||
invalidateCredsCacheAfter = 1 * time.Second
|
||||
)
|
||||
|
||||
// assumeRoleProviderWithRateLimiting makes sure we call the underlying provider only
|
||||
// once after `invalidateCredsCacheAfter` period
|
||||
type assumeRoleProviderWithRateLimiting struct {
|
||||
provider credentials.Provider
|
||||
invalidateCredsCacheAfter time.Duration
|
||||
sync.RWMutex
|
||||
lastError error
|
||||
lastValue credentials.Value
|
||||
lastRetrieveTime time.Time
|
||||
}
|
||||
|
||||
func assumeRoleProvider(provider credentials.Provider) credentials.Provider {
|
||||
return &assumeRoleProviderWithRateLimiting{provider: provider,
|
||||
invalidateCredsCacheAfter: invalidateCredsCacheAfter}
|
||||
}
|
||||
|
||||
func (l *assumeRoleProviderWithRateLimiting) Retrieve() (credentials.Value, error) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
if time.Since(l.lastRetrieveTime) < l.invalidateCredsCacheAfter {
|
||||
if l.lastError != nil {
|
||||
return credentials.Value{}, l.lastError
|
||||
}
|
||||
return l.lastValue, nil
|
||||
}
|
||||
l.lastValue, l.lastError = l.provider.Retrieve()
|
||||
l.lastRetrieveTime = time.Now()
|
||||
return l.lastValue, l.lastError
|
||||
}
|
||||
|
||||
func (l *assumeRoleProviderWithRateLimiting) IsExpired() bool {
|
||||
return l.provider.IsExpired()
|
||||
}
|
@ -1,133 +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 aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
)
|
||||
|
||||
func Test_assumeRoleProviderWithRateLimiting_Retrieve(t *testing.T) {
|
||||
type fields struct {
|
||||
provider credentials.Provider
|
||||
invalidateCredsCacheAfter time.Duration
|
||||
RWMutex sync.RWMutex
|
||||
lastError error
|
||||
lastValue credentials.Value
|
||||
lastRetrieveTime time.Time
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want credentials.Value
|
||||
wantProviderCalled bool
|
||||
sleepBeforeCallingProvider time.Duration
|
||||
wantErr bool
|
||||
wantErrString string
|
||||
}{{
|
||||
name: "Call assume role provider and verify access ID returned",
|
||||
fields: fields{provider: &fakeAssumeRoleProvider{accesskeyID: "fakeID"}},
|
||||
want: credentials.Value{AccessKeyID: "fakeID"},
|
||||
wantProviderCalled: true,
|
||||
}, {
|
||||
name: "Immediate call to assume role API, shouldn't call the underlying provider and return the last value",
|
||||
fields: fields{
|
||||
provider: &fakeAssumeRoleProvider{accesskeyID: "fakeID"},
|
||||
invalidateCredsCacheAfter: 100 * time.Millisecond,
|
||||
lastValue: credentials.Value{AccessKeyID: "fakeID1"},
|
||||
lastRetrieveTime: time.Now(),
|
||||
},
|
||||
want: credentials.Value{AccessKeyID: "fakeID1"},
|
||||
wantProviderCalled: false,
|
||||
sleepBeforeCallingProvider: 10 * time.Millisecond,
|
||||
}, {
|
||||
name: "Assume role provider returns an error when trying to assume a role",
|
||||
fields: fields{
|
||||
provider: &fakeAssumeRoleProvider{err: fmt.Errorf("can't assume fake role")},
|
||||
invalidateCredsCacheAfter: 10 * time.Millisecond,
|
||||
lastRetrieveTime: time.Now(),
|
||||
},
|
||||
wantProviderCalled: true,
|
||||
wantErr: true,
|
||||
wantErrString: "can't assume fake role",
|
||||
sleepBeforeCallingProvider: 15 * time.Millisecond,
|
||||
}, {
|
||||
name: "Immediate call to assume role API, shouldn't call the underlying provider and return the last error value",
|
||||
fields: fields{
|
||||
provider: &fakeAssumeRoleProvider{},
|
||||
invalidateCredsCacheAfter: 100 * time.Millisecond,
|
||||
lastRetrieveTime: time.Now(),
|
||||
},
|
||||
want: credentials.Value{},
|
||||
wantProviderCalled: false,
|
||||
wantErr: true,
|
||||
wantErrString: "can't assume fake role",
|
||||
}, {
|
||||
name: "Delayed call to assume role API, should call the underlying provider",
|
||||
fields: fields{
|
||||
provider: &fakeAssumeRoleProvider{accesskeyID: "fakeID2"},
|
||||
invalidateCredsCacheAfter: 20 * time.Millisecond,
|
||||
lastRetrieveTime: time.Now(),
|
||||
},
|
||||
want: credentials.Value{AccessKeyID: "fakeID2"},
|
||||
wantProviderCalled: true,
|
||||
sleepBeforeCallingProvider: 25 * time.Millisecond,
|
||||
}}
|
||||
//nolint:govet // ignore copying of sync.RWMutex, it is empty
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &assumeRoleProviderWithRateLimiting{
|
||||
provider: tt.fields.provider,
|
||||
invalidateCredsCacheAfter: tt.fields.invalidateCredsCacheAfter,
|
||||
lastError: tt.fields.lastError,
|
||||
lastValue: tt.fields.lastValue,
|
||||
lastRetrieveTime: tt.fields.lastRetrieveTime,
|
||||
}
|
||||
time.Sleep(tt.sleepBeforeCallingProvider)
|
||||
got, err := l.Retrieve()
|
||||
if (err != nil) != tt.wantErr && (tt.wantErr && reflect.DeepEqual(err, tt.wantErrString)) {
|
||||
t.Errorf("assumeRoleProviderWithRateLimiting.Retrieve() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("assumeRoleProviderWithRateLimiting.Retrieve() got = %v, want %v", got, tt.want)
|
||||
return
|
||||
}
|
||||
if tt.wantProviderCalled != tt.fields.provider.(*fakeAssumeRoleProvider).providerCalled {
|
||||
t.Errorf("provider called %v, want %v", tt.fields.provider.(*fakeAssumeRoleProvider).providerCalled, tt.wantProviderCalled)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type fakeAssumeRoleProvider struct {
|
||||
accesskeyID string
|
||||
err error
|
||||
providerCalled bool
|
||||
}
|
||||
|
||||
func (f *fakeAssumeRoleProvider) Retrieve() (credentials.Value, error) {
|
||||
f.providerCalled = true
|
||||
return credentials.Value{AccessKeyID: f.accesskeyID}, f.err
|
||||
}
|
||||
|
||||
func (f *fakeAssumeRoleProvider) IsExpired() bool { return true }
|
@ -1,688 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
Copyright 2017 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 aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/autoscaling"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/elb"
|
||||
"github.com/aws/aws-sdk-go/service/elbv2"
|
||||
"github.com/aws/aws-sdk-go/service/kms"
|
||||
_ "github.com/stretchr/testify/mock"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// FakeAWSServices is an fake AWS session used for testing
|
||||
type FakeAWSServices struct {
|
||||
region string
|
||||
instances []*ec2.Instance
|
||||
selfInstance *ec2.Instance
|
||||
networkInterfacesMacs []string
|
||||
networkInterfacesPrivateIPs [][]string
|
||||
networkInterfacesVpcIDs []string
|
||||
|
||||
ec2 FakeEC2
|
||||
elb ELB
|
||||
elbv2 ELBV2
|
||||
asg *FakeASG
|
||||
metadata *FakeMetadata
|
||||
kms *FakeKMS
|
||||
}
|
||||
|
||||
// NewFakeAWSServices creates a new FakeAWSServices
|
||||
func NewFakeAWSServices(clusterID string) *FakeAWSServices {
|
||||
s := &FakeAWSServices{}
|
||||
s.region = "us-east-1"
|
||||
s.ec2 = &FakeEC2Impl{aws: s}
|
||||
s.elb = &FakeELB{aws: s}
|
||||
s.elbv2 = &FakeELBV2{aws: s}
|
||||
s.asg = &FakeASG{aws: s}
|
||||
s.metadata = &FakeMetadata{aws: s}
|
||||
s.kms = &FakeKMS{aws: s}
|
||||
|
||||
s.networkInterfacesMacs = []string{"aa:bb:cc:dd:ee:00", "aa:bb:cc:dd:ee:01"}
|
||||
s.networkInterfacesVpcIDs = []string{"vpc-mac0", "vpc-mac1"}
|
||||
|
||||
selfInstance := &ec2.Instance{}
|
||||
selfInstance.InstanceId = aws.String("i-self")
|
||||
selfInstance.Placement = &ec2.Placement{
|
||||
AvailabilityZone: aws.String("us-east-1a"),
|
||||
}
|
||||
selfInstance.PrivateDnsName = aws.String("ip-172-20-0-100.ec2.internal")
|
||||
selfInstance.PrivateIpAddress = aws.String("192.168.0.1")
|
||||
selfInstance.PublicIpAddress = aws.String("1.2.3.4")
|
||||
s.selfInstance = selfInstance
|
||||
s.instances = []*ec2.Instance{selfInstance}
|
||||
|
||||
var tag ec2.Tag
|
||||
tag.Key = aws.String(TagNameKubernetesClusterLegacy)
|
||||
tag.Value = aws.String(clusterID)
|
||||
selfInstance.Tags = []*ec2.Tag{&tag}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// WithAz sets the ec2 placement availability zone
|
||||
func (s *FakeAWSServices) WithAz(az string) *FakeAWSServices {
|
||||
if s.selfInstance.Placement == nil {
|
||||
s.selfInstance.Placement = &ec2.Placement{}
|
||||
}
|
||||
s.selfInstance.Placement.AvailabilityZone = aws.String(az)
|
||||
return s
|
||||
}
|
||||
|
||||
// Compute returns a fake EC2 client
|
||||
func (s *FakeAWSServices) Compute(region string) (EC2, error) {
|
||||
return s.ec2, nil
|
||||
}
|
||||
|
||||
// LoadBalancing returns a fake ELB client
|
||||
func (s *FakeAWSServices) LoadBalancing(region string) (ELB, error) {
|
||||
return s.elb, nil
|
||||
}
|
||||
|
||||
// LoadBalancingV2 returns a fake ELBV2 client
|
||||
func (s *FakeAWSServices) LoadBalancingV2(region string) (ELBV2, error) {
|
||||
return s.elbv2, nil
|
||||
}
|
||||
|
||||
// Autoscaling returns a fake ASG client
|
||||
func (s *FakeAWSServices) Autoscaling(region string) (ASG, error) {
|
||||
return s.asg, nil
|
||||
}
|
||||
|
||||
// Metadata returns a fake EC2Metadata client
|
||||
func (s *FakeAWSServices) Metadata() (EC2Metadata, error) {
|
||||
return s.metadata, nil
|
||||
}
|
||||
|
||||
// KeyManagement returns a fake KMS client
|
||||
func (s *FakeAWSServices) KeyManagement(region string) (KMS, error) {
|
||||
return s.kms, nil
|
||||
}
|
||||
|
||||
// FakeEC2 is a fake EC2 client used for testing
|
||||
type FakeEC2 interface {
|
||||
EC2
|
||||
CreateSubnet(*ec2.Subnet) (*ec2.CreateSubnetOutput, error)
|
||||
RemoveSubnets()
|
||||
CreateRouteTable(*ec2.RouteTable) (*ec2.CreateRouteTableOutput, error)
|
||||
RemoveRouteTables()
|
||||
}
|
||||
|
||||
// FakeEC2Impl is an implementation of the FakeEC2 interface used for testing
|
||||
type FakeEC2Impl struct {
|
||||
aws *FakeAWSServices
|
||||
Subnets []*ec2.Subnet
|
||||
DescribeSubnetsInput *ec2.DescribeSubnetsInput
|
||||
RouteTables []*ec2.RouteTable
|
||||
DescribeRouteTablesInput *ec2.DescribeRouteTablesInput
|
||||
}
|
||||
|
||||
// DescribeInstances returns fake instance descriptions
|
||||
func (ec2i *FakeEC2Impl) DescribeInstances(request *ec2.DescribeInstancesInput) ([]*ec2.Instance, error) {
|
||||
matches := []*ec2.Instance{}
|
||||
for _, instance := range ec2i.aws.instances {
|
||||
if request.InstanceIds != nil {
|
||||
if instance.InstanceId == nil {
|
||||
klog.Warning("Instance with no instance id: ", instance)
|
||||
continue
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, instanceID := range request.InstanceIds {
|
||||
if *instanceID == *instance.InstanceId {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if request.Filters != nil {
|
||||
allMatch := true
|
||||
for _, filter := range request.Filters {
|
||||
if !instanceMatchesFilter(instance, filter) {
|
||||
allMatch = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !allMatch {
|
||||
continue
|
||||
}
|
||||
}
|
||||
matches = append(matches, instance)
|
||||
}
|
||||
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
// AttachVolume is not implemented but is required for interface conformance
|
||||
func (ec2i *FakeEC2Impl) AttachVolume(request *ec2.AttachVolumeInput) (resp *ec2.VolumeAttachment, err error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DetachVolume is not implemented but is required for interface conformance
|
||||
func (ec2i *FakeEC2Impl) DetachVolume(request *ec2.DetachVolumeInput) (resp *ec2.VolumeAttachment, err error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DescribeVolumes is not implemented but is required for interface conformance
|
||||
func (ec2i *FakeEC2Impl) DescribeVolumes(request *ec2.DescribeVolumesInput) ([]*ec2.Volume, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// CreateVolume is not implemented but is required for interface conformance
|
||||
func (ec2i *FakeEC2Impl) CreateVolume(request *ec2.CreateVolumeInput) (resp *ec2.Volume, err error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DeleteVolume is not implemented but is required for interface conformance
|
||||
func (ec2i *FakeEC2Impl) DeleteVolume(request *ec2.DeleteVolumeInput) (resp *ec2.DeleteVolumeOutput, err error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DescribeSecurityGroups is not implemented but is required for interface
|
||||
// conformance
|
||||
func (ec2i *FakeEC2Impl) DescribeSecurityGroups(request *ec2.DescribeSecurityGroupsInput) ([]*ec2.SecurityGroup, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// CreateSecurityGroup is not implemented but is required for interface
|
||||
// conformance
|
||||
func (ec2i *FakeEC2Impl) CreateSecurityGroup(*ec2.CreateSecurityGroupInput) (*ec2.CreateSecurityGroupOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DeleteSecurityGroup is not implemented but is required for interface
|
||||
// conformance
|
||||
func (ec2i *FakeEC2Impl) DeleteSecurityGroup(*ec2.DeleteSecurityGroupInput) (*ec2.DeleteSecurityGroupOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// AuthorizeSecurityGroupIngress is not implemented but is required for
|
||||
// interface conformance
|
||||
func (ec2i *FakeEC2Impl) AuthorizeSecurityGroupIngress(*ec2.AuthorizeSecurityGroupIngressInput) (*ec2.AuthorizeSecurityGroupIngressOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// RevokeSecurityGroupIngress is not implemented but is required for interface
|
||||
// conformance
|
||||
func (ec2i *FakeEC2Impl) RevokeSecurityGroupIngress(*ec2.RevokeSecurityGroupIngressInput) (*ec2.RevokeSecurityGroupIngressOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DescribeVolumeModifications is not implemented but is required for interface
|
||||
// conformance
|
||||
func (ec2i *FakeEC2Impl) DescribeVolumeModifications(*ec2.DescribeVolumesModificationsInput) ([]*ec2.VolumeModification, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// ModifyVolume is not implemented but is required for interface conformance
|
||||
func (ec2i *FakeEC2Impl) ModifyVolume(*ec2.ModifyVolumeInput) (*ec2.ModifyVolumeOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// CreateSubnet creates fake subnets
|
||||
func (ec2i *FakeEC2Impl) CreateSubnet(request *ec2.Subnet) (*ec2.CreateSubnetOutput, error) {
|
||||
ec2i.Subnets = append(ec2i.Subnets, request)
|
||||
response := &ec2.CreateSubnetOutput{
|
||||
Subnet: request,
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// DescribeSubnets returns fake subnet descriptions
|
||||
func (ec2i *FakeEC2Impl) DescribeSubnets(request *ec2.DescribeSubnetsInput) ([]*ec2.Subnet, error) {
|
||||
ec2i.DescribeSubnetsInput = request
|
||||
return ec2i.Subnets, nil
|
||||
}
|
||||
|
||||
// RemoveSubnets clears subnets on client
|
||||
func (ec2i *FakeEC2Impl) RemoveSubnets() {
|
||||
ec2i.Subnets = ec2i.Subnets[:0]
|
||||
}
|
||||
|
||||
// CreateTags is not implemented but is required for interface conformance
|
||||
func (ec2i *FakeEC2Impl) CreateTags(*ec2.CreateTagsInput) (*ec2.CreateTagsOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DescribeRouteTables returns fake route table descriptions
|
||||
func (ec2i *FakeEC2Impl) DescribeRouteTables(request *ec2.DescribeRouteTablesInput) ([]*ec2.RouteTable, error) {
|
||||
ec2i.DescribeRouteTablesInput = request
|
||||
return ec2i.RouteTables, nil
|
||||
}
|
||||
|
||||
// CreateRouteTable creates fake route tables
|
||||
func (ec2i *FakeEC2Impl) CreateRouteTable(request *ec2.RouteTable) (*ec2.CreateRouteTableOutput, error) {
|
||||
ec2i.RouteTables = append(ec2i.RouteTables, request)
|
||||
response := &ec2.CreateRouteTableOutput{
|
||||
RouteTable: request,
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// RemoveRouteTables clears route tables on client
|
||||
func (ec2i *FakeEC2Impl) RemoveRouteTables() {
|
||||
ec2i.RouteTables = ec2i.RouteTables[:0]
|
||||
}
|
||||
|
||||
// CreateRoute is not implemented but is required for interface conformance
|
||||
func (ec2i *FakeEC2Impl) CreateRoute(request *ec2.CreateRouteInput) (*ec2.CreateRouteOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DeleteRoute is not implemented but is required for interface conformance
|
||||
func (ec2i *FakeEC2Impl) DeleteRoute(request *ec2.DeleteRouteInput) (*ec2.DeleteRouteOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// ModifyInstanceAttribute is not implemented but is required for interface
|
||||
// conformance
|
||||
func (ec2i *FakeEC2Impl) ModifyInstanceAttribute(request *ec2.ModifyInstanceAttributeInput) (*ec2.ModifyInstanceAttributeOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DescribeVpcs returns fake VPC descriptions
|
||||
func (ec2i *FakeEC2Impl) DescribeVpcs(request *ec2.DescribeVpcsInput) (*ec2.DescribeVpcsOutput, error) {
|
||||
return &ec2.DescribeVpcsOutput{Vpcs: []*ec2.Vpc{{CidrBlock: aws.String("172.20.0.0/16")}}}, nil
|
||||
}
|
||||
|
||||
// FakeMetadata is a fake EC2 metadata service client used for testing
|
||||
type FakeMetadata struct {
|
||||
aws *FakeAWSServices
|
||||
}
|
||||
|
||||
// GetMetadata returns fake EC2 metadata for testing
|
||||
func (m *FakeMetadata) GetMetadata(key string) (string, error) {
|
||||
networkInterfacesPrefix := "network/interfaces/macs/"
|
||||
i := m.aws.selfInstance
|
||||
if key == "placement/availability-zone" {
|
||||
az := ""
|
||||
if i.Placement != nil {
|
||||
az = aws.StringValue(i.Placement.AvailabilityZone)
|
||||
}
|
||||
return az, nil
|
||||
} else if key == "instance-id" {
|
||||
return aws.StringValue(i.InstanceId), nil
|
||||
} else if key == "local-hostname" {
|
||||
return aws.StringValue(i.PrivateDnsName), nil
|
||||
} else if key == "public-hostname" {
|
||||
return aws.StringValue(i.PublicDnsName), nil
|
||||
} else if key == "local-ipv4" {
|
||||
return aws.StringValue(i.PrivateIpAddress), nil
|
||||
} else if key == "public-ipv4" {
|
||||
return aws.StringValue(i.PublicIpAddress), nil
|
||||
} else if strings.HasPrefix(key, networkInterfacesPrefix) {
|
||||
if key == networkInterfacesPrefix {
|
||||
// Return the MACs sorted lexically rather than in device-number
|
||||
// order; this matches AWS's observed behavior and lets us test
|
||||
// that we fix up the ordering correctly in NodeAddresses().
|
||||
macs := make([]string, len(m.aws.networkInterfacesMacs))
|
||||
copy(macs, m.aws.networkInterfacesMacs)
|
||||
sort.Strings(macs)
|
||||
return strings.Join(macs, "/\n") + "/\n", nil
|
||||
}
|
||||
|
||||
keySplit := strings.Split(key, "/")
|
||||
macParam := keySplit[3]
|
||||
if len(keySplit) == 5 && keySplit[4] == "vpc-id" {
|
||||
for i, macElem := range m.aws.networkInterfacesMacs {
|
||||
if macParam == macElem {
|
||||
return m.aws.networkInterfacesVpcIDs[i], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(keySplit) == 5 && keySplit[4] == "device-number" {
|
||||
for i, macElem := range m.aws.networkInterfacesMacs {
|
||||
if macParam == macElem {
|
||||
n := i
|
||||
if n > 0 {
|
||||
// Introduce an artificial gap, just to test eg: [eth0, eth2]
|
||||
n++
|
||||
}
|
||||
return fmt.Sprintf("%d\n", n), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(keySplit) == 5 && keySplit[4] == "local-ipv4s" {
|
||||
for i, macElem := range m.aws.networkInterfacesMacs {
|
||||
if macParam == macElem {
|
||||
return strings.Join(m.aws.networkInterfacesPrivateIPs[i], "/\n"), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// FakeELB is a fake ELB client used for testing
|
||||
type FakeELB struct {
|
||||
aws *FakeAWSServices
|
||||
}
|
||||
|
||||
// CreateLoadBalancer is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELB) CreateLoadBalancer(*elb.CreateLoadBalancerInput) (*elb.CreateLoadBalancerOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DeleteLoadBalancer is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELB) DeleteLoadBalancer(input *elb.DeleteLoadBalancerInput) (*elb.DeleteLoadBalancerOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DescribeLoadBalancers is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELB) DescribeLoadBalancers(input *elb.DescribeLoadBalancersInput) (*elb.DescribeLoadBalancersOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// AddTags is not implemented but is required for interface conformance
|
||||
func (elb *FakeELB) AddTags(input *elb.AddTagsInput) (*elb.AddTagsOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// RegisterInstancesWithLoadBalancer is not implemented but is required for
|
||||
// interface conformance
|
||||
func (elb *FakeELB) RegisterInstancesWithLoadBalancer(*elb.RegisterInstancesWithLoadBalancerInput) (*elb.RegisterInstancesWithLoadBalancerOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DeregisterInstancesFromLoadBalancer is not implemented but is required for
|
||||
// interface conformance
|
||||
func (elb *FakeELB) DeregisterInstancesFromLoadBalancer(*elb.DeregisterInstancesFromLoadBalancerInput) (*elb.DeregisterInstancesFromLoadBalancerOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DetachLoadBalancerFromSubnets is not implemented but is required for
|
||||
// interface conformance
|
||||
func (elb *FakeELB) DetachLoadBalancerFromSubnets(*elb.DetachLoadBalancerFromSubnetsInput) (*elb.DetachLoadBalancerFromSubnetsOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// AttachLoadBalancerToSubnets is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELB) AttachLoadBalancerToSubnets(*elb.AttachLoadBalancerToSubnetsInput) (*elb.AttachLoadBalancerToSubnetsOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// CreateLoadBalancerListeners is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELB) CreateLoadBalancerListeners(*elb.CreateLoadBalancerListenersInput) (*elb.CreateLoadBalancerListenersOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DeleteLoadBalancerListeners is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELB) DeleteLoadBalancerListeners(*elb.DeleteLoadBalancerListenersInput) (*elb.DeleteLoadBalancerListenersOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// ApplySecurityGroupsToLoadBalancer is not implemented but is required for
|
||||
// interface conformance
|
||||
func (elb *FakeELB) ApplySecurityGroupsToLoadBalancer(*elb.ApplySecurityGroupsToLoadBalancerInput) (*elb.ApplySecurityGroupsToLoadBalancerOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// ConfigureHealthCheck is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELB) ConfigureHealthCheck(*elb.ConfigureHealthCheckInput) (*elb.ConfigureHealthCheckOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// CreateLoadBalancerPolicy is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELB) CreateLoadBalancerPolicy(*elb.CreateLoadBalancerPolicyInput) (*elb.CreateLoadBalancerPolicyOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// SetLoadBalancerPoliciesForBackendServer is not implemented but is required
|
||||
// for interface conformance
|
||||
func (elb *FakeELB) SetLoadBalancerPoliciesForBackendServer(*elb.SetLoadBalancerPoliciesForBackendServerInput) (*elb.SetLoadBalancerPoliciesForBackendServerOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// SetLoadBalancerPoliciesOfListener is not implemented but is required for
|
||||
// interface conformance
|
||||
func (elb *FakeELB) SetLoadBalancerPoliciesOfListener(input *elb.SetLoadBalancerPoliciesOfListenerInput) (*elb.SetLoadBalancerPoliciesOfListenerOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DescribeLoadBalancerPolicies is not implemented but is required for
|
||||
// interface conformance
|
||||
func (elb *FakeELB) DescribeLoadBalancerPolicies(input *elb.DescribeLoadBalancerPoliciesInput) (*elb.DescribeLoadBalancerPoliciesOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DescribeLoadBalancerAttributes is not implemented but is required for
|
||||
// interface conformance
|
||||
func (elb *FakeELB) DescribeLoadBalancerAttributes(*elb.DescribeLoadBalancerAttributesInput) (*elb.DescribeLoadBalancerAttributesOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// ModifyLoadBalancerAttributes is not implemented but is required for
|
||||
// interface conformance
|
||||
func (elb *FakeELB) ModifyLoadBalancerAttributes(*elb.ModifyLoadBalancerAttributesInput) (*elb.ModifyLoadBalancerAttributesOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// FakeELBV2 is a fake ELBV2 client used for testing
|
||||
type FakeELBV2 struct {
|
||||
aws *FakeAWSServices
|
||||
}
|
||||
|
||||
// AddTags is not implemented but is required for interface conformance
|
||||
func (elb *FakeELBV2) AddTags(input *elbv2.AddTagsInput) (*elbv2.AddTagsOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// CreateLoadBalancer is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELBV2) CreateLoadBalancer(*elbv2.CreateLoadBalancerInput) (*elbv2.CreateLoadBalancerOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DescribeLoadBalancers is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELBV2) DescribeLoadBalancers(*elbv2.DescribeLoadBalancersInput) (*elbv2.DescribeLoadBalancersOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DeleteLoadBalancer is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELBV2) DeleteLoadBalancer(*elbv2.DeleteLoadBalancerInput) (*elbv2.DeleteLoadBalancerOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// ModifyLoadBalancerAttributes is not implemented but is required for
|
||||
// interface conformance
|
||||
func (elb *FakeELBV2) ModifyLoadBalancerAttributes(*elbv2.ModifyLoadBalancerAttributesInput) (*elbv2.ModifyLoadBalancerAttributesOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DescribeLoadBalancerAttributes is not implemented but is required for
|
||||
// interface conformance
|
||||
func (elb *FakeELBV2) DescribeLoadBalancerAttributes(*elbv2.DescribeLoadBalancerAttributesInput) (*elbv2.DescribeLoadBalancerAttributesOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// CreateTargetGroup is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELBV2) CreateTargetGroup(*elbv2.CreateTargetGroupInput) (*elbv2.CreateTargetGroupOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DescribeTargetGroups is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELBV2) DescribeTargetGroups(*elbv2.DescribeTargetGroupsInput) (*elbv2.DescribeTargetGroupsOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// ModifyTargetGroup is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELBV2) ModifyTargetGroup(*elbv2.ModifyTargetGroupInput) (*elbv2.ModifyTargetGroupOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DeleteTargetGroup is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELBV2) DeleteTargetGroup(*elbv2.DeleteTargetGroupInput) (*elbv2.DeleteTargetGroupOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DescribeTargetHealth is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELBV2) DescribeTargetHealth(input *elbv2.DescribeTargetHealthInput) (*elbv2.DescribeTargetHealthOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DescribeTargetGroupAttributes is not implemented but is required for
|
||||
// interface conformance
|
||||
func (elb *FakeELBV2) DescribeTargetGroupAttributes(*elbv2.DescribeTargetGroupAttributesInput) (*elbv2.DescribeTargetGroupAttributesOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// ModifyTargetGroupAttributes is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELBV2) ModifyTargetGroupAttributes(*elbv2.ModifyTargetGroupAttributesInput) (*elbv2.ModifyTargetGroupAttributesOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// RegisterTargets is not implemented but is required for interface conformance
|
||||
func (elb *FakeELBV2) RegisterTargets(*elbv2.RegisterTargetsInput) (*elbv2.RegisterTargetsOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DeregisterTargets is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELBV2) DeregisterTargets(*elbv2.DeregisterTargetsInput) (*elbv2.DeregisterTargetsOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// CreateListener is not implemented but is required for interface conformance
|
||||
func (elb *FakeELBV2) CreateListener(*elbv2.CreateListenerInput) (*elbv2.CreateListenerOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DescribeListeners is not implemented but is required for interface
|
||||
// conformance
|
||||
func (elb *FakeELBV2) DescribeListeners(*elbv2.DescribeListenersInput) (*elbv2.DescribeListenersOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DeleteListener is not implemented but is required for interface conformance
|
||||
func (elb *FakeELBV2) DeleteListener(*elbv2.DeleteListenerInput) (*elbv2.DeleteListenerOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// ModifyListener is not implemented but is required for interface conformance
|
||||
func (elb *FakeELBV2) ModifyListener(*elbv2.ModifyListenerInput) (*elbv2.ModifyListenerOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// WaitUntilLoadBalancersDeleted is not implemented but is required for
|
||||
// interface conformance
|
||||
func (elb *FakeELBV2) WaitUntilLoadBalancersDeleted(*elbv2.DescribeLoadBalancersInput) error {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// FakeASG is a fake Autoscaling client used for testing
|
||||
type FakeASG struct {
|
||||
aws *FakeAWSServices
|
||||
}
|
||||
|
||||
// UpdateAutoScalingGroup is not implemented but is required for interface
|
||||
// conformance
|
||||
func (a *FakeASG) UpdateAutoScalingGroup(*autoscaling.UpdateAutoScalingGroupInput) (*autoscaling.UpdateAutoScalingGroupOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// DescribeAutoScalingGroups is not implemented but is required for interface
|
||||
// conformance
|
||||
func (a *FakeASG) DescribeAutoScalingGroups(*autoscaling.DescribeAutoScalingGroupsInput) (*autoscaling.DescribeAutoScalingGroupsOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
// FakeKMS is a fake KMS client used for testing
|
||||
type FakeKMS struct {
|
||||
aws *FakeAWSServices
|
||||
}
|
||||
|
||||
// DescribeKey is not implemented but is required for interface conformance
|
||||
func (kms *FakeKMS) DescribeKey(*kms.DescribeKeyInput) (*kms.DescribeKeyOutput, error) {
|
||||
panic("Not implemented")
|
||||
}
|
||||
|
||||
func instanceMatchesFilter(instance *ec2.Instance, filter *ec2.Filter) bool {
|
||||
name := *filter.Name
|
||||
if name == "private-dns-name" {
|
||||
if instance.PrivateDnsName == nil {
|
||||
return false
|
||||
}
|
||||
return contains(filter.Values, *instance.PrivateDnsName)
|
||||
}
|
||||
|
||||
if name == "instance-state-name" {
|
||||
return contains(filter.Values, *instance.State.Name)
|
||||
}
|
||||
|
||||
if name == "tag-key" {
|
||||
for _, instanceTag := range instance.Tags {
|
||||
if contains(filter.Values, aws.StringValue(instanceTag.Key)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "tag:") {
|
||||
tagName := name[4:]
|
||||
for _, instanceTag := range instance.Tags {
|
||||
if aws.StringValue(instanceTag.Key) == tagName && contains(filter.Values, aws.StringValue(instanceTag.Value)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
panic("Unknown filter name: " + name)
|
||||
}
|
||||
|
||||
func contains(haystack []*string, needle string) bool {
|
||||
for _, s := range haystack {
|
||||
// (deliberately panic if s == nil)
|
||||
if needle == *s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
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 aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/autoscaling"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// AWSCloud implements InstanceGroups
|
||||
var _ InstanceGroups = &Cloud{}
|
||||
|
||||
// ResizeInstanceGroup sets the size of the specificed instancegroup Exported
|
||||
// so it can be used by the e2e tests, which don't want to instantiate a full
|
||||
// cloudprovider.
|
||||
func ResizeInstanceGroup(asg ASG, instanceGroupName string, size int) error {
|
||||
request := &autoscaling.UpdateAutoScalingGroupInput{
|
||||
AutoScalingGroupName: aws.String(instanceGroupName),
|
||||
DesiredCapacity: aws.Int64(int64(size)),
|
||||
}
|
||||
if _, err := asg.UpdateAutoScalingGroup(request); err != nil {
|
||||
return fmt.Errorf("error resizing AWS autoscaling group: %q", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResizeInstanceGroup implements InstanceGroups.ResizeInstanceGroup
|
||||
// Set the size to the fixed size
|
||||
func (c *Cloud) ResizeInstanceGroup(instanceGroupName string, size int) error {
|
||||
return ResizeInstanceGroup(c.asg, instanceGroupName, size)
|
||||
}
|
||||
|
||||
// DescribeInstanceGroup gets info about the specified instancegroup
|
||||
// Exported so it can be used by the e2e tests,
|
||||
// which don't want to instantiate a full cloudprovider.
|
||||
func DescribeInstanceGroup(asg ASG, instanceGroupName string) (InstanceGroupInfo, error) {
|
||||
request := &autoscaling.DescribeAutoScalingGroupsInput{
|
||||
AutoScalingGroupNames: []*string{aws.String(instanceGroupName)},
|
||||
}
|
||||
response, err := asg.DescribeAutoScalingGroups(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error listing AWS autoscaling group (%s): %q", instanceGroupName, err)
|
||||
}
|
||||
|
||||
if len(response.AutoScalingGroups) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if len(response.AutoScalingGroups) > 1 {
|
||||
klog.Warning("AWS returned multiple autoscaling groups with name ", instanceGroupName)
|
||||
}
|
||||
group := response.AutoScalingGroups[0]
|
||||
return &awsInstanceGroup{group: group}, nil
|
||||
}
|
||||
|
||||
// DescribeInstanceGroup implements InstanceGroups.DescribeInstanceGroup
|
||||
// Queries the cloud provider for information about the specified instance group
|
||||
func (c *Cloud) DescribeInstanceGroup(instanceGroupName string) (InstanceGroupInfo, error) {
|
||||
return DescribeInstanceGroup(c.asg, instanceGroupName)
|
||||
}
|
||||
|
||||
// awsInstanceGroup implements InstanceGroupInfo
|
||||
var _ InstanceGroupInfo = &awsInstanceGroup{}
|
||||
|
||||
type awsInstanceGroup struct {
|
||||
group *autoscaling.Group
|
||||
}
|
||||
|
||||
// Implement InstanceGroupInfo.CurrentSize
|
||||
// The number of instances currently running under control of this group
|
||||
func (g *awsInstanceGroup) CurrentSize() (int, error) {
|
||||
return len(g.group.Instances), nil
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,75 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
Copyright 2017 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 aws
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
)
|
||||
|
||||
var (
|
||||
awsAPIMetric = metrics.NewHistogramVec(
|
||||
&metrics.HistogramOpts{
|
||||
Name: "cloudprovider_aws_api_request_duration_seconds",
|
||||
Help: "Latency of AWS API calls",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"request"})
|
||||
|
||||
awsAPIErrorMetric = metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Name: "cloudprovider_aws_api_request_errors",
|
||||
Help: "AWS API errors",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"request"})
|
||||
|
||||
awsAPIThrottlesMetric = metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Name: "cloudprovider_aws_api_throttled_requests_total",
|
||||
Help: "AWS API throttled requests",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"operation_name"})
|
||||
)
|
||||
|
||||
func recordAWSMetric(actionName string, timeTaken float64, err error) {
|
||||
if err != nil {
|
||||
awsAPIErrorMetric.With(metrics.Labels{"request": actionName}).Inc()
|
||||
} else {
|
||||
awsAPIMetric.With(metrics.Labels{"request": actionName}).Observe(timeTaken)
|
||||
}
|
||||
}
|
||||
|
||||
func recordAWSThrottlesMetric(operation string) {
|
||||
awsAPIThrottlesMetric.With(metrics.Labels{"operation_name": operation}).Inc()
|
||||
}
|
||||
|
||||
var registerOnce sync.Once
|
||||
|
||||
func registerMetrics() {
|
||||
registerOnce.Do(func() {
|
||||
legacyregistry.MustRegister(awsAPIMetric)
|
||||
legacyregistry.MustRegister(awsAPIErrorMetric)
|
||||
legacyregistry.MustRegister(awsAPIThrottlesMetric)
|
||||
})
|
||||
}
|
@ -1,222 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
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 aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
cloudprovider "k8s.io/cloud-provider"
|
||||
)
|
||||
|
||||
func (c *Cloud) findRouteTable(clusterName string) (*ec2.RouteTable, error) {
|
||||
// This should be unnecessary (we already filter on TagNameKubernetesCluster,
|
||||
// and something is broken if cluster name doesn't match, but anyway...
|
||||
// TODO: All clouds should be cluster-aware by default
|
||||
var tables []*ec2.RouteTable
|
||||
|
||||
if c.cfg.Global.RouteTableID != "" {
|
||||
request := &ec2.DescribeRouteTablesInput{Filters: []*ec2.Filter{newEc2Filter("route-table-id", c.cfg.Global.RouteTableID)}}
|
||||
response, err := c.ec2.DescribeRouteTables(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tables = response
|
||||
} else {
|
||||
request := &ec2.DescribeRouteTablesInput{}
|
||||
response, err := c.ec2.DescribeRouteTables(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, table := range response {
|
||||
if c.tagging.hasClusterTag(table.Tags) {
|
||||
tables = append(tables, table)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(tables) == 0 {
|
||||
return nil, fmt.Errorf("unable to find route table for AWS cluster: %s", clusterName)
|
||||
}
|
||||
|
||||
if len(tables) != 1 {
|
||||
return nil, fmt.Errorf("found multiple matching AWS route tables for AWS cluster: %s", clusterName)
|
||||
}
|
||||
return tables[0], nil
|
||||
}
|
||||
|
||||
// ListRoutes implements Routes.ListRoutes
|
||||
// List all routes that match the filter
|
||||
func (c *Cloud) ListRoutes(ctx context.Context, clusterName string) ([]*cloudprovider.Route, error) {
|
||||
table, err := c.findRouteTable(clusterName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var routes []*cloudprovider.Route
|
||||
var instanceIDs []*string
|
||||
|
||||
for _, r := range table.Routes {
|
||||
instanceID := aws.StringValue(r.InstanceId)
|
||||
|
||||
if instanceID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
instanceIDs = append(instanceIDs, &instanceID)
|
||||
}
|
||||
|
||||
instances, err := c.getInstancesByIDs(instanceIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, r := range table.Routes {
|
||||
destinationCIDR := aws.StringValue(r.DestinationCidrBlock)
|
||||
if destinationCIDR == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
route := &cloudprovider.Route{
|
||||
Name: clusterName + "-" + destinationCIDR,
|
||||
DestinationCIDR: destinationCIDR,
|
||||
}
|
||||
|
||||
// Capture blackhole routes
|
||||
if aws.StringValue(r.State) == ec2.RouteStateBlackhole {
|
||||
route.Blackhole = true
|
||||
routes = append(routes, route)
|
||||
continue
|
||||
}
|
||||
|
||||
// Capture instance routes
|
||||
instanceID := aws.StringValue(r.InstanceId)
|
||||
if instanceID != "" {
|
||||
instance, found := instances[instanceID]
|
||||
if found {
|
||||
route.TargetNode = mapInstanceToNodeName(instance)
|
||||
routes = append(routes, route)
|
||||
} else {
|
||||
klog.Warningf("unable to find instance ID %s in the list of instances being routed to", instanceID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return routes, nil
|
||||
}
|
||||
|
||||
// Sets the instance attribute "source-dest-check" to the specified value
|
||||
func (c *Cloud) configureInstanceSourceDestCheck(instanceID string, sourceDestCheck bool) error {
|
||||
request := &ec2.ModifyInstanceAttributeInput{}
|
||||
request.InstanceId = aws.String(instanceID)
|
||||
request.SourceDestCheck = &ec2.AttributeBooleanValue{Value: aws.Bool(sourceDestCheck)}
|
||||
|
||||
_, err := c.ec2.ModifyInstanceAttribute(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error configuring source-dest-check on instance %s: %q", instanceID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateRoute implements Routes.CreateRoute
|
||||
// Create the described route
|
||||
func (c *Cloud) CreateRoute(ctx context.Context, clusterName string, nameHint string, route *cloudprovider.Route) error {
|
||||
instance, err := c.getInstanceByNodeName(route.TargetNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// In addition to configuring the route itself, we also need to configure the instance to accept that traffic
|
||||
// On AWS, this requires turning source-dest checks off
|
||||
err = c.configureInstanceSourceDestCheck(aws.StringValue(instance.InstanceId), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
table, err := c.findRouteTable(clusterName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var deleteRoute *ec2.Route
|
||||
for _, r := range table.Routes {
|
||||
destinationCIDR := aws.StringValue(r.DestinationCidrBlock)
|
||||
|
||||
if destinationCIDR != route.DestinationCIDR {
|
||||
continue
|
||||
}
|
||||
|
||||
if aws.StringValue(r.State) == ec2.RouteStateBlackhole {
|
||||
deleteRoute = r
|
||||
}
|
||||
}
|
||||
|
||||
if deleteRoute != nil {
|
||||
klog.Infof("deleting blackholed route: %s", aws.StringValue(deleteRoute.DestinationCidrBlock))
|
||||
|
||||
request := &ec2.DeleteRouteInput{}
|
||||
request.DestinationCidrBlock = deleteRoute.DestinationCidrBlock
|
||||
request.RouteTableId = table.RouteTableId
|
||||
|
||||
_, err = c.ec2.DeleteRoute(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting blackholed AWS route (%s): %q", aws.StringValue(deleteRoute.DestinationCidrBlock), err)
|
||||
}
|
||||
}
|
||||
|
||||
request := &ec2.CreateRouteInput{}
|
||||
// TODO: use ClientToken for idempotency?
|
||||
request.DestinationCidrBlock = aws.String(route.DestinationCIDR)
|
||||
request.InstanceId = instance.InstanceId
|
||||
request.RouteTableId = table.RouteTableId
|
||||
|
||||
_, err = c.ec2.CreateRoute(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating AWS route (%s): %q", route.DestinationCIDR, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteRoute implements Routes.DeleteRoute
|
||||
// Delete the specified route
|
||||
func (c *Cloud) DeleteRoute(ctx context.Context, clusterName string, route *cloudprovider.Route) error {
|
||||
table, err := c.findRouteTable(clusterName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
request := &ec2.DeleteRouteInput{}
|
||||
request.DestinationCidrBlock = aws.String(route.DestinationCIDR)
|
||||
request.RouteTableId = table.RouteTableId
|
||||
|
||||
_, err = c.ec2.DeleteRoute(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deleting AWS route (%s): %q", route.DestinationCIDR, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,48 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
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 aws
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
func stringSetToPointers(in sets.String) []*string {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]*string, 0, len(in))
|
||||
for k := range in {
|
||||
out = append(out, aws.String(k))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func stringSetFromPointers(in []*string) sets.String {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := sets.NewString()
|
||||
for i := range in {
|
||||
out.Insert(aws.StringValue(in[i]))
|
||||
}
|
||||
return out
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
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 aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ExistingDevices is a map of assigned devices. Presence of a key with a device
|
||||
// name in the map means that the device is allocated. Value is irrelevant and
|
||||
// can be used for anything that DeviceAllocator user wants.
|
||||
// Only the relevant part of device name should be in the map, e.g. "ba" for
|
||||
// "/dev/xvdba".
|
||||
type ExistingDevices map[mountDevice]EBSVolumeID
|
||||
|
||||
// DeviceAllocator finds available device name, taking into account already
|
||||
// assigned device names from ExistingDevices map. It tries to find the next
|
||||
// device name to the previously assigned one (from previous DeviceAllocator
|
||||
// call), so all available device names are used eventually and it minimizes
|
||||
// device name reuse.
|
||||
//
|
||||
// All these allocations are in-memory, nothing is written to / read from
|
||||
// /dev directory.
|
||||
//
|
||||
// On AWS, we should assign new (not yet used) device names to attached volumes.
|
||||
// If we reuse a previously used name, we may get the volume "attaching" forever,
|
||||
// see https://aws.amazon.com/premiumsupport/knowledge-center/ebs-stuck-attaching/.
|
||||
type DeviceAllocator interface {
|
||||
// GetNext returns a free device name or error when there is no free device
|
||||
// name. Only the device suffix is returned, e.g. "ba" for "/dev/xvdba".
|
||||
// It's up to the called to add appropriate "/dev/sd" or "/dev/xvd" prefix.
|
||||
GetNext(existingDevices ExistingDevices) (mountDevice, error)
|
||||
|
||||
// Deprioritize the device so as it can't be used immediately again
|
||||
Deprioritize(mountDevice)
|
||||
|
||||
// Lock the deviceAllocator
|
||||
Lock()
|
||||
|
||||
// Unlock the deviceAllocator
|
||||
Unlock()
|
||||
}
|
||||
|
||||
type deviceAllocator struct {
|
||||
possibleDevices map[mountDevice]int
|
||||
counter int
|
||||
deviceLock sync.Mutex
|
||||
}
|
||||
|
||||
var _ DeviceAllocator = &deviceAllocator{}
|
||||
|
||||
type devicePair struct {
|
||||
deviceName mountDevice
|
||||
deviceIndex int
|
||||
}
|
||||
|
||||
type devicePairList []devicePair
|
||||
|
||||
func (p devicePairList) Len() int { return len(p) }
|
||||
func (p devicePairList) Less(i, j int) bool { return p[i].deviceIndex < p[j].deviceIndex }
|
||||
func (p devicePairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
// NewDeviceAllocator allocates device names according to scheme ba..bz, ca..cz
|
||||
// it moves along the ring and always picks next device until device list is
|
||||
// exhausted.
|
||||
func NewDeviceAllocator() DeviceAllocator {
|
||||
possibleDevices := make(map[mountDevice]int)
|
||||
for _, firstChar := range []rune{'b', 'c'} {
|
||||
for i := 'a'; i <= 'z'; i++ {
|
||||
dev := mountDevice([]rune{firstChar, i})
|
||||
possibleDevices[dev] = 0
|
||||
}
|
||||
}
|
||||
return &deviceAllocator{
|
||||
possibleDevices: possibleDevices,
|
||||
counter: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// GetNext gets next available device from the pool, this function assumes that caller
|
||||
// holds the necessary lock on deviceAllocator
|
||||
func (d *deviceAllocator) GetNext(existingDevices ExistingDevices) (mountDevice, error) {
|
||||
for _, devicePair := range d.sortByCount() {
|
||||
if _, found := existingDevices[devicePair.deviceName]; !found {
|
||||
return devicePair.deviceName, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("no devices are available")
|
||||
}
|
||||
|
||||
func (d *deviceAllocator) sortByCount() devicePairList {
|
||||
dpl := make(devicePairList, 0)
|
||||
for deviceName, deviceIndex := range d.possibleDevices {
|
||||
dpl = append(dpl, devicePair{deviceName, deviceIndex})
|
||||
}
|
||||
sort.Sort(dpl)
|
||||
return dpl
|
||||
}
|
||||
|
||||
func (d *deviceAllocator) Lock() {
|
||||
d.deviceLock.Lock()
|
||||
}
|
||||
|
||||
func (d *deviceAllocator) Unlock() {
|
||||
d.deviceLock.Unlock()
|
||||
}
|
||||
|
||||
// Deprioritize the device so as it can't be used immediately again
|
||||
func (d *deviceAllocator) Deprioritize(chosen mountDevice) {
|
||||
d.deviceLock.Lock()
|
||||
defer d.deviceLock.Unlock()
|
||||
if _, ok := d.possibleDevices[chosen]; ok {
|
||||
d.counter++
|
||||
d.possibleDevices[chosen] = d.counter
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
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 aws
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDeviceAllocator(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
existingDevices ExistingDevices
|
||||
deviceMap map[mountDevice]int
|
||||
expectedOutput mountDevice
|
||||
}{
|
||||
{
|
||||
"empty device list with wrap",
|
||||
ExistingDevices{},
|
||||
generateUnsortedDeviceList(),
|
||||
"bd", // next to 'zz' is the first one, 'ba'
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
allocator := NewDeviceAllocator().(*deviceAllocator)
|
||||
for k, v := range test.deviceMap {
|
||||
allocator.possibleDevices[k] = v
|
||||
}
|
||||
|
||||
got, err := allocator.GetNext(test.existingDevices)
|
||||
if err != nil {
|
||||
t.Errorf("text %q: unexpected error: %v", test.name, err)
|
||||
}
|
||||
if got != test.expectedOutput {
|
||||
t.Errorf("text %q: expected %q, got %q", test.name, test.expectedOutput, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func generateUnsortedDeviceList() map[mountDevice]int {
|
||||
possibleDevices := make(map[mountDevice]int)
|
||||
for _, firstChar := range []rune{'b', 'c'} {
|
||||
for i := 'a'; i <= 'z'; i++ {
|
||||
dev := mountDevice([]rune{firstChar, i})
|
||||
possibleDevices[dev] = 3
|
||||
}
|
||||
}
|
||||
possibleDevices["bd"] = 0
|
||||
return possibleDevices
|
||||
}
|
||||
|
||||
func TestDeviceAllocatorError(t *testing.T) {
|
||||
allocator := NewDeviceAllocator().(*deviceAllocator)
|
||||
existingDevices := ExistingDevices{}
|
||||
|
||||
// make all devices used
|
||||
var first, second byte
|
||||
for first = 'b'; first <= 'c'; first++ {
|
||||
for second = 'a'; second <= 'z'; second++ {
|
||||
device := [2]byte{first, second}
|
||||
existingDevices[mountDevice(device[:])] = "used"
|
||||
}
|
||||
}
|
||||
|
||||
device, err := allocator.GetNext(existingDevices)
|
||||
if err == nil {
|
||||
t.Errorf("expected error, got device %q", device)
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 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 aws
|
@ -1,276 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
Copyright 2017 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 aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// awsInstanceRegMatch represents Regex Match for AWS instance.
|
||||
var awsInstanceRegMatch = regexp.MustCompile("^i-[^/]*$")
|
||||
|
||||
// InstanceID represents the ID of the instance in the AWS API, e.g. i-12345678
|
||||
// The "traditional" format is "i-12345678"
|
||||
// A new longer format is also being introduced: "i-12345678abcdef01"
|
||||
// We should not assume anything about the length or format, though it seems
|
||||
// reasonable to assume that instances will continue to start with "i-".
|
||||
type InstanceID string
|
||||
|
||||
func (i InstanceID) awsString() *string {
|
||||
return aws.String(string(i))
|
||||
}
|
||||
|
||||
// KubernetesInstanceID represents the id for an instance in the kubernetes API;
|
||||
// the following form
|
||||
// - aws:///<zone>/<awsInstanceId>
|
||||
// - aws:////<awsInstanceId>
|
||||
// - <awsInstanceId>
|
||||
type KubernetesInstanceID string
|
||||
|
||||
// MapToAWSInstanceID extracts the InstanceID from the KubernetesInstanceID
|
||||
func (name KubernetesInstanceID) MapToAWSInstanceID() (InstanceID, error) {
|
||||
s := string(name)
|
||||
|
||||
if !strings.HasPrefix(s, "aws://") {
|
||||
// Assume a bare aws volume id (vol-1234...)
|
||||
// Build a URL with an empty host (AZ)
|
||||
s = "aws://" + "/" + "/" + s
|
||||
}
|
||||
url, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Invalid instance name (%s): %v", name, err)
|
||||
}
|
||||
if url.Scheme != "aws" {
|
||||
return "", fmt.Errorf("Invalid scheme for AWS instance (%s)", name)
|
||||
}
|
||||
|
||||
awsID := ""
|
||||
tokens := strings.Split(strings.Trim(url.Path, "/"), "/")
|
||||
if len(tokens) == 1 {
|
||||
// instanceId
|
||||
awsID = tokens[0]
|
||||
} else if len(tokens) == 2 {
|
||||
// az/instanceId
|
||||
awsID = tokens[1]
|
||||
}
|
||||
|
||||
// We sanity check the resulting volume; the two known formats are
|
||||
// i-12345678 and i-12345678abcdef01
|
||||
if awsID == "" || !awsInstanceRegMatch.MatchString(awsID) {
|
||||
return "", fmt.Errorf("Invalid format for AWS instance (%s)", name)
|
||||
}
|
||||
|
||||
return InstanceID(awsID), nil
|
||||
}
|
||||
|
||||
// mapToAWSInstanceID extracts the InstanceIDs from the Nodes, returning an error if a Node cannot be mapped
|
||||
func mapToAWSInstanceIDs(nodes []*v1.Node) ([]InstanceID, error) {
|
||||
var instanceIDs []InstanceID
|
||||
for _, node := range nodes {
|
||||
if node.Spec.ProviderID == "" {
|
||||
return nil, fmt.Errorf("node %q did not have ProviderID set", node.Name)
|
||||
}
|
||||
instanceID, err := KubernetesInstanceID(node.Spec.ProviderID).MapToAWSInstanceID()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse ProviderID %q for node %q", node.Spec.ProviderID, node.Name)
|
||||
}
|
||||
instanceIDs = append(instanceIDs, instanceID)
|
||||
}
|
||||
|
||||
return instanceIDs, nil
|
||||
}
|
||||
|
||||
// mapToAWSInstanceIDsTolerant extracts the InstanceIDs from the Nodes, skipping Nodes that cannot be mapped
|
||||
func mapToAWSInstanceIDsTolerant(nodes []*v1.Node) []InstanceID {
|
||||
var instanceIDs []InstanceID
|
||||
for _, node := range nodes {
|
||||
if node.Spec.ProviderID == "" {
|
||||
klog.Warningf("node %q did not have ProviderID set", node.Name)
|
||||
continue
|
||||
}
|
||||
instanceID, err := KubernetesInstanceID(node.Spec.ProviderID).MapToAWSInstanceID()
|
||||
if err != nil {
|
||||
klog.Warningf("unable to parse ProviderID %q for node %q", node.Spec.ProviderID, node.Name)
|
||||
continue
|
||||
}
|
||||
instanceIDs = append(instanceIDs, instanceID)
|
||||
}
|
||||
|
||||
return instanceIDs
|
||||
}
|
||||
|
||||
// Gets the full information about this instance from the EC2 API
|
||||
func describeInstance(ec2Client EC2, instanceID InstanceID) (*ec2.Instance, error) {
|
||||
request := &ec2.DescribeInstancesInput{
|
||||
InstanceIds: []*string{instanceID.awsString()},
|
||||
}
|
||||
|
||||
instances, err := ec2Client.DescribeInstances(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(instances) == 0 {
|
||||
return nil, fmt.Errorf("no instances found for instance: %s", instanceID)
|
||||
}
|
||||
if len(instances) > 1 {
|
||||
return nil, fmt.Errorf("multiple instances found for instance: %s", instanceID)
|
||||
}
|
||||
return instances[0], nil
|
||||
}
|
||||
|
||||
// instanceCache manages the cache of DescribeInstances
|
||||
type instanceCache struct {
|
||||
// TODO: Get rid of this field, send all calls through the instanceCache
|
||||
cloud *Cloud
|
||||
|
||||
mutex sync.Mutex
|
||||
snapshot *allInstancesSnapshot
|
||||
}
|
||||
|
||||
// Gets the full information about these instance from the EC2 API
|
||||
func (c *instanceCache) describeAllInstancesUncached() (*allInstancesSnapshot, error) {
|
||||
now := time.Now()
|
||||
|
||||
klog.V(4).Infof("EC2 DescribeInstances - fetching all instances")
|
||||
|
||||
var filters []*ec2.Filter
|
||||
instances, err := c.cloud.describeInstances(filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := make(map[InstanceID]*ec2.Instance)
|
||||
for _, i := range instances {
|
||||
id := InstanceID(aws.StringValue(i.InstanceId))
|
||||
m[id] = i
|
||||
}
|
||||
|
||||
snapshot := &allInstancesSnapshot{now, m}
|
||||
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
if c.snapshot != nil && snapshot.olderThan(c.snapshot) {
|
||||
// If this happens a lot, we could run this function in a mutex and only return one result
|
||||
klog.Infof("Not caching concurrent AWS DescribeInstances results")
|
||||
} else {
|
||||
c.snapshot = snapshot
|
||||
}
|
||||
|
||||
return snapshot, nil
|
||||
}
|
||||
|
||||
// cacheCriteria holds criteria that must hold to use a cached snapshot
|
||||
type cacheCriteria struct {
|
||||
// MaxAge indicates the maximum age of a cached snapshot we can accept.
|
||||
// If set to 0 (i.e. unset), cached values will not time out because of age.
|
||||
MaxAge time.Duration
|
||||
|
||||
// HasInstances is a list of InstanceIDs that must be in a cached snapshot for it to be considered valid.
|
||||
// If an instance is not found in the cached snapshot, the snapshot be ignored and we will re-fetch.
|
||||
HasInstances []InstanceID
|
||||
}
|
||||
|
||||
// describeAllInstancesCached returns all instances, using cached results if applicable
|
||||
func (c *instanceCache) describeAllInstancesCached(criteria cacheCriteria) (*allInstancesSnapshot, error) {
|
||||
var err error
|
||||
snapshot := c.getSnapshot()
|
||||
if snapshot != nil && !snapshot.MeetsCriteria(criteria) {
|
||||
snapshot = nil
|
||||
}
|
||||
|
||||
if snapshot == nil {
|
||||
snapshot, err = c.describeAllInstancesUncached()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
klog.V(6).Infof("EC2 DescribeInstances - using cached results")
|
||||
}
|
||||
|
||||
return snapshot, nil
|
||||
}
|
||||
|
||||
// getSnapshot returns a snapshot if one exists
|
||||
func (c *instanceCache) getSnapshot() *allInstancesSnapshot {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
return c.snapshot
|
||||
}
|
||||
|
||||
// olderThan is a simple helper to encapsulate timestamp comparison
|
||||
func (s *allInstancesSnapshot) olderThan(other *allInstancesSnapshot) bool {
|
||||
// After() is technically broken by time changes until we have monotonic time
|
||||
return other.timestamp.After(s.timestamp)
|
||||
}
|
||||
|
||||
// MeetsCriteria returns true if the snapshot meets the criteria in cacheCriteria
|
||||
func (s *allInstancesSnapshot) MeetsCriteria(criteria cacheCriteria) bool {
|
||||
if criteria.MaxAge > 0 {
|
||||
// Sub() is technically broken by time changes until we have monotonic time
|
||||
now := time.Now()
|
||||
if now.Sub(s.timestamp) > criteria.MaxAge {
|
||||
klog.V(6).Infof("instanceCache snapshot cannot be used as is older than MaxAge=%s", criteria.MaxAge)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if len(criteria.HasInstances) != 0 {
|
||||
for _, id := range criteria.HasInstances {
|
||||
if nil == s.instances[id] {
|
||||
klog.V(6).Infof("instanceCache snapshot cannot be used as does not contain instance %s", id)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// allInstancesSnapshot holds the results from querying for all instances,
|
||||
// along with the timestamp for cache-invalidation purposes
|
||||
type allInstancesSnapshot struct {
|
||||
timestamp time.Time
|
||||
instances map[InstanceID]*ec2.Instance
|
||||
}
|
||||
|
||||
// FindInstances returns the instances corresponding to the specified ids. If an id is not found, it is ignored.
|
||||
func (s *allInstancesSnapshot) FindInstances(ids []InstanceID) map[InstanceID]*ec2.Instance {
|
||||
m := make(map[InstanceID]*ec2.Instance)
|
||||
for _, id := range ids {
|
||||
instance := s.instances[id]
|
||||
if instance != nil {
|
||||
m[id] = instance
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
@ -1,204 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
Copyright 2017 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 aws
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func TestMapToAWSInstanceIDs(t *testing.T) {
|
||||
tests := []struct {
|
||||
Kubernetes KubernetesInstanceID
|
||||
Aws InstanceID
|
||||
ExpectError bool
|
||||
}{
|
||||
{
|
||||
Kubernetes: "aws:///us-east-1a/i-12345678",
|
||||
Aws: "i-12345678",
|
||||
},
|
||||
{
|
||||
Kubernetes: "aws:////i-12345678",
|
||||
Aws: "i-12345678",
|
||||
},
|
||||
{
|
||||
Kubernetes: "i-12345678",
|
||||
Aws: "i-12345678",
|
||||
},
|
||||
{
|
||||
Kubernetes: "aws:///us-east-1a/i-12345678abcdef01",
|
||||
Aws: "i-12345678abcdef01",
|
||||
},
|
||||
{
|
||||
Kubernetes: "aws:////i-12345678abcdef01",
|
||||
Aws: "i-12345678abcdef01",
|
||||
},
|
||||
{
|
||||
Kubernetes: "i-12345678abcdef01",
|
||||
Aws: "i-12345678abcdef01",
|
||||
},
|
||||
{
|
||||
Kubernetes: "vol-123456789",
|
||||
ExpectError: true,
|
||||
},
|
||||
{
|
||||
Kubernetes: "aws:///us-east-1a/vol-12345678abcdef01",
|
||||
ExpectError: true,
|
||||
},
|
||||
{
|
||||
Kubernetes: "aws://accountid/us-east-1a/vol-12345678abcdef01",
|
||||
ExpectError: true,
|
||||
},
|
||||
{
|
||||
Kubernetes: "aws:///us-east-1a/vol-12345678abcdef01/suffix",
|
||||
ExpectError: true,
|
||||
},
|
||||
{
|
||||
Kubernetes: "",
|
||||
ExpectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
awsID, err := test.Kubernetes.MapToAWSInstanceID()
|
||||
if err != nil {
|
||||
if !test.ExpectError {
|
||||
t.Errorf("unexpected error parsing %s: %v", test.Kubernetes, err)
|
||||
}
|
||||
} else {
|
||||
if test.ExpectError {
|
||||
t.Errorf("expected error parsing %s", test.Kubernetes)
|
||||
} else if test.Aws != awsID {
|
||||
t.Errorf("unexpected value parsing %s, got %s", test.Kubernetes, awsID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
node := &v1.Node{}
|
||||
node.Spec.ProviderID = string(test.Kubernetes)
|
||||
|
||||
awsInstanceIds, err := mapToAWSInstanceIDs([]*v1.Node{node})
|
||||
if err != nil {
|
||||
if !test.ExpectError {
|
||||
t.Errorf("unexpected error parsing %s: %v", test.Kubernetes, err)
|
||||
}
|
||||
} else {
|
||||
if test.ExpectError {
|
||||
t.Errorf("expected error parsing %s", test.Kubernetes)
|
||||
} else if len(awsInstanceIds) != 1 {
|
||||
t.Errorf("unexpected value parsing %s, got %s", test.Kubernetes, awsInstanceIds)
|
||||
} else if awsInstanceIds[0] != test.Aws {
|
||||
t.Errorf("unexpected value parsing %s, got %s", test.Kubernetes, awsInstanceIds)
|
||||
}
|
||||
}
|
||||
|
||||
awsInstanceIds = mapToAWSInstanceIDsTolerant([]*v1.Node{node})
|
||||
if test.ExpectError {
|
||||
if len(awsInstanceIds) != 0 {
|
||||
t.Errorf("unexpected results parsing %s: %s", test.Kubernetes, awsInstanceIds)
|
||||
}
|
||||
} else {
|
||||
if len(awsInstanceIds) != 1 {
|
||||
t.Errorf("unexpected value parsing %s, got %s", test.Kubernetes, awsInstanceIds)
|
||||
} else if awsInstanceIds[0] != test.Aws {
|
||||
t.Errorf("unexpected value parsing %s, got %s", test.Kubernetes, awsInstanceIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnapshotMeetsCriteria(t *testing.T) {
|
||||
snapshot := &allInstancesSnapshot{timestamp: time.Now().Add(-3601 * time.Second)}
|
||||
|
||||
if !snapshot.MeetsCriteria(cacheCriteria{}) {
|
||||
t.Errorf("Snapshot should always meet empty criteria")
|
||||
}
|
||||
|
||||
if snapshot.MeetsCriteria(cacheCriteria{MaxAge: time.Hour}) {
|
||||
t.Errorf("Snapshot did not honor MaxAge")
|
||||
}
|
||||
|
||||
if snapshot.MeetsCriteria(cacheCriteria{HasInstances: []InstanceID{InstanceID("i-12345678")}}) {
|
||||
t.Errorf("Snapshot did not honor HasInstances with missing instances")
|
||||
}
|
||||
|
||||
snapshot.instances = make(map[InstanceID]*ec2.Instance)
|
||||
snapshot.instances[InstanceID("i-12345678")] = &ec2.Instance{}
|
||||
|
||||
if !snapshot.MeetsCriteria(cacheCriteria{HasInstances: []InstanceID{InstanceID("i-12345678")}}) {
|
||||
t.Errorf("Snapshot did not honor HasInstances with matching instances")
|
||||
}
|
||||
|
||||
if snapshot.MeetsCriteria(cacheCriteria{HasInstances: []InstanceID{InstanceID("i-12345678"), InstanceID("i-00000000")}}) {
|
||||
t.Errorf("Snapshot did not honor HasInstances with partially matching instances")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOlderThan(t *testing.T) {
|
||||
t1 := time.Now()
|
||||
t2 := t1.Add(time.Second)
|
||||
|
||||
s1 := &allInstancesSnapshot{timestamp: t1}
|
||||
s2 := &allInstancesSnapshot{timestamp: t2}
|
||||
|
||||
assert.True(t, s1.olderThan(s2), "s1 should be olderThan s2")
|
||||
assert.False(t, s2.olderThan(s1), "s2 not should be olderThan s1")
|
||||
assert.False(t, s1.olderThan(s1), "s1 not should be olderThan itself")
|
||||
}
|
||||
|
||||
func TestSnapshotFindInstances(t *testing.T) {
|
||||
snapshot := &allInstancesSnapshot{}
|
||||
|
||||
snapshot.instances = make(map[InstanceID]*ec2.Instance)
|
||||
{
|
||||
id := InstanceID("i-12345678")
|
||||
snapshot.instances[id] = &ec2.Instance{InstanceId: id.awsString()}
|
||||
}
|
||||
{
|
||||
id := InstanceID("i-23456789")
|
||||
snapshot.instances[id] = &ec2.Instance{InstanceId: id.awsString()}
|
||||
}
|
||||
|
||||
instances := snapshot.FindInstances([]InstanceID{InstanceID("i-12345678"), InstanceID("i-23456789"), InstanceID("i-00000000")})
|
||||
if len(instances) != 2 {
|
||||
t.Errorf("findInstances returned %d results, expected 2", len(instances))
|
||||
}
|
||||
|
||||
for _, id := range []InstanceID{InstanceID("i-12345678"), InstanceID("i-23456789")} {
|
||||
i := instances[id]
|
||||
if i == nil {
|
||||
t.Errorf("findInstances did not return %s", id)
|
||||
continue
|
||||
}
|
||||
if aws.StringValue(i.InstanceId) != string(id) {
|
||||
t.Errorf("findInstances did not return expected instanceId for %s", id)
|
||||
}
|
||||
if i != snapshot.instances[id] {
|
||||
t.Errorf("findInstances did not return expected instance (reference equality) for %s", id)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
Copyright 2015 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 aws
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// Handler for aws-sdk-go that logs all requests
|
||||
func awsHandlerLogger(req *request.Request) {
|
||||
service, name := awsServiceAndName(req)
|
||||
klog.V(4).Infof("AWS request: %s %s", service, name)
|
||||
}
|
||||
|
||||
func awsSendHandlerLogger(req *request.Request) {
|
||||
service, name := awsServiceAndName(req)
|
||||
klog.V(4).Infof("AWS API Send: %s %s %v %v", service, name, req.Operation, req.Params)
|
||||
}
|
||||
|
||||
func awsValidateResponseHandlerLogger(req *request.Request) {
|
||||
service, name := awsServiceAndName(req)
|
||||
klog.V(4).Infof("AWS API ValidateResponse: %s %s %v %v %s", service, name, req.Operation, req.Params, req.HTTPResponse.Status)
|
||||
}
|
||||
|
||||
func awsServiceAndName(req *request.Request) (string, string) {
|
||||
service := req.ClientInfo.ServiceName
|
||||
|
||||
name := "?"
|
||||
if req.Operation != nil {
|
||||
name = req.Operation.Name
|
||||
}
|
||||
return service, name
|
||||
}
|
@ -1,178 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
Copyright 2015 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 aws
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
decayIntervalSeconds = 20
|
||||
decayFraction = 0.8
|
||||
maxDelay = 60 * time.Second
|
||||
)
|
||||
|
||||
// CrossRequestRetryDelay inserts delays before AWS calls, when we are observing RequestLimitExceeded errors
|
||||
// Note that we share a CrossRequestRetryDelay across multiple AWS requests; this is a process-wide back-off,
|
||||
// whereas the aws-sdk-go implements a per-request exponential backoff/retry
|
||||
type CrossRequestRetryDelay struct {
|
||||
backoff Backoff
|
||||
}
|
||||
|
||||
// NewCrossRequestRetryDelay creates a new CrossRequestRetryDelay
|
||||
func NewCrossRequestRetryDelay() *CrossRequestRetryDelay {
|
||||
c := &CrossRequestRetryDelay{}
|
||||
c.backoff.init(decayIntervalSeconds, decayFraction, maxDelay)
|
||||
return c
|
||||
}
|
||||
|
||||
// BeforeSign is added to the Sign chain; called before each request
|
||||
func (c *CrossRequestRetryDelay) BeforeSign(r *request.Request) {
|
||||
now := time.Now()
|
||||
delay := c.backoff.ComputeDelayForRequest(now)
|
||||
if delay > 0 {
|
||||
klog.Warningf("Inserting delay before AWS request (%s) to avoid RequestLimitExceeded: %s",
|
||||
describeRequest(r), delay.String())
|
||||
|
||||
if sleepFn := r.Config.SleepDelay; sleepFn != nil {
|
||||
// Support SleepDelay for backwards compatibility
|
||||
sleepFn(delay)
|
||||
} else if err := aws.SleepWithContext(r.Context(), delay); err != nil {
|
||||
r.Error = awserr.New(request.CanceledErrorCode, "request context canceled", err)
|
||||
r.Retryable = aws.Bool(false)
|
||||
return
|
||||
}
|
||||
|
||||
// Avoid clock skew problems
|
||||
r.Time = now
|
||||
}
|
||||
}
|
||||
|
||||
// Return the operation name, for use in log messages and metrics
|
||||
func operationName(r *request.Request) string {
|
||||
name := "?"
|
||||
if r.Operation != nil {
|
||||
name = r.Operation.Name
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// Return a user-friendly string describing the request, for use in log messages
|
||||
func describeRequest(r *request.Request) string {
|
||||
service := r.ClientInfo.ServiceName
|
||||
return service + "::" + operationName(r)
|
||||
}
|
||||
|
||||
// AfterRetry is added to the AfterRetry chain; called after any error
|
||||
func (c *CrossRequestRetryDelay) AfterRetry(r *request.Request) {
|
||||
if r.Error == nil {
|
||||
return
|
||||
}
|
||||
awsError, ok := r.Error.(awserr.Error)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if awsError.Code() == "RequestLimitExceeded" {
|
||||
c.backoff.ReportError()
|
||||
recordAWSThrottlesMetric(operationName(r))
|
||||
klog.Warningf("Got RequestLimitExceeded error on AWS request (%s)",
|
||||
describeRequest(r))
|
||||
}
|
||||
}
|
||||
|
||||
// Backoff manages a backoff that varies based on the recently observed failures
|
||||
type Backoff struct {
|
||||
decayIntervalSeconds int64
|
||||
decayFraction float64
|
||||
maxDelay time.Duration
|
||||
|
||||
mutex sync.Mutex
|
||||
|
||||
// We count all requests & the number of requests which hit a
|
||||
// RequestLimit. We only really care about 'recent' requests, so we
|
||||
// decay the counts exponentially to bias towards recent values.
|
||||
countErrorsRequestLimit float32
|
||||
countRequests float32
|
||||
lastDecay int64
|
||||
}
|
||||
|
||||
func (b *Backoff) init(decayIntervalSeconds int, decayFraction float64, maxDelay time.Duration) {
|
||||
b.lastDecay = time.Now().Unix()
|
||||
// Bias so that if the first request hits the limit we don't immediately apply the full delay
|
||||
b.countRequests = 4
|
||||
b.decayIntervalSeconds = int64(decayIntervalSeconds)
|
||||
b.decayFraction = decayFraction
|
||||
b.maxDelay = maxDelay
|
||||
}
|
||||
|
||||
// ComputeDelayForRequest computes the delay required for a request, also
|
||||
// updates internal state to count this request
|
||||
func (b *Backoff) ComputeDelayForRequest(now time.Time) time.Duration {
|
||||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
|
||||
// Apply exponential decay to the counters
|
||||
timeDeltaSeconds := now.Unix() - b.lastDecay
|
||||
if timeDeltaSeconds > b.decayIntervalSeconds {
|
||||
intervals := float64(timeDeltaSeconds) / float64(b.decayIntervalSeconds)
|
||||
decay := float32(math.Pow(b.decayFraction, intervals))
|
||||
b.countErrorsRequestLimit *= decay
|
||||
b.countRequests *= decay
|
||||
b.lastDecay = now.Unix()
|
||||
}
|
||||
|
||||
// Count this request
|
||||
b.countRequests += 1.0
|
||||
|
||||
// Compute the failure rate
|
||||
errorFraction := float32(0.0)
|
||||
if b.countRequests > 0.5 {
|
||||
// Avoid tiny residuals & rounding errors
|
||||
errorFraction = b.countErrorsRequestLimit / b.countRequests
|
||||
}
|
||||
|
||||
// Ignore a low fraction of errors
|
||||
// This also allows them to time-out
|
||||
if errorFraction < 0.1 {
|
||||
return time.Duration(0)
|
||||
}
|
||||
|
||||
// Delay by the max delay multiplied by the recent error rate
|
||||
// (i.e. we apply a linear delay function)
|
||||
// TODO: This is pretty arbitrary
|
||||
delay := time.Nanosecond * time.Duration(float32(b.maxDelay.Nanoseconds())*errorFraction)
|
||||
// Round down to the nearest second for sanity
|
||||
return time.Second * time.Duration(int(delay.Seconds()))
|
||||
}
|
||||
|
||||
// ReportError is called when we observe a throttling error
|
||||
func (b *Backoff) ReportError() {
|
||||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
|
||||
b.countErrorsRequestLimit += 1.0
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
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 aws
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// There follows a group of tests for the backoff logic. There's nothing
|
||||
// particularly special about the values chosen: if we tweak the values in the
|
||||
// backoff logic then we might well have to update the tests. However the key
|
||||
// behavioural elements should remain (e.g. no errors => no backoff), and these
|
||||
// are each tested by one of the tests below.
|
||||
|
||||
// Test that we don't apply any delays when there are no errors
|
||||
func TestBackoffNoErrors(t *testing.T) {
|
||||
b := &Backoff{}
|
||||
b.init(decayIntervalSeconds, decayFraction, maxDelay)
|
||||
|
||||
now := time.Now()
|
||||
for i := 0; i < 100; i++ {
|
||||
d := b.ComputeDelayForRequest(now)
|
||||
if d.Nanoseconds() != 0 {
|
||||
t.Fatalf("unexpected delay during no-error case")
|
||||
}
|
||||
now = now.Add(time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that we always apply a delay when there are errors, and also that we
|
||||
// don't "flap" - that our own delay doesn't cause us to oscillate between
|
||||
// delay and no-delay.
|
||||
func TestBackoffAllErrors(t *testing.T) {
|
||||
b := &Backoff{}
|
||||
b.init(decayIntervalSeconds, decayFraction, maxDelay)
|
||||
|
||||
now := time.Now()
|
||||
// Warm up
|
||||
for i := 0; i < 10; i++ {
|
||||
_ = b.ComputeDelayForRequest(now)
|
||||
b.ReportError()
|
||||
now = now.Add(time.Second)
|
||||
}
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
d := b.ComputeDelayForRequest(now)
|
||||
b.ReportError()
|
||||
if d.Seconds() < 5 {
|
||||
t.Fatalf("unexpected short-delay during all-error case: %v", d)
|
||||
}
|
||||
t.Logf("delay @%d %v", i, d)
|
||||
now = now.Add(d)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that we do come close to our max delay, when we see all errors at 1
|
||||
// second intervals (this simulates multiple concurrent requests, because we
|
||||
// don't wait for delay in between requests)
|
||||
func TestBackoffHitsMax(t *testing.T) {
|
||||
b := &Backoff{}
|
||||
b.init(decayIntervalSeconds, decayFraction, maxDelay)
|
||||
|
||||
now := time.Now()
|
||||
for i := 0; i < 100; i++ {
|
||||
_ = b.ComputeDelayForRequest(now)
|
||||
b.ReportError()
|
||||
now = now.Add(time.Second)
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
d := b.ComputeDelayForRequest(now)
|
||||
b.ReportError()
|
||||
if float32(d.Nanoseconds()) < (float32(maxDelay.Nanoseconds()) * 0.95) {
|
||||
t.Fatalf("expected delay to be >= 95 percent of max delay, was %v", d)
|
||||
}
|
||||
t.Logf("delay @%d %v", i, d)
|
||||
now = now.Add(time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that after a phase of errors, we eventually stop applying a delay once there are
|
||||
// no more errors.
|
||||
func TestBackoffRecovers(t *testing.T) {
|
||||
b := &Backoff{}
|
||||
b.init(decayIntervalSeconds, decayFraction, maxDelay)
|
||||
|
||||
now := time.Now()
|
||||
|
||||
// Phase of all-errors
|
||||
for i := 0; i < 100; i++ {
|
||||
_ = b.ComputeDelayForRequest(now)
|
||||
b.ReportError()
|
||||
now = now.Add(time.Second)
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
d := b.ComputeDelayForRequest(now)
|
||||
b.ReportError()
|
||||
if d.Seconds() < 5 {
|
||||
t.Fatalf("unexpected short-delay during all-error phase: %v", d)
|
||||
}
|
||||
t.Logf("error phase delay @%d %v", i, d)
|
||||
now = now.Add(time.Second)
|
||||
}
|
||||
|
||||
// Phase of no errors
|
||||
for i := 0; i < 100; i++ {
|
||||
_ = b.ComputeDelayForRequest(now)
|
||||
now = now.Add(3 * time.Second)
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
d := b.ComputeDelayForRequest(now)
|
||||
if d.Seconds() != 0 {
|
||||
t.Fatalf("unexpected delay during error recovery phase: %v", d)
|
||||
}
|
||||
t.Logf("no-error phase delay @%d %v", i, d)
|
||||
now = now.Add(time.Second)
|
||||
}
|
||||
}
|
@ -1,219 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
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 aws
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
)
|
||||
|
||||
// IPPermissionSet maps IP strings of strings to EC2 IpPermissions
|
||||
type IPPermissionSet map[string]*ec2.IpPermission
|
||||
|
||||
// IPPermissionPredicate is an predicate to test whether IPPermission matches some condition.
|
||||
type IPPermissionPredicate interface {
|
||||
// Test checks whether specified IPPermission matches condition.
|
||||
Test(perm *ec2.IpPermission) bool
|
||||
}
|
||||
|
||||
// NewIPPermissionSet creates a new IPPermissionSet
|
||||
func NewIPPermissionSet(items ...*ec2.IpPermission) IPPermissionSet {
|
||||
s := make(IPPermissionSet)
|
||||
s.Insert(items...)
|
||||
return s
|
||||
}
|
||||
|
||||
// Ungroup splits permissions out into individual permissions
|
||||
// EC2 will combine permissions with the same port but different SourceRanges together, for example
|
||||
// We ungroup them so we can process them
|
||||
func (s IPPermissionSet) Ungroup() IPPermissionSet {
|
||||
l := []*ec2.IpPermission{}
|
||||
for _, p := range s.List() {
|
||||
if len(p.IpRanges) <= 1 {
|
||||
l = append(l, p)
|
||||
continue
|
||||
}
|
||||
for _, ipRange := range p.IpRanges {
|
||||
c := &ec2.IpPermission{}
|
||||
*c = *p
|
||||
c.IpRanges = []*ec2.IpRange{ipRange}
|
||||
l = append(l, c)
|
||||
}
|
||||
}
|
||||
|
||||
l2 := []*ec2.IpPermission{}
|
||||
for _, p := range l {
|
||||
if len(p.UserIdGroupPairs) <= 1 {
|
||||
l2 = append(l2, p)
|
||||
continue
|
||||
}
|
||||
for _, u := range p.UserIdGroupPairs {
|
||||
c := &ec2.IpPermission{}
|
||||
*c = *p
|
||||
c.UserIdGroupPairs = []*ec2.UserIdGroupPair{u}
|
||||
l2 = append(l, c)
|
||||
}
|
||||
}
|
||||
|
||||
l3 := []*ec2.IpPermission{}
|
||||
for _, p := range l2 {
|
||||
if len(p.PrefixListIds) <= 1 {
|
||||
l3 = append(l3, p)
|
||||
continue
|
||||
}
|
||||
for _, v := range p.PrefixListIds {
|
||||
c := &ec2.IpPermission{}
|
||||
*c = *p
|
||||
c.PrefixListIds = []*ec2.PrefixListId{v}
|
||||
l3 = append(l3, c)
|
||||
}
|
||||
}
|
||||
|
||||
return NewIPPermissionSet(l3...)
|
||||
}
|
||||
|
||||
// Insert adds items to the set.
|
||||
func (s IPPermissionSet) Insert(items ...*ec2.IpPermission) {
|
||||
for _, p := range items {
|
||||
k := keyForIPPermission(p)
|
||||
s[k] = p
|
||||
}
|
||||
}
|
||||
|
||||
// Delete delete permission from the set.
|
||||
func (s IPPermissionSet) Delete(items ...*ec2.IpPermission) {
|
||||
for _, p := range items {
|
||||
k := keyForIPPermission(p)
|
||||
delete(s, k)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteIf delete permission from the set if permission matches predicate.
|
||||
func (s IPPermissionSet) DeleteIf(predicate IPPermissionPredicate) {
|
||||
for k, p := range s {
|
||||
if predicate.Test(p) {
|
||||
delete(s, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List returns the contents as a slice. Order is not defined.
|
||||
func (s IPPermissionSet) List() []*ec2.IpPermission {
|
||||
res := make([]*ec2.IpPermission, 0, len(s))
|
||||
for _, v := range s {
|
||||
res = append(res, v)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// IsSuperset returns true if and only if s is a superset of s2.
|
||||
func (s IPPermissionSet) IsSuperset(s2 IPPermissionSet) bool {
|
||||
for k := range s2 {
|
||||
_, found := s[k]
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Equal returns true if and only if s is equal (as a set) to s2.
|
||||
// Two sets are equal if their membership is identical.
|
||||
// (In practice, this means same elements, order doesn't matter)
|
||||
func (s IPPermissionSet) Equal(s2 IPPermissionSet) bool {
|
||||
return len(s) == len(s2) && s.IsSuperset(s2)
|
||||
}
|
||||
|
||||
// Difference returns a set of objects that are not in s2
|
||||
// For example:
|
||||
// s1 = {a1, a2, a3}
|
||||
// s2 = {a1, a2, a4, a5}
|
||||
// s1.Difference(s2) = {a3}
|
||||
// s2.Difference(s1) = {a4, a5}
|
||||
func (s IPPermissionSet) Difference(s2 IPPermissionSet) IPPermissionSet {
|
||||
result := NewIPPermissionSet()
|
||||
for k, v := range s {
|
||||
_, found := s2[k]
|
||||
if !found {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Len returns the size of the set.
|
||||
func (s IPPermissionSet) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func keyForIPPermission(p *ec2.IpPermission) string {
|
||||
v, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error building JSON representation of ec2.IpPermission: %v", err))
|
||||
}
|
||||
return string(v)
|
||||
}
|
||||
|
||||
var _ IPPermissionPredicate = IPPermissionMatchDesc{}
|
||||
|
||||
// IPPermissionMatchDesc checks whether specific IPPermission contains description.
|
||||
type IPPermissionMatchDesc struct {
|
||||
Description string
|
||||
}
|
||||
|
||||
// Test whether specific IPPermission contains description.
|
||||
func (p IPPermissionMatchDesc) Test(perm *ec2.IpPermission) bool {
|
||||
for _, v4Range := range perm.IpRanges {
|
||||
if aws.StringValue(v4Range.Description) == p.Description {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, v6Range := range perm.Ipv6Ranges {
|
||||
if aws.StringValue(v6Range.Description) == p.Description {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, prefixListID := range perm.PrefixListIds {
|
||||
if aws.StringValue(prefixListID.Description) == p.Description {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, group := range perm.UserIdGroupPairs {
|
||||
if aws.StringValue(group.Description) == p.Description {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var _ IPPermissionPredicate = IPPermissionNotMatch{}
|
||||
|
||||
// IPPermissionNotMatch is the *not* operator for Predicate
|
||||
type IPPermissionNotMatch struct {
|
||||
Predicate IPPermissionPredicate
|
||||
}
|
||||
|
||||
// Test whether specific IPPermission not match the embed predicate.
|
||||
func (p IPPermissionNotMatch) Test(perm *ec2.IpPermission) bool {
|
||||
return !p.Predicate.Test(perm)
|
||||
}
|
@ -1,315 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
Copyright 2017 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 aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
)
|
||||
|
||||
// TagNameKubernetesClusterPrefix is the tag name we use to differentiate multiple
|
||||
// logically independent clusters running in the same AZ.
|
||||
// The tag key = TagNameKubernetesClusterPrefix + clusterID
|
||||
// The tag value is an ownership value
|
||||
const TagNameKubernetesClusterPrefix = "kubernetes.io/cluster/"
|
||||
|
||||
// TagNameKubernetesClusterLegacy is the legacy tag name we use to differentiate multiple
|
||||
// logically independent clusters running in the same AZ. The problem with it was that it
|
||||
// did not allow shared resources.
|
||||
const TagNameKubernetesClusterLegacy = "KubernetesCluster"
|
||||
|
||||
// ResourceLifecycle is the cluster lifecycle state used in tagging
|
||||
type ResourceLifecycle string
|
||||
|
||||
const (
|
||||
// ResourceLifecycleOwned is the value we use when tagging resources to indicate
|
||||
// that the resource is considered owned and managed by the cluster,
|
||||
// and in particular that the lifecycle is tied to the lifecycle of the cluster.
|
||||
ResourceLifecycleOwned = "owned"
|
||||
// ResourceLifecycleShared is the value we use when tagging resources to indicate
|
||||
// that the resource is shared between multiple clusters, and should not be destroyed
|
||||
// if the cluster is destroyed.
|
||||
ResourceLifecycleShared = "shared"
|
||||
)
|
||||
|
||||
type awsTagging struct {
|
||||
// ClusterID is our cluster identifier: we tag AWS resources with this value,
|
||||
// and thus we can run two independent clusters in the same VPC or subnets.
|
||||
// This gives us similar functionality to GCE projects.
|
||||
ClusterID string
|
||||
|
||||
// usesLegacyTags is true if we are using the legacy TagNameKubernetesClusterLegacy tags
|
||||
usesLegacyTags bool
|
||||
}
|
||||
|
||||
func (t *awsTagging) init(legacyClusterID string, clusterID string) error {
|
||||
if legacyClusterID != "" {
|
||||
if clusterID != "" && legacyClusterID != clusterID {
|
||||
return fmt.Errorf("clusterID tags did not match: %q vs %q", clusterID, legacyClusterID)
|
||||
}
|
||||
t.usesLegacyTags = true
|
||||
clusterID = legacyClusterID
|
||||
}
|
||||
|
||||
t.ClusterID = clusterID
|
||||
|
||||
if clusterID != "" {
|
||||
klog.Infof("AWS cloud filtering on ClusterID: %v", clusterID)
|
||||
} else {
|
||||
return fmt.Errorf("AWS cloud failed to find ClusterID")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Extracts a clusterID from the given tags, if one is present
|
||||
// If no clusterID is found, returns "", nil
|
||||
// If multiple (different) clusterIDs are found, returns an error
|
||||
func (t *awsTagging) initFromTags(tags []*ec2.Tag) error {
|
||||
legacyClusterID, newClusterID, err := findClusterIDs(tags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if legacyClusterID == "" && newClusterID == "" {
|
||||
klog.Errorf("Tag %q nor %q not found; Kubernetes may behave unexpectedly.", TagNameKubernetesClusterLegacy, TagNameKubernetesClusterPrefix+"...")
|
||||
}
|
||||
|
||||
return t.init(legacyClusterID, newClusterID)
|
||||
}
|
||||
|
||||
// Extracts the legacy & new cluster ids from the given tags, if they are present
|
||||
// If duplicate tags are found, returns an error
|
||||
func findClusterIDs(tags []*ec2.Tag) (string, string, error) {
|
||||
legacyClusterID := ""
|
||||
newClusterID := ""
|
||||
|
||||
for _, tag := range tags {
|
||||
tagKey := aws.StringValue(tag.Key)
|
||||
if strings.HasPrefix(tagKey, TagNameKubernetesClusterPrefix) {
|
||||
id := strings.TrimPrefix(tagKey, TagNameKubernetesClusterPrefix)
|
||||
if newClusterID != "" {
|
||||
return "", "", fmt.Errorf("Found multiple cluster tags with prefix %s (%q and %q)", TagNameKubernetesClusterPrefix, newClusterID, id)
|
||||
}
|
||||
newClusterID = id
|
||||
}
|
||||
|
||||
if tagKey == TagNameKubernetesClusterLegacy {
|
||||
id := aws.StringValue(tag.Value)
|
||||
if legacyClusterID != "" {
|
||||
return "", "", fmt.Errorf("Found multiple %s tags (%q and %q)", TagNameKubernetesClusterLegacy, legacyClusterID, id)
|
||||
}
|
||||
legacyClusterID = id
|
||||
}
|
||||
}
|
||||
|
||||
return legacyClusterID, newClusterID, nil
|
||||
}
|
||||
|
||||
func (t *awsTagging) clusterTagKey() string {
|
||||
return TagNameKubernetesClusterPrefix + t.ClusterID
|
||||
}
|
||||
|
||||
func (t *awsTagging) hasClusterTag(tags []*ec2.Tag) bool {
|
||||
// if the clusterID is not configured -- we consider all instances.
|
||||
if len(t.ClusterID) == 0 {
|
||||
return true
|
||||
}
|
||||
clusterTagKey := t.clusterTagKey()
|
||||
for _, tag := range tags {
|
||||
tagKey := aws.StringValue(tag.Key)
|
||||
// For 1.6, we continue to recognize the legacy tags, for the 1.5 -> 1.6 upgrade
|
||||
// Note that we want to continue traversing tag list if we see a legacy tag with value != ClusterID
|
||||
if (tagKey == TagNameKubernetesClusterLegacy) && (aws.StringValue(tag.Value) == t.ClusterID) {
|
||||
return true
|
||||
}
|
||||
if tagKey == clusterTagKey {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *awsTagging) hasNoClusterPrefixTag(tags []*ec2.Tag) bool {
|
||||
for _, tag := range tags {
|
||||
if strings.HasPrefix(aws.StringValue(tag.Key), TagNameKubernetesClusterPrefix) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Ensure that a resource has the correct tags
|
||||
// If it has no tags, we assume that this was a problem caused by an error in between creation and tagging,
|
||||
// and we add the tags. If it has a different cluster's tags, that is an error.
|
||||
func (t *awsTagging) readRepairClusterTags(client EC2, resourceID string, lifecycle ResourceLifecycle, additionalTags map[string]string, observedTags []*ec2.Tag) error {
|
||||
actualTagMap := make(map[string]string)
|
||||
for _, tag := range observedTags {
|
||||
actualTagMap[aws.StringValue(tag.Key)] = aws.StringValue(tag.Value)
|
||||
}
|
||||
|
||||
expectedTags := t.buildTags(lifecycle, additionalTags)
|
||||
|
||||
addTags := make(map[string]string)
|
||||
for k, expected := range expectedTags {
|
||||
actual := actualTagMap[k]
|
||||
if actual == expected {
|
||||
continue
|
||||
}
|
||||
if actual == "" {
|
||||
klog.Warningf("Resource %q was missing expected cluster tag %q. Will add (with value %q)", resourceID, k, expected)
|
||||
addTags[k] = expected
|
||||
} else {
|
||||
return fmt.Errorf("resource %q has tag belonging to another cluster: %q=%q (expected %q)", resourceID, k, actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
if len(addTags) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := t.createTags(client, resourceID, lifecycle, addTags); err != nil {
|
||||
return fmt.Errorf("error adding missing tags to resource %q: %q", resourceID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createTags calls EC2 CreateTags, but adds retry-on-failure logic
|
||||
// We retry mainly because if we create an object, we cannot tag it until it is "fully created" (eventual consistency)
|
||||
// The error code varies though (depending on what we are tagging), so we simply retry on all errors
|
||||
func (t *awsTagging) createTags(client EC2, resourceID string, lifecycle ResourceLifecycle, additionalTags map[string]string) error {
|
||||
tags := t.buildTags(lifecycle, additionalTags)
|
||||
|
||||
if tags == nil || len(tags) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var awsTags []*ec2.Tag
|
||||
for k, v := range tags {
|
||||
tag := &ec2.Tag{
|
||||
Key: aws.String(k),
|
||||
Value: aws.String(v),
|
||||
}
|
||||
awsTags = append(awsTags, tag)
|
||||
}
|
||||
|
||||
backoff := wait.Backoff{
|
||||
Duration: createTagInitialDelay,
|
||||
Factor: createTagFactor,
|
||||
Steps: createTagSteps,
|
||||
}
|
||||
request := &ec2.CreateTagsInput{}
|
||||
request.Resources = []*string{&resourceID}
|
||||
request.Tags = awsTags
|
||||
|
||||
var lastErr error
|
||||
err := wait.ExponentialBackoff(backoff, func() (bool, error) {
|
||||
_, err := client.CreateTags(request)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// We could check that the error is retryable, but the error code changes based on what we are tagging
|
||||
// SecurityGroup: InvalidGroup.NotFound
|
||||
klog.V(2).Infof("Failed to create tags; will retry. Error was %q", err)
|
||||
lastErr = err
|
||||
return false, nil
|
||||
})
|
||||
if err == wait.ErrWaitTimeout {
|
||||
// return real CreateTags error instead of timeout
|
||||
err = lastErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Add additional filters, to match on our tags
|
||||
// This lets us run multiple k8s clusters in a single EC2 AZ
|
||||
func (t *awsTagging) addFilters(filters []*ec2.Filter) []*ec2.Filter {
|
||||
// if there are no clusterID configured - no filtering by special tag names
|
||||
// should be applied to revert to legacy behaviour.
|
||||
if len(t.ClusterID) == 0 {
|
||||
if len(filters) == 0 {
|
||||
// We can't pass a zero-length Filters to AWS (it's an error)
|
||||
// So if we end up with no filters; just return nil
|
||||
return nil
|
||||
}
|
||||
return filters
|
||||
}
|
||||
|
||||
f := newEc2Filter("tag-key", t.clusterTagKey())
|
||||
filters = append(filters, f)
|
||||
return filters
|
||||
}
|
||||
|
||||
// Add additional filters, to match on our tags. This uses the tag for legacy
|
||||
// 1.5 -> 1.6 clusters and exists for backwards compatibility
|
||||
//
|
||||
// This lets us run multiple k8s clusters in a single EC2 AZ
|
||||
func (t *awsTagging) addLegacyFilters(filters []*ec2.Filter) []*ec2.Filter {
|
||||
// if there are no clusterID configured - no filtering by special tag names
|
||||
// should be applied to revert to legacy behaviour.
|
||||
if len(t.ClusterID) == 0 {
|
||||
if len(filters) == 0 {
|
||||
// We can't pass a zero-length Filters to AWS (it's an error)
|
||||
// So if we end up with no filters; just return nil
|
||||
return nil
|
||||
}
|
||||
return filters
|
||||
}
|
||||
|
||||
f := newEc2Filter(fmt.Sprintf("tag:%s", TagNameKubernetesClusterLegacy), t.ClusterID)
|
||||
|
||||
// We can't pass a zero-length Filters to AWS (it's an error)
|
||||
// So if we end up with no filters; we need to return nil
|
||||
filters = append(filters, f)
|
||||
return filters
|
||||
}
|
||||
|
||||
func (t *awsTagging) buildTags(lifecycle ResourceLifecycle, additionalTags map[string]string) map[string]string {
|
||||
tags := make(map[string]string)
|
||||
for k, v := range additionalTags {
|
||||
tags[k] = v
|
||||
}
|
||||
|
||||
// no clusterID is a sign of misconfigured cluster, but we can't be tagging the resources with empty
|
||||
// strings
|
||||
if len(t.ClusterID) == 0 {
|
||||
return tags
|
||||
}
|
||||
|
||||
// We only create legacy tags if we are using legacy tags, i.e. if we have seen a legacy tag on our instance
|
||||
if t.usesLegacyTags {
|
||||
tags[TagNameKubernetesClusterLegacy] = t.ClusterID
|
||||
}
|
||||
tags[t.clusterTagKey()] = string(lifecycle)
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
func (t *awsTagging) clusterID() string {
|
||||
return t.ClusterID
|
||||
}
|
@ -1,235 +0,0 @@
|
||||
//go:build !nolegacyroviders
|
||||
// +build !nolegacyroviders
|
||||
|
||||
/*
|
||||
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 aws
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFilterTags(t *testing.T) {
|
||||
awsServices := NewFakeAWSServices(TestClusterID)
|
||||
c, err := newAWSCloud(CloudConfig{}, awsServices)
|
||||
if err != nil {
|
||||
t.Errorf("Error building aws cloud: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if c.tagging.ClusterID != TestClusterID {
|
||||
t.Errorf("unexpected ClusterID: %v", c.tagging.ClusterID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindClusterID(t *testing.T) {
|
||||
grid := []struct {
|
||||
Tags map[string]string
|
||||
ExpectedNew string
|
||||
ExpectedLegacy string
|
||||
ExpectError bool
|
||||
}{
|
||||
{
|
||||
Tags: map[string]string{},
|
||||
},
|
||||
{
|
||||
Tags: map[string]string{
|
||||
TagNameKubernetesClusterLegacy: "a",
|
||||
},
|
||||
ExpectedLegacy: "a",
|
||||
},
|
||||
{
|
||||
Tags: map[string]string{
|
||||
TagNameKubernetesClusterPrefix + "a": "owned",
|
||||
},
|
||||
ExpectedNew: "a",
|
||||
},
|
||||
{
|
||||
Tags: map[string]string{
|
||||
TagNameKubernetesClusterPrefix + "a": "shared",
|
||||
},
|
||||
ExpectedNew: "a",
|
||||
},
|
||||
{
|
||||
Tags: map[string]string{
|
||||
TagNameKubernetesClusterPrefix + "a": "",
|
||||
},
|
||||
ExpectedNew: "a",
|
||||
},
|
||||
{
|
||||
Tags: map[string]string{
|
||||
TagNameKubernetesClusterLegacy: "a",
|
||||
TagNameKubernetesClusterPrefix + "a": "",
|
||||
},
|
||||
ExpectedLegacy: "a",
|
||||
ExpectedNew: "a",
|
||||
},
|
||||
{
|
||||
Tags: map[string]string{
|
||||
TagNameKubernetesClusterPrefix + "a": "",
|
||||
TagNameKubernetesClusterPrefix + "b": "",
|
||||
},
|
||||
ExpectError: true,
|
||||
},
|
||||
}
|
||||
for _, g := range grid {
|
||||
var ec2Tags []*ec2.Tag
|
||||
for k, v := range g.Tags {
|
||||
ec2Tags = append(ec2Tags, &ec2.Tag{Key: aws.String(k), Value: aws.String(v)})
|
||||
}
|
||||
actualLegacy, actualNew, err := findClusterIDs(ec2Tags)
|
||||
if g.ExpectError {
|
||||
if err == nil {
|
||||
t.Errorf("expected error for tags %v", g.Tags)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error for tags %v: %v", g.Tags, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if g.ExpectedNew != actualNew {
|
||||
t.Errorf("unexpected new clusterid for tags %v: %s vs %s", g.Tags, g.ExpectedNew, actualNew)
|
||||
continue
|
||||
}
|
||||
|
||||
if g.ExpectedLegacy != actualLegacy {
|
||||
t.Errorf("unexpected new clusterid for tags %v: %s vs %s", g.Tags, g.ExpectedLegacy, actualLegacy)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasClusterTag(t *testing.T) {
|
||||
awsServices := NewFakeAWSServices(TestClusterID)
|
||||
c, err := newAWSCloud(CloudConfig{}, awsServices)
|
||||
if err != nil {
|
||||
t.Errorf("Error building aws cloud: %v", err)
|
||||
return
|
||||
}
|
||||
grid := []struct {
|
||||
Tags map[string]string
|
||||
Expected bool
|
||||
}{
|
||||
{
|
||||
Tags: map[string]string{},
|
||||
},
|
||||
{
|
||||
Tags: map[string]string{
|
||||
TagNameKubernetesClusterLegacy: TestClusterID,
|
||||
},
|
||||
Expected: true,
|
||||
},
|
||||
{
|
||||
Tags: map[string]string{
|
||||
TagNameKubernetesClusterLegacy: "a",
|
||||
},
|
||||
Expected: false,
|
||||
},
|
||||
{
|
||||
Tags: map[string]string{
|
||||
TagNameKubernetesClusterPrefix + TestClusterID: "owned",
|
||||
},
|
||||
Expected: true,
|
||||
},
|
||||
{
|
||||
Tags: map[string]string{
|
||||
TagNameKubernetesClusterPrefix + TestClusterID: "",
|
||||
},
|
||||
Expected: true,
|
||||
},
|
||||
{
|
||||
Tags: map[string]string{
|
||||
TagNameKubernetesClusterLegacy: "a",
|
||||
TagNameKubernetesClusterPrefix + TestClusterID: "shared",
|
||||
},
|
||||
Expected: true,
|
||||
},
|
||||
{
|
||||
Tags: map[string]string{
|
||||
TagNameKubernetesClusterPrefix + TestClusterID: "shared",
|
||||
TagNameKubernetesClusterPrefix + "b": "shared",
|
||||
},
|
||||
Expected: true,
|
||||
},
|
||||
}
|
||||
for _, g := range grid {
|
||||
var ec2Tags []*ec2.Tag
|
||||
for k, v := range g.Tags {
|
||||
ec2Tags = append(ec2Tags, &ec2.Tag{Key: aws.String(k), Value: aws.String(v)})
|
||||
}
|
||||
result := c.tagging.hasClusterTag(ec2Tags)
|
||||
if result != g.Expected {
|
||||
t.Errorf("Unexpected result for tags %v: %t", g.Tags, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasNoClusterPrefixTag(t *testing.T) {
|
||||
awsServices := NewFakeAWSServices(TestClusterID)
|
||||
c, err := newAWSCloud(CloudConfig{}, awsServices)
|
||||
if err != nil {
|
||||
t.Errorf("Error building aws cloud: %v", err)
|
||||
return
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
tags []*ec2.Tag
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "no tags",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "no cluster tags",
|
||||
tags: []*ec2.Tag{
|
||||
{
|
||||
Key: aws.String("not a cluster tag"),
|
||||
Value: aws.String("true"),
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "contains cluster tags",
|
||||
tags: []*ec2.Tag{
|
||||
{
|
||||
Key: aws.String("tag1"),
|
||||
Value: aws.String("value1"),
|
||||
},
|
||||
{
|
||||
Key: aws.String("kubernetes.io/cluster/test.cluster"),
|
||||
Value: aws.String("owned"),
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, c.tagging.hasNoClusterPrefixTag(tt.tags))
|
||||
})
|
||||
}
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
//go:build !providerless
|
||||
// +build !providerless
|
||||
|
||||
/*
|
||||
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 aws
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
csimigration "k8s.io/csi-translation-lib/plugins"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// EBSVolumeID represents the ID of the volume in the AWS API, e.g.
|
||||
// vol-12345678 The "traditional" format is "vol-12345678" A new longer format
|
||||
// is also being introduced: "vol-12345678abcdef01" We should not assume
|
||||
// anything about the length or format, though it seems reasonable to assume
|
||||
// that volumes will continue to start with "vol-".
|
||||
type EBSVolumeID string
|
||||
|
||||
func (i EBSVolumeID) awsString() *string {
|
||||
return aws.String(string(i))
|
||||
}
|
||||
|
||||
// KubernetesVolumeID represents the id for a volume in the kubernetes API;
|
||||
// a few forms are recognized:
|
||||
// - aws://<zone>/<awsVolumeId>
|
||||
// - aws:///<awsVolumeId>
|
||||
// - <awsVolumeId>
|
||||
type KubernetesVolumeID string
|
||||
|
||||
// DiskInfo returns aws disk information in easy to use manner
|
||||
type diskInfo struct {
|
||||
ec2Instance *ec2.Instance
|
||||
nodeName types.NodeName
|
||||
volumeState string
|
||||
attachmentState string
|
||||
hasAttachment bool
|
||||
disk *awsDisk
|
||||
}
|
||||
|
||||
// MapToAWSVolumeID extracts the EBSVolumeID from the KubernetesVolumeID
|
||||
func (name KubernetesVolumeID) MapToAWSVolumeID() (EBSVolumeID, error) {
|
||||
awsID, err := csimigration.KubernetesVolumeIDToEBSVolumeID(string(name))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return EBSVolumeID(awsID), nil
|
||||
}
|
||||
|
||||
// GetAWSVolumeID converts a Kubernetes volume ID to an AWS volume ID
|
||||
func GetAWSVolumeID(kubeVolumeID string) (string, error) {
|
||||
kid := KubernetesVolumeID(kubeVolumeID)
|
||||
awsID, err := kid.MapToAWSVolumeID()
|
||||
return string(awsID), err
|
||||
}
|
||||
|
||||
func (c *Cloud) checkIfAttachedToNode(diskName KubernetesVolumeID, nodeName types.NodeName) (*diskInfo, bool, error) {
|
||||
disk, err := newAWSDisk(c, diskName)
|
||||
|
||||
if err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
|
||||
awsDiskInfo := &diskInfo{
|
||||
disk: disk,
|
||||
}
|
||||
|
||||
info, err := disk.describeVolume()
|
||||
|
||||
if err != nil {
|
||||
klog.Warningf("Error describing volume %s with %v", diskName, err)
|
||||
awsDiskInfo.volumeState = "unknown"
|
||||
return awsDiskInfo, false, err
|
||||
}
|
||||
|
||||
awsDiskInfo.volumeState = aws.StringValue(info.State)
|
||||
|
||||
if len(info.Attachments) > 0 {
|
||||
attachment := info.Attachments[0]
|
||||
awsDiskInfo.attachmentState = aws.StringValue(attachment.State)
|
||||
instanceID := aws.StringValue(attachment.InstanceId)
|
||||
instanceInfo, err := c.getInstanceByID(instanceID)
|
||||
|
||||
// This should never happen but if it does it could mean there was a race and instance
|
||||
// has been deleted
|
||||
if err != nil {
|
||||
fetchErr := fmt.Errorf("error fetching instance %s for volume %s", instanceID, diskName)
|
||||
klog.Warning(fetchErr)
|
||||
return awsDiskInfo, false, fetchErr
|
||||
}
|
||||
|
||||
awsDiskInfo.ec2Instance = instanceInfo
|
||||
awsDiskInfo.nodeName = mapInstanceToNodeName(instanceInfo)
|
||||
awsDiskInfo.hasAttachment = true
|
||||
if awsDiskInfo.nodeName == nodeName {
|
||||
return awsDiskInfo, true, nil
|
||||
}
|
||||
}
|
||||
return awsDiskInfo, false, nil
|
||||
}
|
@ -11,7 +11,6 @@ require (
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.20
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.4.2
|
||||
github.com/GoogleCloudPlatform/k8s-cloud-provider v1.18.1-0.20220218231025-f11817397a1b
|
||||
github.com/aws/aws-sdk-go v1.44.147
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/rubiojr/go-vhd v0.0.0-20200706105327-02e210299021
|
||||
@ -26,7 +25,6 @@ require (
|
||||
k8s.io/client-go v0.0.0
|
||||
k8s.io/cloud-provider v0.0.0
|
||||
k8s.io/component-base v0.0.0
|
||||
k8s.io/csi-translation-lib v0.0.0
|
||||
k8s.io/klog/v2 v2.90.1
|
||||
k8s.io/utils v0.0.0-20230209194617-a36077c30491
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
@ -59,7 +57,6 @@ require (
|
||||
github.com/google/gofuzz v1.1.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
@ -74,7 +71,6 @@ require (
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
@ -103,7 +99,6 @@ replace (
|
||||
k8s.io/component-base => ../component-base
|
||||
k8s.io/component-helpers => ../component-helpers
|
||||
k8s.io/controller-manager => ../controller-manager
|
||||
k8s.io/csi-translation-lib => ../csi-translation-lib
|
||||
k8s.io/kms => ../kms
|
||||
k8s.io/legacy-cloud-providers => ../legacy-cloud-providers
|
||||
)
|
||||
|
18
staging/src/k8s.io/legacy-cloud-providers/go.sum
generated
18
staging/src/k8s.io/legacy-cloud-providers/go.sum
generated
@ -77,8 +77,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/aws/aws-sdk-go v1.44.147 h1:C/YQv0QAvRHio4cESBTFGh8aI/JM9VdRislDIOz/Dx4=
|
||||
github.com/aws/aws-sdk-go v1.44.147/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
@ -242,10 +240,6 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
@ -337,7 +331,6 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
@ -356,7 +349,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
@ -411,7 +403,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -453,8 +444,6 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@ -486,7 +475,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -544,14 +532,10 @@ golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -563,7 +547,6 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -624,7 +607,6 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -17,20 +17,7 @@ limitations under the License.
|
||||
package aws
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/autoscaling"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/kubernetes/test/e2e/framework"
|
||||
e2epv "k8s.io/kubernetes/test/e2e/framework/pv"
|
||||
awscloud "k8s.io/legacy-cloud-providers/aws"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -38,162 +25,13 @@ func init() {
|
||||
}
|
||||
|
||||
func newProvider() (framework.ProviderInterface, error) {
|
||||
if framework.TestContext.CloudConfig.Zone == "" {
|
||||
framework.Logf("Warning: gce-zone not specified! Some tests that use the AWS SDK may select the wrong region and fail.")
|
||||
}
|
||||
return &Provider{}, nil
|
||||
}
|
||||
|
||||
// Provider is a structure to handle AWS clouds for e2e testing
|
||||
// It does not do anything useful, it's there only to provide valid
|
||||
// --provider=aws cmdline option to allow testing of CSI migration
|
||||
// tests of kubernetes.io/aws-ebs volume plugin.
|
||||
type Provider struct {
|
||||
framework.NullProvider
|
||||
}
|
||||
|
||||
// ResizeGroup resizes an instance group
|
||||
func (p *Provider) ResizeGroup(group string, size int32) error {
|
||||
awsSession, err := session.NewSession()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := autoscaling.New(awsSession)
|
||||
return awscloud.ResizeInstanceGroup(client, group, int(size))
|
||||
}
|
||||
|
||||
// GroupSize returns the size of an instance group
|
||||
func (p *Provider) GroupSize(group string) (int, error) {
|
||||
awsSession, err := session.NewSession()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
client := autoscaling.New(awsSession)
|
||||
instanceGroup, err := awscloud.DescribeInstanceGroup(client, group)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("error describing instance group: %w", err)
|
||||
}
|
||||
if instanceGroup == nil {
|
||||
return -1, fmt.Errorf("instance group not found: %s", group)
|
||||
}
|
||||
return instanceGroup.CurrentSize()
|
||||
}
|
||||
|
||||
// DeleteNode deletes a node which is specified as the argument
|
||||
func (p *Provider) DeleteNode(node *v1.Node) error {
|
||||
client := newAWSClient("")
|
||||
|
||||
instanceID, err := awscloud.KubernetesInstanceID(node.Spec.ProviderID).MapToAWSInstanceID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := &ec2.TerminateInstancesInput{
|
||||
InstanceIds: []*string{
|
||||
aws.String(string(instanceID)),
|
||||
},
|
||||
}
|
||||
_, err = client.TerminateInstances(req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *Provider) CreateShare() (string, string, string, error) {
|
||||
return "", "", "", nil
|
||||
}
|
||||
|
||||
func (p *Provider) DeleteShare(accountName, shareName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreatePD creates a persistent volume on the specified availability zone
|
||||
func (p *Provider) CreatePD(zone string) (string, error) {
|
||||
client := newAWSClient(zone)
|
||||
request := &ec2.CreateVolumeInput{}
|
||||
request.AvailabilityZone = aws.String(zone)
|
||||
request.Size = aws.Int64(10)
|
||||
request.VolumeType = aws.String(awscloud.DefaultVolumeType)
|
||||
|
||||
// We need to tag the volume so that locked-down IAM configurations can still mount it
|
||||
if framework.TestContext.CloudConfig.ClusterTag != "" {
|
||||
clusterID := framework.TestContext.CloudConfig.ClusterTag
|
||||
|
||||
legacyTag := &ec2.Tag{
|
||||
Key: aws.String(awscloud.TagNameKubernetesClusterLegacy),
|
||||
Value: aws.String(clusterID),
|
||||
}
|
||||
|
||||
newTag := &ec2.Tag{
|
||||
Key: aws.String(awscloud.TagNameKubernetesClusterPrefix + clusterID),
|
||||
Value: aws.String(awscloud.ResourceLifecycleOwned),
|
||||
}
|
||||
|
||||
tagSpecification := &ec2.TagSpecification{
|
||||
ResourceType: aws.String(ec2.ResourceTypeVolume),
|
||||
Tags: []*ec2.Tag{legacyTag, newTag},
|
||||
}
|
||||
|
||||
request.TagSpecifications = append(request.TagSpecifications, tagSpecification)
|
||||
}
|
||||
|
||||
response, err := client.CreateVolume(request)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
az := aws.StringValue(response.AvailabilityZone)
|
||||
awsID := aws.StringValue(response.VolumeId)
|
||||
|
||||
volumeName := "aws://" + az + "/" + awsID
|
||||
return volumeName, nil
|
||||
}
|
||||
|
||||
// DeletePD deletes a persistent volume
|
||||
func (p *Provider) DeletePD(pdName string) error {
|
||||
client := newAWSClient("")
|
||||
|
||||
tokens := strings.Split(pdName, "/")
|
||||
awsVolumeID := tokens[len(tokens)-1]
|
||||
|
||||
request := &ec2.DeleteVolumeInput{VolumeId: aws.String(awsVolumeID)}
|
||||
_, err := client.DeleteVolume(request)
|
||||
if err != nil {
|
||||
if awsError, ok := err.(awserr.Error); ok && awsError.Code() == "InvalidVolume.NotFound" {
|
||||
framework.Logf("volume deletion implicitly succeeded because volume %q does not exist.", pdName)
|
||||
} else {
|
||||
return fmt.Errorf("error deleting EBS volumes: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreatePVSource creates a persistent volume source
|
||||
func (p *Provider) CreatePVSource(ctx context.Context, zone, diskName string) (*v1.PersistentVolumeSource, error) {
|
||||
return &v1.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: diskName,
|
||||
FSType: "ext3",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeletePVSource deletes a persistent volume source
|
||||
func (p *Provider) DeletePVSource(ctx context.Context, pvSource *v1.PersistentVolumeSource) error {
|
||||
return e2epv.DeletePDWithRetry(ctx, pvSource.AWSElasticBlockStore.VolumeID)
|
||||
}
|
||||
|
||||
func newAWSClient(zone string) *ec2.EC2 {
|
||||
var cfg *aws.Config
|
||||
|
||||
if zone == "" {
|
||||
zone = framework.TestContext.CloudConfig.Zone
|
||||
}
|
||||
if zone == "" {
|
||||
framework.Logf("Warning: No AWS zone configured!")
|
||||
cfg = nil
|
||||
} else {
|
||||
region := zone[:len(zone)-1]
|
||||
cfg = &aws.Config{Region: aws.String(region)}
|
||||
}
|
||||
session, err := session.NewSession()
|
||||
if err != nil {
|
||||
framework.Logf("Warning: failed to create aws session")
|
||||
}
|
||||
return ec2.New(session, cfg)
|
||||
}
|
||||
|
@ -1463,15 +1463,7 @@ type awsDriver struct {
|
||||
driverInfo storageframework.DriverInfo
|
||||
}
|
||||
|
||||
type awsVolume struct {
|
||||
volumeName string
|
||||
}
|
||||
|
||||
var _ storageframework.TestDriver = &awsDriver{}
|
||||
|
||||
var _ storageframework.PreprovisionedVolumeTestDriver = &awsDriver{}
|
||||
var _ storageframework.InlineVolumeTestDriver = &awsDriver{}
|
||||
var _ storageframework.PreprovisionedPVTestDriver = &awsDriver{}
|
||||
var _ storageframework.DynamicPVTestDriver = &awsDriver{}
|
||||
|
||||
// InitAwsDriver returns awsDriver that implements TestDriver interface
|
||||
@ -1520,40 +1512,6 @@ func (a *awsDriver) SkipUnsupportedTest(pattern storageframework.TestPattern) {
|
||||
e2eskipper.SkipUnlessProviderIs("aws")
|
||||
}
|
||||
|
||||
func (a *awsDriver) GetVolumeSource(readOnly bool, fsType string, e2evolume storageframework.TestVolume) *v1.VolumeSource {
|
||||
av, ok := e2evolume.(*awsVolume)
|
||||
if !ok {
|
||||
framework.Failf("Failed to cast test volume of type %T to the AWS test volume", e2evolume)
|
||||
}
|
||||
volSource := v1.VolumeSource{
|
||||
AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: av.volumeName,
|
||||
ReadOnly: readOnly,
|
||||
},
|
||||
}
|
||||
if fsType != "" {
|
||||
volSource.AWSElasticBlockStore.FSType = fsType
|
||||
}
|
||||
return &volSource
|
||||
}
|
||||
|
||||
func (a *awsDriver) GetPersistentVolumeSource(readOnly bool, fsType string, e2evolume storageframework.TestVolume) (*v1.PersistentVolumeSource, *v1.VolumeNodeAffinity) {
|
||||
av, ok := e2evolume.(*awsVolume)
|
||||
if !ok {
|
||||
framework.Failf("Failed to cast test volume of type %T to the AWS test volume", e2evolume)
|
||||
}
|
||||
pvSource := v1.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: av.volumeName,
|
||||
ReadOnly: readOnly,
|
||||
},
|
||||
}
|
||||
if fsType != "" {
|
||||
pvSource.AWSElasticBlockStore.FSType = fsType
|
||||
}
|
||||
return &pvSource, nil
|
||||
}
|
||||
|
||||
func (a *awsDriver) GetDynamicProvisionStorageClass(ctx context.Context, config *storageframework.PerTestConfig, fsType string) *storagev1.StorageClass {
|
||||
provisioner := "kubernetes.io/aws-ebs"
|
||||
parameters := map[string]string{}
|
||||
@ -1583,29 +1541,6 @@ func (a *awsDriver) PrepareTest(ctx context.Context, f *framework.Framework) *st
|
||||
return config
|
||||
}
|
||||
|
||||
func (a *awsDriver) CreateVolume(ctx context.Context, config *storageframework.PerTestConfig, volType storageframework.TestVolType) storageframework.TestVolume {
|
||||
zone := getInlineVolumeZone(ctx, config.Framework)
|
||||
if volType == storageframework.InlineVolume || volType == storageframework.PreprovisionedPV {
|
||||
// PD will be created in framework.TestContext.CloudConfig.Zone zone,
|
||||
// so pods should be also scheduled there.
|
||||
config.ClientNodeSelection = e2epod.NodeSelection{
|
||||
Selector: map[string]string{
|
||||
v1.LabelTopologyZone: zone,
|
||||
},
|
||||
}
|
||||
}
|
||||
ginkgo.By("creating a test aws volume")
|
||||
vname, err := e2epv.CreatePDWithRetryAndZone(ctx, zone)
|
||||
framework.ExpectNoError(err)
|
||||
return &awsVolume{
|
||||
volumeName: vname,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *awsVolume) DeleteVolume(ctx context.Context) {
|
||||
_ = e2epv.DeletePDWithRetry(ctx, v.volumeName)
|
||||
}
|
||||
|
||||
// local
|
||||
type localDriver struct {
|
||||
driverInfo storageframework.DriverInfo
|
||||
|
20556
vendor/github.com/aws/aws-sdk-go/service/autoscaling/api.go
generated
vendored
20556
vendor/github.com/aws/aws-sdk-go/service/autoscaling/api.go
generated
vendored
File diff suppressed because it is too large
Load Diff
33
vendor/github.com/aws/aws-sdk-go/service/autoscaling/doc.go
generated
vendored
33
vendor/github.com/aws/aws-sdk-go/service/autoscaling/doc.go
generated
vendored
@ -1,33 +0,0 @@
|
||||
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
|
||||
|
||||
// Package autoscaling provides the client and types for making API
|
||||
// requests to Auto Scaling.
|
||||
//
|
||||
// Amazon EC2 Auto Scaling is designed to automatically launch and terminate
|
||||
// EC2 instances based on user-defined scaling policies, scheduled actions,
|
||||
// and health checks.
|
||||
//
|
||||
// For more information, see the Amazon EC2 Auto Scaling User Guide (https://docs.aws.amazon.com/autoscaling/ec2/userguide/)
|
||||
// and the Amazon EC2 Auto Scaling API Reference (https://docs.aws.amazon.com/autoscaling/ec2/APIReference/Welcome.html).
|
||||
//
|
||||
// See https://docs.aws.amazon.com/goto/WebAPI/autoscaling-2011-01-01 for more information on this service.
|
||||
//
|
||||
// See autoscaling package documentation for more information.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/service/autoscaling/
|
||||
//
|
||||
// # Using the Client
|
||||
//
|
||||
// To contact Auto Scaling with the SDK use the New function to create
|
||||
// a new service client. With that client you can make API requests to the service.
|
||||
// These clients are safe to use concurrently.
|
||||
//
|
||||
// See the SDK's documentation for more information on how to use the SDK.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/
|
||||
//
|
||||
// See aws.Config documentation for more information on configuring SDK clients.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/aws/#Config
|
||||
//
|
||||
// See the Auto Scaling client AutoScaling for more
|
||||
// information on creating client for this service.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/service/autoscaling/#New
|
||||
package autoscaling
|
68
vendor/github.com/aws/aws-sdk-go/service/autoscaling/errors.go
generated
vendored
68
vendor/github.com/aws/aws-sdk-go/service/autoscaling/errors.go
generated
vendored
@ -1,68 +0,0 @@
|
||||
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
|
||||
|
||||
package autoscaling
|
||||
|
||||
const (
|
||||
|
||||
// ErrCodeActiveInstanceRefreshNotFoundFault for service response error code
|
||||
// "ActiveInstanceRefreshNotFound".
|
||||
//
|
||||
// The request failed because an active instance refresh for the specified Auto
|
||||
// Scaling group was not found.
|
||||
ErrCodeActiveInstanceRefreshNotFoundFault = "ActiveInstanceRefreshNotFound"
|
||||
|
||||
// ErrCodeAlreadyExistsFault for service response error code
|
||||
// "AlreadyExists".
|
||||
//
|
||||
// You already have an Auto Scaling group or launch configuration with this
|
||||
// name.
|
||||
ErrCodeAlreadyExistsFault = "AlreadyExists"
|
||||
|
||||
// ErrCodeInstanceRefreshInProgressFault for service response error code
|
||||
// "InstanceRefreshInProgress".
|
||||
//
|
||||
// The request failed because an active instance refresh operation already exists
|
||||
// for the specified Auto Scaling group.
|
||||
ErrCodeInstanceRefreshInProgressFault = "InstanceRefreshInProgress"
|
||||
|
||||
// ErrCodeInvalidNextToken for service response error code
|
||||
// "InvalidNextToken".
|
||||
//
|
||||
// The NextToken value is not valid.
|
||||
ErrCodeInvalidNextToken = "InvalidNextToken"
|
||||
|
||||
// ErrCodeLimitExceededFault for service response error code
|
||||
// "LimitExceeded".
|
||||
//
|
||||
// You have already reached a limit for your Amazon EC2 Auto Scaling resources
|
||||
// (for example, Auto Scaling groups, launch configurations, or lifecycle hooks).
|
||||
// For more information, see DescribeAccountLimits (https://docs.aws.amazon.com/autoscaling/ec2/APIReference/API_DescribeAccountLimits.html)
|
||||
// in the Amazon EC2 Auto Scaling API Reference.
|
||||
ErrCodeLimitExceededFault = "LimitExceeded"
|
||||
|
||||
// ErrCodeResourceContentionFault for service response error code
|
||||
// "ResourceContention".
|
||||
//
|
||||
// You already have a pending update to an Amazon EC2 Auto Scaling resource
|
||||
// (for example, an Auto Scaling group, instance, or load balancer).
|
||||
ErrCodeResourceContentionFault = "ResourceContention"
|
||||
|
||||
// ErrCodeResourceInUseFault for service response error code
|
||||
// "ResourceInUse".
|
||||
//
|
||||
// The operation can't be performed because the resource is in use.
|
||||
ErrCodeResourceInUseFault = "ResourceInUse"
|
||||
|
||||
// ErrCodeScalingActivityInProgressFault for service response error code
|
||||
// "ScalingActivityInProgress".
|
||||
//
|
||||
// The operation can't be performed because there are scaling activities in
|
||||
// progress.
|
||||
ErrCodeScalingActivityInProgressFault = "ScalingActivityInProgress"
|
||||
|
||||
// ErrCodeServiceLinkedRoleFailure for service response error code
|
||||
// "ServiceLinkedRoleFailure".
|
||||
//
|
||||
// The service-linked role is not yet ready for use.
|
||||
ErrCodeServiceLinkedRoleFailure = "ServiceLinkedRoleFailure"
|
||||
)
|
104
vendor/github.com/aws/aws-sdk-go/service/autoscaling/service.go
generated
vendored
104
vendor/github.com/aws/aws-sdk-go/service/autoscaling/service.go
generated
vendored
@ -1,104 +0,0 @@
|
||||
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
|
||||
|
||||
package autoscaling
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/client"
|
||||
"github.com/aws/aws-sdk-go/aws/client/metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/signer/v4"
|
||||
"github.com/aws/aws-sdk-go/private/protocol/query"
|
||||
)
|
||||
|
||||
// AutoScaling provides the API operation methods for making requests to
|
||||
// Auto Scaling. See this package's package overview docs
|
||||
// for details on the service.
|
||||
//
|
||||
// AutoScaling methods are safe to use concurrently. It is not safe to
|
||||
// modify mutate any of the struct's properties though.
|
||||
type AutoScaling struct {
|
||||
*client.Client
|
||||
}
|
||||
|
||||
// Used for custom client initialization logic
|
||||
var initClient func(*client.Client)
|
||||
|
||||
// Used for custom request initialization logic
|
||||
var initRequest func(*request.Request)
|
||||
|
||||
// Service information constants
|
||||
const (
|
||||
ServiceName = "autoscaling" // Name of service.
|
||||
EndpointsID = ServiceName // ID to lookup a service endpoint with.
|
||||
ServiceID = "Auto Scaling" // ServiceID is a unique identifier of a specific service.
|
||||
)
|
||||
|
||||
// New creates a new instance of the AutoScaling client with a session.
|
||||
// If additional configuration is needed for the client instance use the optional
|
||||
// aws.Config parameter to add your extra config.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// mySession := session.Must(session.NewSession())
|
||||
//
|
||||
// // Create a AutoScaling client from just a session.
|
||||
// svc := autoscaling.New(mySession)
|
||||
//
|
||||
// // Create a AutoScaling client with additional configuration
|
||||
// svc := autoscaling.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
|
||||
func New(p client.ConfigProvider, cfgs ...*aws.Config) *AutoScaling {
|
||||
c := p.ClientConfig(EndpointsID, cfgs...)
|
||||
if c.SigningNameDerived || len(c.SigningName) == 0 {
|
||||
c.SigningName = EndpointsID
|
||||
// No Fallback
|
||||
}
|
||||
return newClient(*c.Config, c.Handlers, c.PartitionID, c.Endpoint, c.SigningRegion, c.SigningName, c.ResolvedRegion)
|
||||
}
|
||||
|
||||
// newClient creates, initializes and returns a new service client instance.
|
||||
func newClient(cfg aws.Config, handlers request.Handlers, partitionID, endpoint, signingRegion, signingName, resolvedRegion string) *AutoScaling {
|
||||
svc := &AutoScaling{
|
||||
Client: client.New(
|
||||
cfg,
|
||||
metadata.ClientInfo{
|
||||
ServiceName: ServiceName,
|
||||
ServiceID: ServiceID,
|
||||
SigningName: signingName,
|
||||
SigningRegion: signingRegion,
|
||||
PartitionID: partitionID,
|
||||
Endpoint: endpoint,
|
||||
APIVersion: "2011-01-01",
|
||||
ResolvedRegion: resolvedRegion,
|
||||
},
|
||||
handlers,
|
||||
),
|
||||
}
|
||||
|
||||
// Handlers
|
||||
svc.Handlers.Sign.PushBackNamed(v4.SignRequestHandler)
|
||||
svc.Handlers.Build.PushBackNamed(query.BuildHandler)
|
||||
svc.Handlers.Unmarshal.PushBackNamed(query.UnmarshalHandler)
|
||||
svc.Handlers.UnmarshalMeta.PushBackNamed(query.UnmarshalMetaHandler)
|
||||
svc.Handlers.UnmarshalError.PushBackNamed(query.UnmarshalErrorHandler)
|
||||
|
||||
// Run custom client initialization if present
|
||||
if initClient != nil {
|
||||
initClient(svc.Client)
|
||||
}
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
// newRequest creates a new request for a AutoScaling operation and runs any
|
||||
// custom request initialization.
|
||||
func (c *AutoScaling) newRequest(op *request.Operation, params, data interface{}) *request.Request {
|
||||
req := c.NewRequest(op, params, data)
|
||||
|
||||
// Run custom request initialization if present
|
||||
if initRequest != nil {
|
||||
initRequest(req)
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
163
vendor/github.com/aws/aws-sdk-go/service/autoscaling/waiters.go
generated
vendored
163
vendor/github.com/aws/aws-sdk-go/service/autoscaling/waiters.go
generated
vendored
@ -1,163 +0,0 @@
|
||||
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
|
||||
|
||||
package autoscaling
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
)
|
||||
|
||||
// WaitUntilGroupExists uses the Auto Scaling API operation
|
||||
// DescribeAutoScalingGroups to wait for a condition to be met before returning.
|
||||
// If the condition is not met within the max attempt window, an error will
|
||||
// be returned.
|
||||
func (c *AutoScaling) WaitUntilGroupExists(input *DescribeAutoScalingGroupsInput) error {
|
||||
return c.WaitUntilGroupExistsWithContext(aws.BackgroundContext(), input)
|
||||
}
|
||||
|
||||
// WaitUntilGroupExistsWithContext is an extended version of WaitUntilGroupExists.
|
||||
// With the support for passing in a context and options to configure the
|
||||
// Waiter and the underlying request options.
|
||||
//
|
||||
// The context must be non-nil and will be used for request cancellation. If
|
||||
// the context is nil a panic will occur. In the future the SDK may create
|
||||
// sub-contexts for http.Requests. See https://golang.org/pkg/context/
|
||||
// for more information on using Contexts.
|
||||
func (c *AutoScaling) WaitUntilGroupExistsWithContext(ctx aws.Context, input *DescribeAutoScalingGroupsInput, opts ...request.WaiterOption) error {
|
||||
w := request.Waiter{
|
||||
Name: "WaitUntilGroupExists",
|
||||
MaxAttempts: 10,
|
||||
Delay: request.ConstantWaiterDelay(5 * time.Second),
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
Matcher: request.PathWaiterMatch, Argument: "length(AutoScalingGroups) > `0`",
|
||||
Expected: true,
|
||||
},
|
||||
{
|
||||
State: request.RetryWaiterState,
|
||||
Matcher: request.PathWaiterMatch, Argument: "length(AutoScalingGroups) > `0`",
|
||||
Expected: false,
|
||||
},
|
||||
},
|
||||
Logger: c.Config.Logger,
|
||||
NewRequest: func(opts []request.Option) (*request.Request, error) {
|
||||
var inCpy *DescribeAutoScalingGroupsInput
|
||||
if input != nil {
|
||||
tmp := *input
|
||||
inCpy = &tmp
|
||||
}
|
||||
req, _ := c.DescribeAutoScalingGroupsRequest(inCpy)
|
||||
req.SetContext(ctx)
|
||||
req.ApplyOptions(opts...)
|
||||
return req, nil
|
||||
},
|
||||
}
|
||||
w.ApplyOptions(opts...)
|
||||
|
||||
return w.WaitWithContext(ctx)
|
||||
}
|
||||
|
||||
// WaitUntilGroupInService uses the Auto Scaling API operation
|
||||
// DescribeAutoScalingGroups to wait for a condition to be met before returning.
|
||||
// If the condition is not met within the max attempt window, an error will
|
||||
// be returned.
|
||||
func (c *AutoScaling) WaitUntilGroupInService(input *DescribeAutoScalingGroupsInput) error {
|
||||
return c.WaitUntilGroupInServiceWithContext(aws.BackgroundContext(), input)
|
||||
}
|
||||
|
||||
// WaitUntilGroupInServiceWithContext is an extended version of WaitUntilGroupInService.
|
||||
// With the support for passing in a context and options to configure the
|
||||
// Waiter and the underlying request options.
|
||||
//
|
||||
// The context must be non-nil and will be used for request cancellation. If
|
||||
// the context is nil a panic will occur. In the future the SDK may create
|
||||
// sub-contexts for http.Requests. See https://golang.org/pkg/context/
|
||||
// for more information on using Contexts.
|
||||
func (c *AutoScaling) WaitUntilGroupInServiceWithContext(ctx aws.Context, input *DescribeAutoScalingGroupsInput, opts ...request.WaiterOption) error {
|
||||
w := request.Waiter{
|
||||
Name: "WaitUntilGroupInService",
|
||||
MaxAttempts: 40,
|
||||
Delay: request.ConstantWaiterDelay(15 * time.Second),
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
Matcher: request.PathWaiterMatch, Argument: "contains(AutoScalingGroups[].[length(Instances[?LifecycleState=='InService']) >= MinSize][], `false`)",
|
||||
Expected: false,
|
||||
},
|
||||
{
|
||||
State: request.RetryWaiterState,
|
||||
Matcher: request.PathWaiterMatch, Argument: "contains(AutoScalingGroups[].[length(Instances[?LifecycleState=='InService']) >= MinSize][], `false`)",
|
||||
Expected: true,
|
||||
},
|
||||
},
|
||||
Logger: c.Config.Logger,
|
||||
NewRequest: func(opts []request.Option) (*request.Request, error) {
|
||||
var inCpy *DescribeAutoScalingGroupsInput
|
||||
if input != nil {
|
||||
tmp := *input
|
||||
inCpy = &tmp
|
||||
}
|
||||
req, _ := c.DescribeAutoScalingGroupsRequest(inCpy)
|
||||
req.SetContext(ctx)
|
||||
req.ApplyOptions(opts...)
|
||||
return req, nil
|
||||
},
|
||||
}
|
||||
w.ApplyOptions(opts...)
|
||||
|
||||
return w.WaitWithContext(ctx)
|
||||
}
|
||||
|
||||
// WaitUntilGroupNotExists uses the Auto Scaling API operation
|
||||
// DescribeAutoScalingGroups to wait for a condition to be met before returning.
|
||||
// If the condition is not met within the max attempt window, an error will
|
||||
// be returned.
|
||||
func (c *AutoScaling) WaitUntilGroupNotExists(input *DescribeAutoScalingGroupsInput) error {
|
||||
return c.WaitUntilGroupNotExistsWithContext(aws.BackgroundContext(), input)
|
||||
}
|
||||
|
||||
// WaitUntilGroupNotExistsWithContext is an extended version of WaitUntilGroupNotExists.
|
||||
// With the support for passing in a context and options to configure the
|
||||
// Waiter and the underlying request options.
|
||||
//
|
||||
// The context must be non-nil and will be used for request cancellation. If
|
||||
// the context is nil a panic will occur. In the future the SDK may create
|
||||
// sub-contexts for http.Requests. See https://golang.org/pkg/context/
|
||||
// for more information on using Contexts.
|
||||
func (c *AutoScaling) WaitUntilGroupNotExistsWithContext(ctx aws.Context, input *DescribeAutoScalingGroupsInput, opts ...request.WaiterOption) error {
|
||||
w := request.Waiter{
|
||||
Name: "WaitUntilGroupNotExists",
|
||||
MaxAttempts: 40,
|
||||
Delay: request.ConstantWaiterDelay(15 * time.Second),
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
Matcher: request.PathWaiterMatch, Argument: "length(AutoScalingGroups) > `0`",
|
||||
Expected: false,
|
||||
},
|
||||
{
|
||||
State: request.RetryWaiterState,
|
||||
Matcher: request.PathWaiterMatch, Argument: "length(AutoScalingGroups) > `0`",
|
||||
Expected: true,
|
||||
},
|
||||
},
|
||||
Logger: c.Config.Logger,
|
||||
NewRequest: func(opts []request.Option) (*request.Request, error) {
|
||||
var inCpy *DescribeAutoScalingGroupsInput
|
||||
if input != nil {
|
||||
tmp := *input
|
||||
inCpy = &tmp
|
||||
}
|
||||
req, _ := c.DescribeAutoScalingGroupsRequest(inCpy)
|
||||
req.SetContext(ctx)
|
||||
req.ApplyOptions(opts...)
|
||||
return req, nil
|
||||
},
|
||||
}
|
||||
w.ApplyOptions(opts...)
|
||||
|
||||
return w.WaitWithContext(ctx)
|
||||
}
|
7168
vendor/github.com/aws/aws-sdk-go/service/elb/api.go
generated
vendored
7168
vendor/github.com/aws/aws-sdk-go/service/elb/api.go
generated
vendored
File diff suppressed because it is too large
Load Diff
50
vendor/github.com/aws/aws-sdk-go/service/elb/doc.go
generated
vendored
50
vendor/github.com/aws/aws-sdk-go/service/elb/doc.go
generated
vendored
@ -1,50 +0,0 @@
|
||||
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
|
||||
|
||||
// Package elb provides the client and types for making API
|
||||
// requests to Elastic Load Balancing.
|
||||
//
|
||||
// A load balancer can distribute incoming traffic across your EC2 instances.
|
||||
// This enables you to increase the availability of your application. The load
|
||||
// balancer also monitors the health of its registered instances and ensures
|
||||
// that it routes traffic only to healthy instances. You configure your load
|
||||
// balancer to accept incoming traffic by specifying one or more listeners,
|
||||
// which are configured with a protocol and port number for connections from
|
||||
// clients to the load balancer and a protocol and port number for connections
|
||||
// from the load balancer to the instances.
|
||||
//
|
||||
// Elastic Load Balancing supports three types of load balancers: Application
|
||||
// Load Balancers, Network Load Balancers, and Classic Load Balancers. You can
|
||||
// select a load balancer based on your application needs. For more information,
|
||||
// see the Elastic Load Balancing User Guide (https://docs.aws.amazon.com/elasticloadbalancing/latest/userguide/).
|
||||
//
|
||||
// This reference covers the 2012-06-01 API, which supports Classic Load Balancers.
|
||||
// The 2015-12-01 API supports Application Load Balancers and Network Load Balancers.
|
||||
//
|
||||
// To get started, create a load balancer with one or more listeners using CreateLoadBalancer.
|
||||
// Register your instances with the load balancer using RegisterInstancesWithLoadBalancer.
|
||||
//
|
||||
// All Elastic Load Balancing operations are idempotent, which means that they
|
||||
// complete at most one time. If you repeat an operation, it succeeds with a
|
||||
// 200 OK response code.
|
||||
//
|
||||
// See https://docs.aws.amazon.com/goto/WebAPI/elasticloadbalancing-2012-06-01 for more information on this service.
|
||||
//
|
||||
// See elb package documentation for more information.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/service/elb/
|
||||
//
|
||||
// # Using the Client
|
||||
//
|
||||
// To contact Elastic Load Balancing with the SDK use the New function to create
|
||||
// a new service client. With that client you can make API requests to the service.
|
||||
// These clients are safe to use concurrently.
|
||||
//
|
||||
// See the SDK's documentation for more information on how to use the SDK.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/
|
||||
//
|
||||
// See aws.Config documentation for more information on configuring SDK clients.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/aws/#Config
|
||||
//
|
||||
// See the Elastic Load Balancing client ELB for more
|
||||
// information on creating client for this service.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/service/elb/#New
|
||||
package elb
|
145
vendor/github.com/aws/aws-sdk-go/service/elb/errors.go
generated
vendored
145
vendor/github.com/aws/aws-sdk-go/service/elb/errors.go
generated
vendored
@ -1,145 +0,0 @@
|
||||
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
|
||||
|
||||
package elb
|
||||
|
||||
const (
|
||||
|
||||
// ErrCodeAccessPointNotFoundException for service response error code
|
||||
// "LoadBalancerNotFound".
|
||||
//
|
||||
// The specified load balancer does not exist.
|
||||
ErrCodeAccessPointNotFoundException = "LoadBalancerNotFound"
|
||||
|
||||
// ErrCodeCertificateNotFoundException for service response error code
|
||||
// "CertificateNotFound".
|
||||
//
|
||||
// The specified ARN does not refer to a valid SSL certificate in AWS Identity
|
||||
// and Access Management (IAM) or AWS Certificate Manager (ACM). Note that if
|
||||
// you recently uploaded the certificate to IAM, this error might indicate that
|
||||
// the certificate is not fully available yet.
|
||||
ErrCodeCertificateNotFoundException = "CertificateNotFound"
|
||||
|
||||
// ErrCodeDependencyThrottleException for service response error code
|
||||
// "DependencyThrottle".
|
||||
//
|
||||
// A request made by Elastic Load Balancing to another service exceeds the maximum
|
||||
// request rate permitted for your account.
|
||||
ErrCodeDependencyThrottleException = "DependencyThrottle"
|
||||
|
||||
// ErrCodeDuplicateAccessPointNameException for service response error code
|
||||
// "DuplicateLoadBalancerName".
|
||||
//
|
||||
// The specified load balancer name already exists for this account.
|
||||
ErrCodeDuplicateAccessPointNameException = "DuplicateLoadBalancerName"
|
||||
|
||||
// ErrCodeDuplicateListenerException for service response error code
|
||||
// "DuplicateListener".
|
||||
//
|
||||
// A listener already exists for the specified load balancer name and port,
|
||||
// but with a different instance port, protocol, or SSL certificate.
|
||||
ErrCodeDuplicateListenerException = "DuplicateListener"
|
||||
|
||||
// ErrCodeDuplicatePolicyNameException for service response error code
|
||||
// "DuplicatePolicyName".
|
||||
//
|
||||
// A policy with the specified name already exists for this load balancer.
|
||||
ErrCodeDuplicatePolicyNameException = "DuplicatePolicyName"
|
||||
|
||||
// ErrCodeDuplicateTagKeysException for service response error code
|
||||
// "DuplicateTagKeys".
|
||||
//
|
||||
// A tag key was specified more than once.
|
||||
ErrCodeDuplicateTagKeysException = "DuplicateTagKeys"
|
||||
|
||||
// ErrCodeInvalidConfigurationRequestException for service response error code
|
||||
// "InvalidConfigurationRequest".
|
||||
//
|
||||
// The requested configuration change is not valid.
|
||||
ErrCodeInvalidConfigurationRequestException = "InvalidConfigurationRequest"
|
||||
|
||||
// ErrCodeInvalidEndPointException for service response error code
|
||||
// "InvalidInstance".
|
||||
//
|
||||
// The specified endpoint is not valid.
|
||||
ErrCodeInvalidEndPointException = "InvalidInstance"
|
||||
|
||||
// ErrCodeInvalidSchemeException for service response error code
|
||||
// "InvalidScheme".
|
||||
//
|
||||
// The specified value for the schema is not valid. You can only specify a scheme
|
||||
// for load balancers in a VPC.
|
||||
ErrCodeInvalidSchemeException = "InvalidScheme"
|
||||
|
||||
// ErrCodeInvalidSecurityGroupException for service response error code
|
||||
// "InvalidSecurityGroup".
|
||||
//
|
||||
// One or more of the specified security groups do not exist.
|
||||
ErrCodeInvalidSecurityGroupException = "InvalidSecurityGroup"
|
||||
|
||||
// ErrCodeInvalidSubnetException for service response error code
|
||||
// "InvalidSubnet".
|
||||
//
|
||||
// The specified VPC has no associated Internet gateway.
|
||||
ErrCodeInvalidSubnetException = "InvalidSubnet"
|
||||
|
||||
// ErrCodeListenerNotFoundException for service response error code
|
||||
// "ListenerNotFound".
|
||||
//
|
||||
// The load balancer does not have a listener configured at the specified port.
|
||||
ErrCodeListenerNotFoundException = "ListenerNotFound"
|
||||
|
||||
// ErrCodeLoadBalancerAttributeNotFoundException for service response error code
|
||||
// "LoadBalancerAttributeNotFound".
|
||||
//
|
||||
// The specified load balancer attribute does not exist.
|
||||
ErrCodeLoadBalancerAttributeNotFoundException = "LoadBalancerAttributeNotFound"
|
||||
|
||||
// ErrCodeOperationNotPermittedException for service response error code
|
||||
// "OperationNotPermitted".
|
||||
//
|
||||
// This operation is not allowed.
|
||||
ErrCodeOperationNotPermittedException = "OperationNotPermitted"
|
||||
|
||||
// ErrCodePolicyNotFoundException for service response error code
|
||||
// "PolicyNotFound".
|
||||
//
|
||||
// One or more of the specified policies do not exist.
|
||||
ErrCodePolicyNotFoundException = "PolicyNotFound"
|
||||
|
||||
// ErrCodePolicyTypeNotFoundException for service response error code
|
||||
// "PolicyTypeNotFound".
|
||||
//
|
||||
// One or more of the specified policy types do not exist.
|
||||
ErrCodePolicyTypeNotFoundException = "PolicyTypeNotFound"
|
||||
|
||||
// ErrCodeSubnetNotFoundException for service response error code
|
||||
// "SubnetNotFound".
|
||||
//
|
||||
// One or more of the specified subnets do not exist.
|
||||
ErrCodeSubnetNotFoundException = "SubnetNotFound"
|
||||
|
||||
// ErrCodeTooManyAccessPointsException for service response error code
|
||||
// "TooManyLoadBalancers".
|
||||
//
|
||||
// The quota for the number of load balancers has been reached.
|
||||
ErrCodeTooManyAccessPointsException = "TooManyLoadBalancers"
|
||||
|
||||
// ErrCodeTooManyPoliciesException for service response error code
|
||||
// "TooManyPolicies".
|
||||
//
|
||||
// The quota for the number of policies for this load balancer has been reached.
|
||||
ErrCodeTooManyPoliciesException = "TooManyPolicies"
|
||||
|
||||
// ErrCodeTooManyTagsException for service response error code
|
||||
// "TooManyTags".
|
||||
//
|
||||
// The quota for the number of tags that can be assigned to a load balancer
|
||||
// has been reached.
|
||||
ErrCodeTooManyTagsException = "TooManyTags"
|
||||
|
||||
// ErrCodeUnsupportedProtocolException for service response error code
|
||||
// "UnsupportedProtocol".
|
||||
//
|
||||
// The specified protocol or signature version is not supported.
|
||||
ErrCodeUnsupportedProtocolException = "UnsupportedProtocol"
|
||||
)
|
104
vendor/github.com/aws/aws-sdk-go/service/elb/service.go
generated
vendored
104
vendor/github.com/aws/aws-sdk-go/service/elb/service.go
generated
vendored
@ -1,104 +0,0 @@
|
||||
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
|
||||
|
||||
package elb
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/client"
|
||||
"github.com/aws/aws-sdk-go/aws/client/metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/signer/v4"
|
||||
"github.com/aws/aws-sdk-go/private/protocol/query"
|
||||
)
|
||||
|
||||
// ELB provides the API operation methods for making requests to
|
||||
// Elastic Load Balancing. See this package's package overview docs
|
||||
// for details on the service.
|
||||
//
|
||||
// ELB methods are safe to use concurrently. It is not safe to
|
||||
// modify mutate any of the struct's properties though.
|
||||
type ELB struct {
|
||||
*client.Client
|
||||
}
|
||||
|
||||
// Used for custom client initialization logic
|
||||
var initClient func(*client.Client)
|
||||
|
||||
// Used for custom request initialization logic
|
||||
var initRequest func(*request.Request)
|
||||
|
||||
// Service information constants
|
||||
const (
|
||||
ServiceName = "elasticloadbalancing" // Name of service.
|
||||
EndpointsID = ServiceName // ID to lookup a service endpoint with.
|
||||
ServiceID = "Elastic Load Balancing" // ServiceID is a unique identifier of a specific service.
|
||||
)
|
||||
|
||||
// New creates a new instance of the ELB client with a session.
|
||||
// If additional configuration is needed for the client instance use the optional
|
||||
// aws.Config parameter to add your extra config.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// mySession := session.Must(session.NewSession())
|
||||
//
|
||||
// // Create a ELB client from just a session.
|
||||
// svc := elb.New(mySession)
|
||||
//
|
||||
// // Create a ELB client with additional configuration
|
||||
// svc := elb.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
|
||||
func New(p client.ConfigProvider, cfgs ...*aws.Config) *ELB {
|
||||
c := p.ClientConfig(EndpointsID, cfgs...)
|
||||
if c.SigningNameDerived || len(c.SigningName) == 0 {
|
||||
c.SigningName = EndpointsID
|
||||
// No Fallback
|
||||
}
|
||||
return newClient(*c.Config, c.Handlers, c.PartitionID, c.Endpoint, c.SigningRegion, c.SigningName, c.ResolvedRegion)
|
||||
}
|
||||
|
||||
// newClient creates, initializes and returns a new service client instance.
|
||||
func newClient(cfg aws.Config, handlers request.Handlers, partitionID, endpoint, signingRegion, signingName, resolvedRegion string) *ELB {
|
||||
svc := &ELB{
|
||||
Client: client.New(
|
||||
cfg,
|
||||
metadata.ClientInfo{
|
||||
ServiceName: ServiceName,
|
||||
ServiceID: ServiceID,
|
||||
SigningName: signingName,
|
||||
SigningRegion: signingRegion,
|
||||
PartitionID: partitionID,
|
||||
Endpoint: endpoint,
|
||||
APIVersion: "2012-06-01",
|
||||
ResolvedRegion: resolvedRegion,
|
||||
},
|
||||
handlers,
|
||||
),
|
||||
}
|
||||
|
||||
// Handlers
|
||||
svc.Handlers.Sign.PushBackNamed(v4.SignRequestHandler)
|
||||
svc.Handlers.Build.PushBackNamed(query.BuildHandler)
|
||||
svc.Handlers.Unmarshal.PushBackNamed(query.UnmarshalHandler)
|
||||
svc.Handlers.UnmarshalMeta.PushBackNamed(query.UnmarshalMetaHandler)
|
||||
svc.Handlers.UnmarshalError.PushBackNamed(query.UnmarshalErrorHandler)
|
||||
|
||||
// Run custom client initialization if present
|
||||
if initClient != nil {
|
||||
initClient(svc.Client)
|
||||
}
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
// newRequest creates a new request for a ELB operation and runs any
|
||||
// custom request initialization.
|
||||
func (c *ELB) newRequest(op *request.Operation, params, data interface{}) *request.Request {
|
||||
req := c.NewRequest(op, params, data)
|
||||
|
||||
// Run custom request initialization if present
|
||||
if initRequest != nil {
|
||||
initRequest(req)
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
158
vendor/github.com/aws/aws-sdk-go/service/elb/waiters.go
generated
vendored
158
vendor/github.com/aws/aws-sdk-go/service/elb/waiters.go
generated
vendored
@ -1,158 +0,0 @@
|
||||
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
|
||||
|
||||
package elb
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
)
|
||||
|
||||
// WaitUntilAnyInstanceInService uses the Elastic Load Balancing API operation
|
||||
// DescribeInstanceHealth to wait for a condition to be met before returning.
|
||||
// If the condition is not met within the max attempt window, an error will
|
||||
// be returned.
|
||||
func (c *ELB) WaitUntilAnyInstanceInService(input *DescribeInstanceHealthInput) error {
|
||||
return c.WaitUntilAnyInstanceInServiceWithContext(aws.BackgroundContext(), input)
|
||||
}
|
||||
|
||||
// WaitUntilAnyInstanceInServiceWithContext is an extended version of WaitUntilAnyInstanceInService.
|
||||
// With the support for passing in a context and options to configure the
|
||||
// Waiter and the underlying request options.
|
||||
//
|
||||
// The context must be non-nil and will be used for request cancellation. If
|
||||
// the context is nil a panic will occur. In the future the SDK may create
|
||||
// sub-contexts for http.Requests. See https://golang.org/pkg/context/
|
||||
// for more information on using Contexts.
|
||||
func (c *ELB) WaitUntilAnyInstanceInServiceWithContext(ctx aws.Context, input *DescribeInstanceHealthInput, opts ...request.WaiterOption) error {
|
||||
w := request.Waiter{
|
||||
Name: "WaitUntilAnyInstanceInService",
|
||||
MaxAttempts: 40,
|
||||
Delay: request.ConstantWaiterDelay(15 * time.Second),
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
Matcher: request.PathAnyWaiterMatch, Argument: "InstanceStates[].State",
|
||||
Expected: "InService",
|
||||
},
|
||||
},
|
||||
Logger: c.Config.Logger,
|
||||
NewRequest: func(opts []request.Option) (*request.Request, error) {
|
||||
var inCpy *DescribeInstanceHealthInput
|
||||
if input != nil {
|
||||
tmp := *input
|
||||
inCpy = &tmp
|
||||
}
|
||||
req, _ := c.DescribeInstanceHealthRequest(inCpy)
|
||||
req.SetContext(ctx)
|
||||
req.ApplyOptions(opts...)
|
||||
return req, nil
|
||||
},
|
||||
}
|
||||
w.ApplyOptions(opts...)
|
||||
|
||||
return w.WaitWithContext(ctx)
|
||||
}
|
||||
|
||||
// WaitUntilInstanceDeregistered uses the Elastic Load Balancing API operation
|
||||
// DescribeInstanceHealth to wait for a condition to be met before returning.
|
||||
// If the condition is not met within the max attempt window, an error will
|
||||
// be returned.
|
||||
func (c *ELB) WaitUntilInstanceDeregistered(input *DescribeInstanceHealthInput) error {
|
||||
return c.WaitUntilInstanceDeregisteredWithContext(aws.BackgroundContext(), input)
|
||||
}
|
||||
|
||||
// WaitUntilInstanceDeregisteredWithContext is an extended version of WaitUntilInstanceDeregistered.
|
||||
// With the support for passing in a context and options to configure the
|
||||
// Waiter and the underlying request options.
|
||||
//
|
||||
// The context must be non-nil and will be used for request cancellation. If
|
||||
// the context is nil a panic will occur. In the future the SDK may create
|
||||
// sub-contexts for http.Requests. See https://golang.org/pkg/context/
|
||||
// for more information on using Contexts.
|
||||
func (c *ELB) WaitUntilInstanceDeregisteredWithContext(ctx aws.Context, input *DescribeInstanceHealthInput, opts ...request.WaiterOption) error {
|
||||
w := request.Waiter{
|
||||
Name: "WaitUntilInstanceDeregistered",
|
||||
MaxAttempts: 40,
|
||||
Delay: request.ConstantWaiterDelay(15 * time.Second),
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
Matcher: request.PathAllWaiterMatch, Argument: "InstanceStates[].State",
|
||||
Expected: "OutOfService",
|
||||
},
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
Matcher: request.ErrorWaiterMatch,
|
||||
Expected: "InvalidInstance",
|
||||
},
|
||||
},
|
||||
Logger: c.Config.Logger,
|
||||
NewRequest: func(opts []request.Option) (*request.Request, error) {
|
||||
var inCpy *DescribeInstanceHealthInput
|
||||
if input != nil {
|
||||
tmp := *input
|
||||
inCpy = &tmp
|
||||
}
|
||||
req, _ := c.DescribeInstanceHealthRequest(inCpy)
|
||||
req.SetContext(ctx)
|
||||
req.ApplyOptions(opts...)
|
||||
return req, nil
|
||||
},
|
||||
}
|
||||
w.ApplyOptions(opts...)
|
||||
|
||||
return w.WaitWithContext(ctx)
|
||||
}
|
||||
|
||||
// WaitUntilInstanceInService uses the Elastic Load Balancing API operation
|
||||
// DescribeInstanceHealth to wait for a condition to be met before returning.
|
||||
// If the condition is not met within the max attempt window, an error will
|
||||
// be returned.
|
||||
func (c *ELB) WaitUntilInstanceInService(input *DescribeInstanceHealthInput) error {
|
||||
return c.WaitUntilInstanceInServiceWithContext(aws.BackgroundContext(), input)
|
||||
}
|
||||
|
||||
// WaitUntilInstanceInServiceWithContext is an extended version of WaitUntilInstanceInService.
|
||||
// With the support for passing in a context and options to configure the
|
||||
// Waiter and the underlying request options.
|
||||
//
|
||||
// The context must be non-nil and will be used for request cancellation. If
|
||||
// the context is nil a panic will occur. In the future the SDK may create
|
||||
// sub-contexts for http.Requests. See https://golang.org/pkg/context/
|
||||
// for more information on using Contexts.
|
||||
func (c *ELB) WaitUntilInstanceInServiceWithContext(ctx aws.Context, input *DescribeInstanceHealthInput, opts ...request.WaiterOption) error {
|
||||
w := request.Waiter{
|
||||
Name: "WaitUntilInstanceInService",
|
||||
MaxAttempts: 40,
|
||||
Delay: request.ConstantWaiterDelay(15 * time.Second),
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
Matcher: request.PathAllWaiterMatch, Argument: "InstanceStates[].State",
|
||||
Expected: "InService",
|
||||
},
|
||||
{
|
||||
State: request.RetryWaiterState,
|
||||
Matcher: request.ErrorWaiterMatch,
|
||||
Expected: "InvalidInstance",
|
||||
},
|
||||
},
|
||||
Logger: c.Config.Logger,
|
||||
NewRequest: func(opts []request.Option) (*request.Request, error) {
|
||||
var inCpy *DescribeInstanceHealthInput
|
||||
if input != nil {
|
||||
tmp := *input
|
||||
inCpy = &tmp
|
||||
}
|
||||
req, _ := c.DescribeInstanceHealthRequest(inCpy)
|
||||
req.SetContext(ctx)
|
||||
req.ApplyOptions(opts...)
|
||||
return req, nil
|
||||
},
|
||||
}
|
||||
w.ApplyOptions(opts...)
|
||||
|
||||
return w.WaitWithContext(ctx)
|
||||
}
|
10571
vendor/github.com/aws/aws-sdk-go/service/elbv2/api.go
generated
vendored
10571
vendor/github.com/aws/aws-sdk-go/service/elbv2/api.go
generated
vendored
File diff suppressed because it is too large
Load Diff
54
vendor/github.com/aws/aws-sdk-go/service/elbv2/doc.go
generated
vendored
54
vendor/github.com/aws/aws-sdk-go/service/elbv2/doc.go
generated
vendored
@ -1,54 +0,0 @@
|
||||
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
|
||||
|
||||
// Package elbv2 provides the client and types for making API
|
||||
// requests to Elastic Load Balancing.
|
||||
//
|
||||
// A load balancer distributes incoming traffic across targets, such as your
|
||||
// EC2 instances. This enables you to increase the availability of your application.
|
||||
// The load balancer also monitors the health of its registered targets and
|
||||
// ensures that it routes traffic only to healthy targets. You configure your
|
||||
// load balancer to accept incoming traffic by specifying one or more listeners,
|
||||
// which are configured with a protocol and port number for connections from
|
||||
// clients to the load balancer. You configure a target group with a protocol
|
||||
// and port number for connections from the load balancer to the targets, and
|
||||
// with health check settings to be used when checking the health status of
|
||||
// the targets.
|
||||
//
|
||||
// Elastic Load Balancing supports the following types of load balancers: Application
|
||||
// Load Balancers, Network Load Balancers, Gateway Load Balancers, and Classic
|
||||
// Load Balancers. This reference covers the following load balancer types:
|
||||
//
|
||||
// - Application Load Balancer - Operates at the application layer (layer
|
||||
// 7) and supports HTTP and HTTPS.
|
||||
//
|
||||
// - Network Load Balancer - Operates at the transport layer (layer 4) and
|
||||
// supports TCP, TLS, and UDP.
|
||||
//
|
||||
// - Gateway Load Balancer - Operates at the network layer (layer 3).
|
||||
//
|
||||
// For more information, see the Elastic Load Balancing User Guide (https://docs.aws.amazon.com/elasticloadbalancing/latest/userguide/).
|
||||
//
|
||||
// All Elastic Load Balancing operations are idempotent, which means that they
|
||||
// complete at most one time. If you repeat an operation, it succeeds.
|
||||
//
|
||||
// See https://docs.aws.amazon.com/goto/WebAPI/elasticloadbalancingv2-2015-12-01 for more information on this service.
|
||||
//
|
||||
// See elbv2 package documentation for more information.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/service/elbv2/
|
||||
//
|
||||
// # Using the Client
|
||||
//
|
||||
// To contact Elastic Load Balancing with the SDK use the New function to create
|
||||
// a new service client. With that client you can make API requests to the service.
|
||||
// These clients are safe to use concurrently.
|
||||
//
|
||||
// See the SDK's documentation for more information on how to use the SDK.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/
|
||||
//
|
||||
// See aws.Config documentation for more information on configuring SDK clients.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/aws/#Config
|
||||
//
|
||||
// See the Elastic Load Balancing client ELBV2 for more
|
||||
// information on creating client for this service.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/service/elbv2/#New
|
||||
package elbv2
|
235
vendor/github.com/aws/aws-sdk-go/service/elbv2/errors.go
generated
vendored
235
vendor/github.com/aws/aws-sdk-go/service/elbv2/errors.go
generated
vendored
@ -1,235 +0,0 @@
|
||||
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
|
||||
|
||||
package elbv2
|
||||
|
||||
const (
|
||||
|
||||
// ErrCodeALPNPolicyNotSupportedException for service response error code
|
||||
// "ALPNPolicyNotFound".
|
||||
//
|
||||
// The specified ALPN policy is not supported.
|
||||
ErrCodeALPNPolicyNotSupportedException = "ALPNPolicyNotFound"
|
||||
|
||||
// ErrCodeAllocationIdNotFoundException for service response error code
|
||||
// "AllocationIdNotFound".
|
||||
//
|
||||
// The specified allocation ID does not exist.
|
||||
ErrCodeAllocationIdNotFoundException = "AllocationIdNotFound"
|
||||
|
||||
// ErrCodeAvailabilityZoneNotSupportedException for service response error code
|
||||
// "AvailabilityZoneNotSupported".
|
||||
//
|
||||
// The specified Availability Zone is not supported.
|
||||
ErrCodeAvailabilityZoneNotSupportedException = "AvailabilityZoneNotSupported"
|
||||
|
||||
// ErrCodeCertificateNotFoundException for service response error code
|
||||
// "CertificateNotFound".
|
||||
//
|
||||
// The specified certificate does not exist.
|
||||
ErrCodeCertificateNotFoundException = "CertificateNotFound"
|
||||
|
||||
// ErrCodeDuplicateListenerException for service response error code
|
||||
// "DuplicateListener".
|
||||
//
|
||||
// A listener with the specified port already exists.
|
||||
ErrCodeDuplicateListenerException = "DuplicateListener"
|
||||
|
||||
// ErrCodeDuplicateLoadBalancerNameException for service response error code
|
||||
// "DuplicateLoadBalancerName".
|
||||
//
|
||||
// A load balancer with the specified name already exists.
|
||||
ErrCodeDuplicateLoadBalancerNameException = "DuplicateLoadBalancerName"
|
||||
|
||||
// ErrCodeDuplicateTagKeysException for service response error code
|
||||
// "DuplicateTagKeys".
|
||||
//
|
||||
// A tag key was specified more than once.
|
||||
ErrCodeDuplicateTagKeysException = "DuplicateTagKeys"
|
||||
|
||||
// ErrCodeDuplicateTargetGroupNameException for service response error code
|
||||
// "DuplicateTargetGroupName".
|
||||
//
|
||||
// A target group with the specified name already exists.
|
||||
ErrCodeDuplicateTargetGroupNameException = "DuplicateTargetGroupName"
|
||||
|
||||
// ErrCodeHealthUnavailableException for service response error code
|
||||
// "HealthUnavailable".
|
||||
//
|
||||
// The health of the specified targets could not be retrieved due to an internal
|
||||
// error.
|
||||
ErrCodeHealthUnavailableException = "HealthUnavailable"
|
||||
|
||||
// ErrCodeIncompatibleProtocolsException for service response error code
|
||||
// "IncompatibleProtocols".
|
||||
//
|
||||
// The specified configuration is not valid with this protocol.
|
||||
ErrCodeIncompatibleProtocolsException = "IncompatibleProtocols"
|
||||
|
||||
// ErrCodeInvalidConfigurationRequestException for service response error code
|
||||
// "InvalidConfigurationRequest".
|
||||
//
|
||||
// The requested configuration is not valid.
|
||||
ErrCodeInvalidConfigurationRequestException = "InvalidConfigurationRequest"
|
||||
|
||||
// ErrCodeInvalidLoadBalancerActionException for service response error code
|
||||
// "InvalidLoadBalancerAction".
|
||||
//
|
||||
// The requested action is not valid.
|
||||
ErrCodeInvalidLoadBalancerActionException = "InvalidLoadBalancerAction"
|
||||
|
||||
// ErrCodeInvalidSchemeException for service response error code
|
||||
// "InvalidScheme".
|
||||
//
|
||||
// The requested scheme is not valid.
|
||||
ErrCodeInvalidSchemeException = "InvalidScheme"
|
||||
|
||||
// ErrCodeInvalidSecurityGroupException for service response error code
|
||||
// "InvalidSecurityGroup".
|
||||
//
|
||||
// The specified security group does not exist.
|
||||
ErrCodeInvalidSecurityGroupException = "InvalidSecurityGroup"
|
||||
|
||||
// ErrCodeInvalidSubnetException for service response error code
|
||||
// "InvalidSubnet".
|
||||
//
|
||||
// The specified subnet is out of available addresses.
|
||||
ErrCodeInvalidSubnetException = "InvalidSubnet"
|
||||
|
||||
// ErrCodeInvalidTargetException for service response error code
|
||||
// "InvalidTarget".
|
||||
//
|
||||
// The specified target does not exist, is not in the same VPC as the target
|
||||
// group, or has an unsupported instance type.
|
||||
ErrCodeInvalidTargetException = "InvalidTarget"
|
||||
|
||||
// ErrCodeListenerNotFoundException for service response error code
|
||||
// "ListenerNotFound".
|
||||
//
|
||||
// The specified listener does not exist.
|
||||
ErrCodeListenerNotFoundException = "ListenerNotFound"
|
||||
|
||||
// ErrCodeLoadBalancerNotFoundException for service response error code
|
||||
// "LoadBalancerNotFound".
|
||||
//
|
||||
// The specified load balancer does not exist.
|
||||
ErrCodeLoadBalancerNotFoundException = "LoadBalancerNotFound"
|
||||
|
||||
// ErrCodeOperationNotPermittedException for service response error code
|
||||
// "OperationNotPermitted".
|
||||
//
|
||||
// This operation is not allowed.
|
||||
ErrCodeOperationNotPermittedException = "OperationNotPermitted"
|
||||
|
||||
// ErrCodePriorityInUseException for service response error code
|
||||
// "PriorityInUse".
|
||||
//
|
||||
// The specified priority is in use.
|
||||
ErrCodePriorityInUseException = "PriorityInUse"
|
||||
|
||||
// ErrCodeResourceInUseException for service response error code
|
||||
// "ResourceInUse".
|
||||
//
|
||||
// A specified resource is in use.
|
||||
ErrCodeResourceInUseException = "ResourceInUse"
|
||||
|
||||
// ErrCodeRuleNotFoundException for service response error code
|
||||
// "RuleNotFound".
|
||||
//
|
||||
// The specified rule does not exist.
|
||||
ErrCodeRuleNotFoundException = "RuleNotFound"
|
||||
|
||||
// ErrCodeSSLPolicyNotFoundException for service response error code
|
||||
// "SSLPolicyNotFound".
|
||||
//
|
||||
// The specified SSL policy does not exist.
|
||||
ErrCodeSSLPolicyNotFoundException = "SSLPolicyNotFound"
|
||||
|
||||
// ErrCodeSubnetNotFoundException for service response error code
|
||||
// "SubnetNotFound".
|
||||
//
|
||||
// The specified subnet does not exist.
|
||||
ErrCodeSubnetNotFoundException = "SubnetNotFound"
|
||||
|
||||
// ErrCodeTargetGroupAssociationLimitException for service response error code
|
||||
// "TargetGroupAssociationLimit".
|
||||
//
|
||||
// You've reached the limit on the number of load balancers per target group.
|
||||
ErrCodeTargetGroupAssociationLimitException = "TargetGroupAssociationLimit"
|
||||
|
||||
// ErrCodeTargetGroupNotFoundException for service response error code
|
||||
// "TargetGroupNotFound".
|
||||
//
|
||||
// The specified target group does not exist.
|
||||
ErrCodeTargetGroupNotFoundException = "TargetGroupNotFound"
|
||||
|
||||
// ErrCodeTooManyActionsException for service response error code
|
||||
// "TooManyActions".
|
||||
//
|
||||
// You've reached the limit on the number of actions per rule.
|
||||
ErrCodeTooManyActionsException = "TooManyActions"
|
||||
|
||||
// ErrCodeTooManyCertificatesException for service response error code
|
||||
// "TooManyCertificates".
|
||||
//
|
||||
// You've reached the limit on the number of certificates per load balancer.
|
||||
ErrCodeTooManyCertificatesException = "TooManyCertificates"
|
||||
|
||||
// ErrCodeTooManyListenersException for service response error code
|
||||
// "TooManyListeners".
|
||||
//
|
||||
// You've reached the limit on the number of listeners per load balancer.
|
||||
ErrCodeTooManyListenersException = "TooManyListeners"
|
||||
|
||||
// ErrCodeTooManyLoadBalancersException for service response error code
|
||||
// "TooManyLoadBalancers".
|
||||
//
|
||||
// You've reached the limit on the number of load balancers for your Amazon
|
||||
// Web Services account.
|
||||
ErrCodeTooManyLoadBalancersException = "TooManyLoadBalancers"
|
||||
|
||||
// ErrCodeTooManyRegistrationsForTargetIdException for service response error code
|
||||
// "TooManyRegistrationsForTargetId".
|
||||
//
|
||||
// You've reached the limit on the number of times a target can be registered
|
||||
// with a load balancer.
|
||||
ErrCodeTooManyRegistrationsForTargetIdException = "TooManyRegistrationsForTargetId"
|
||||
|
||||
// ErrCodeTooManyRulesException for service response error code
|
||||
// "TooManyRules".
|
||||
//
|
||||
// You've reached the limit on the number of rules per load balancer.
|
||||
ErrCodeTooManyRulesException = "TooManyRules"
|
||||
|
||||
// ErrCodeTooManyTagsException for service response error code
|
||||
// "TooManyTags".
|
||||
//
|
||||
// You've reached the limit on the number of tags per load balancer.
|
||||
ErrCodeTooManyTagsException = "TooManyTags"
|
||||
|
||||
// ErrCodeTooManyTargetGroupsException for service response error code
|
||||
// "TooManyTargetGroups".
|
||||
//
|
||||
// You've reached the limit on the number of target groups for your Amazon Web
|
||||
// Services account.
|
||||
ErrCodeTooManyTargetGroupsException = "TooManyTargetGroups"
|
||||
|
||||
// ErrCodeTooManyTargetsException for service response error code
|
||||
// "TooManyTargets".
|
||||
//
|
||||
// You've reached the limit on the number of targets.
|
||||
ErrCodeTooManyTargetsException = "TooManyTargets"
|
||||
|
||||
// ErrCodeTooManyUniqueTargetGroupsPerLoadBalancerException for service response error code
|
||||
// "TooManyUniqueTargetGroupsPerLoadBalancer".
|
||||
//
|
||||
// You've reached the limit on the number of unique target groups per load balancer
|
||||
// across all listeners. If a target group is used by multiple actions for a
|
||||
// load balancer, it is counted as only one use.
|
||||
ErrCodeTooManyUniqueTargetGroupsPerLoadBalancerException = "TooManyUniqueTargetGroupsPerLoadBalancer"
|
||||
|
||||
// ErrCodeUnsupportedProtocolException for service response error code
|
||||
// "UnsupportedProtocol".
|
||||
//
|
||||
// The specified protocol is not supported.
|
||||
ErrCodeUnsupportedProtocolException = "UnsupportedProtocol"
|
||||
)
|
104
vendor/github.com/aws/aws-sdk-go/service/elbv2/service.go
generated
vendored
104
vendor/github.com/aws/aws-sdk-go/service/elbv2/service.go
generated
vendored
@ -1,104 +0,0 @@
|
||||
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
|
||||
|
||||
package elbv2
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/client"
|
||||
"github.com/aws/aws-sdk-go/aws/client/metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/signer/v4"
|
||||
"github.com/aws/aws-sdk-go/private/protocol/query"
|
||||
)
|
||||
|
||||
// ELBV2 provides the API operation methods for making requests to
|
||||
// Elastic Load Balancing. See this package's package overview docs
|
||||
// for details on the service.
|
||||
//
|
||||
// ELBV2 methods are safe to use concurrently. It is not safe to
|
||||
// modify mutate any of the struct's properties though.
|
||||
type ELBV2 struct {
|
||||
*client.Client
|
||||
}
|
||||
|
||||
// Used for custom client initialization logic
|
||||
var initClient func(*client.Client)
|
||||
|
||||
// Used for custom request initialization logic
|
||||
var initRequest func(*request.Request)
|
||||
|
||||
// Service information constants
|
||||
const (
|
||||
ServiceName = "elasticloadbalancing" // Name of service.
|
||||
EndpointsID = ServiceName // ID to lookup a service endpoint with.
|
||||
ServiceID = "Elastic Load Balancing v2" // ServiceID is a unique identifier of a specific service.
|
||||
)
|
||||
|
||||
// New creates a new instance of the ELBV2 client with a session.
|
||||
// If additional configuration is needed for the client instance use the optional
|
||||
// aws.Config parameter to add your extra config.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// mySession := session.Must(session.NewSession())
|
||||
//
|
||||
// // Create a ELBV2 client from just a session.
|
||||
// svc := elbv2.New(mySession)
|
||||
//
|
||||
// // Create a ELBV2 client with additional configuration
|
||||
// svc := elbv2.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
|
||||
func New(p client.ConfigProvider, cfgs ...*aws.Config) *ELBV2 {
|
||||
c := p.ClientConfig(EndpointsID, cfgs...)
|
||||
if c.SigningNameDerived || len(c.SigningName) == 0 {
|
||||
c.SigningName = EndpointsID
|
||||
// No Fallback
|
||||
}
|
||||
return newClient(*c.Config, c.Handlers, c.PartitionID, c.Endpoint, c.SigningRegion, c.SigningName, c.ResolvedRegion)
|
||||
}
|
||||
|
||||
// newClient creates, initializes and returns a new service client instance.
|
||||
func newClient(cfg aws.Config, handlers request.Handlers, partitionID, endpoint, signingRegion, signingName, resolvedRegion string) *ELBV2 {
|
||||
svc := &ELBV2{
|
||||
Client: client.New(
|
||||
cfg,
|
||||
metadata.ClientInfo{
|
||||
ServiceName: ServiceName,
|
||||
ServiceID: ServiceID,
|
||||
SigningName: signingName,
|
||||
SigningRegion: signingRegion,
|
||||
PartitionID: partitionID,
|
||||
Endpoint: endpoint,
|
||||
APIVersion: "2015-12-01",
|
||||
ResolvedRegion: resolvedRegion,
|
||||
},
|
||||
handlers,
|
||||
),
|
||||
}
|
||||
|
||||
// Handlers
|
||||
svc.Handlers.Sign.PushBackNamed(v4.SignRequestHandler)
|
||||
svc.Handlers.Build.PushBackNamed(query.BuildHandler)
|
||||
svc.Handlers.Unmarshal.PushBackNamed(query.UnmarshalHandler)
|
||||
svc.Handlers.UnmarshalMeta.PushBackNamed(query.UnmarshalMetaHandler)
|
||||
svc.Handlers.UnmarshalError.PushBackNamed(query.UnmarshalErrorHandler)
|
||||
|
||||
// Run custom client initialization if present
|
||||
if initClient != nil {
|
||||
initClient(svc.Client)
|
||||
}
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
// newRequest creates a new request for a ELBV2 operation and runs any
|
||||
// custom request initialization.
|
||||
func (c *ELBV2) newRequest(op *request.Operation, params, data interface{}) *request.Request {
|
||||
req := c.NewRequest(op, params, data)
|
||||
|
||||
// Run custom request initialization if present
|
||||
if initRequest != nil {
|
||||
initRequest(req)
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
270
vendor/github.com/aws/aws-sdk-go/service/elbv2/waiters.go
generated
vendored
270
vendor/github.com/aws/aws-sdk-go/service/elbv2/waiters.go
generated
vendored
@ -1,270 +0,0 @@
|
||||
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
|
||||
|
||||
package elbv2
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
)
|
||||
|
||||
// WaitUntilLoadBalancerAvailable uses the Elastic Load Balancing v2 API operation
|
||||
// DescribeLoadBalancers to wait for a condition to be met before returning.
|
||||
// If the condition is not met within the max attempt window, an error will
|
||||
// be returned.
|
||||
func (c *ELBV2) WaitUntilLoadBalancerAvailable(input *DescribeLoadBalancersInput) error {
|
||||
return c.WaitUntilLoadBalancerAvailableWithContext(aws.BackgroundContext(), input)
|
||||
}
|
||||
|
||||
// WaitUntilLoadBalancerAvailableWithContext is an extended version of WaitUntilLoadBalancerAvailable.
|
||||
// With the support for passing in a context and options to configure the
|
||||
// Waiter and the underlying request options.
|
||||
//
|
||||
// The context must be non-nil and will be used for request cancellation. If
|
||||
// the context is nil a panic will occur. In the future the SDK may create
|
||||
// sub-contexts for http.Requests. See https://golang.org/pkg/context/
|
||||
// for more information on using Contexts.
|
||||
func (c *ELBV2) WaitUntilLoadBalancerAvailableWithContext(ctx aws.Context, input *DescribeLoadBalancersInput, opts ...request.WaiterOption) error {
|
||||
w := request.Waiter{
|
||||
Name: "WaitUntilLoadBalancerAvailable",
|
||||
MaxAttempts: 40,
|
||||
Delay: request.ConstantWaiterDelay(15 * time.Second),
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
Matcher: request.PathAllWaiterMatch, Argument: "LoadBalancers[].State.Code",
|
||||
Expected: "active",
|
||||
},
|
||||
{
|
||||
State: request.RetryWaiterState,
|
||||
Matcher: request.PathAnyWaiterMatch, Argument: "LoadBalancers[].State.Code",
|
||||
Expected: "provisioning",
|
||||
},
|
||||
{
|
||||
State: request.RetryWaiterState,
|
||||
Matcher: request.ErrorWaiterMatch,
|
||||
Expected: "LoadBalancerNotFound",
|
||||
},
|
||||
},
|
||||
Logger: c.Config.Logger,
|
||||
NewRequest: func(opts []request.Option) (*request.Request, error) {
|
||||
var inCpy *DescribeLoadBalancersInput
|
||||
if input != nil {
|
||||
tmp := *input
|
||||
inCpy = &tmp
|
||||
}
|
||||
req, _ := c.DescribeLoadBalancersRequest(inCpy)
|
||||
req.SetContext(ctx)
|
||||
req.ApplyOptions(opts...)
|
||||
return req, nil
|
||||
},
|
||||
}
|
||||
w.ApplyOptions(opts...)
|
||||
|
||||
return w.WaitWithContext(ctx)
|
||||
}
|
||||
|
||||
// WaitUntilLoadBalancerExists uses the Elastic Load Balancing v2 API operation
|
||||
// DescribeLoadBalancers to wait for a condition to be met before returning.
|
||||
// If the condition is not met within the max attempt window, an error will
|
||||
// be returned.
|
||||
func (c *ELBV2) WaitUntilLoadBalancerExists(input *DescribeLoadBalancersInput) error {
|
||||
return c.WaitUntilLoadBalancerExistsWithContext(aws.BackgroundContext(), input)
|
||||
}
|
||||
|
||||
// WaitUntilLoadBalancerExistsWithContext is an extended version of WaitUntilLoadBalancerExists.
|
||||
// With the support for passing in a context and options to configure the
|
||||
// Waiter and the underlying request options.
|
||||
//
|
||||
// The context must be non-nil and will be used for request cancellation. If
|
||||
// the context is nil a panic will occur. In the future the SDK may create
|
||||
// sub-contexts for http.Requests. See https://golang.org/pkg/context/
|
||||
// for more information on using Contexts.
|
||||
func (c *ELBV2) WaitUntilLoadBalancerExistsWithContext(ctx aws.Context, input *DescribeLoadBalancersInput, opts ...request.WaiterOption) error {
|
||||
w := request.Waiter{
|
||||
Name: "WaitUntilLoadBalancerExists",
|
||||
MaxAttempts: 40,
|
||||
Delay: request.ConstantWaiterDelay(15 * time.Second),
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
Matcher: request.StatusWaiterMatch,
|
||||
Expected: 200,
|
||||
},
|
||||
{
|
||||
State: request.RetryWaiterState,
|
||||
Matcher: request.ErrorWaiterMatch,
|
||||
Expected: "LoadBalancerNotFound",
|
||||
},
|
||||
},
|
||||
Logger: c.Config.Logger,
|
||||
NewRequest: func(opts []request.Option) (*request.Request, error) {
|
||||
var inCpy *DescribeLoadBalancersInput
|
||||
if input != nil {
|
||||
tmp := *input
|
||||
inCpy = &tmp
|
||||
}
|
||||
req, _ := c.DescribeLoadBalancersRequest(inCpy)
|
||||
req.SetContext(ctx)
|
||||
req.ApplyOptions(opts...)
|
||||
return req, nil
|
||||
},
|
||||
}
|
||||
w.ApplyOptions(opts...)
|
||||
|
||||
return w.WaitWithContext(ctx)
|
||||
}
|
||||
|
||||
// WaitUntilLoadBalancersDeleted uses the Elastic Load Balancing v2 API operation
|
||||
// DescribeLoadBalancers to wait for a condition to be met before returning.
|
||||
// If the condition is not met within the max attempt window, an error will
|
||||
// be returned.
|
||||
func (c *ELBV2) WaitUntilLoadBalancersDeleted(input *DescribeLoadBalancersInput) error {
|
||||
return c.WaitUntilLoadBalancersDeletedWithContext(aws.BackgroundContext(), input)
|
||||
}
|
||||
|
||||
// WaitUntilLoadBalancersDeletedWithContext is an extended version of WaitUntilLoadBalancersDeleted.
|
||||
// With the support for passing in a context and options to configure the
|
||||
// Waiter and the underlying request options.
|
||||
//
|
||||
// The context must be non-nil and will be used for request cancellation. If
|
||||
// the context is nil a panic will occur. In the future the SDK may create
|
||||
// sub-contexts for http.Requests. See https://golang.org/pkg/context/
|
||||
// for more information on using Contexts.
|
||||
func (c *ELBV2) WaitUntilLoadBalancersDeletedWithContext(ctx aws.Context, input *DescribeLoadBalancersInput, opts ...request.WaiterOption) error {
|
||||
w := request.Waiter{
|
||||
Name: "WaitUntilLoadBalancersDeleted",
|
||||
MaxAttempts: 40,
|
||||
Delay: request.ConstantWaiterDelay(15 * time.Second),
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.RetryWaiterState,
|
||||
Matcher: request.PathAllWaiterMatch, Argument: "LoadBalancers[].State.Code",
|
||||
Expected: "active",
|
||||
},
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
Matcher: request.ErrorWaiterMatch,
|
||||
Expected: "LoadBalancerNotFound",
|
||||
},
|
||||
},
|
||||
Logger: c.Config.Logger,
|
||||
NewRequest: func(opts []request.Option) (*request.Request, error) {
|
||||
var inCpy *DescribeLoadBalancersInput
|
||||
if input != nil {
|
||||
tmp := *input
|
||||
inCpy = &tmp
|
||||
}
|
||||
req, _ := c.DescribeLoadBalancersRequest(inCpy)
|
||||
req.SetContext(ctx)
|
||||
req.ApplyOptions(opts...)
|
||||
return req, nil
|
||||
},
|
||||
}
|
||||
w.ApplyOptions(opts...)
|
||||
|
||||
return w.WaitWithContext(ctx)
|
||||
}
|
||||
|
||||
// WaitUntilTargetDeregistered uses the Elastic Load Balancing v2 API operation
|
||||
// DescribeTargetHealth to wait for a condition to be met before returning.
|
||||
// If the condition is not met within the max attempt window, an error will
|
||||
// be returned.
|
||||
func (c *ELBV2) WaitUntilTargetDeregistered(input *DescribeTargetHealthInput) error {
|
||||
return c.WaitUntilTargetDeregisteredWithContext(aws.BackgroundContext(), input)
|
||||
}
|
||||
|
||||
// WaitUntilTargetDeregisteredWithContext is an extended version of WaitUntilTargetDeregistered.
|
||||
// With the support for passing in a context and options to configure the
|
||||
// Waiter and the underlying request options.
|
||||
//
|
||||
// The context must be non-nil and will be used for request cancellation. If
|
||||
// the context is nil a panic will occur. In the future the SDK may create
|
||||
// sub-contexts for http.Requests. See https://golang.org/pkg/context/
|
||||
// for more information on using Contexts.
|
||||
func (c *ELBV2) WaitUntilTargetDeregisteredWithContext(ctx aws.Context, input *DescribeTargetHealthInput, opts ...request.WaiterOption) error {
|
||||
w := request.Waiter{
|
||||
Name: "WaitUntilTargetDeregistered",
|
||||
MaxAttempts: 40,
|
||||
Delay: request.ConstantWaiterDelay(15 * time.Second),
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
Matcher: request.ErrorWaiterMatch,
|
||||
Expected: "InvalidTarget",
|
||||
},
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
Matcher: request.PathAllWaiterMatch, Argument: "TargetHealthDescriptions[].TargetHealth.State",
|
||||
Expected: "unused",
|
||||
},
|
||||
},
|
||||
Logger: c.Config.Logger,
|
||||
NewRequest: func(opts []request.Option) (*request.Request, error) {
|
||||
var inCpy *DescribeTargetHealthInput
|
||||
if input != nil {
|
||||
tmp := *input
|
||||
inCpy = &tmp
|
||||
}
|
||||
req, _ := c.DescribeTargetHealthRequest(inCpy)
|
||||
req.SetContext(ctx)
|
||||
req.ApplyOptions(opts...)
|
||||
return req, nil
|
||||
},
|
||||
}
|
||||
w.ApplyOptions(opts...)
|
||||
|
||||
return w.WaitWithContext(ctx)
|
||||
}
|
||||
|
||||
// WaitUntilTargetInService uses the Elastic Load Balancing v2 API operation
|
||||
// DescribeTargetHealth to wait for a condition to be met before returning.
|
||||
// If the condition is not met within the max attempt window, an error will
|
||||
// be returned.
|
||||
func (c *ELBV2) WaitUntilTargetInService(input *DescribeTargetHealthInput) error {
|
||||
return c.WaitUntilTargetInServiceWithContext(aws.BackgroundContext(), input)
|
||||
}
|
||||
|
||||
// WaitUntilTargetInServiceWithContext is an extended version of WaitUntilTargetInService.
|
||||
// With the support for passing in a context and options to configure the
|
||||
// Waiter and the underlying request options.
|
||||
//
|
||||
// The context must be non-nil and will be used for request cancellation. If
|
||||
// the context is nil a panic will occur. In the future the SDK may create
|
||||
// sub-contexts for http.Requests. See https://golang.org/pkg/context/
|
||||
// for more information on using Contexts.
|
||||
func (c *ELBV2) WaitUntilTargetInServiceWithContext(ctx aws.Context, input *DescribeTargetHealthInput, opts ...request.WaiterOption) error {
|
||||
w := request.Waiter{
|
||||
Name: "WaitUntilTargetInService",
|
||||
MaxAttempts: 40,
|
||||
Delay: request.ConstantWaiterDelay(15 * time.Second),
|
||||
Acceptors: []request.WaiterAcceptor{
|
||||
{
|
||||
State: request.SuccessWaiterState,
|
||||
Matcher: request.PathAllWaiterMatch, Argument: "TargetHealthDescriptions[].TargetHealth.State",
|
||||
Expected: "healthy",
|
||||
},
|
||||
{
|
||||
State: request.RetryWaiterState,
|
||||
Matcher: request.ErrorWaiterMatch,
|
||||
Expected: "InvalidInstance",
|
||||
},
|
||||
},
|
||||
Logger: c.Config.Logger,
|
||||
NewRequest: func(opts []request.Option) (*request.Request, error) {
|
||||
var inCpy *DescribeTargetHealthInput
|
||||
if input != nil {
|
||||
tmp := *input
|
||||
inCpy = &tmp
|
||||
}
|
||||
req, _ := c.DescribeTargetHealthRequest(inCpy)
|
||||
req.SetContext(ctx)
|
||||
req.ApplyOptions(opts...)
|
||||
return req, nil
|
||||
},
|
||||
}
|
||||
w.ApplyOptions(opts...)
|
||||
|
||||
return w.WaitWithContext(ctx)
|
||||
}
|
19280
vendor/github.com/aws/aws-sdk-go/service/kms/api.go
generated
vendored
19280
vendor/github.com/aws/aws-sdk-go/service/kms/api.go
generated
vendored
File diff suppressed because it is too large
Load Diff
113
vendor/github.com/aws/aws-sdk-go/service/kms/doc.go
generated
vendored
113
vendor/github.com/aws/aws-sdk-go/service/kms/doc.go
generated
vendored
@ -1,113 +0,0 @@
|
||||
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
|
||||
|
||||
// Package kms provides the client and types for making API
|
||||
// requests to AWS Key Management Service.
|
||||
//
|
||||
// Key Management Service (KMS) is an encryption and key management web service.
|
||||
// This guide describes the KMS operations that you can call programmatically.
|
||||
// For general information about KMS, see the Key Management Service Developer
|
||||
// Guide (https://docs.aws.amazon.com/kms/latest/developerguide/).
|
||||
//
|
||||
// KMS is replacing the term customer master key (CMK) with KMS key and KMS
|
||||
// key. The concept has not changed. To prevent breaking changes, KMS is keeping
|
||||
// some variations of this term.
|
||||
//
|
||||
// Amazon Web Services provides SDKs that consist of libraries and sample code
|
||||
// for various programming languages and platforms (Java, Ruby, .Net, macOS,
|
||||
// Android, etc.). The SDKs provide a convenient way to create programmatic
|
||||
// access to KMS and other Amazon Web Services services. For example, the SDKs
|
||||
// take care of tasks such as signing requests (see below), managing errors,
|
||||
// and retrying requests automatically. For more information about the Amazon
|
||||
// Web Services SDKs, including how to download and install them, see Tools
|
||||
// for Amazon Web Services (http://aws.amazon.com/tools/).
|
||||
//
|
||||
// We recommend that you use the Amazon Web Services SDKs to make programmatic
|
||||
// API calls to KMS.
|
||||
//
|
||||
// If you need to use FIPS 140-2 validated cryptographic modules when communicating
|
||||
// with Amazon Web Services, use the FIPS endpoint in your preferred Amazon
|
||||
// Web Services Region. For more information about the available FIPS endpoints,
|
||||
// see Service endpoints (https://docs.aws.amazon.com/general/latest/gr/kms.html#kms_region)
|
||||
// in the Key Management Service topic of the Amazon Web Services General Reference.
|
||||
//
|
||||
// All KMS API calls must be signed and be transmitted using Transport Layer
|
||||
// Security (TLS). KMS recommends you always use the latest supported TLS version.
|
||||
// Clients must also support cipher suites with Perfect Forward Secrecy (PFS)
|
||||
// such as Ephemeral Diffie-Hellman (DHE) or Elliptic Curve Ephemeral Diffie-Hellman
|
||||
// (ECDHE). Most modern systems such as Java 7 and later support these modes.
|
||||
//
|
||||
// # Signing Requests
|
||||
//
|
||||
// Requests must be signed by using an access key ID and a secret access key.
|
||||
// We strongly recommend that you do not use your Amazon Web Services account
|
||||
// (root) access key ID and secret key for everyday work with KMS. Instead,
|
||||
// use the access key ID and secret access key for an IAM user. You can also
|
||||
// use the Amazon Web Services Security Token Service to generate temporary
|
||||
// security credentials that you can use to sign requests.
|
||||
//
|
||||
// All KMS operations require Signature Version 4 (https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html).
|
||||
//
|
||||
// # Logging API Requests
|
||||
//
|
||||
// KMS supports CloudTrail, a service that logs Amazon Web Services API calls
|
||||
// and related events for your Amazon Web Services account and delivers them
|
||||
// to an Amazon S3 bucket that you specify. By using the information collected
|
||||
// by CloudTrail, you can determine what requests were made to KMS, who made
|
||||
// the request, when it was made, and so on. To learn more about CloudTrail,
|
||||
// including how to turn it on and find your log files, see the CloudTrail User
|
||||
// Guide (https://docs.aws.amazon.com/awscloudtrail/latest/userguide/).
|
||||
//
|
||||
// # Additional Resources
|
||||
//
|
||||
// For more information about credentials and request signing, see the following:
|
||||
//
|
||||
// - Amazon Web Services Security Credentials (https://docs.aws.amazon.com/general/latest/gr/aws-security-credentials.html)
|
||||
//
|
||||
// - This topic provides general information about the types of credentials
|
||||
// used to access Amazon Web Services.
|
||||
//
|
||||
// - Temporary Security Credentials (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html)
|
||||
//
|
||||
// - This section of the IAM User Guide describes how to create and use temporary
|
||||
// security credentials.
|
||||
//
|
||||
// - Signature Version 4 Signing Process (https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)
|
||||
//
|
||||
// - This set of topics walks you through the process of signing a request
|
||||
// using an access key ID and a secret access key.
|
||||
//
|
||||
// # Commonly Used API Operations
|
||||
//
|
||||
// Of the API operations discussed in this guide, the following will prove the
|
||||
// most useful for most applications. You will likely perform operations other
|
||||
// than these, such as creating keys and assigning policies, by using the console.
|
||||
//
|
||||
// - Encrypt
|
||||
//
|
||||
// - Decrypt
|
||||
//
|
||||
// - GenerateDataKey
|
||||
//
|
||||
// - GenerateDataKeyWithoutPlaintext
|
||||
//
|
||||
// See https://docs.aws.amazon.com/goto/WebAPI/kms-2014-11-01 for more information on this service.
|
||||
//
|
||||
// See kms package documentation for more information.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/service/kms/
|
||||
//
|
||||
// # Using the Client
|
||||
//
|
||||
// To contact AWS Key Management Service with the SDK use the New function to create
|
||||
// a new service client. With that client you can make API requests to the service.
|
||||
// These clients are safe to use concurrently.
|
||||
//
|
||||
// See the SDK's documentation for more information on how to use the SDK.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/
|
||||
//
|
||||
// See aws.Config documentation for more information on configuring SDK clients.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/aws/#Config
|
||||
//
|
||||
// See the AWS Key Management Service client KMS for more
|
||||
// information on creating client for this service.
|
||||
// https://docs.aws.amazon.com/sdk-for-go/api/service/kms/#New
|
||||
package kms
|
376
vendor/github.com/aws/aws-sdk-go/service/kms/errors.go
generated
vendored
376
vendor/github.com/aws/aws-sdk-go/service/kms/errors.go
generated
vendored
@ -1,376 +0,0 @@
|
||||
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
|
||||
|
||||
package kms
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/private/protocol"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
// ErrCodeAlreadyExistsException for service response error code
|
||||
// "AlreadyExistsException".
|
||||
//
|
||||
// The request was rejected because it attempted to create a resource that already
|
||||
// exists.
|
||||
ErrCodeAlreadyExistsException = "AlreadyExistsException"
|
||||
|
||||
// ErrCodeCloudHsmClusterInUseException for service response error code
|
||||
// "CloudHsmClusterInUseException".
|
||||
//
|
||||
// The request was rejected because the specified CloudHSM cluster is already
|
||||
// associated with a custom key store or it shares a backup history with a cluster
|
||||
// that is associated with a custom key store. Each custom key store must be
|
||||
// associated with a different CloudHSM cluster.
|
||||
//
|
||||
// Clusters that share a backup history have the same cluster certificate. To
|
||||
// view the cluster certificate of a cluster, use the DescribeClusters (https://docs.aws.amazon.com/cloudhsm/latest/APIReference/API_DescribeClusters.html)
|
||||
// operation.
|
||||
ErrCodeCloudHsmClusterInUseException = "CloudHsmClusterInUseException"
|
||||
|
||||
// ErrCodeCloudHsmClusterInvalidConfigurationException for service response error code
|
||||
// "CloudHsmClusterInvalidConfigurationException".
|
||||
//
|
||||
// The request was rejected because the associated CloudHSM cluster did not
|
||||
// meet the configuration requirements for a custom key store.
|
||||
//
|
||||
// * The cluster must be configured with private subnets in at least two
|
||||
// different Availability Zones in the Region.
|
||||
//
|
||||
// * The security group for the cluster (https://docs.aws.amazon.com/cloudhsm/latest/userguide/configure-sg.html)
|
||||
// (cloudhsm-cluster-<cluster-id>-sg) must include inbound rules and outbound
|
||||
// rules that allow TCP traffic on ports 2223-2225. The Source in the inbound
|
||||
// rules and the Destination in the outbound rules must match the security
|
||||
// group ID. These rules are set by default when you create the cluster.
|
||||
// Do not delete or change them. To get information about a particular security
|
||||
// group, use the DescribeSecurityGroups (https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSecurityGroups.html)
|
||||
// operation.
|
||||
//
|
||||
// * The cluster must contain at least as many HSMs as the operation requires.
|
||||
// To add HSMs, use the CloudHSM CreateHsm (https://docs.aws.amazon.com/cloudhsm/latest/APIReference/API_CreateHsm.html)
|
||||
// operation. For the CreateCustomKeyStore, UpdateCustomKeyStore, and CreateKey
|
||||
// operations, the CloudHSM cluster must have at least two active HSMs, each
|
||||
// in a different Availability Zone. For the ConnectCustomKeyStore operation,
|
||||
// the CloudHSM must contain at least one active HSM.
|
||||
//
|
||||
// For information about the requirements for an CloudHSM cluster that is associated
|
||||
// with a custom key store, see Assemble the Prerequisites (https://docs.aws.amazon.com/kms/latest/developerguide/create-keystore.html#before-keystore)
|
||||
// in the Key Management Service Developer Guide. For information about creating
|
||||
// a private subnet for an CloudHSM cluster, see Create a Private Subnet (https://docs.aws.amazon.com/cloudhsm/latest/userguide/create-subnets.html)
|
||||
// in the CloudHSM User Guide. For information about cluster security groups,
|
||||
// see Configure a Default Security Group (https://docs.aws.amazon.com/cloudhsm/latest/userguide/configure-sg.html)
|
||||
// in the CloudHSM User Guide .
|
||||
ErrCodeCloudHsmClusterInvalidConfigurationException = "CloudHsmClusterInvalidConfigurationException"
|
||||
|
||||
// ErrCodeCloudHsmClusterNotActiveException for service response error code
|
||||
// "CloudHsmClusterNotActiveException".
|
||||
//
|
||||
// The request was rejected because the CloudHSM cluster that is associated
|
||||
// with the custom key store is not active. Initialize and activate the cluster
|
||||
// and try the command again. For detailed instructions, see Getting Started
|
||||
// (https://docs.aws.amazon.com/cloudhsm/latest/userguide/getting-started.html)
|
||||
// in the CloudHSM User Guide.
|
||||
ErrCodeCloudHsmClusterNotActiveException = "CloudHsmClusterNotActiveException"
|
||||
|
||||
// ErrCodeCloudHsmClusterNotFoundException for service response error code
|
||||
// "CloudHsmClusterNotFoundException".
|
||||
//
|
||||
// The request was rejected because KMS cannot find the CloudHSM cluster with
|
||||
// the specified cluster ID. Retry the request with a different cluster ID.
|
||||
ErrCodeCloudHsmClusterNotFoundException = "CloudHsmClusterNotFoundException"
|
||||
|
||||
// ErrCodeCloudHsmClusterNotRelatedException for service response error code
|
||||
// "CloudHsmClusterNotRelatedException".
|
||||
//
|
||||
// The request was rejected because the specified CloudHSM cluster has a different
|
||||
// cluster certificate than the original cluster. You cannot use the operation
|
||||
// to specify an unrelated cluster.
|
||||
//
|
||||
// Specify a cluster that shares a backup history with the original cluster.
|
||||
// This includes clusters that were created from a backup of the current cluster,
|
||||
// and clusters that were created from the same backup that produced the current
|
||||
// cluster.
|
||||
//
|
||||
// Clusters that share a backup history have the same cluster certificate. To
|
||||
// view the cluster certificate of a cluster, use the DescribeClusters (https://docs.aws.amazon.com/cloudhsm/latest/APIReference/API_DescribeClusters.html)
|
||||
// operation.
|
||||
ErrCodeCloudHsmClusterNotRelatedException = "CloudHsmClusterNotRelatedException"
|
||||
|
||||
// ErrCodeCustomKeyStoreHasCMKsException for service response error code
|
||||
// "CustomKeyStoreHasCMKsException".
|
||||
//
|
||||
// The request was rejected because the custom key store contains KMS keys.
|
||||
// After verifying that you do not need to use the KMS keys, use the ScheduleKeyDeletion
|
||||
// operation to delete the KMS keys. After they are deleted, you can delete
|
||||
// the custom key store.
|
||||
ErrCodeCustomKeyStoreHasCMKsException = "CustomKeyStoreHasCMKsException"
|
||||
|
||||
// ErrCodeCustomKeyStoreInvalidStateException for service response error code
|
||||
// "CustomKeyStoreInvalidStateException".
|
||||
//
|
||||
// The request was rejected because of the ConnectionState of the custom key
|
||||
// store. To get the ConnectionState of a custom key store, use the DescribeCustomKeyStores
|
||||
// operation.
|
||||
//
|
||||
// This exception is thrown under the following conditions:
|
||||
//
|
||||
// * You requested the CreateKey or GenerateRandom operation in a custom
|
||||
// key store that is not connected. These operations are valid only when
|
||||
// the custom key store ConnectionState is CONNECTED.
|
||||
//
|
||||
// * You requested the UpdateCustomKeyStore or DeleteCustomKeyStore operation
|
||||
// on a custom key store that is not disconnected. This operation is valid
|
||||
// only when the custom key store ConnectionState is DISCONNECTED.
|
||||
//
|
||||
// * You requested the ConnectCustomKeyStore operation on a custom key store
|
||||
// with a ConnectionState of DISCONNECTING or FAILED. This operation is valid
|
||||
// for all other ConnectionState values.
|
||||
ErrCodeCustomKeyStoreInvalidStateException = "CustomKeyStoreInvalidStateException"
|
||||
|
||||
// ErrCodeCustomKeyStoreNameInUseException for service response error code
|
||||
// "CustomKeyStoreNameInUseException".
|
||||
//
|
||||
// The request was rejected because the specified custom key store name is already
|
||||
// assigned to another custom key store in the account. Try again with a custom
|
||||
// key store name that is unique in the account.
|
||||
ErrCodeCustomKeyStoreNameInUseException = "CustomKeyStoreNameInUseException"
|
||||
|
||||
// ErrCodeCustomKeyStoreNotFoundException for service response error code
|
||||
// "CustomKeyStoreNotFoundException".
|
||||
//
|
||||
// The request was rejected because KMS cannot find a custom key store with
|
||||
// the specified key store name or ID.
|
||||
ErrCodeCustomKeyStoreNotFoundException = "CustomKeyStoreNotFoundException"
|
||||
|
||||
// ErrCodeDependencyTimeoutException for service response error code
|
||||
// "DependencyTimeoutException".
|
||||
//
|
||||
// The system timed out while trying to fulfill the request. The request can
|
||||
// be retried.
|
||||
ErrCodeDependencyTimeoutException = "DependencyTimeoutException"
|
||||
|
||||
// ErrCodeDisabledException for service response error code
|
||||
// "DisabledException".
|
||||
//
|
||||
// The request was rejected because the specified KMS key is not enabled.
|
||||
ErrCodeDisabledException = "DisabledException"
|
||||
|
||||
// ErrCodeExpiredImportTokenException for service response error code
|
||||
// "ExpiredImportTokenException".
|
||||
//
|
||||
// The request was rejected because the specified import token is expired. Use
|
||||
// GetParametersForImport to get a new import token and public key, use the
|
||||
// new public key to encrypt the key material, and then try the request again.
|
||||
ErrCodeExpiredImportTokenException = "ExpiredImportTokenException"
|
||||
|
||||
// ErrCodeIncorrectKeyException for service response error code
|
||||
// "IncorrectKeyException".
|
||||
//
|
||||
// The request was rejected because the specified KMS key cannot decrypt the
|
||||
// data. The KeyId in a Decrypt request and the SourceKeyId in a ReEncrypt request
|
||||
// must identify the same KMS key that was used to encrypt the ciphertext.
|
||||
ErrCodeIncorrectKeyException = "IncorrectKeyException"
|
||||
|
||||
// ErrCodeIncorrectKeyMaterialException for service response error code
|
||||
// "IncorrectKeyMaterialException".
|
||||
//
|
||||
// The request was rejected because the key material in the request is, expired,
|
||||
// invalid, or is not the same key material that was previously imported into
|
||||
// this KMS key.
|
||||
ErrCodeIncorrectKeyMaterialException = "IncorrectKeyMaterialException"
|
||||
|
||||
// ErrCodeIncorrectTrustAnchorException for service response error code
|
||||
// "IncorrectTrustAnchorException".
|
||||
//
|
||||
// The request was rejected because the trust anchor certificate in the request
|
||||
// is not the trust anchor certificate for the specified CloudHSM cluster.
|
||||
//
|
||||
// When you initialize the cluster (https://docs.aws.amazon.com/cloudhsm/latest/userguide/initialize-cluster.html#sign-csr),
|
||||
// you create the trust anchor certificate and save it in the customerCA.crt
|
||||
// file.
|
||||
ErrCodeIncorrectTrustAnchorException = "IncorrectTrustAnchorException"
|
||||
|
||||
// ErrCodeInternalException for service response error code
|
||||
// "KMSInternalException".
|
||||
//
|
||||
// The request was rejected because an internal exception occurred. The request
|
||||
// can be retried.
|
||||
ErrCodeInternalException = "KMSInternalException"
|
||||
|
||||
// ErrCodeInvalidAliasNameException for service response error code
|
||||
// "InvalidAliasNameException".
|
||||
//
|
||||
// The request was rejected because the specified alias name is not valid.
|
||||
ErrCodeInvalidAliasNameException = "InvalidAliasNameException"
|
||||
|
||||
// ErrCodeInvalidArnException for service response error code
|
||||
// "InvalidArnException".
|
||||
//
|
||||
// The request was rejected because a specified ARN, or an ARN in a key policy,
|
||||
// is not valid.
|
||||
ErrCodeInvalidArnException = "InvalidArnException"
|
||||
|
||||
// ErrCodeInvalidCiphertextException for service response error code
|
||||
// "InvalidCiphertextException".
|
||||
//
|
||||
// From the Decrypt or ReEncrypt operation, the request was rejected because
|
||||
// the specified ciphertext, or additional authenticated data incorporated into
|
||||
// the ciphertext, such as the encryption context, is corrupted, missing, or
|
||||
// otherwise invalid.
|
||||
//
|
||||
// From the ImportKeyMaterial operation, the request was rejected because KMS
|
||||
// could not decrypt the encrypted (wrapped) key material.
|
||||
ErrCodeInvalidCiphertextException = "InvalidCiphertextException"
|
||||
|
||||
// ErrCodeInvalidGrantIdException for service response error code
|
||||
// "InvalidGrantIdException".
|
||||
//
|
||||
// The request was rejected because the specified GrantId is not valid.
|
||||
ErrCodeInvalidGrantIdException = "InvalidGrantIdException"
|
||||
|
||||
// ErrCodeInvalidGrantTokenException for service response error code
|
||||
// "InvalidGrantTokenException".
|
||||
//
|
||||
// The request was rejected because the specified grant token is not valid.
|
||||
ErrCodeInvalidGrantTokenException = "InvalidGrantTokenException"
|
||||
|
||||
// ErrCodeInvalidImportTokenException for service response error code
|
||||
// "InvalidImportTokenException".
|
||||
//
|
||||
// The request was rejected because the provided import token is invalid or
|
||||
// is associated with a different KMS key.
|
||||
ErrCodeInvalidImportTokenException = "InvalidImportTokenException"
|
||||
|
||||
// ErrCodeInvalidKeyUsageException for service response error code
|
||||
// "InvalidKeyUsageException".
|
||||
//
|
||||
// The request was rejected for one of the following reasons:
|
||||
//
|
||||
// * The KeyUsage value of the KMS key is incompatible with the API operation.
|
||||
//
|
||||
// * The encryption algorithm or signing algorithm specified for the operation
|
||||
// is incompatible with the type of key material in the KMS key (KeySpec).
|
||||
//
|
||||
// For encrypting, decrypting, re-encrypting, and generating data keys, the
|
||||
// KeyUsage must be ENCRYPT_DECRYPT. For signing and verifying messages, the
|
||||
// KeyUsage must be SIGN_VERIFY. For generating and verifying message authentication
|
||||
// codes (MACs), the KeyUsage must be GENERATE_VERIFY_MAC. To find the KeyUsage
|
||||
// of a KMS key, use the DescribeKey operation.
|
||||
//
|
||||
// To find the encryption or signing algorithms supported for a particular KMS
|
||||
// key, use the DescribeKey operation.
|
||||
ErrCodeInvalidKeyUsageException = "InvalidKeyUsageException"
|
||||
|
||||
// ErrCodeInvalidMarkerException for service response error code
|
||||
// "InvalidMarkerException".
|
||||
//
|
||||
// The request was rejected because the marker that specifies where pagination
|
||||
// should next begin is not valid.
|
||||
ErrCodeInvalidMarkerException = "InvalidMarkerException"
|
||||
|
||||
// ErrCodeInvalidStateException for service response error code
|
||||
// "KMSInvalidStateException".
|
||||
//
|
||||
// The request was rejected because the state of the specified resource is not
|
||||
// valid for this request.
|
||||
//
|
||||
// For more information about how key state affects the use of a KMS key, see
|
||||
// Key states of KMS keys (https://docs.aws.amazon.com/kms/latest/developerguide/key-state.html)
|
||||
// in the Key Management Service Developer Guide .
|
||||
ErrCodeInvalidStateException = "KMSInvalidStateException"
|
||||
|
||||
// ErrCodeKMSInvalidMacException for service response error code
|
||||
// "KMSInvalidMacException".
|
||||
//
|
||||
// The request was rejected because the HMAC verification failed. HMAC verification
|
||||
// fails when the HMAC computed by using the specified message, HMAC KMS key,
|
||||
// and MAC algorithm does not match the HMAC specified in the request.
|
||||
ErrCodeKMSInvalidMacException = "KMSInvalidMacException"
|
||||
|
||||
// ErrCodeKMSInvalidSignatureException for service response error code
|
||||
// "KMSInvalidSignatureException".
|
||||
//
|
||||
// The request was rejected because the signature verification failed. Signature
|
||||
// verification fails when it cannot confirm that signature was produced by
|
||||
// signing the specified message with the specified KMS key and signing algorithm.
|
||||
ErrCodeKMSInvalidSignatureException = "KMSInvalidSignatureException"
|
||||
|
||||
// ErrCodeKeyUnavailableException for service response error code
|
||||
// "KeyUnavailableException".
|
||||
//
|
||||
// The request was rejected because the specified KMS key was not available.
|
||||
// You can retry the request.
|
||||
ErrCodeKeyUnavailableException = "KeyUnavailableException"
|
||||
|
||||
// ErrCodeLimitExceededException for service response error code
|
||||
// "LimitExceededException".
|
||||
//
|
||||
// The request was rejected because a quota was exceeded. For more information,
|
||||
// see Quotas (https://docs.aws.amazon.com/kms/latest/developerguide/limits.html)
|
||||
// in the Key Management Service Developer Guide.
|
||||
ErrCodeLimitExceededException = "LimitExceededException"
|
||||
|
||||
// ErrCodeMalformedPolicyDocumentException for service response error code
|
||||
// "MalformedPolicyDocumentException".
|
||||
//
|
||||
// The request was rejected because the specified policy is not syntactically
|
||||
// or semantically correct.
|
||||
ErrCodeMalformedPolicyDocumentException = "MalformedPolicyDocumentException"
|
||||
|
||||
// ErrCodeNotFoundException for service response error code
|
||||
// "NotFoundException".
|
||||
//
|
||||
// The request was rejected because the specified entity or resource could not
|
||||
// be found.
|
||||
ErrCodeNotFoundException = "NotFoundException"
|
||||
|
||||
// ErrCodeTagException for service response error code
|
||||
// "TagException".
|
||||
//
|
||||
// The request was rejected because one or more tags are not valid.
|
||||
ErrCodeTagException = "TagException"
|
||||
|
||||
// ErrCodeUnsupportedOperationException for service response error code
|
||||
// "UnsupportedOperationException".
|
||||
//
|
||||
// The request was rejected because a specified parameter is not supported or
|
||||
// a specified resource is not valid for this operation.
|
||||
ErrCodeUnsupportedOperationException = "UnsupportedOperationException"
|
||||
)
|
||||
|
||||
var exceptionFromCode = map[string]func(protocol.ResponseMetadata) error{
|
||||
"AlreadyExistsException": newErrorAlreadyExistsException,
|
||||
"CloudHsmClusterInUseException": newErrorCloudHsmClusterInUseException,
|
||||
"CloudHsmClusterInvalidConfigurationException": newErrorCloudHsmClusterInvalidConfigurationException,
|
||||
"CloudHsmClusterNotActiveException": newErrorCloudHsmClusterNotActiveException,
|
||||
"CloudHsmClusterNotFoundException": newErrorCloudHsmClusterNotFoundException,
|
||||
"CloudHsmClusterNotRelatedException": newErrorCloudHsmClusterNotRelatedException,
|
||||
"CustomKeyStoreHasCMKsException": newErrorCustomKeyStoreHasCMKsException,
|
||||
"CustomKeyStoreInvalidStateException": newErrorCustomKeyStoreInvalidStateException,
|
||||
"CustomKeyStoreNameInUseException": newErrorCustomKeyStoreNameInUseException,
|
||||
"CustomKeyStoreNotFoundException": newErrorCustomKeyStoreNotFoundException,
|
||||
"DependencyTimeoutException": newErrorDependencyTimeoutException,
|
||||
"DisabledException": newErrorDisabledException,
|
||||
"ExpiredImportTokenException": newErrorExpiredImportTokenException,
|
||||
"IncorrectKeyException": newErrorIncorrectKeyException,
|
||||
"IncorrectKeyMaterialException": newErrorIncorrectKeyMaterialException,
|
||||
"IncorrectTrustAnchorException": newErrorIncorrectTrustAnchorException,
|
||||
"KMSInternalException": newErrorInternalException,
|
||||
"InvalidAliasNameException": newErrorInvalidAliasNameException,
|
||||
"InvalidArnException": newErrorInvalidArnException,
|
||||
"InvalidCiphertextException": newErrorInvalidCiphertextException,
|
||||
"InvalidGrantIdException": newErrorInvalidGrantIdException,
|
||||
"InvalidGrantTokenException": newErrorInvalidGrantTokenException,
|
||||
"InvalidImportTokenException": newErrorInvalidImportTokenException,
|
||||
"InvalidKeyUsageException": newErrorInvalidKeyUsageException,
|
||||
"InvalidMarkerException": newErrorInvalidMarkerException,
|
||||
"KMSInvalidStateException": newErrorInvalidStateException,
|
||||
"KMSInvalidMacException": newErrorKMSInvalidMacException,
|
||||
"KMSInvalidSignatureException": newErrorKMSInvalidSignatureException,
|
||||
"KeyUnavailableException": newErrorKeyUnavailableException,
|
||||
"LimitExceededException": newErrorLimitExceededException,
|
||||
"MalformedPolicyDocumentException": newErrorMalformedPolicyDocumentException,
|
||||
"NotFoundException": newErrorNotFoundException,
|
||||
"TagException": newErrorTagException,
|
||||
"UnsupportedOperationException": newErrorUnsupportedOperationException,
|
||||
}
|
109
vendor/github.com/aws/aws-sdk-go/service/kms/service.go
generated
vendored
109
vendor/github.com/aws/aws-sdk-go/service/kms/service.go
generated
vendored
@ -1,109 +0,0 @@
|
||||
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
|
||||
|
||||
package kms
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/client"
|
||||
"github.com/aws/aws-sdk-go/aws/client/metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/aws/signer/v4"
|
||||
"github.com/aws/aws-sdk-go/private/protocol"
|
||||
"github.com/aws/aws-sdk-go/private/protocol/jsonrpc"
|
||||
)
|
||||
|
||||
// KMS provides the API operation methods for making requests to
|
||||
// AWS Key Management Service. See this package's package overview docs
|
||||
// for details on the service.
|
||||
//
|
||||
// KMS methods are safe to use concurrently. It is not safe to
|
||||
// modify mutate any of the struct's properties though.
|
||||
type KMS struct {
|
||||
*client.Client
|
||||
}
|
||||
|
||||
// Used for custom client initialization logic
|
||||
var initClient func(*client.Client)
|
||||
|
||||
// Used for custom request initialization logic
|
||||
var initRequest func(*request.Request)
|
||||
|
||||
// Service information constants
|
||||
const (
|
||||
ServiceName = "kms" // Name of service.
|
||||
EndpointsID = ServiceName // ID to lookup a service endpoint with.
|
||||
ServiceID = "KMS" // ServiceID is a unique identifier of a specific service.
|
||||
)
|
||||
|
||||
// New creates a new instance of the KMS client with a session.
|
||||
// If additional configuration is needed for the client instance use the optional
|
||||
// aws.Config parameter to add your extra config.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// mySession := session.Must(session.NewSession())
|
||||
//
|
||||
// // Create a KMS client from just a session.
|
||||
// svc := kms.New(mySession)
|
||||
//
|
||||
// // Create a KMS client with additional configuration
|
||||
// svc := kms.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
|
||||
func New(p client.ConfigProvider, cfgs ...*aws.Config) *KMS {
|
||||
c := p.ClientConfig(EndpointsID, cfgs...)
|
||||
if c.SigningNameDerived || len(c.SigningName) == 0 {
|
||||
c.SigningName = EndpointsID
|
||||
// No Fallback
|
||||
}
|
||||
return newClient(*c.Config, c.Handlers, c.PartitionID, c.Endpoint, c.SigningRegion, c.SigningName, c.ResolvedRegion)
|
||||
}
|
||||
|
||||
// newClient creates, initializes and returns a new service client instance.
|
||||
func newClient(cfg aws.Config, handlers request.Handlers, partitionID, endpoint, signingRegion, signingName, resolvedRegion string) *KMS {
|
||||
svc := &KMS{
|
||||
Client: client.New(
|
||||
cfg,
|
||||
metadata.ClientInfo{
|
||||
ServiceName: ServiceName,
|
||||
ServiceID: ServiceID,
|
||||
SigningName: signingName,
|
||||
SigningRegion: signingRegion,
|
||||
PartitionID: partitionID,
|
||||
Endpoint: endpoint,
|
||||
APIVersion: "2014-11-01",
|
||||
ResolvedRegion: resolvedRegion,
|
||||
JSONVersion: "1.1",
|
||||
TargetPrefix: "TrentService",
|
||||
},
|
||||
handlers,
|
||||
),
|
||||
}
|
||||
|
||||
// Handlers
|
||||
svc.Handlers.Sign.PushBackNamed(v4.SignRequestHandler)
|
||||
svc.Handlers.Build.PushBackNamed(jsonrpc.BuildHandler)
|
||||
svc.Handlers.Unmarshal.PushBackNamed(jsonrpc.UnmarshalHandler)
|
||||
svc.Handlers.UnmarshalMeta.PushBackNamed(jsonrpc.UnmarshalMetaHandler)
|
||||
svc.Handlers.UnmarshalError.PushBackNamed(
|
||||
protocol.NewUnmarshalErrorHandler(jsonrpc.NewUnmarshalTypedError(exceptionFromCode)).NamedHandler(),
|
||||
)
|
||||
|
||||
// Run custom client initialization if present
|
||||
if initClient != nil {
|
||||
initClient(svc.Client)
|
||||
}
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
// newRequest creates a new request for a KMS operation and runs any
|
||||
// custom request initialization.
|
||||
func (c *KMS) newRequest(op *request.Operation, params, data interface{}) *request.Request {
|
||||
req := c.NewRequest(op, params, data)
|
||||
|
||||
// Run custom request initialization if present
|
||||
if initRequest != nil {
|
||||
initRequest(req)
|
||||
}
|
||||
|
||||
return req
|
||||
}
|
78
vendor/github.com/google/cadvisor/utils/cloudinfo/aws/aws.go
generated
vendored
78
vendor/github.com/google/cadvisor/utils/cloudinfo/aws/aws.go
generated
vendored
@ -1,78 +0,0 @@
|
||||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 cloudinfo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/ec2metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
|
||||
info "github.com/google/cadvisor/info/v1"
|
||||
"github.com/google/cadvisor/utils/cloudinfo"
|
||||
)
|
||||
|
||||
const (
|
||||
productVerFileName = "/sys/class/dmi/id/product_version"
|
||||
biosVerFileName = "/sys/class/dmi/id/bios_vendor"
|
||||
systemdOSReleaseFileName = "/etc/os-release"
|
||||
amazon = "amazon"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cloudinfo.RegisterCloudProvider(info.AWS, &provider{})
|
||||
}
|
||||
|
||||
type provider struct{}
|
||||
|
||||
var _ cloudinfo.CloudProvider = provider{}
|
||||
|
||||
func (provider) IsActiveProvider() bool {
|
||||
return fileContainsAmazonIdentifier(productVerFileName) ||
|
||||
fileContainsAmazonIdentifier(biosVerFileName) ||
|
||||
fileContainsAmazonIdentifier(systemdOSReleaseFileName)
|
||||
}
|
||||
|
||||
func fileContainsAmazonIdentifier(filename string) bool {
|
||||
fileContent, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return strings.Contains(string(fileContent), amazon)
|
||||
}
|
||||
|
||||
func getAwsMetadata(name string) string {
|
||||
sess, err := session.NewSession(&aws.Config{})
|
||||
if err != nil {
|
||||
return info.UnknownInstance
|
||||
}
|
||||
client := ec2metadata.New(sess)
|
||||
data, err := client.GetMetadata(name)
|
||||
if err != nil {
|
||||
return info.UnknownInstance
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (provider) GetInstanceType() info.InstanceType {
|
||||
return info.InstanceType(getAwsMetadata("instance-type"))
|
||||
}
|
||||
|
||||
func (provider) GetInstanceID() info.InstanceID {
|
||||
return info.InstanceID(getAwsMetadata("instance-id"))
|
||||
}
|
21
vendor/github.com/stretchr/objx/.codeclimate.yml
generated
vendored
21
vendor/github.com/stretchr/objx/.codeclimate.yml
generated
vendored
@ -1,21 +0,0 @@
|
||||
engines:
|
||||
gofmt:
|
||||
enabled: true
|
||||
golint:
|
||||
enabled: true
|
||||
govet:
|
||||
enabled: true
|
||||
|
||||
exclude_patterns:
|
||||
- ".github/"
|
||||
- "vendor/"
|
||||
- "codegen/"
|
||||
- "*.yml"
|
||||
- ".*.yml"
|
||||
- "*.md"
|
||||
- "Gopkg.*"
|
||||
- "doc.go"
|
||||
- "type_specific_codegen_test.go"
|
||||
- "type_specific_codegen.go"
|
||||
- ".gitignore"
|
||||
- "LICENSE"
|
11
vendor/github.com/stretchr/objx/.gitignore
generated
vendored
11
vendor/github.com/stretchr/objx/.gitignore
generated
vendored
@ -1,11 +0,0 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
22
vendor/github.com/stretchr/objx/LICENSE
generated
vendored
22
vendor/github.com/stretchr/objx/LICENSE
generated
vendored
@ -1,22 +0,0 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2014 Stretchr, Inc.
|
||||
Copyright (c) 2017-2018 objx contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
80
vendor/github.com/stretchr/objx/README.md
generated
vendored
80
vendor/github.com/stretchr/objx/README.md
generated
vendored
@ -1,80 +0,0 @@
|
||||
# Objx
|
||||
[](https://travis-ci.org/stretchr/objx)
|
||||
[](https://goreportcard.com/report/github.com/stretchr/objx)
|
||||
[](https://codeclimate.com/github/stretchr/objx/maintainability)
|
||||
[](https://codeclimate.com/github/stretchr/objx/test_coverage)
|
||||
[](https://sourcegraph.com/github.com/stretchr/objx)
|
||||
[](https://godoc.org/github.com/stretchr/objx)
|
||||
|
||||
Objx - Go package for dealing with maps, slices, JSON and other data.
|
||||
|
||||
Get started:
|
||||
|
||||
- Install Objx with [one line of code](#installation), or [update it with another](#staying-up-to-date)
|
||||
- Check out the API Documentation http://godoc.org/github.com/stretchr/objx
|
||||
|
||||
## Overview
|
||||
Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes a powerful `Get` method (among others) that allows you to easily and quickly get access to data within the map, without having to worry too much about type assertions, missing data, default values etc.
|
||||
|
||||
### Pattern
|
||||
Objx uses a preditable pattern to make access data from within `map[string]interface{}` easy. Call one of the `objx.` functions to create your `objx.Map` to get going:
|
||||
|
||||
m, err := objx.FromJSON(json)
|
||||
|
||||
NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong, the rest will be optimistic and try to figure things out without panicking.
|
||||
|
||||
Use `Get` to access the value you're interested in. You can use dot and array
|
||||
notation too:
|
||||
|
||||
m.Get("places[0].latlng")
|
||||
|
||||
Once you have sought the `Value` you're interested in, you can use the `Is*` methods to determine its type.
|
||||
|
||||
if m.Get("code").IsStr() { // Your code... }
|
||||
|
||||
Or you can just assume the type, and use one of the strong type methods to extract the real value:
|
||||
|
||||
m.Get("code").Int()
|
||||
|
||||
If there's no value there (or if it's the wrong type) then a default value will be returned, or you can be explicit about the default value.
|
||||
|
||||
Get("code").Int(-1)
|
||||
|
||||
If you're dealing with a slice of data as a value, Objx provides many useful methods for iterating, manipulating and selecting that data. You can find out more by exploring the index below.
|
||||
|
||||
### Reading data
|
||||
A simple example of how to use Objx:
|
||||
|
||||
// Use MustFromJSON to make an objx.Map from some JSON
|
||||
m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`)
|
||||
|
||||
// Get the details
|
||||
name := m.Get("name").Str()
|
||||
age := m.Get("age").Int()
|
||||
|
||||
// Get their nickname (or use their name if they don't have one)
|
||||
nickname := m.Get("nickname").Str(name)
|
||||
|
||||
### Ranging
|
||||
Since `objx.Map` is a `map[string]interface{}` you can treat it as such. For example, to `range` the data, do what you would expect:
|
||||
|
||||
m := objx.MustFromJSON(json)
|
||||
for key, value := range m {
|
||||
// Your code...
|
||||
}
|
||||
|
||||
## Installation
|
||||
To install Objx, use go get:
|
||||
|
||||
go get github.com/stretchr/objx
|
||||
|
||||
### Staying up to date
|
||||
To update Objx to the latest version, run:
|
||||
|
||||
go get -u github.com/stretchr/objx
|
||||
|
||||
### Supported go versions
|
||||
We support the lastest three major Go versions, which are 1.10, 1.11 and 1.12 at the moment.
|
||||
|
||||
## Contributing
|
||||
Please feel free to submit issues, fork the repository and send pull requests!
|
30
vendor/github.com/stretchr/objx/Taskfile.yml
generated
vendored
30
vendor/github.com/stretchr/objx/Taskfile.yml
generated
vendored
@ -1,30 +0,0 @@
|
||||
version: '2'
|
||||
|
||||
env:
|
||||
GOFLAGS: -mod=vendor
|
||||
|
||||
tasks:
|
||||
default:
|
||||
deps: [test]
|
||||
|
||||
lint:
|
||||
desc: Checks code style
|
||||
cmds:
|
||||
- gofmt -d -s *.go
|
||||
- go vet ./...
|
||||
silent: true
|
||||
|
||||
lint-fix:
|
||||
desc: Fixes code style
|
||||
cmds:
|
||||
- gofmt -w -s *.go
|
||||
|
||||
test:
|
||||
desc: Runs go tests
|
||||
cmds:
|
||||
- go test -race ./...
|
||||
|
||||
test-coverage:
|
||||
desc: Runs go tests and calculates test coverage
|
||||
cmds:
|
||||
- go test -race -coverprofile=c.out ./...
|
197
vendor/github.com/stretchr/objx/accessors.go
generated
vendored
197
vendor/github.com/stretchr/objx/accessors.go
generated
vendored
@ -1,197 +0,0 @@
|
||||
package objx
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// PathSeparator is the character used to separate the elements
|
||||
// of the keypath.
|
||||
//
|
||||
// For example, `location.address.city`
|
||||
PathSeparator string = "."
|
||||
|
||||
// arrayAccesRegexString is the regex used to extract the array number
|
||||
// from the access path
|
||||
arrayAccesRegexString = `^(.+)\[([0-9]+)\]$`
|
||||
|
||||
// mapAccessRegexString is the regex used to extract the map key
|
||||
// from the access path
|
||||
mapAccessRegexString = `^([^\[]*)\[([^\]]+)\](.*)$`
|
||||
)
|
||||
|
||||
// arrayAccesRegex is the compiled arrayAccesRegexString
|
||||
var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString)
|
||||
|
||||
// mapAccessRegex is the compiled mapAccessRegexString
|
||||
var mapAccessRegex = regexp.MustCompile(mapAccessRegexString)
|
||||
|
||||
// Get gets the value using the specified selector and
|
||||
// returns it inside a new Obj object.
|
||||
//
|
||||
// If it cannot find the value, Get will return a nil
|
||||
// value inside an instance of Obj.
|
||||
//
|
||||
// Get can only operate directly on map[string]interface{} and []interface.
|
||||
//
|
||||
// Example
|
||||
//
|
||||
// To access the title of the third chapter of the second book, do:
|
||||
//
|
||||
// o.Get("books[1].chapters[2].title")
|
||||
func (m Map) Get(selector string) *Value {
|
||||
rawObj := access(m, selector, nil, false)
|
||||
return &Value{data: rawObj}
|
||||
}
|
||||
|
||||
// Set sets the value using the specified selector and
|
||||
// returns the object on which Set was called.
|
||||
//
|
||||
// Set can only operate directly on map[string]interface{} and []interface
|
||||
//
|
||||
// Example
|
||||
//
|
||||
// To set the title of the third chapter of the second book, do:
|
||||
//
|
||||
// o.Set("books[1].chapters[2].title","Time to Go")
|
||||
func (m Map) Set(selector string, value interface{}) Map {
|
||||
access(m, selector, value, true)
|
||||
return m
|
||||
}
|
||||
|
||||
// getIndex returns the index, which is hold in s by two braches.
|
||||
// It also returns s withour the index part, e.g. name[1] will return (1, name).
|
||||
// If no index is found, -1 is returned
|
||||
func getIndex(s string) (int, string) {
|
||||
arrayMatches := arrayAccesRegex.FindStringSubmatch(s)
|
||||
if len(arrayMatches) > 0 {
|
||||
// Get the key into the map
|
||||
selector := arrayMatches[1]
|
||||
// Get the index into the array at the key
|
||||
// We know this cannt fail because arrayMatches[2] is an int for sure
|
||||
index, _ := strconv.Atoi(arrayMatches[2])
|
||||
return index, selector
|
||||
}
|
||||
return -1, s
|
||||
}
|
||||
|
||||
// getKey returns the key which is held in s by two brackets.
|
||||
// It also returns the next selector.
|
||||
func getKey(s string) (string, string) {
|
||||
selSegs := strings.SplitN(s, PathSeparator, 2)
|
||||
thisSel := selSegs[0]
|
||||
nextSel := ""
|
||||
|
||||
if len(selSegs) > 1 {
|
||||
nextSel = selSegs[1]
|
||||
}
|
||||
|
||||
mapMatches := mapAccessRegex.FindStringSubmatch(s)
|
||||
if len(mapMatches) > 0 {
|
||||
if _, err := strconv.Atoi(mapMatches[2]); err != nil {
|
||||
thisSel = mapMatches[1]
|
||||
nextSel = "[" + mapMatches[2] + "]" + mapMatches[3]
|
||||
|
||||
if thisSel == "" {
|
||||
thisSel = mapMatches[2]
|
||||
nextSel = mapMatches[3]
|
||||
}
|
||||
|
||||
if nextSel == "" {
|
||||
selSegs = []string{"", ""}
|
||||
} else if nextSel[0] == '.' {
|
||||
nextSel = nextSel[1:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return thisSel, nextSel
|
||||
}
|
||||
|
||||
// access accesses the object using the selector and performs the
|
||||
// appropriate action.
|
||||
func access(current interface{}, selector string, value interface{}, isSet bool) interface{} {
|
||||
thisSel, nextSel := getKey(selector)
|
||||
|
||||
indexes := []int{}
|
||||
for strings.Contains(thisSel, "[") {
|
||||
prevSel := thisSel
|
||||
index := -1
|
||||
index, thisSel = getIndex(thisSel)
|
||||
indexes = append(indexes, index)
|
||||
if prevSel == thisSel {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if curMap, ok := current.(Map); ok {
|
||||
current = map[string]interface{}(curMap)
|
||||
}
|
||||
// get the object in question
|
||||
switch current.(type) {
|
||||
case map[string]interface{}:
|
||||
curMSI := current.(map[string]interface{})
|
||||
if nextSel == "" && isSet {
|
||||
curMSI[thisSel] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
_, ok := curMSI[thisSel].(map[string]interface{})
|
||||
if !ok {
|
||||
_, ok = curMSI[thisSel].(Map)
|
||||
}
|
||||
|
||||
if (curMSI[thisSel] == nil || !ok) && len(indexes) == 0 && isSet {
|
||||
curMSI[thisSel] = map[string]interface{}{}
|
||||
}
|
||||
|
||||
current = curMSI[thisSel]
|
||||
default:
|
||||
current = nil
|
||||
}
|
||||
|
||||
// do we need to access the item of an array?
|
||||
if len(indexes) > 0 {
|
||||
num := len(indexes)
|
||||
for num > 0 {
|
||||
num--
|
||||
index := indexes[num]
|
||||
indexes = indexes[:num]
|
||||
if array, ok := interSlice(current); ok {
|
||||
if index < len(array) {
|
||||
current = array[index]
|
||||
} else {
|
||||
current = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if nextSel != "" {
|
||||
current = access(current, nextSel, value, isSet)
|
||||
}
|
||||
return current
|
||||
}
|
||||
|
||||
func interSlice(slice interface{}) ([]interface{}, bool) {
|
||||
if array, ok := slice.([]interface{}); ok {
|
||||
return array, ok
|
||||
}
|
||||
|
||||
s := reflect.ValueOf(slice)
|
||||
if s.Kind() != reflect.Slice {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
ret := make([]interface{}, s.Len())
|
||||
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
ret[i] = s.Index(i).Interface()
|
||||
}
|
||||
|
||||
return ret, true
|
||||
}
|
280
vendor/github.com/stretchr/objx/conversions.go
generated
vendored
280
vendor/github.com/stretchr/objx/conversions.go
generated
vendored
@ -1,280 +0,0 @@
|
||||
package objx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// SignatureSeparator is the character that is used to
|
||||
// separate the Base64 string from the security signature.
|
||||
const SignatureSeparator = "_"
|
||||
|
||||
// URLValuesSliceKeySuffix is the character that is used to
|
||||
// specify a suffic for slices parsed by URLValues.
|
||||
// If the suffix is set to "[i]", then the index of the slice
|
||||
// is used in place of i
|
||||
// Ex: Suffix "[]" would have the form a[]=b&a[]=c
|
||||
// OR Suffix "[i]" would have the form a[0]=b&a[1]=c
|
||||
// OR Suffix "" would have the form a=b&a=c
|
||||
var urlValuesSliceKeySuffix = "[]"
|
||||
|
||||
const (
|
||||
URLValuesSliceKeySuffixEmpty = ""
|
||||
URLValuesSliceKeySuffixArray = "[]"
|
||||
URLValuesSliceKeySuffixIndex = "[i]"
|
||||
)
|
||||
|
||||
// SetURLValuesSliceKeySuffix sets the character that is used to
|
||||
// specify a suffic for slices parsed by URLValues.
|
||||
// If the suffix is set to "[i]", then the index of the slice
|
||||
// is used in place of i
|
||||
// Ex: Suffix "[]" would have the form a[]=b&a[]=c
|
||||
// OR Suffix "[i]" would have the form a[0]=b&a[1]=c
|
||||
// OR Suffix "" would have the form a=b&a=c
|
||||
func SetURLValuesSliceKeySuffix(s string) error {
|
||||
if s == URLValuesSliceKeySuffixEmpty || s == URLValuesSliceKeySuffixArray || s == URLValuesSliceKeySuffixIndex {
|
||||
urlValuesSliceKeySuffix = s
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("objx: Invalid URLValuesSliceKeySuffix provided.")
|
||||
}
|
||||
|
||||
// JSON converts the contained object to a JSON string
|
||||
// representation
|
||||
func (m Map) JSON() (string, error) {
|
||||
for k, v := range m {
|
||||
m[k] = cleanUp(v)
|
||||
}
|
||||
|
||||
result, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
err = errors.New("objx: JSON encode failed with: " + err.Error())
|
||||
}
|
||||
return string(result), err
|
||||
}
|
||||
|
||||
func cleanUpInterfaceArray(in []interface{}) []interface{} {
|
||||
result := make([]interface{}, len(in))
|
||||
for i, v := range in {
|
||||
result[i] = cleanUp(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func cleanUpInterfaceMap(in map[interface{}]interface{}) Map {
|
||||
result := Map{}
|
||||
for k, v := range in {
|
||||
result[fmt.Sprintf("%v", k)] = cleanUp(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func cleanUpStringMap(in map[string]interface{}) Map {
|
||||
result := Map{}
|
||||
for k, v := range in {
|
||||
result[k] = cleanUp(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func cleanUpMSIArray(in []map[string]interface{}) []Map {
|
||||
result := make([]Map, len(in))
|
||||
for i, v := range in {
|
||||
result[i] = cleanUpStringMap(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func cleanUpMapArray(in []Map) []Map {
|
||||
result := make([]Map, len(in))
|
||||
for i, v := range in {
|
||||
result[i] = cleanUpStringMap(v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func cleanUp(v interface{}) interface{} {
|
||||
switch v := v.(type) {
|
||||
case []interface{}:
|
||||
return cleanUpInterfaceArray(v)
|
||||
case []map[string]interface{}:
|
||||
return cleanUpMSIArray(v)
|
||||
case map[interface{}]interface{}:
|
||||
return cleanUpInterfaceMap(v)
|
||||
case Map:
|
||||
return cleanUpStringMap(v)
|
||||
case []Map:
|
||||
return cleanUpMapArray(v)
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// MustJSON converts the contained object to a JSON string
|
||||
// representation and panics if there is an error
|
||||
func (m Map) MustJSON() string {
|
||||
result, err := m.JSON()
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Base64 converts the contained object to a Base64 string
|
||||
// representation of the JSON string representation
|
||||
func (m Map) Base64() (string, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
jsonData, err := m.JSON()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
encoder := base64.NewEncoder(base64.StdEncoding, &buf)
|
||||
_, _ = encoder.Write([]byte(jsonData))
|
||||
_ = encoder.Close()
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// MustBase64 converts the contained object to a Base64 string
|
||||
// representation of the JSON string representation and panics
|
||||
// if there is an error
|
||||
func (m Map) MustBase64() string {
|
||||
result, err := m.Base64()
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// SignedBase64 converts the contained object to a Base64 string
|
||||
// representation of the JSON string representation and signs it
|
||||
// using the provided key.
|
||||
func (m Map) SignedBase64(key string) (string, error) {
|
||||
base64, err := m.Base64()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
sig := HashWithKey(base64, key)
|
||||
return base64 + SignatureSeparator + sig, nil
|
||||
}
|
||||
|
||||
// MustSignedBase64 converts the contained object to a Base64 string
|
||||
// representation of the JSON string representation and signs it
|
||||
// using the provided key and panics if there is an error
|
||||
func (m Map) MustSignedBase64(key string) string {
|
||||
result, err := m.SignedBase64(key)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/*
|
||||
URL Query
|
||||
------------------------------------------------
|
||||
*/
|
||||
|
||||
// URLValues creates a url.Values object from an Obj. This
|
||||
// function requires that the wrapped object be a map[string]interface{}
|
||||
func (m Map) URLValues() url.Values {
|
||||
vals := make(url.Values)
|
||||
|
||||
m.parseURLValues(m, vals, "")
|
||||
|
||||
return vals
|
||||
}
|
||||
|
||||
func (m Map) parseURLValues(queryMap Map, vals url.Values, key string) {
|
||||
useSliceIndex := false
|
||||
if urlValuesSliceKeySuffix == "[i]" {
|
||||
useSliceIndex = true
|
||||
}
|
||||
|
||||
for k, v := range queryMap {
|
||||
val := &Value{data: v}
|
||||
switch {
|
||||
case val.IsObjxMap():
|
||||
if key == "" {
|
||||
m.parseURLValues(val.ObjxMap(), vals, k)
|
||||
} else {
|
||||
m.parseURLValues(val.ObjxMap(), vals, key+"["+k+"]")
|
||||
}
|
||||
case val.IsObjxMapSlice():
|
||||
sliceKey := k
|
||||
if key != "" {
|
||||
sliceKey = key + "[" + k + "]"
|
||||
}
|
||||
|
||||
if useSliceIndex {
|
||||
for i, sv := range val.MustObjxMapSlice() {
|
||||
sk := sliceKey + "[" + strconv.FormatInt(int64(i), 10) + "]"
|
||||
m.parseURLValues(sv, vals, sk)
|
||||
}
|
||||
} else {
|
||||
sliceKey = sliceKey + urlValuesSliceKeySuffix
|
||||
for _, sv := range val.MustObjxMapSlice() {
|
||||
m.parseURLValues(sv, vals, sliceKey)
|
||||
}
|
||||
}
|
||||
case val.IsMSISlice():
|
||||
sliceKey := k
|
||||
if key != "" {
|
||||
sliceKey = key + "[" + k + "]"
|
||||
}
|
||||
|
||||
if useSliceIndex {
|
||||
for i, sv := range val.MustMSISlice() {
|
||||
sk := sliceKey + "[" + strconv.FormatInt(int64(i), 10) + "]"
|
||||
m.parseURLValues(New(sv), vals, sk)
|
||||
}
|
||||
} else {
|
||||
sliceKey = sliceKey + urlValuesSliceKeySuffix
|
||||
for _, sv := range val.MustMSISlice() {
|
||||
m.parseURLValues(New(sv), vals, sliceKey)
|
||||
}
|
||||
}
|
||||
case val.IsStrSlice(), val.IsBoolSlice(),
|
||||
val.IsFloat32Slice(), val.IsFloat64Slice(),
|
||||
val.IsIntSlice(), val.IsInt8Slice(), val.IsInt16Slice(), val.IsInt32Slice(), val.IsInt64Slice(),
|
||||
val.IsUintSlice(), val.IsUint8Slice(), val.IsUint16Slice(), val.IsUint32Slice(), val.IsUint64Slice():
|
||||
|
||||
sliceKey := k
|
||||
if key != "" {
|
||||
sliceKey = key + "[" + k + "]"
|
||||
}
|
||||
|
||||
if useSliceIndex {
|
||||
for i, sv := range val.StringSlice() {
|
||||
sk := sliceKey + "[" + strconv.FormatInt(int64(i), 10) + "]"
|
||||
vals.Set(sk, sv)
|
||||
}
|
||||
} else {
|
||||
sliceKey = sliceKey + urlValuesSliceKeySuffix
|
||||
vals[sliceKey] = val.StringSlice()
|
||||
}
|
||||
|
||||
default:
|
||||
if key == "" {
|
||||
vals.Set(k, val.String())
|
||||
} else {
|
||||
vals.Set(key+"["+k+"]", val.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// URLQuery gets an encoded URL query representing the given
|
||||
// Obj. This function requires that the wrapped object be a
|
||||
// map[string]interface{}
|
||||
func (m Map) URLQuery() (string, error) {
|
||||
return m.URLValues().Encode(), nil
|
||||
}
|
66
vendor/github.com/stretchr/objx/doc.go
generated
vendored
66
vendor/github.com/stretchr/objx/doc.go
generated
vendored
@ -1,66 +0,0 @@
|
||||
/*
|
||||
Objx - Go package for dealing with maps, slices, JSON and other data.
|
||||
|
||||
Overview
|
||||
|
||||
Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes
|
||||
a powerful `Get` method (among others) that allows you to easily and quickly get
|
||||
access to data within the map, without having to worry too much about type assertions,
|
||||
missing data, default values etc.
|
||||
|
||||
Pattern
|
||||
|
||||
Objx uses a preditable pattern to make access data from within `map[string]interface{}` easy.
|
||||
Call one of the `objx.` functions to create your `objx.Map` to get going:
|
||||
|
||||
m, err := objx.FromJSON(json)
|
||||
|
||||
NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong,
|
||||
the rest will be optimistic and try to figure things out without panicking.
|
||||
|
||||
Use `Get` to access the value you're interested in. You can use dot and array
|
||||
notation too:
|
||||
|
||||
m.Get("places[0].latlng")
|
||||
|
||||
Once you have sought the `Value` you're interested in, you can use the `Is*` methods to determine its type.
|
||||
|
||||
if m.Get("code").IsStr() { // Your code... }
|
||||
|
||||
Or you can just assume the type, and use one of the strong type methods to extract the real value:
|
||||
|
||||
m.Get("code").Int()
|
||||
|
||||
If there's no value there (or if it's the wrong type) then a default value will be returned,
|
||||
or you can be explicit about the default value.
|
||||
|
||||
Get("code").Int(-1)
|
||||
|
||||
If you're dealing with a slice of data as a value, Objx provides many useful methods for iterating,
|
||||
manipulating and selecting that data. You can find out more by exploring the index below.
|
||||
|
||||
Reading data
|
||||
|
||||
A simple example of how to use Objx:
|
||||
|
||||
// Use MustFromJSON to make an objx.Map from some JSON
|
||||
m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`)
|
||||
|
||||
// Get the details
|
||||
name := m.Get("name").Str()
|
||||
age := m.Get("age").Int()
|
||||
|
||||
// Get their nickname (or use their name if they don't have one)
|
||||
nickname := m.Get("nickname").Str(name)
|
||||
|
||||
Ranging
|
||||
|
||||
Since `objx.Map` is a `map[string]interface{}` you can treat it as such.
|
||||
For example, to `range` the data, do what you would expect:
|
||||
|
||||
m := objx.MustFromJSON(json)
|
||||
for key, value := range m {
|
||||
// Your code...
|
||||
}
|
||||
*/
|
||||
package objx
|
215
vendor/github.com/stretchr/objx/map.go
generated
vendored
215
vendor/github.com/stretchr/objx/map.go
generated
vendored
@ -1,215 +0,0 @@
|
||||
package objx
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MSIConvertable is an interface that defines methods for converting your
|
||||
// custom types to a map[string]interface{} representation.
|
||||
type MSIConvertable interface {
|
||||
// MSI gets a map[string]interface{} (msi) representing the
|
||||
// object.
|
||||
MSI() map[string]interface{}
|
||||
}
|
||||
|
||||
// Map provides extended functionality for working with
|
||||
// untyped data, in particular map[string]interface (msi).
|
||||
type Map map[string]interface{}
|
||||
|
||||
// Value returns the internal value instance
|
||||
func (m Map) Value() *Value {
|
||||
return &Value{data: m}
|
||||
}
|
||||
|
||||
// Nil represents a nil Map.
|
||||
var Nil = New(nil)
|
||||
|
||||
// New creates a new Map containing the map[string]interface{} in the data argument.
|
||||
// If the data argument is not a map[string]interface, New attempts to call the
|
||||
// MSI() method on the MSIConvertable interface to create one.
|
||||
func New(data interface{}) Map {
|
||||
if _, ok := data.(map[string]interface{}); !ok {
|
||||
if converter, ok := data.(MSIConvertable); ok {
|
||||
data = converter.MSI()
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return Map(data.(map[string]interface{}))
|
||||
}
|
||||
|
||||
// MSI creates a map[string]interface{} and puts it inside a new Map.
|
||||
//
|
||||
// The arguments follow a key, value pattern.
|
||||
//
|
||||
//
|
||||
// Returns nil if any key argument is non-string or if there are an odd number of arguments.
|
||||
//
|
||||
// Example
|
||||
//
|
||||
// To easily create Maps:
|
||||
//
|
||||
// m := objx.MSI("name", "Mat", "age", 29, "subobj", objx.MSI("active", true))
|
||||
//
|
||||
// // creates an Map equivalent to
|
||||
// m := objx.Map{"name": "Mat", "age": 29, "subobj": objx.Map{"active": true}}
|
||||
func MSI(keyAndValuePairs ...interface{}) Map {
|
||||
newMap := Map{}
|
||||
keyAndValuePairsLen := len(keyAndValuePairs)
|
||||
if keyAndValuePairsLen%2 != 0 {
|
||||
return nil
|
||||
}
|
||||
for i := 0; i < keyAndValuePairsLen; i = i + 2 {
|
||||
key := keyAndValuePairs[i]
|
||||
value := keyAndValuePairs[i+1]
|
||||
|
||||
// make sure the key is a string
|
||||
keyString, keyStringOK := key.(string)
|
||||
if !keyStringOK {
|
||||
return nil
|
||||
}
|
||||
newMap[keyString] = value
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// ****** Conversion Constructors
|
||||
|
||||
// MustFromJSON creates a new Map containing the data specified in the
|
||||
// jsonString.
|
||||
//
|
||||
// Panics if the JSON is invalid.
|
||||
func MustFromJSON(jsonString string) Map {
|
||||
o, err := FromJSON(jsonString)
|
||||
if err != nil {
|
||||
panic("objx: MustFromJSON failed with error: " + err.Error())
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// MustFromJSONSlice creates a new slice of Map containing the data specified in the
|
||||
// jsonString. Works with jsons with a top level array
|
||||
//
|
||||
// Panics if the JSON is invalid.
|
||||
func MustFromJSONSlice(jsonString string) []Map {
|
||||
slice, err := FromJSONSlice(jsonString)
|
||||
if err != nil {
|
||||
panic("objx: MustFromJSONSlice failed with error: " + err.Error())
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// FromJSON creates a new Map containing the data specified in the
|
||||
// jsonString.
|
||||
//
|
||||
// Returns an error if the JSON is invalid.
|
||||
func FromJSON(jsonString string) (Map, error) {
|
||||
var m Map
|
||||
err := json.Unmarshal([]byte(jsonString), &m)
|
||||
if err != nil {
|
||||
return Nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// FromJSONSlice creates a new slice of Map containing the data specified in the
|
||||
// jsonString. Works with jsons with a top level array
|
||||
//
|
||||
// Returns an error if the JSON is invalid.
|
||||
func FromJSONSlice(jsonString string) ([]Map, error) {
|
||||
var slice []Map
|
||||
err := json.Unmarshal([]byte(jsonString), &slice)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
// FromBase64 creates a new Obj containing the data specified
|
||||
// in the Base64 string.
|
||||
//
|
||||
// The string is an encoded JSON string returned by Base64
|
||||
func FromBase64(base64String string) (Map, error) {
|
||||
decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64String))
|
||||
decoded, err := ioutil.ReadAll(decoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return FromJSON(string(decoded))
|
||||
}
|
||||
|
||||
// MustFromBase64 creates a new Obj containing the data specified
|
||||
// in the Base64 string and panics if there is an error.
|
||||
//
|
||||
// The string is an encoded JSON string returned by Base64
|
||||
func MustFromBase64(base64String string) Map {
|
||||
result, err := FromBase64(base64String)
|
||||
if err != nil {
|
||||
panic("objx: MustFromBase64 failed with error: " + err.Error())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// FromSignedBase64 creates a new Obj containing the data specified
|
||||
// in the Base64 string.
|
||||
//
|
||||
// The string is an encoded JSON string returned by SignedBase64
|
||||
func FromSignedBase64(base64String, key string) (Map, error) {
|
||||
parts := strings.Split(base64String, SignatureSeparator)
|
||||
if len(parts) != 2 {
|
||||
return nil, errors.New("objx: Signed base64 string is malformed")
|
||||
}
|
||||
|
||||
sig := HashWithKey(parts[0], key)
|
||||
if parts[1] != sig {
|
||||
return nil, errors.New("objx: Signature for base64 data does not match")
|
||||
}
|
||||
return FromBase64(parts[0])
|
||||
}
|
||||
|
||||
// MustFromSignedBase64 creates a new Obj containing the data specified
|
||||
// in the Base64 string and panics if there is an error.
|
||||
//
|
||||
// The string is an encoded JSON string returned by Base64
|
||||
func MustFromSignedBase64(base64String, key string) Map {
|
||||
result, err := FromSignedBase64(base64String, key)
|
||||
if err != nil {
|
||||
panic("objx: MustFromSignedBase64 failed with error: " + err.Error())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// FromURLQuery generates a new Obj by parsing the specified
|
||||
// query.
|
||||
//
|
||||
// For queries with multiple values, the first value is selected.
|
||||
func FromURLQuery(query string) (Map, error) {
|
||||
vals, err := url.ParseQuery(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := Map{}
|
||||
for k, vals := range vals {
|
||||
m[k] = vals[0]
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// MustFromURLQuery generates a new Obj by parsing the specified
|
||||
// query.
|
||||
//
|
||||
// For queries with multiple values, the first value is selected.
|
||||
//
|
||||
// Panics if it encounters an error
|
||||
func MustFromURLQuery(query string) Map {
|
||||
o, err := FromURLQuery(query)
|
||||
if err != nil {
|
||||
panic("objx: MustFromURLQuery failed with error: " + err.Error())
|
||||
}
|
||||
return o
|
||||
}
|
77
vendor/github.com/stretchr/objx/mutations.go
generated
vendored
77
vendor/github.com/stretchr/objx/mutations.go
generated
vendored
@ -1,77 +0,0 @@
|
||||
package objx
|
||||
|
||||
// Exclude returns a new Map with the keys in the specified []string
|
||||
// excluded.
|
||||
func (m Map) Exclude(exclude []string) Map {
|
||||
excluded := make(Map)
|
||||
for k, v := range m {
|
||||
if !contains(exclude, k) {
|
||||
excluded[k] = v
|
||||
}
|
||||
}
|
||||
return excluded
|
||||
}
|
||||
|
||||
// Copy creates a shallow copy of the Obj.
|
||||
func (m Map) Copy() Map {
|
||||
copied := Map{}
|
||||
for k, v := range m {
|
||||
copied[k] = v
|
||||
}
|
||||
return copied
|
||||
}
|
||||
|
||||
// Merge blends the specified map with a copy of this map and returns the result.
|
||||
//
|
||||
// Keys that appear in both will be selected from the specified map.
|
||||
// This method requires that the wrapped object be a map[string]interface{}
|
||||
func (m Map) Merge(merge Map) Map {
|
||||
return m.Copy().MergeHere(merge)
|
||||
}
|
||||
|
||||
// MergeHere blends the specified map with this map and returns the current map.
|
||||
//
|
||||
// Keys that appear in both will be selected from the specified map. The original map
|
||||
// will be modified. This method requires that
|
||||
// the wrapped object be a map[string]interface{}
|
||||
func (m Map) MergeHere(merge Map) Map {
|
||||
for k, v := range merge {
|
||||
m[k] = v
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Transform builds a new Obj giving the transformer a chance
|
||||
// to change the keys and values as it goes. This method requires that
|
||||
// the wrapped object be a map[string]interface{}
|
||||
func (m Map) Transform(transformer func(key string, value interface{}) (string, interface{})) Map {
|
||||
newMap := Map{}
|
||||
for k, v := range m {
|
||||
modifiedKey, modifiedVal := transformer(k, v)
|
||||
newMap[modifiedKey] = modifiedVal
|
||||
}
|
||||
return newMap
|
||||
}
|
||||
|
||||
// TransformKeys builds a new map using the specified key mapping.
|
||||
//
|
||||
// Unspecified keys will be unaltered.
|
||||
// This method requires that the wrapped object be a map[string]interface{}
|
||||
func (m Map) TransformKeys(mapping map[string]string) Map {
|
||||
return m.Transform(func(key string, value interface{}) (string, interface{}) {
|
||||
if newKey, ok := mapping[key]; ok {
|
||||
return newKey, value
|
||||
}
|
||||
return key, value
|
||||
})
|
||||
}
|
||||
|
||||
// Checks if a string slice contains a string
|
||||
func contains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
12
vendor/github.com/stretchr/objx/security.go
generated
vendored
12
vendor/github.com/stretchr/objx/security.go
generated
vendored
@ -1,12 +0,0 @@
|
||||
package objx
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// HashWithKey hashes the specified string using the security key
|
||||
func HashWithKey(data, key string) string {
|
||||
d := sha1.Sum([]byte(data + ":" + key))
|
||||
return hex.EncodeToString(d[:])
|
||||
}
|
17
vendor/github.com/stretchr/objx/tests.go
generated
vendored
17
vendor/github.com/stretchr/objx/tests.go
generated
vendored
@ -1,17 +0,0 @@
|
||||
package objx
|
||||
|
||||
// Has gets whether there is something at the specified selector
|
||||
// or not.
|
||||
//
|
||||
// If m is nil, Has will always return false.
|
||||
func (m Map) Has(selector string) bool {
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
return !m.Get(selector).IsNil()
|
||||
}
|
||||
|
||||
// IsNil gets whether the data is nil or not.
|
||||
func (v *Value) IsNil() bool {
|
||||
return v == nil || v.data == nil
|
||||
}
|
346
vendor/github.com/stretchr/objx/type_specific.go
generated
vendored
346
vendor/github.com/stretchr/objx/type_specific.go
generated
vendored
@ -1,346 +0,0 @@
|
||||
package objx
|
||||
|
||||
/*
|
||||
MSI (map[string]interface{} and []map[string]interface{})
|
||||
*/
|
||||
|
||||
// MSI gets the value as a map[string]interface{}, returns the optionalDefault
|
||||
// value or a system default object if the value is the wrong type.
|
||||
func (v *Value) MSI(optionalDefault ...map[string]interface{}) map[string]interface{} {
|
||||
if s, ok := v.data.(map[string]interface{}); ok {
|
||||
return s
|
||||
}
|
||||
if s, ok := v.data.(Map); ok {
|
||||
return map[string]interface{}(s)
|
||||
}
|
||||
if len(optionalDefault) == 1 {
|
||||
return optionalDefault[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MustMSI gets the value as a map[string]interface{}.
|
||||
//
|
||||
// Panics if the object is not a map[string]interface{}.
|
||||
func (v *Value) MustMSI() map[string]interface{} {
|
||||
if s, ok := v.data.(Map); ok {
|
||||
return map[string]interface{}(s)
|
||||
}
|
||||
return v.data.(map[string]interface{})
|
||||
}
|
||||
|
||||
// MSISlice gets the value as a []map[string]interface{}, returns the optionalDefault
|
||||
// value or nil if the value is not a []map[string]interface{}.
|
||||
func (v *Value) MSISlice(optionalDefault ...[]map[string]interface{}) []map[string]interface{} {
|
||||
if s, ok := v.data.([]map[string]interface{}); ok {
|
||||
return s
|
||||
}
|
||||
|
||||
s := v.ObjxMapSlice()
|
||||
if s == nil {
|
||||
if len(optionalDefault) == 1 {
|
||||
return optionalDefault[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make([]map[string]interface{}, len(s))
|
||||
for i := range s {
|
||||
result[i] = s[i].Value().MSI()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// MustMSISlice gets the value as a []map[string]interface{}.
|
||||
//
|
||||
// Panics if the object is not a []map[string]interface{}.
|
||||
func (v *Value) MustMSISlice() []map[string]interface{} {
|
||||
if s := v.MSISlice(); s != nil {
|
||||
return s
|
||||
}
|
||||
|
||||
return v.data.([]map[string]interface{})
|
||||
}
|
||||
|
||||
// IsMSI gets whether the object contained is a map[string]interface{} or not.
|
||||
func (v *Value) IsMSI() bool {
|
||||
_, ok := v.data.(map[string]interface{})
|
||||
if !ok {
|
||||
_, ok = v.data.(Map)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsMSISlice gets whether the object contained is a []map[string]interface{} or not.
|
||||
func (v *Value) IsMSISlice() bool {
|
||||
_, ok := v.data.([]map[string]interface{})
|
||||
if !ok {
|
||||
_, ok = v.data.([]Map)
|
||||
if !ok {
|
||||
s, ok := v.data.([]interface{})
|
||||
if ok {
|
||||
for i := range s {
|
||||
switch s[i].(type) {
|
||||
case Map:
|
||||
case map[string]interface{}:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// EachMSI calls the specified callback for each object
|
||||
// in the []map[string]interface{}.
|
||||
//
|
||||
// Panics if the object is the wrong type.
|
||||
func (v *Value) EachMSI(callback func(int, map[string]interface{}) bool) *Value {
|
||||
for index, val := range v.MustMSISlice() {
|
||||
carryon := callback(index, val)
|
||||
if !carryon {
|
||||
break
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// WhereMSI uses the specified decider function to select items
|
||||
// from the []map[string]interface{}. The object contained in the result will contain
|
||||
// only the selected items.
|
||||
func (v *Value) WhereMSI(decider func(int, map[string]interface{}) bool) *Value {
|
||||
var selected []map[string]interface{}
|
||||
v.EachMSI(func(index int, val map[string]interface{}) bool {
|
||||
shouldSelect := decider(index, val)
|
||||
if !shouldSelect {
|
||||
selected = append(selected, val)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return &Value{data: selected}
|
||||
}
|
||||
|
||||
// GroupMSI uses the specified grouper function to group the items
|
||||
// keyed by the return of the grouper. The object contained in the
|
||||
// result will contain a map[string][]map[string]interface{}.
|
||||
func (v *Value) GroupMSI(grouper func(int, map[string]interface{}) string) *Value {
|
||||
groups := make(map[string][]map[string]interface{})
|
||||
v.EachMSI(func(index int, val map[string]interface{}) bool {
|
||||
group := grouper(index, val)
|
||||
if _, ok := groups[group]; !ok {
|
||||
groups[group] = make([]map[string]interface{}, 0)
|
||||
}
|
||||
groups[group] = append(groups[group], val)
|
||||
return true
|
||||
})
|
||||
return &Value{data: groups}
|
||||
}
|
||||
|
||||
// ReplaceMSI uses the specified function to replace each map[string]interface{}s
|
||||
// by iterating each item. The data in the returned result will be a
|
||||
// []map[string]interface{} containing the replaced items.
|
||||
func (v *Value) ReplaceMSI(replacer func(int, map[string]interface{}) map[string]interface{}) *Value {
|
||||
arr := v.MustMSISlice()
|
||||
replaced := make([]map[string]interface{}, len(arr))
|
||||
v.EachMSI(func(index int, val map[string]interface{}) bool {
|
||||
replaced[index] = replacer(index, val)
|
||||
return true
|
||||
})
|
||||
return &Value{data: replaced}
|
||||
}
|
||||
|
||||
// CollectMSI uses the specified collector function to collect a value
|
||||
// for each of the map[string]interface{}s in the slice. The data returned will be a
|
||||
// []interface{}.
|
||||
func (v *Value) CollectMSI(collector func(int, map[string]interface{}) interface{}) *Value {
|
||||
arr := v.MustMSISlice()
|
||||
collected := make([]interface{}, len(arr))
|
||||
v.EachMSI(func(index int, val map[string]interface{}) bool {
|
||||
collected[index] = collector(index, val)
|
||||
return true
|
||||
})
|
||||
return &Value{data: collected}
|
||||
}
|
||||
|
||||
/*
|
||||
ObjxMap ((Map) and [](Map))
|
||||
*/
|
||||
|
||||
// ObjxMap gets the value as a (Map), returns the optionalDefault
|
||||
// value or a system default object if the value is the wrong type.
|
||||
func (v *Value) ObjxMap(optionalDefault ...(Map)) Map {
|
||||
if s, ok := v.data.((Map)); ok {
|
||||
return s
|
||||
}
|
||||
if s, ok := v.data.(map[string]interface{}); ok {
|
||||
return s
|
||||
}
|
||||
if len(optionalDefault) == 1 {
|
||||
return optionalDefault[0]
|
||||
}
|
||||
return New(nil)
|
||||
}
|
||||
|
||||
// MustObjxMap gets the value as a (Map).
|
||||
//
|
||||
// Panics if the object is not a (Map).
|
||||
func (v *Value) MustObjxMap() Map {
|
||||
if s, ok := v.data.(map[string]interface{}); ok {
|
||||
return s
|
||||
}
|
||||
return v.data.((Map))
|
||||
}
|
||||
|
||||
// ObjxMapSlice gets the value as a [](Map), returns the optionalDefault
|
||||
// value or nil if the value is not a [](Map).
|
||||
func (v *Value) ObjxMapSlice(optionalDefault ...[](Map)) [](Map) {
|
||||
if s, ok := v.data.([]Map); ok {
|
||||
return s
|
||||
}
|
||||
|
||||
if s, ok := v.data.([]map[string]interface{}); ok {
|
||||
result := make([]Map, len(s))
|
||||
for i := range s {
|
||||
result[i] = s[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
s, ok := v.data.([]interface{})
|
||||
if !ok {
|
||||
if len(optionalDefault) == 1 {
|
||||
return optionalDefault[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
result := make([]Map, len(s))
|
||||
for i := range s {
|
||||
switch s[i].(type) {
|
||||
case Map:
|
||||
result[i] = s[i].(Map)
|
||||
case map[string]interface{}:
|
||||
result[i] = New(s[i])
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// MustObjxMapSlice gets the value as a [](Map).
|
||||
//
|
||||
// Panics if the object is not a [](Map).
|
||||
func (v *Value) MustObjxMapSlice() [](Map) {
|
||||
if s := v.ObjxMapSlice(); s != nil {
|
||||
return s
|
||||
}
|
||||
return v.data.([](Map))
|
||||
}
|
||||
|
||||
// IsObjxMap gets whether the object contained is a (Map) or not.
|
||||
func (v *Value) IsObjxMap() bool {
|
||||
_, ok := v.data.((Map))
|
||||
if !ok {
|
||||
_, ok = v.data.(map[string]interface{})
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsObjxMapSlice gets whether the object contained is a [](Map) or not.
|
||||
func (v *Value) IsObjxMapSlice() bool {
|
||||
_, ok := v.data.([](Map))
|
||||
if !ok {
|
||||
_, ok = v.data.([]map[string]interface{})
|
||||
if !ok {
|
||||
s, ok := v.data.([]interface{})
|
||||
if ok {
|
||||
for i := range s {
|
||||
switch s[i].(type) {
|
||||
case Map:
|
||||
case map[string]interface{}:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
// EachObjxMap calls the specified callback for each object
|
||||
// in the [](Map).
|
||||
//
|
||||
// Panics if the object is the wrong type.
|
||||
func (v *Value) EachObjxMap(callback func(int, Map) bool) *Value {
|
||||
for index, val := range v.MustObjxMapSlice() {
|
||||
carryon := callback(index, val)
|
||||
if !carryon {
|
||||
break
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// WhereObjxMap uses the specified decider function to select items
|
||||
// from the [](Map). The object contained in the result will contain
|
||||
// only the selected items.
|
||||
func (v *Value) WhereObjxMap(decider func(int, Map) bool) *Value {
|
||||
var selected [](Map)
|
||||
v.EachObjxMap(func(index int, val Map) bool {
|
||||
shouldSelect := decider(index, val)
|
||||
if !shouldSelect {
|
||||
selected = append(selected, val)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return &Value{data: selected}
|
||||
}
|
||||
|
||||
// GroupObjxMap uses the specified grouper function to group the items
|
||||
// keyed by the return of the grouper. The object contained in the
|
||||
// result will contain a map[string][](Map).
|
||||
func (v *Value) GroupObjxMap(grouper func(int, Map) string) *Value {
|
||||
groups := make(map[string][](Map))
|
||||
v.EachObjxMap(func(index int, val Map) bool {
|
||||
group := grouper(index, val)
|
||||
if _, ok := groups[group]; !ok {
|
||||
groups[group] = make([](Map), 0)
|
||||
}
|
||||
groups[group] = append(groups[group], val)
|
||||
return true
|
||||
})
|
||||
return &Value{data: groups}
|
||||
}
|
||||
|
||||
// ReplaceObjxMap uses the specified function to replace each (Map)s
|
||||
// by iterating each item. The data in the returned result will be a
|
||||
// [](Map) containing the replaced items.
|
||||
func (v *Value) ReplaceObjxMap(replacer func(int, Map) Map) *Value {
|
||||
arr := v.MustObjxMapSlice()
|
||||
replaced := make([](Map), len(arr))
|
||||
v.EachObjxMap(func(index int, val Map) bool {
|
||||
replaced[index] = replacer(index, val)
|
||||
return true
|
||||
})
|
||||
return &Value{data: replaced}
|
||||
}
|
||||
|
||||
// CollectObjxMap uses the specified collector function to collect a value
|
||||
// for each of the (Map)s in the slice. The data returned will be a
|
||||
// []interface{}.
|
||||
func (v *Value) CollectObjxMap(collector func(int, Map) interface{}) *Value {
|
||||
arr := v.MustObjxMapSlice()
|
||||
collected := make([]interface{}, len(arr))
|
||||
v.EachObjxMap(func(index int, val Map) bool {
|
||||
collected[index] = collector(index, val)
|
||||
return true
|
||||
})
|
||||
return &Value{data: collected}
|
||||
}
|
2261
vendor/github.com/stretchr/objx/type_specific_codegen.go
generated
vendored
2261
vendor/github.com/stretchr/objx/type_specific_codegen.go
generated
vendored
File diff suppressed because it is too large
Load Diff
159
vendor/github.com/stretchr/objx/value.go
generated
vendored
159
vendor/github.com/stretchr/objx/value.go
generated
vendored
@ -1,159 +0,0 @@
|
||||
package objx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Value provides methods for extracting interface{} data in various
|
||||
// types.
|
||||
type Value struct {
|
||||
// data contains the raw data being managed by this Value
|
||||
data interface{}
|
||||
}
|
||||
|
||||
// Data returns the raw data contained by this Value
|
||||
func (v *Value) Data() interface{} {
|
||||
return v.data
|
||||
}
|
||||
|
||||
// String returns the value always as a string
|
||||
func (v *Value) String() string {
|
||||
switch {
|
||||
case v.IsNil():
|
||||
return ""
|
||||
case v.IsStr():
|
||||
return v.Str()
|
||||
case v.IsBool():
|
||||
return strconv.FormatBool(v.Bool())
|
||||
case v.IsFloat32():
|
||||
return strconv.FormatFloat(float64(v.Float32()), 'f', -1, 32)
|
||||
case v.IsFloat64():
|
||||
return strconv.FormatFloat(v.Float64(), 'f', -1, 64)
|
||||
case v.IsInt():
|
||||
return strconv.FormatInt(int64(v.Int()), 10)
|
||||
case v.IsInt8():
|
||||
return strconv.FormatInt(int64(v.Int8()), 10)
|
||||
case v.IsInt16():
|
||||
return strconv.FormatInt(int64(v.Int16()), 10)
|
||||
case v.IsInt32():
|
||||
return strconv.FormatInt(int64(v.Int32()), 10)
|
||||
case v.IsInt64():
|
||||
return strconv.FormatInt(v.Int64(), 10)
|
||||
case v.IsUint():
|
||||
return strconv.FormatUint(uint64(v.Uint()), 10)
|
||||
case v.IsUint8():
|
||||
return strconv.FormatUint(uint64(v.Uint8()), 10)
|
||||
case v.IsUint16():
|
||||
return strconv.FormatUint(uint64(v.Uint16()), 10)
|
||||
case v.IsUint32():
|
||||
return strconv.FormatUint(uint64(v.Uint32()), 10)
|
||||
case v.IsUint64():
|
||||
return strconv.FormatUint(v.Uint64(), 10)
|
||||
}
|
||||
return fmt.Sprintf("%#v", v.Data())
|
||||
}
|
||||
|
||||
// StringSlice returns the value always as a []string
|
||||
func (v *Value) StringSlice(optionalDefault ...[]string) []string {
|
||||
switch {
|
||||
case v.IsStrSlice():
|
||||
return v.MustStrSlice()
|
||||
case v.IsBoolSlice():
|
||||
slice := v.MustBoolSlice()
|
||||
vals := make([]string, len(slice))
|
||||
for i, iv := range slice {
|
||||
vals[i] = strconv.FormatBool(iv)
|
||||
}
|
||||
return vals
|
||||
case v.IsFloat32Slice():
|
||||
slice := v.MustFloat32Slice()
|
||||
vals := make([]string, len(slice))
|
||||
for i, iv := range slice {
|
||||
vals[i] = strconv.FormatFloat(float64(iv), 'f', -1, 32)
|
||||
}
|
||||
return vals
|
||||
case v.IsFloat64Slice():
|
||||
slice := v.MustFloat64Slice()
|
||||
vals := make([]string, len(slice))
|
||||
for i, iv := range slice {
|
||||
vals[i] = strconv.FormatFloat(iv, 'f', -1, 64)
|
||||
}
|
||||
return vals
|
||||
case v.IsIntSlice():
|
||||
slice := v.MustIntSlice()
|
||||
vals := make([]string, len(slice))
|
||||
for i, iv := range slice {
|
||||
vals[i] = strconv.FormatInt(int64(iv), 10)
|
||||
}
|
||||
return vals
|
||||
case v.IsInt8Slice():
|
||||
slice := v.MustInt8Slice()
|
||||
vals := make([]string, len(slice))
|
||||
for i, iv := range slice {
|
||||
vals[i] = strconv.FormatInt(int64(iv), 10)
|
||||
}
|
||||
return vals
|
||||
case v.IsInt16Slice():
|
||||
slice := v.MustInt16Slice()
|
||||
vals := make([]string, len(slice))
|
||||
for i, iv := range slice {
|
||||
vals[i] = strconv.FormatInt(int64(iv), 10)
|
||||
}
|
||||
return vals
|
||||
case v.IsInt32Slice():
|
||||
slice := v.MustInt32Slice()
|
||||
vals := make([]string, len(slice))
|
||||
for i, iv := range slice {
|
||||
vals[i] = strconv.FormatInt(int64(iv), 10)
|
||||
}
|
||||
return vals
|
||||
case v.IsInt64Slice():
|
||||
slice := v.MustInt64Slice()
|
||||
vals := make([]string, len(slice))
|
||||
for i, iv := range slice {
|
||||
vals[i] = strconv.FormatInt(iv, 10)
|
||||
}
|
||||
return vals
|
||||
case v.IsUintSlice():
|
||||
slice := v.MustUintSlice()
|
||||
vals := make([]string, len(slice))
|
||||
for i, iv := range slice {
|
||||
vals[i] = strconv.FormatUint(uint64(iv), 10)
|
||||
}
|
||||
return vals
|
||||
case v.IsUint8Slice():
|
||||
slice := v.MustUint8Slice()
|
||||
vals := make([]string, len(slice))
|
||||
for i, iv := range slice {
|
||||
vals[i] = strconv.FormatUint(uint64(iv), 10)
|
||||
}
|
||||
return vals
|
||||
case v.IsUint16Slice():
|
||||
slice := v.MustUint16Slice()
|
||||
vals := make([]string, len(slice))
|
||||
for i, iv := range slice {
|
||||
vals[i] = strconv.FormatUint(uint64(iv), 10)
|
||||
}
|
||||
return vals
|
||||
case v.IsUint32Slice():
|
||||
slice := v.MustUint32Slice()
|
||||
vals := make([]string, len(slice))
|
||||
for i, iv := range slice {
|
||||
vals[i] = strconv.FormatUint(uint64(iv), 10)
|
||||
}
|
||||
return vals
|
||||
case v.IsUint64Slice():
|
||||
slice := v.MustUint64Slice()
|
||||
vals := make([]string, len(slice))
|
||||
for i, iv := range slice {
|
||||
vals[i] = strconv.FormatUint(iv, 10)
|
||||
}
|
||||
return vals
|
||||
}
|
||||
if len(optionalDefault) == 1 {
|
||||
return optionalDefault[0]
|
||||
}
|
||||
|
||||
return []string{}
|
||||
}
|
44
vendor/github.com/stretchr/testify/mock/doc.go
generated
vendored
44
vendor/github.com/stretchr/testify/mock/doc.go
generated
vendored
@ -1,44 +0,0 @@
|
||||
// Package mock provides a system by which it is possible to mock your objects
|
||||
// and verify calls are happening as expected.
|
||||
//
|
||||
// Example Usage
|
||||
//
|
||||
// The mock package provides an object, Mock, that tracks activity on another object. It is usually
|
||||
// embedded into a test object as shown below:
|
||||
//
|
||||
// type MyTestObject struct {
|
||||
// // add a Mock object instance
|
||||
// mock.Mock
|
||||
//
|
||||
// // other fields go here as normal
|
||||
// }
|
||||
//
|
||||
// When implementing the methods of an interface, you wire your functions up
|
||||
// to call the Mock.Called(args...) method, and return the appropriate values.
|
||||
//
|
||||
// For example, to mock a method that saves the name and age of a person and returns
|
||||
// the year of their birth or an error, you might write this:
|
||||
//
|
||||
// func (o *MyTestObject) SavePersonDetails(firstname, lastname string, age int) (int, error) {
|
||||
// args := o.Called(firstname, lastname, age)
|
||||
// return args.Int(0), args.Error(1)
|
||||
// }
|
||||
//
|
||||
// The Int, Error and Bool methods are examples of strongly typed getters that take the argument
|
||||
// index position. Given this argument list:
|
||||
//
|
||||
// (12, true, "Something")
|
||||
//
|
||||
// You could read them out strongly typed like this:
|
||||
//
|
||||
// args.Int(0)
|
||||
// args.Bool(1)
|
||||
// args.String(2)
|
||||
//
|
||||
// For objects of your own type, use the generic Arguments.Get(index) method and make a type assertion:
|
||||
//
|
||||
// return args.Get(0).(*MyObject), args.Get(1).(*AnotherObjectOfMine)
|
||||
//
|
||||
// This may cause a panic if the object you are getting is nil (the type assertion will fail), in those
|
||||
// cases you should check for nil first.
|
||||
package mock
|
1098
vendor/github.com/stretchr/testify/mock/mock.go
generated
vendored
1098
vendor/github.com/stretchr/testify/mock/mock.go
generated
vendored
File diff suppressed because it is too large
Load Diff
10
vendor/modules.txt
vendored
10
vendor/modules.txt
vendored
@ -144,12 +144,8 @@ github.com/aws/aws-sdk-go/private/protocol/query/queryutil
|
||||
github.com/aws/aws-sdk-go/private/protocol/rest
|
||||
github.com/aws/aws-sdk-go/private/protocol/restjson
|
||||
github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil
|
||||
github.com/aws/aws-sdk-go/service/autoscaling
|
||||
github.com/aws/aws-sdk-go/service/ec2
|
||||
github.com/aws/aws-sdk-go/service/ecr
|
||||
github.com/aws/aws-sdk-go/service/elb
|
||||
github.com/aws/aws-sdk-go/service/elbv2
|
||||
github.com/aws/aws-sdk-go/service/kms
|
||||
github.com/aws/aws-sdk-go/service/sso
|
||||
github.com/aws/aws-sdk-go/service/sso/ssoiface
|
||||
github.com/aws/aws-sdk-go/service/sts
|
||||
@ -388,7 +384,6 @@ github.com/google/cadvisor/third_party/containerd/api/types
|
||||
github.com/google/cadvisor/third_party/containerd/api/types/task
|
||||
github.com/google/cadvisor/utils
|
||||
github.com/google/cadvisor/utils/cloudinfo
|
||||
github.com/google/cadvisor/utils/cloudinfo/aws
|
||||
github.com/google/cadvisor/utils/cloudinfo/azure
|
||||
github.com/google/cadvisor/utils/cloudinfo/gce
|
||||
github.com/google/cadvisor/utils/cpuload
|
||||
@ -703,13 +698,9 @@ github.com/spf13/pflag
|
||||
# github.com/stoewer/go-strcase v1.2.0
|
||||
## explicit; go 1.11
|
||||
github.com/stoewer/go-strcase
|
||||
# github.com/stretchr/objx v0.5.0
|
||||
## explicit; go 1.12
|
||||
github.com/stretchr/objx
|
||||
# github.com/stretchr/testify v1.8.1
|
||||
## explicit; go 1.13
|
||||
github.com/stretchr/testify/assert
|
||||
github.com/stretchr/testify/mock
|
||||
github.com/stretchr/testify/require
|
||||
# github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635
|
||||
## explicit
|
||||
@ -2285,7 +2276,6 @@ k8s.io/kubelet/pkg/apis/podresources/v1alpha1
|
||||
k8s.io/kubelet/pkg/apis/stats/v1alpha1
|
||||
# k8s.io/legacy-cloud-providers v0.0.0 => ./staging/src/k8s.io/legacy-cloud-providers
|
||||
## explicit; go 1.19
|
||||
k8s.io/legacy-cloud-providers/aws
|
||||
k8s.io/legacy-cloud-providers/azure
|
||||
k8s.io/legacy-cloud-providers/azure/auth
|
||||
k8s.io/legacy-cloud-providers/azure/cache
|
||||
|
Loading…
Reference in New Issue
Block a user