mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-01 15:58:37 +00:00
cleanup: Remove quobyte volume plugins from k8s codebase
This commit is contained in:
parent
83591a1196
commit
9c869b3dfc
32
LICENSES/vendor/github.com/quobyte/api/LICENSE
generated
vendored
32
LICENSES/vendor/github.com/quobyte/api/LICENSE
generated
vendored
@ -1,32 +0,0 @@
|
||||
= vendor/github.com/quobyte/api licensed under: =
|
||||
|
||||
|
||||
Copyright (c) 2016, Quobyte Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of quobyte-automation nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
= vendor/github.com/quobyte/api/LICENSE beacc5ea3bcda24bdcec545022dbb0b4
|
@ -40,7 +40,6 @@ import (
|
||||
"k8s.io/kubernetes/pkg/volume/iscsi"
|
||||
"k8s.io/kubernetes/pkg/volume/local"
|
||||
"k8s.io/kubernetes/pkg/volume/nfs"
|
||||
"k8s.io/kubernetes/pkg/volume/quobyte"
|
||||
"k8s.io/kubernetes/pkg/volume/storageos"
|
||||
volumeutil "k8s.io/kubernetes/pkg/volume/util"
|
||||
|
||||
@ -125,8 +124,7 @@ func ProbeControllerVolumePlugins(cloud cloudprovider.Interface, config persiste
|
||||
}
|
||||
allPlugins = append(allPlugins, nfs.ProbeVolumePlugins(nfsConfig)...)
|
||||
allPlugins = append(allPlugins, glusterfs.ProbeVolumePlugins()...)
|
||||
// add rbd provisioner
|
||||
allPlugins = append(allPlugins, quobyte.ProbeVolumePlugins()...)
|
||||
|
||||
var err error
|
||||
allPlugins, err = appendExpandableLegacyProviderVolumes(allPlugins, utilfeature.DefaultFeatureGate)
|
||||
if err != nil {
|
||||
|
@ -38,7 +38,6 @@ import (
|
||||
"k8s.io/kubernetes/pkg/volume/local"
|
||||
"k8s.io/kubernetes/pkg/volume/nfs"
|
||||
"k8s.io/kubernetes/pkg/volume/projected"
|
||||
"k8s.io/kubernetes/pkg/volume/quobyte"
|
||||
"k8s.io/kubernetes/pkg/volume/secret"
|
||||
"k8s.io/kubernetes/pkg/volume/storageos"
|
||||
|
||||
@ -68,7 +67,6 @@ func ProbeVolumePlugins(featureGate featuregate.FeatureGate) ([]volume.VolumePlu
|
||||
allPlugins = append(allPlugins, secret.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, iscsi.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, glusterfs.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, quobyte.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, cephfs.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, downwardapi.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, fc.ProbeVolumePlugins()...)
|
||||
|
2
go.mod
2
go.mod
@ -63,7 +63,6 @@ require (
|
||||
github.com/prometheus/client_golang v1.12.1
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/prometheus/common v0.32.1
|
||||
github.com/quobyte/api v0.1.8
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
@ -473,7 +472,6 @@ replace (
|
||||
github.com/prometheus/client_model => github.com/prometheus/client_model v0.2.0
|
||||
github.com/prometheus/common => github.com/prometheus/common v0.32.1
|
||||
github.com/prometheus/procfs => github.com/prometheus/procfs v0.7.3
|
||||
github.com/quobyte/api => github.com/quobyte/api v0.1.8
|
||||
github.com/remyoudompheng/bigfft => github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446
|
||||
github.com/robfig/cron/v3 => github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/rogpeppe/fastuuid => github.com/rogpeppe/fastuuid v1.2.0
|
||||
|
2
go.sum
2
go.sum
@ -367,8 +367,6 @@ github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuI
|
||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/quobyte/api v0.1.8 h1:+sOX1gIlC/OaLipqVZWrHgly9Kh9Qo8OygeS0mWAg30=
|
||||
github.com/quobyte/api v0.1.8/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H6VI=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
|
@ -53,7 +53,6 @@ import (
|
||||
"k8s.io/kubernetes/pkg/volume/nfs"
|
||||
"k8s.io/kubernetes/pkg/volume/portworx"
|
||||
"k8s.io/kubernetes/pkg/volume/projected"
|
||||
"k8s.io/kubernetes/pkg/volume/quobyte"
|
||||
"k8s.io/kubernetes/pkg/volume/rbd"
|
||||
"k8s.io/kubernetes/pkg/volume/secret"
|
||||
"k8s.io/kubernetes/pkg/volume/storageos"
|
||||
@ -78,7 +77,6 @@ func volumePlugins() []volume.VolumePlugin {
|
||||
allPlugins = append(allPlugins, iscsi.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, glusterfs.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, rbd.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, quobyte.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, cephfs.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, downwardapi.ProbeVolumePlugins()...)
|
||||
allPlugins = append(allPlugins, fc.ProbeVolumePlugins()...)
|
||||
|
@ -1,11 +0,0 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- saad-ali
|
||||
reviewers:
|
||||
- saad-ali
|
||||
- jsafrane
|
||||
- jingxu97
|
||||
- msau42
|
||||
emeritus_approvers:
|
||||
- rootfs
|
@ -1,19 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package quobyte contains the internal representation of Quobyte
|
||||
// volumes.
|
||||
package quobyte
|
@ -1,513 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package quobyte
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
gostrings "strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/mount-utils"
|
||||
utilstrings "k8s.io/utils/strings"
|
||||
|
||||
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"
|
||||
proxyutil "k8s.io/kubernetes/pkg/proxy/util"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util"
|
||||
)
|
||||
|
||||
// ProbeVolumePlugins is the primary entrypoint for volume plugins.
|
||||
func ProbeVolumePlugins() []volume.VolumePlugin {
|
||||
return []volume.VolumePlugin{&quobytePlugin{nil}}
|
||||
}
|
||||
|
||||
type quobytePlugin struct {
|
||||
host volume.VolumeHost
|
||||
}
|
||||
|
||||
// This user is used to authenticate against the
|
||||
// Quobyte API server and holds all information
|
||||
type quobyteAPIConfig struct {
|
||||
quobyteUser string
|
||||
quobytePassword string `datapolicy:"password"`
|
||||
quobyteAPIServer string
|
||||
}
|
||||
|
||||
var _ volume.VolumePlugin = &quobytePlugin{}
|
||||
var _ volume.PersistentVolumePlugin = &quobytePlugin{}
|
||||
var _ volume.DeletableVolumePlugin = &quobytePlugin{}
|
||||
var _ volume.ProvisionableVolumePlugin = &quobytePlugin{}
|
||||
var _ volume.Provisioner = &quobyteVolumeProvisioner{}
|
||||
var _ volume.Deleter = &quobyteVolumeDeleter{}
|
||||
|
||||
const (
|
||||
quobytePluginName = "kubernetes.io/quobyte"
|
||||
)
|
||||
|
||||
func (plugin *quobytePlugin) Init(host volume.VolumeHost) error {
|
||||
if host == nil {
|
||||
return errors.New("host must not be nil")
|
||||
}
|
||||
plugin.host = host
|
||||
return nil
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) GetPluginName() string {
|
||||
return quobytePluginName
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) GetVolumeName(spec *volume.Spec) (string, error) {
|
||||
volumeSource, _, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
"%v:%v",
|
||||
volumeSource.Registry,
|
||||
volumeSource.Volume), nil
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) CanSupport(spec *volume.Spec) bool {
|
||||
if (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Quobyte == nil) ||
|
||||
(spec.Volume != nil && spec.Volume.Quobyte == nil) {
|
||||
return false
|
||||
}
|
||||
|
||||
// If Quobyte is already mounted we don't need to check if the binary is installed
|
||||
if mounter, err := plugin.newMounterInternal(spec, nil, plugin.host.GetMounter(plugin.GetPluginName())); err == nil {
|
||||
qm, _ := mounter.(*quobyteMounter)
|
||||
pluginDir := plugin.host.GetPluginDir(utilstrings.EscapeQualifiedName(quobytePluginName))
|
||||
if mounted, err := qm.pluginDirIsMounted(pluginDir); mounted && err == nil {
|
||||
klog.V(4).Infof("quobyte: can support")
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
klog.V(4).Infof("quobyte: Error: %v", err)
|
||||
}
|
||||
|
||||
exec := plugin.host.GetExec(plugin.GetPluginName())
|
||||
if out, err := exec.Command("ls", "/sbin/mount.quobyte").CombinedOutput(); err == nil {
|
||||
klog.V(4).Infof("quobyte: can support: %s", string(out))
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) RequiresRemount(spec *volume.Spec) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) SupportsMountOption() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) SupportsBulkVolumeVerification() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
|
||||
return []v1.PersistentVolumeAccessMode{
|
||||
v1.ReadWriteOnce,
|
||||
v1.ReadOnlyMany,
|
||||
v1.ReadWriteMany,
|
||||
}
|
||||
}
|
||||
|
||||
func getVolumeSource(spec *volume.Spec) (*v1.QuobyteVolumeSource, bool, error) {
|
||||
if spec.Volume != nil && spec.Volume.Quobyte != nil {
|
||||
return spec.Volume.Quobyte, spec.Volume.Quobyte.ReadOnly, nil
|
||||
} else if spec.PersistentVolume != nil &&
|
||||
spec.PersistentVolume.Spec.Quobyte != nil {
|
||||
return spec.PersistentVolume.Spec.Quobyte, spec.ReadOnly, nil
|
||||
}
|
||||
|
||||
return nil, false, fmt.Errorf("Spec does not reference a Quobyte volume type")
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
|
||||
quobyteVolume := &v1.Volume{
|
||||
Name: volumeName,
|
||||
VolumeSource: v1.VolumeSource{
|
||||
Quobyte: &v1.QuobyteVolumeSource{
|
||||
Volume: volumeName,
|
||||
},
|
||||
},
|
||||
}
|
||||
return volume.NewSpecFromVolume(quobyteVolume), nil
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
|
||||
return plugin.newMounterInternal(spec, pod, plugin.host.GetMounter(plugin.GetPluginName()))
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, mounter mount.Interface) (volume.Mounter, error) {
|
||||
source, readOnly, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &quobyteMounter{
|
||||
quobyte: &quobyte{
|
||||
volName: spec.Name(),
|
||||
user: source.User,
|
||||
group: source.Group,
|
||||
mounter: mounter,
|
||||
pod: pod,
|
||||
volume: source.Volume,
|
||||
plugin: plugin,
|
||||
},
|
||||
registry: source.Registry,
|
||||
readOnly: readOnly,
|
||||
mountOptions: util.MountOptionFromSpec(spec),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
|
||||
return plugin.newUnmounterInternal(volName, podUID, plugin.host.GetMounter(plugin.GetPluginName()))
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) newUnmounterInternal(volName string, podUID types.UID, mounter mount.Interface) (volume.Unmounter, error) {
|
||||
return &quobyteUnmounter{
|
||||
&quobyte{
|
||||
volName: volName,
|
||||
mounter: mounter,
|
||||
pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: podUID}},
|
||||
plugin: plugin,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Quobyte volumes represent a bare host directory mount of an quobyte export.
|
||||
type quobyte struct {
|
||||
volName string
|
||||
pod *v1.Pod
|
||||
user string
|
||||
group string
|
||||
volume string
|
||||
tenant string
|
||||
config string
|
||||
mounter mount.Interface
|
||||
plugin *quobytePlugin
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
type quobyteMounter struct {
|
||||
*quobyte
|
||||
registry string
|
||||
readOnly bool
|
||||
mountOptions []string
|
||||
}
|
||||
|
||||
var _ volume.Mounter = &quobyteMounter{}
|
||||
|
||||
func (mounter *quobyteMounter) GetAttributes() volume.Attributes {
|
||||
return volume.Attributes{
|
||||
ReadOnly: mounter.readOnly,
|
||||
Managed: false,
|
||||
SELinuxRelabel: false,
|
||||
}
|
||||
}
|
||||
|
||||
// SetUp attaches the disk and bind mounts to the volume path.
|
||||
func (mounter *quobyteMounter) SetUp(mounterArgs volume.MounterArgs) error {
|
||||
pluginDir := mounter.plugin.host.GetPluginDir(utilstrings.EscapeQualifiedName(quobytePluginName))
|
||||
return mounter.SetUpAt(pluginDir, mounterArgs)
|
||||
}
|
||||
|
||||
func (mounter *quobyteMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
|
||||
// Check if Quobyte is already mounted on the host in the Plugin Dir
|
||||
// if so we can use this mountpoint instead of creating a new one
|
||||
// IsLikelyNotMountPoint wouldn't check the mount type
|
||||
if mounted, err := mounter.pluginDirIsMounted(dir); err != nil {
|
||||
return err
|
||||
} else if mounted {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(dir, 0750); err != nil {
|
||||
return err
|
||||
}
|
||||
var options []string
|
||||
options = append(options, "allow-usermapping-in-volumename")
|
||||
if mounter.readOnly {
|
||||
options = append(options, "ro")
|
||||
}
|
||||
|
||||
//if a trailing slash is missing we add it here
|
||||
mountOptions := util.JoinMountOptions(mounter.mountOptions, options)
|
||||
if err := mounter.mounter.MountSensitiveWithoutSystemd(mounter.correctTraillingSlash(mounter.registry), dir, "quobyte", mountOptions, nil); err != nil {
|
||||
return fmt.Errorf("quobyte: mount failed: %v", err)
|
||||
}
|
||||
|
||||
klog.V(4).Infof("quobyte: mount set up: %s", dir)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPath returns the path to the user specific mount of a Quobyte volume
|
||||
// Returns a path in the format ../user#group@volume
|
||||
func (quobyteVolume *quobyte) GetPath() string {
|
||||
user := quobyteVolume.user
|
||||
if len(user) == 0 {
|
||||
user = "root"
|
||||
}
|
||||
|
||||
group := quobyteVolume.group
|
||||
if len(group) == 0 {
|
||||
group = "nfsnobody"
|
||||
}
|
||||
|
||||
// Quobyte has only one mount in the PluginDir where all Volumes are mounted
|
||||
// The Quobyte client does a fixed-user mapping
|
||||
pluginDir := quobyteVolume.plugin.host.GetPluginDir(utilstrings.EscapeQualifiedName(quobytePluginName))
|
||||
return filepath.Join(pluginDir, fmt.Sprintf("%s#%s@%s", user, group, quobyteVolume.volume))
|
||||
}
|
||||
|
||||
type quobyteUnmounter struct {
|
||||
*quobyte
|
||||
}
|
||||
|
||||
var _ volume.Unmounter = &quobyteUnmounter{}
|
||||
|
||||
func (unmounter *quobyteUnmounter) TearDown() error {
|
||||
return unmounter.TearDownAt(unmounter.GetPath())
|
||||
}
|
||||
|
||||
// We don't need to unmount on the host because only one mount exists
|
||||
func (unmounter *quobyteUnmounter) TearDownAt(dir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type quobyteVolumeDeleter struct {
|
||||
*quobyteMounter
|
||||
pv *v1.PersistentVolume
|
||||
dialOptions *proxyutil.FilteredDialOptions
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) {
|
||||
if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Quobyte == nil {
|
||||
return nil, fmt.Errorf("spec.PersistentVolume.Spec.Quobyte is nil")
|
||||
}
|
||||
|
||||
return plugin.newDeleterInternal(spec)
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) newDeleterInternal(spec *volume.Spec) (volume.Deleter, error) {
|
||||
source, readOnly, err := getVolumeSource(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if plugin.host == nil {
|
||||
return nil, errors.New("host must not be nil")
|
||||
}
|
||||
|
||||
return &quobyteVolumeDeleter{
|
||||
quobyteMounter: &quobyteMounter{
|
||||
quobyte: &quobyte{
|
||||
volName: spec.Name(),
|
||||
user: source.User,
|
||||
group: source.Group,
|
||||
volume: source.Volume,
|
||||
plugin: plugin,
|
||||
tenant: source.Tenant,
|
||||
},
|
||||
registry: source.Registry,
|
||||
readOnly: readOnly,
|
||||
},
|
||||
pv: spec.PersistentVolume,
|
||||
dialOptions: plugin.host.GetFilteredDialOptions(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
|
||||
return plugin.newProvisionerInternal(options)
|
||||
}
|
||||
|
||||
func (plugin *quobytePlugin) newProvisionerInternal(options volume.VolumeOptions) (volume.Provisioner, error) {
|
||||
if plugin.host == nil {
|
||||
return nil, errors.New("host must not be nil")
|
||||
}
|
||||
return &quobyteVolumeProvisioner{
|
||||
quobyteMounter: &quobyteMounter{
|
||||
quobyte: &quobyte{
|
||||
plugin: plugin,
|
||||
},
|
||||
},
|
||||
options: options,
|
||||
dialOptions: plugin.host.GetFilteredDialOptions(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type quobyteVolumeProvisioner struct {
|
||||
*quobyteMounter
|
||||
options volume.VolumeOptions
|
||||
dialOptions *proxyutil.FilteredDialOptions
|
||||
}
|
||||
|
||||
func (provisioner *quobyteVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
|
||||
if !util.ContainsAllAccessModes(provisioner.plugin.GetAccessModes(), provisioner.options.PVC.Spec.AccessModes) {
|
||||
return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", provisioner.options.PVC.Spec.AccessModes, provisioner.plugin.GetAccessModes())
|
||||
}
|
||||
|
||||
if util.CheckPersistentVolumeClaimModeBlock(provisioner.options.PVC) {
|
||||
return nil, fmt.Errorf("%s does not support block volume provisioning", provisioner.plugin.GetPluginName())
|
||||
}
|
||||
|
||||
if provisioner.options.PVC.Spec.Selector != nil {
|
||||
return nil, fmt.Errorf("claim Selector is not supported")
|
||||
}
|
||||
provisioner.config = "BASE"
|
||||
provisioner.tenant = "DEFAULT"
|
||||
createQuota := false
|
||||
|
||||
cfg, err := parseAPIConfig(provisioner.plugin, provisioner.options.Parameters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range provisioner.options.Parameters {
|
||||
switch gostrings.ToLower(k) {
|
||||
case "registry":
|
||||
provisioner.registry = v
|
||||
case "user":
|
||||
provisioner.user = v
|
||||
case "group":
|
||||
provisioner.group = v
|
||||
case "quobytetenant":
|
||||
provisioner.tenant = v
|
||||
case "quobyteconfig":
|
||||
provisioner.config = v
|
||||
case "createquota":
|
||||
createQuota = gostrings.ToLower(v) == "true"
|
||||
case "adminsecretname",
|
||||
"adminsecretnamespace",
|
||||
"quobyteapiserver":
|
||||
continue
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid option %q for volume plugin %s", k, provisioner.plugin.GetPluginName())
|
||||
}
|
||||
}
|
||||
|
||||
if !validateRegistry(provisioner.registry) {
|
||||
return nil, fmt.Errorf("quobyte registry missing or malformed: must be a host:port pair or multiple pairs separated by commas")
|
||||
}
|
||||
|
||||
// create random image name
|
||||
provisioner.volume = fmt.Sprintf("kubernetes-dynamic-pvc-%s", uuid.New().String())
|
||||
|
||||
manager := &quobyteVolumeManager{
|
||||
config: cfg,
|
||||
dialOptions: provisioner.dialOptions,
|
||||
}
|
||||
|
||||
vol, sizeGB, err := manager.createVolume(provisioner, createQuota)
|
||||
if err != nil {
|
||||
// don't log error details from client calls in events
|
||||
klog.V(4).Infof("CreateVolume failed: %v", err)
|
||||
return nil, errors.New("CreateVolume failed: see kube-controller-manager.log for details")
|
||||
}
|
||||
pv := new(v1.PersistentVolume)
|
||||
metav1.SetMetaDataAnnotation(&pv.ObjectMeta, util.VolumeDynamicallyCreatedByKey, "quobyte-dynamic-provisioner")
|
||||
pv.Spec.PersistentVolumeSource.Quobyte = vol
|
||||
pv.Spec.PersistentVolumeReclaimPolicy = provisioner.options.PersistentVolumeReclaimPolicy
|
||||
pv.Spec.AccessModes = provisioner.options.PVC.Spec.AccessModes
|
||||
if len(pv.Spec.AccessModes) == 0 {
|
||||
pv.Spec.AccessModes = provisioner.plugin.GetAccessModes()
|
||||
}
|
||||
pv.Spec.Capacity = v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGB)),
|
||||
}
|
||||
pv.Spec.MountOptions = provisioner.options.MountOptions
|
||||
pv.Spec.PersistentVolumeSource.Quobyte.Tenant = provisioner.tenant
|
||||
return pv, nil
|
||||
}
|
||||
|
||||
func (deleter *quobyteVolumeDeleter) GetPath() string {
|
||||
return deleter.quobyte.GetPath()
|
||||
}
|
||||
|
||||
func (deleter *quobyteVolumeDeleter) Delete() error {
|
||||
class, err := util.GetClassForVolume(deleter.plugin.host.GetKubeClient(), deleter.pv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := parseAPIConfig(deleter.plugin, class.Parameters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
manager := &quobyteVolumeManager{
|
||||
config: cfg,
|
||||
dialOptions: deleter.dialOptions,
|
||||
}
|
||||
err = manager.deleteVolume(deleter)
|
||||
if err != nil {
|
||||
// don't log error details from client calls in events
|
||||
klog.V(4).Infof("DeleteVolume failed: %v", err)
|
||||
return errors.New("DeleteVolume failed: see kube-controller-manager.log for details")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse API configuration (url, username and password) out of class.Parameters.
|
||||
func parseAPIConfig(plugin *quobytePlugin, params map[string]string) (*quobyteAPIConfig, error) {
|
||||
var apiServer, secretName string
|
||||
secretNamespace := "default"
|
||||
|
||||
for k, v := range params {
|
||||
switch gostrings.ToLower(k) {
|
||||
case "adminsecretname":
|
||||
secretName = v
|
||||
case "adminsecretnamespace":
|
||||
secretNamespace = v
|
||||
case "quobyteapiserver":
|
||||
apiServer = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(apiServer) == 0 {
|
||||
return nil, fmt.Errorf("quobyte API server missing or malformed: must be a http(s)://host:port pair or multiple pairs separated by commas")
|
||||
}
|
||||
|
||||
secretMap, err := util.GetSecretForPV(secretNamespace, secretName, quobytePluginName, plugin.host.GetKubeClient())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := &quobyteAPIConfig{
|
||||
quobyteAPIServer: apiServer,
|
||||
}
|
||||
|
||||
var ok bool
|
||||
if cfg.quobyteUser, ok = secretMap["user"]; !ok {
|
||||
return nil, fmt.Errorf("missing \"user\" in secret %s/%s", secretNamespace, secretName)
|
||||
}
|
||||
|
||||
if cfg.quobytePassword, ok = secretMap["password"]; !ok {
|
||||
return nil, fmt.Errorf("missing \"password\" in secret %s/%s", secretNamespace, secretName)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
@ -1,197 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package quobyte
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"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/client-go/kubernetes/fake"
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
volumetest "k8s.io/kubernetes/pkg/volume/testing"
|
||||
)
|
||||
|
||||
func TestCanSupport(t *testing.T) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("quobyte_test")
|
||||
if err != nil {
|
||||
t.Fatalf("error creating 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/quobyte")
|
||||
if err != nil {
|
||||
t.Fatal("Can't find the plugin by name")
|
||||
}
|
||||
if plug.GetPluginName() != "kubernetes.io/quobyte" {
|
||||
t.Errorf("Wrong name: %s", plug.GetPluginName())
|
||||
}
|
||||
if plug.CanSupport(&volume.Spec{PersistentVolume: &v1.PersistentVolume{Spec: v1.PersistentVolumeSpec{PersistentVolumeSource: v1.PersistentVolumeSource{}}}}) {
|
||||
t.Errorf("Expected false")
|
||||
}
|
||||
if plug.CanSupport(&volume.Spec{Volume: &v1.Volume{VolumeSource: v1.VolumeSource{}}}) {
|
||||
t.Errorf("Expected false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAccessModes(t *testing.T) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("quobyte_test")
|
||||
if err != nil {
|
||||
t.Fatalf("error creating 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.FindPersistentPluginByName("kubernetes.io/quobyte")
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
if !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteOnce) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadOnlyMany) || !volumetest.ContainsAccessMode(plug.GetAccessModes(), v1.ReadWriteMany) {
|
||||
t.Errorf("Expected three AccessModeTypes: %s, %s, and %s", v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadWriteMany)
|
||||
}
|
||||
}
|
||||
|
||||
func doTestPlugin(t *testing.T, spec *volume.Spec) {
|
||||
tmpDir, err := utiltesting.MkTmpdir("quobyte_test")
|
||||
if err != nil {
|
||||
t.Fatalf("error creating 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/quobyte")
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
|
||||
pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: types.UID("poduid")}}
|
||||
mounter, err := plug.(*quobytePlugin).newMounterInternal(spec, pod, mount.NewFakeMounter(nil))
|
||||
if err != nil {
|
||||
t.Errorf("Failed to make a new Mounter: %v", err)
|
||||
}
|
||||
if mounter == nil {
|
||||
t.Fatal("Got a nil Mounter")
|
||||
}
|
||||
|
||||
volumePath := mounter.GetPath()
|
||||
if volumePath != filepath.Join(tmpDir, "plugins/kubernetes.io~quobyte/root#root@vol") {
|
||||
t.Errorf("Got unexpected path: %s expected: %s", volumePath, fmt.Sprintf("%s/plugins/kubernetes.io~quobyte/root#root@vol", tmpDir))
|
||||
}
|
||||
if err := mounter.SetUp(volume.MounterArgs{}); err != nil {
|
||||
t.Errorf("Expected success, got: %v", err)
|
||||
}
|
||||
unmounter, err := plug.(*quobytePlugin).newUnmounterInternal("vol", types.UID("poduid"), mount.NewFakeMounter(nil))
|
||||
if err != nil {
|
||||
t.Errorf("Failed to make a new unmounter: %v", err)
|
||||
}
|
||||
if unmounter == nil {
|
||||
t.Error("Got a nil unmounter")
|
||||
}
|
||||
if err := unmounter.TearDown(); err != nil {
|
||||
t.Errorf("Expected success, got: %v", err)
|
||||
}
|
||||
// We don't need to check tear down, we don't unmount quobyte
|
||||
}
|
||||
|
||||
func TestPluginVolume(t *testing.T) {
|
||||
vol := &v1.Volume{
|
||||
Name: "vol1",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
Quobyte: &v1.QuobyteVolumeSource{Registry: "reg:7861", Volume: "vol", ReadOnly: false, User: "root", Group: "root"},
|
||||
},
|
||||
}
|
||||
doTestPlugin(t, volume.NewSpecFromVolume(vol))
|
||||
}
|
||||
|
||||
func TestPluginPersistentVolume(t *testing.T) {
|
||||
vol := &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "vol1",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
Quobyte: &v1.QuobyteVolumeSource{Registry: "reg:7861", Volume: "vol", ReadOnly: false, User: "root", Group: "root"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
doTestPlugin(t, volume.NewSpecFromPersistentVolume(vol, false))
|
||||
}
|
||||
|
||||
func TestPersistentClaimReadOnlyFlag(t *testing.T) {
|
||||
pv := &v1.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pvA",
|
||||
},
|
||||
Spec: v1.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: v1.PersistentVolumeSource{
|
||||
Quobyte: &v1.QuobyteVolumeSource{Registry: "reg:7861", Volume: "vol", ReadOnly: false, User: "root", Group: "root"},
|
||||
},
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
tmpDir, err := utiltesting.MkTmpdir("quobyte_test")
|
||||
if err != nil {
|
||||
t.Fatalf("error creating temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
client := fake.NewSimpleClientset(pv, claim)
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(), nil /* prober */, volumetest.NewFakeVolumeHost(t, tmpDir, client, nil))
|
||||
plug, _ := plugMgr.FindPluginByName(quobytePluginName)
|
||||
|
||||
// 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 == nil {
|
||||
t.Fatalf("Got a nil Mounter")
|
||||
}
|
||||
|
||||
if !mounter.GetAttributes().ReadOnly {
|
||||
t.Errorf("Expected true for mounter.IsReadOnly")
|
||||
}
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package quobyte
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
volumehelpers "k8s.io/cloud-provider/volume/helpers"
|
||||
proxyutil "k8s.io/kubernetes/pkg/proxy/util"
|
||||
|
||||
quobyteapi "github.com/quobyte/api"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type quobyteVolumeManager struct {
|
||||
config *quobyteAPIConfig
|
||||
dialOptions *proxyutil.FilteredDialOptions
|
||||
}
|
||||
|
||||
func (manager *quobyteVolumeManager) createVolume(provisioner *quobyteVolumeProvisioner, createQuota bool) (quobyte *v1.QuobyteVolumeSource, size int, err error) {
|
||||
capacity := provisioner.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
|
||||
volumeSize, err := volumehelpers.RoundUpToGiBInt(capacity)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
// Quobyte has the concept of Volumes which doesn't have a specific size (they can grow unlimited)
|
||||
// to simulate a size constraint we set here a Quota for logical space
|
||||
volumeRequest := &quobyteapi.CreateVolumeRequest{
|
||||
Name: provisioner.volume,
|
||||
RootUserID: provisioner.user,
|
||||
RootGroupID: provisioner.group,
|
||||
TenantID: provisioner.tenant,
|
||||
ConfigurationName: provisioner.config,
|
||||
}
|
||||
|
||||
quobyteClient := manager.createQuobyteClient()
|
||||
volumeUUID, err := quobyteClient.CreateVolume(volumeRequest)
|
||||
if err != nil {
|
||||
return &v1.QuobyteVolumeSource{}, volumeSize, err
|
||||
}
|
||||
|
||||
// Set Quota for Volume with specified byte size
|
||||
if createQuota {
|
||||
err = quobyteClient.SetVolumeQuota(volumeUUID, uint64(capacity.Value()))
|
||||
if err != nil {
|
||||
return &v1.QuobyteVolumeSource{}, volumeSize, err
|
||||
}
|
||||
}
|
||||
|
||||
klog.V(4).Infof("Created Quobyte volume %s", provisioner.volume)
|
||||
return &v1.QuobyteVolumeSource{
|
||||
Registry: provisioner.registry,
|
||||
Volume: provisioner.volume,
|
||||
User: provisioner.user,
|
||||
Group: provisioner.group,
|
||||
}, volumeSize, nil
|
||||
}
|
||||
|
||||
func (manager *quobyteVolumeManager) deleteVolume(deleter *quobyteVolumeDeleter) error {
|
||||
return manager.createQuobyteClient().DeleteVolumeByName(deleter.volume, deleter.tenant)
|
||||
}
|
||||
|
||||
func (manager *quobyteVolumeManager) createQuobyteClient() *quobyteapi.QuobyteClient {
|
||||
client := quobyteapi.NewQuobyteClient(
|
||||
manager.config.quobyteAPIServer,
|
||||
manager.config.quobyteUser,
|
||||
manager.config.quobytePassword,
|
||||
)
|
||||
// quobyte client library @v0.1.7 uses a zero-value http.Client with a nil
|
||||
// transport which is equivalent to using http.DefaultTransport.
|
||||
rt := http.DefaultTransport.(*http.Transport).Clone()
|
||||
rt.DialContext = proxyutil.NewFilteredDialContext(rt.DialContext, nil, manager.dialOptions)
|
||||
client.SetTransport(rt)
|
||||
return client
|
||||
}
|
||||
|
||||
func (mounter *quobyteMounter) pluginDirIsMounted(pluginDir string) (bool, error) {
|
||||
mounts, err := mounter.mounter.List()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, mountPoint := range mounts {
|
||||
if strings.HasPrefix(mountPoint.Type, "quobyte") {
|
||||
continue
|
||||
}
|
||||
|
||||
if mountPoint.Path == pluginDir {
|
||||
klog.V(4).Infof("quobyte: found mountpoint %s in /proc/mounts", mountPoint.Path)
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (mounter *quobyteMounter) correctTraillingSlash(regStr string) string {
|
||||
return filepath.Clean(regStr) + string(os.PathSeparator)
|
||||
}
|
||||
|
||||
func validateRegistry(registry string) bool {
|
||||
if len(registry) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, hostPortPair := range strings.Split(registry, ",") {
|
||||
if _, _, err := net.SplitHostPort(hostPortPair); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
28
vendor/github.com/quobyte/api/LICENSE
generated
vendored
28
vendor/github.com/quobyte/api/LICENSE
generated
vendored
@ -1,28 +0,0 @@
|
||||
|
||||
Copyright (c) 2016, Quobyte Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of quobyte-automation nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
40
vendor/github.com/quobyte/api/README.md
generated
vendored
40
vendor/github.com/quobyte/api/README.md
generated
vendored
@ -1,40 +0,0 @@
|
||||
# Quobyte API Clients
|
||||
|
||||
Get the quobyte api client
|
||||
|
||||
```bash
|
||||
go get github.com/quobyte/api
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
quobyte_api "github.com/quobyte/api"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client := quobyte_api.NewQuobyteClient("http://apiserver:7860", "user", "password")
|
||||
client.SetAPIRetryPolicy(quobyte_api.RetryInfinitely) // Default quobyte_api.RetryInteractive
|
||||
req := &quobyte_api.CreateVolumeRequest{
|
||||
Name: "MyVolume",
|
||||
RootUserID: "root",
|
||||
RootGroupID: "root",
|
||||
ConfigurationName: "BASE",
|
||||
Labels: []quobyte_api.Label{
|
||||
{Name: "label1", Value: "value1"},
|
||||
{Name: "label2", Value: "value2"},
|
||||
},
|
||||
}
|
||||
|
||||
volumeUUID, err := client.CreateVolume(req)
|
||||
if err != nil {
|
||||
log.Fatalf("Error:", err)
|
||||
}
|
||||
|
||||
log.Printf("%s", volumeUUID)
|
||||
}
|
||||
```
|
245
vendor/github.com/quobyte/api/quobyte.go
generated
vendored
245
vendor/github.com/quobyte/api/quobyte.go
generated
vendored
@ -1,245 +0,0 @@
|
||||
// Package quobyte represents a golang API for the Quobyte Storage System
|
||||
package quobyte
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// retry policy codes
|
||||
const (
|
||||
RetryNever string = "NEVER"
|
||||
RetryInteractive string = "INTERACTIVE"
|
||||
RetryInfinitely string = "INFINITELY"
|
||||
RetryOncePerTarget string = "ONCE_PER_TARGET"
|
||||
)
|
||||
|
||||
var UUIDValidator = regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$")
|
||||
|
||||
type QuobyteClient struct {
|
||||
client *http.Client
|
||||
url string
|
||||
username string
|
||||
password string
|
||||
apiRetryPolicy string
|
||||
}
|
||||
|
||||
func (client *QuobyteClient) SetAPIRetryPolicy(retry string) {
|
||||
client.apiRetryPolicy = retry
|
||||
}
|
||||
|
||||
func (client *QuobyteClient) GetAPIRetryPolicy() string {
|
||||
return client.apiRetryPolicy
|
||||
}
|
||||
|
||||
func (client *QuobyteClient) SetTransport(t http.RoundTripper) {
|
||||
client.client.Transport = t
|
||||
}
|
||||
|
||||
// NewQuobyteClient creates a new Quobyte API client
|
||||
func NewQuobyteClient(url string, username string, password string) *QuobyteClient {
|
||||
return &QuobyteClient{
|
||||
client: &http.Client{},
|
||||
url: url,
|
||||
username: username,
|
||||
password: password,
|
||||
apiRetryPolicy: RetryInteractive,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateVolume creates a new Quobyte volume. Its root directory will be owned by given user and group
|
||||
func (client QuobyteClient) CreateVolume(request *CreateVolumeRequest) (string, error) {
|
||||
var response volumeUUID
|
||||
tenantUUID, err := client.GetTenantUUID(request.TenantID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
request.TenantID = tenantUUID
|
||||
|
||||
if err = client.sendRequest("createVolume", request, &response); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return response.VolumeUUID, nil
|
||||
}
|
||||
|
||||
// GetVolumeUUID resolves the volumeUUID for the given volume and tenant name.
|
||||
// This method should be used when it is not clear if the given string is volume UUID or Name.
|
||||
func (client QuobyteClient) GetVolumeUUID(volume, tenant string) (string, error) {
|
||||
if len(volume) != 0 && !IsValidUUID(volume) {
|
||||
tenantUUID, err := client.GetTenantUUID(tenant)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
volUUID, err := client.ResolveVolumeNameToUUID(volume, tenantUUID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return volUUID, nil
|
||||
}
|
||||
return volume, nil
|
||||
}
|
||||
|
||||
// GetTenantUUID resolves the tenatnUUID for the given name
|
||||
// This method should be used when it is not clear if the given string is Tenant UUID or Name.
|
||||
func (client QuobyteClient) GetTenantUUID(tenant string) (string, error) {
|
||||
if len(tenant) != 0 && !IsValidUUID(tenant) {
|
||||
tenantUUID, err := client.ResolveTenantNameToUUID(tenant)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tenantUUID, nil
|
||||
}
|
||||
return tenant, nil
|
||||
}
|
||||
|
||||
// ResolveVolumeNameToUUID resolves a volume name to a UUID
|
||||
func (client *QuobyteClient) ResolveVolumeNameToUUID(volumeName, tenant string) (string, error) {
|
||||
request := &resolveVolumeNameRequest{
|
||||
VolumeName: volumeName,
|
||||
TenantDomain: tenant,
|
||||
}
|
||||
var response volumeUUID
|
||||
if err := client.sendRequest("resolveVolumeName", request, &response); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return response.VolumeUUID, nil
|
||||
}
|
||||
|
||||
// DeleteVolumeByResolvingNamesToUUID deletes the volume by resolving the volume name and tenant name to
|
||||
// respective UUID if required.
|
||||
// This method should be used if the given volume, tenant information is name or UUID.
|
||||
func (client *QuobyteClient) DeleteVolumeByResolvingNamesToUUID(volume, tenant string) error {
|
||||
volumeUUID, err := client.GetVolumeUUID(volume, tenant)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.DeleteVolume(volumeUUID)
|
||||
}
|
||||
|
||||
// DeleteVolume deletes a Quobyte volume
|
||||
func (client *QuobyteClient) DeleteVolume(UUID string) error {
|
||||
return client.sendRequest(
|
||||
"deleteVolume",
|
||||
&volumeUUID{
|
||||
VolumeUUID: UUID,
|
||||
},
|
||||
nil)
|
||||
}
|
||||
|
||||
// DeleteVolumeByName deletes a volume by a given name
|
||||
func (client *QuobyteClient) DeleteVolumeByName(volumeName, tenant string) error {
|
||||
uuid, err := client.ResolveVolumeNameToUUID(volumeName, tenant)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.DeleteVolume(uuid)
|
||||
}
|
||||
|
||||
// GetClientList returns a list of all active clients
|
||||
func (client *QuobyteClient) GetClientList(tenant string) (GetClientListResponse, error) {
|
||||
request := &getClientListRequest{
|
||||
TenantDomain: tenant,
|
||||
}
|
||||
|
||||
var response GetClientListResponse
|
||||
if err := client.sendRequest("getClientListRequest", request, &response); err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// SetVolumeQuota sets a Quota to the specified Volume
|
||||
func (client *QuobyteClient) SetVolumeQuota(volumeUUID string, quotaSize uint64) error {
|
||||
request := &setQuotaRequest{
|
||||
Quotas: []*quota{
|
||||
"a{
|
||||
Consumer: []*consumingEntity{
|
||||
&consumingEntity{
|
||||
Type: "VOLUME",
|
||||
Identifier: volumeUUID,
|
||||
},
|
||||
},
|
||||
Limits: []*resource{
|
||||
&resource{
|
||||
Type: "LOGICAL_DISK_SPACE",
|
||||
Value: quotaSize,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return client.sendRequest("setQuota", request, nil)
|
||||
}
|
||||
|
||||
// GetTenant returns the Tenant configuration for all specified tenants
|
||||
func (client *QuobyteClient) GetTenant(tenantIDs []string) (GetTenantResponse, error) {
|
||||
request := &getTenantRequest{TenantIDs: tenantIDs}
|
||||
|
||||
var response GetTenantResponse
|
||||
err := client.sendRequest("getTenant", request, &response)
|
||||
if err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// GetTenantMap returns a map that contains all tenant names and there ID's
|
||||
func (client *QuobyteClient) GetTenantMap() (map[string]string, error) {
|
||||
result := map[string]string{}
|
||||
response, err := client.GetTenant([]string{})
|
||||
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
for _, tenant := range response.Tenants {
|
||||
result[tenant.Name] = tenant.TenantID
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SetTenant creates a Tenant with the specified name
|
||||
func (client *QuobyteClient) SetTenant(tenantName string) (string, error) {
|
||||
request := &setTenantRequest{
|
||||
&TenantDomainConfiguration{
|
||||
Name: tenantName,
|
||||
},
|
||||
retryPolicy{client.GetAPIRetryPolicy()},
|
||||
}
|
||||
|
||||
var response setTenantResponse
|
||||
err := client.sendRequest("setTenant", request, &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return response.TenantID, nil
|
||||
}
|
||||
|
||||
// IsValidUUID Validates the given uuid
|
||||
func IsValidUUID(uuid string) bool {
|
||||
return UUIDValidator.MatchString(uuid)
|
||||
}
|
||||
|
||||
// ResolveTenantNameToUUID Returns UUID for given name, error if not found.
|
||||
func (client *QuobyteClient) ResolveTenantNameToUUID(name string) (string, error) {
|
||||
request := &resolveTenantNameRequest{
|
||||
TenantName: name,
|
||||
}
|
||||
|
||||
var response resolveTenantNameResponse
|
||||
err := client.sendRequest("resolveTenantName", request, &response)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return response.TenantID, nil
|
||||
}
|
124
vendor/github.com/quobyte/api/rpc_client.go
generated
vendored
124
vendor/github.com/quobyte/api/rpc_client.go
generated
vendored
@ -1,124 +0,0 @@
|
||||
package quobyte
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const (
|
||||
emptyResponse string = "Empty result and no error occured"
|
||||
)
|
||||
|
||||
type request struct {
|
||||
ID string `json:"id"`
|
||||
Version string `json:"jsonrpc"`
|
||||
Method string `json:"method"`
|
||||
Params interface{} `json:"params"`
|
||||
}
|
||||
|
||||
type response struct {
|
||||
ID string `json:"id"`
|
||||
Version string `json:"jsonrpc"`
|
||||
Result *json.RawMessage `json:"result"`
|
||||
Error *json.RawMessage `json:"error"`
|
||||
}
|
||||
|
||||
type rpcError struct {
|
||||
Code int64 `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (err *rpcError) decodeErrorCode() string {
|
||||
switch err.Code {
|
||||
case -32600:
|
||||
return "ERROR_CODE_INVALID_REQUEST"
|
||||
case -32603:
|
||||
return "ERROR_CODE_JSON_ENCODING_FAILED"
|
||||
case -32601:
|
||||
return "ERROR_CODE_METHOD_NOT_FOUND"
|
||||
case -32700:
|
||||
return "ERROR_CODE_PARSE_ERROR"
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func encodeRequest(method string, params interface{}) ([]byte, error) {
|
||||
return json.Marshal(&request{
|
||||
// Generate random ID and convert it to a string
|
||||
ID: strconv.FormatInt(rand.Int63(), 10),
|
||||
Version: "2.0",
|
||||
Method: method,
|
||||
Params: params,
|
||||
})
|
||||
}
|
||||
|
||||
func decodeResponse(ioReader io.Reader, reply interface{}) error {
|
||||
var resp response
|
||||
if err := json.NewDecoder(ioReader).Decode(&resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.Error != nil {
|
||||
var rpcErr rpcError
|
||||
if err := json.Unmarshal(*resp.Error, &rpcErr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rpcErr.Message != "" {
|
||||
return errors.New(rpcErr.Message)
|
||||
}
|
||||
|
||||
respError := rpcErr.decodeErrorCode()
|
||||
if respError != "" {
|
||||
return errors.New(respError)
|
||||
}
|
||||
}
|
||||
|
||||
if resp.Result != nil && reply != nil {
|
||||
return json.Unmarshal(*resp.Result, reply)
|
||||
}
|
||||
|
||||
return errors.New(emptyResponse)
|
||||
}
|
||||
|
||||
func (client QuobyteClient) sendRequest(method string, request interface{}, response interface{}) error {
|
||||
etype := reflect.ValueOf(request).Elem()
|
||||
field := etype.FieldByName("RetryPolicy")
|
||||
if field.IsValid() {
|
||||
field.SetString(client.GetAPIRetryPolicy())
|
||||
}
|
||||
message, err := encodeRequest(method, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest("POST", client.url, bytes.NewBuffer(message))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.SetBasicAuth(client.username, client.password)
|
||||
resp, err := client.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||
log.Printf("Warning: HTTP status code for request is %s\n",
|
||||
strconv.Itoa(resp.StatusCode))
|
||||
if resp.StatusCode == 401 {
|
||||
return errors.New("Unable to authenticate with Quobyte API service")
|
||||
}
|
||||
return fmt.Errorf("JsonRPC failed with error code %d", resp.StatusCode)
|
||||
}
|
||||
return decodeResponse(resp.Body, &response)
|
||||
}
|
109
vendor/github.com/quobyte/api/types.go
generated
vendored
109
vendor/github.com/quobyte/api/types.go
generated
vendored
@ -1,109 +0,0 @@
|
||||
package quobyte
|
||||
|
||||
type retryPolicy struct {
|
||||
RetryPolicy string `json:"retry,omitempty"`
|
||||
}
|
||||
|
||||
// CreateVolumeRequest represents a CreateVolumeRequest
|
||||
type CreateVolumeRequest struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
RootUserID string `json:"root_user_id,omitempty"`
|
||||
RootGroupID string `json:"root_group_id,omitempty"`
|
||||
ReplicaDeviceIDS []uint64 `json:"replica_device_ids,string,omitempty"`
|
||||
ConfigurationName string `json:"configuration_name,omitempty"`
|
||||
Labels []Label `json:"label,omitempty"`
|
||||
AccessMode uint32 `json:"access_mode,uint32,omitempty"`
|
||||
TenantID string `json:"tenant_id,omitempty"`
|
||||
retryPolicy
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
Name string `json:"name,string,omitempty"`
|
||||
Value string `json:"value,string,omitempty"`
|
||||
}
|
||||
|
||||
type resolveVolumeNameRequest struct {
|
||||
VolumeName string `json:"volume_name,omitempty"`
|
||||
TenantDomain string `json:"tenant_domain,omitempty"`
|
||||
retryPolicy
|
||||
}
|
||||
|
||||
type resolveTenantNameRequest struct {
|
||||
TenantName string `json:"tenant_name,omitempty"`
|
||||
}
|
||||
|
||||
type resolveTenantNameResponse struct {
|
||||
TenantID string `json:"tenant_id,omitempty"`
|
||||
}
|
||||
|
||||
type volumeUUID struct {
|
||||
VolumeUUID string `json:"volume_uuid,omitempty"`
|
||||
}
|
||||
|
||||
type getClientListRequest struct {
|
||||
TenantDomain string `json:"tenant_domain,omitempty"`
|
||||
retryPolicy
|
||||
}
|
||||
|
||||
type GetClientListResponse struct {
|
||||
Clients []Client `json:"client,omitempty"`
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
MountedUserName string `json:"mount_user_name,omitempty"`
|
||||
MountedVolumeUUID string `json:"mounted_volume_uuid,omitempty"`
|
||||
}
|
||||
|
||||
type consumingEntity struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Identifier string `json:"identifier,omitempty"`
|
||||
TenantID string `json:"tenant_id,omitempty"`
|
||||
}
|
||||
|
||||
type resource struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Value uint64 `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type quota struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Consumer []*consumingEntity `json:"consumer,omitempty"`
|
||||
Limits []*resource `json:"limits,omitempty"`
|
||||
Currentusage []*resource `json:"current_usage,omitempty"`
|
||||
}
|
||||
|
||||
type setQuotaRequest struct {
|
||||
Quotas []*quota `json:"quotas,omitempty"`
|
||||
retryPolicy
|
||||
}
|
||||
|
||||
type getTenantRequest struct {
|
||||
TenantIDs []string `json:"tenant_id,omitempty"`
|
||||
retryPolicy
|
||||
}
|
||||
|
||||
type GetTenantResponse struct {
|
||||
Tenants []*TenantDomainConfiguration `json:"tenant,omitempty"`
|
||||
}
|
||||
|
||||
type TenantDomainConfiguration struct {
|
||||
TenantID string `json:"tenant_id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
RestrictToNetwork []string `json:"restrict_to_network,omitempty"`
|
||||
VolumeAccess []*TenantDomainConfigurationVolumeAccess `json:"volume_access,omitempty"`
|
||||
}
|
||||
|
||||
type TenantDomainConfigurationVolumeAccess struct {
|
||||
VolumeUUID string `json:"volume_uuid,omitempty"`
|
||||
RestrictToNetwork string `json:"restrict_to_network,omitempty"`
|
||||
ReadOnly bool `json:"read_only,omitempty"`
|
||||
}
|
||||
|
||||
type setTenantRequest struct {
|
||||
Tenants *TenantDomainConfiguration `json:"tenant,omitempty"`
|
||||
retryPolicy
|
||||
}
|
||||
|
||||
type setTenantResponse struct {
|
||||
TenantID string `json:"tenant_id,omitempty"`
|
||||
}
|
4
vendor/modules.txt
vendored
4
vendor/modules.txt
vendored
@ -734,9 +734,6 @@ github.com/prometheus/common/model
|
||||
github.com/prometheus/procfs
|
||||
github.com/prometheus/procfs/internal/fs
|
||||
github.com/prometheus/procfs/internal/util
|
||||
# github.com/quobyte/api v0.1.8 => github.com/quobyte/api v0.1.8
|
||||
## explicit
|
||||
github.com/quobyte/api
|
||||
# github.com/robfig/cron/v3 v3.0.1 => github.com/robfig/cron/v3 v3.0.1
|
||||
## explicit; go 1.12
|
||||
github.com/robfig/cron/v3
|
||||
@ -2763,7 +2760,6 @@ sigs.k8s.io/yaml
|
||||
# github.com/prometheus/client_model => github.com/prometheus/client_model v0.2.0
|
||||
# github.com/prometheus/common => github.com/prometheus/common v0.32.1
|
||||
# github.com/prometheus/procfs => github.com/prometheus/procfs v0.7.3
|
||||
# github.com/quobyte/api => github.com/quobyte/api v0.1.8
|
||||
# github.com/remyoudompheng/bigfft => github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446
|
||||
# github.com/robfig/cron/v3 => github.com/robfig/cron/v3 v3.0.1
|
||||
# github.com/rogpeppe/fastuuid => github.com/rogpeppe/fastuuid v1.2.0
|
||||
|
Loading…
Reference in New Issue
Block a user