vendor files

This commit is contained in:
Serguei Bezverkhi
2018-01-09 13:57:14 -05:00
parent 558bc6c02a
commit 7b24313bd6
16547 changed files with 4527373 additions and 0 deletions

77
vendor/github.com/kubernetes-csi/drivers/.gitignore generated vendored Normal file
View File

@ -0,0 +1,77 @@
# OSX leaves these everywhere on SMB shares
._*
# OSX trash
.DS_Store
# Eclipse files
.classpath
.project
.settings/**
# Files generated by JetBrains IDEs, e.g. IntelliJ IDEA
.idea/
*.iml
# Vscode files
.vscode
# This is where the result of the go build goes
/output*/
/_output*/
/_output
# Emacs save files
*~
\#*\#
.\#*
# Vim-related files
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
*.un~
Session.vim
.netrwhist
# cscope-related files
cscope.*
# Go test binaries
*.test
# JUnit test output from ginkgo e2e tests
/junit*.xml
# Mercurial files
**/.hg
**/.hg*
# Vagrant
.vagrant
.tags*
# Test artifacts produced by Jenkins jobs
/_artifacts/
# Go dependencies installed on Jenkins
/_gopath/
# Config directories created by gcloud and gsutil on Jenkins
/.config/gcloud*/
/.gsutil/
# direnv .envrc files
.envrc
# This file used by some vendor repos (e.g. github.com/go-openapi/...) to store secret variables and should not be ignored
!\.drone\.sec
# Godeps or dep workspace
/Godeps/_workspace
vendor
vendor.*
/bazel-*
*.pyc

19
vendor/github.com/kubernetes-csi/drivers/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,19 @@
# Setting "sudo" to false forces Travis-CI to use its
# container-based build infrastructure, which has shorter
# queue times.
sudo: false
# Use the newer Travis-CI build templates based on the
# Debian Linux distribution "Trusty" release.
dist: trusty
# Select Go as the language used to run the build.
language: go
go: 1.9.x
go_import_path: github.com/kubernetes-csi/drivers
install:
- make
script:
- go test -v github.com/kubernetes-csi/drivers/... -cover

362
vendor/github.com/kubernetes-csi/drivers/Gopkg.lock generated vendored Normal file
View File

@ -0,0 +1,362 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/PuerkitoBio/purell"
packages = ["."]
revision = "0bcb03f4b4d0a9428594752bd2a3b9aa0a9d4bd4"
version = "v1.1.0"
[[projects]]
branch = "master"
name = "github.com/PuerkitoBio/urlesc"
packages = ["."]
revision = "de5bf2ad457846296e2031421a34e2568e304e35"
[[projects]]
branch = "master"
name = "github.com/beorn7/perks"
packages = ["quantile"]
revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9"
[[projects]]
branch = "master"
name = "github.com/container-storage-interface/spec"
packages = ["lib/go/csi"]
revision = "51e48d38ac8a5b4bdcf41826b77dc016ad83451e"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
name = "github.com/dgrijalva/jwt-go"
packages = ["."]
revision = "dbeaa9332f19a944acb5736b4456cfcc02140e29"
version = "v3.1.0"
[[projects]]
name = "github.com/docker/distribution"
packages = ["digestset","reference"]
revision = "edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c"
[[projects]]
name = "github.com/emicklei/go-restful"
packages = [".","log"]
revision = "5741799b275a3c4a5a9623a993576d7545cf7b5c"
version = "v2.4.0"
[[projects]]
name = "github.com/emicklei/go-restful-swagger12"
packages = ["."]
revision = "dcef7f55730566d41eae5db10e7d6981829720f6"
version = "1.0.1"
[[projects]]
name = "github.com/ghodss/yaml"
packages = ["."]
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/go-openapi/jsonpointer"
packages = ["."]
revision = "779f45308c19820f1a69e9a4cd965f496e0da10f"
[[projects]]
branch = "master"
name = "github.com/go-openapi/jsonreference"
packages = ["."]
revision = "36d33bfe519efae5632669801b180bf1a245da3b"
[[projects]]
branch = "master"
name = "github.com/go-openapi/spec"
packages = ["."]
revision = "a4fa9574c7aa73b2fc54e251eb9524d0482bb592"
[[projects]]
branch = "master"
name = "github.com/go-openapi/swag"
packages = ["."]
revision = "cf0bdb963811675a4d7e74901cefc7411a1df939"
[[projects]]
name = "github.com/gogo/protobuf"
packages = ["proto","sortkeys"]
revision = "342cbe0a04158f6dcb03ca0079991a51a4248c02"
version = "v0.5"
[[projects]]
branch = "master"
name = "github.com/golang/glog"
packages = ["."]
revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
[[projects]]
branch = "master"
name = "github.com/golang/groupcache"
packages = ["lru"]
revision = "84a468cf14b4376def5d68c722b139b881c450a4"
[[projects]]
branch = "master"
name = "github.com/golang/protobuf"
packages = ["proto","ptypes","ptypes/any","ptypes/duration","ptypes/timestamp"]
revision = "1643683e1b54a9e88ad26d98f81400c8c9d9f4f9"
[[projects]]
branch = "master"
name = "github.com/google/btree"
packages = ["."]
revision = "316fb6d3f031ae8f4d457c6c5186b9e3ded70435"
[[projects]]
branch = "master"
name = "github.com/google/gofuzz"
packages = ["."]
revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1"
[[projects]]
name = "github.com/googleapis/gnostic"
packages = ["OpenAPIv2","compiler","extensions"]
revision = "ee43cbb60db7bd22502942cccbc39059117352ab"
version = "v0.1.0"
[[projects]]
branch = "master"
name = "github.com/gophercloud/gophercloud"
packages = [".","openstack","openstack/blockstorage/v3/volumes","openstack/compute/v2/extensions/volumeattach","openstack/identity/v2/tenants","openstack/identity/v2/tokens","openstack/identity/v3/tokens","openstack/utils","pagination"]
revision = "4d2733c962898d8cd68456bd275aee64256cd2a1"
[[projects]]
branch = "master"
name = "github.com/gregjones/httpcache"
packages = [".","diskcache"]
revision = "22a0b1feae53974ed4cfe27bcce70dba061cc5fd"
[[projects]]
branch = "master"
name = "github.com/hashicorp/golang-lru"
packages = [".","simplelru"]
revision = "0a025b7e63adc15a622f29b0b2c4c3848243bbf6"
[[projects]]
name = "github.com/inconshreveable/mousetrap"
packages = ["."]
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
[[projects]]
name = "github.com/json-iterator/go"
packages = ["."]
revision = "6240e1e7983a85228f7fd9c3e1b6932d46ec58e2"
version = "1.0.3"
[[projects]]
branch = "master"
name = "github.com/juju/ratelimit"
packages = ["."]
revision = "59fac5042749a5afb9af70e813da1dd5474f0167"
[[projects]]
branch = "master"
name = "github.com/mailru/easyjson"
packages = ["buffer","jlexer","jwriter"]
revision = "7578e5f07ef653b5940eff36e0d0d84e4362bea4"
[[projects]]
name = "github.com/matttproud/golang_protobuf_extensions"
packages = ["pbutil"]
revision = "3247c84500bff8d9fb6d579d800f20b3e091582c"
version = "v1.0.0"
[[projects]]
name = "github.com/opencontainers/go-digest"
packages = ["."]
revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf"
version = "v1.0.0-rc1"
[[projects]]
name = "github.com/pborman/uuid"
packages = ["."]
revision = "e790cca94e6cc75c7064b1332e63811d4aae1a53"
version = "v1.1"
[[projects]]
branch = "master"
name = "github.com/petar/GoLLRB"
packages = ["llrb"]
revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4"
[[projects]]
name = "github.com/peterbourgon/diskv"
packages = ["."]
revision = "5f041e8faa004a95c88a202771f4cc3e991971e6"
version = "v2.0.1"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
name = "github.com/prometheus/client_golang"
packages = ["prometheus"]
revision = "c5b7fccd204277076155f10851dad72b76a49317"
version = "v0.8.0"
[[projects]]
branch = "master"
name = "github.com/prometheus/client_model"
packages = ["go"]
revision = "6f3806018612930941127f2a7c6c453ba2c527d2"
[[projects]]
branch = "master"
name = "github.com/prometheus/common"
packages = ["expfmt","internal/bitbucket.org/ww/goautoneg","model"]
revision = "e3fb1a1acd7605367a2b378bc2e2f893c05174b7"
[[projects]]
branch = "master"
name = "github.com/prometheus/procfs"
packages = [".","xfs"]
revision = "a6e9df898b1336106c743392c48ee0b71f5c4efa"
[[projects]]
name = "github.com/spf13/cobra"
packages = ["."]
revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b"
version = "v0.0.1"
[[projects]]
name = "github.com/spf13/pflag"
packages = ["."]
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/stretchr/objx"
packages = ["."]
revision = "1a9d0bb9f541897e62256577b352fdbc1fb4fd94"
[[projects]]
name = "github.com/stretchr/testify"
packages = ["assert","mock"]
revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0"
version = "v1.1.4"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = ["context","http2","http2/hpack","idna","internal/timeseries","lex/httplex","trace"]
revision = "a337091b0525af65de94df2eb7e98bd9962dcbe2"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "bf42f188b9bc6f2cf5b8ee5a912ef1aedd0eba4c"
[[projects]]
branch = "master"
name = "golang.org/x/text"
packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable","width"]
revision = "88f656faf3f37f690df1a32515b479415e1a6769"
[[projects]]
branch = "master"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
revision = "11c7f9e547da6db876260ce49ea7536985904c9b"
[[projects]]
name = "google.golang.org/grpc"
packages = [".","balancer","codes","connectivity","credentials","grpclb/grpc_lb_v1/messages","grpclog","internal","keepalive","metadata","naming","peer","resolver","stats","status","tap","transport"]
revision = "5ffe3083946d5603a0578721101dc8165b1d5b5f"
version = "v1.7.2"
[[projects]]
name = "gopkg.in/gcfg.v1"
packages = [".","scanner","token","types"]
revision = "298b7a6a3838f79debfaee8bd3bfb2b8d779e756"
version = "v1.2.1"
[[projects]]
name = "gopkg.in/inf.v0"
packages = ["."]
revision = "3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4"
version = "v0.9.0"
[[projects]]
name = "gopkg.in/warnings.v0"
packages = ["."]
revision = "ec4a0fea49c7b46c2aeb0b51aac55779c607e52b"
version = "v0.1.2"
[[projects]]
branch = "v2"
name = "gopkg.in/yaml.v2"
packages = ["."]
revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f"
[[projects]]
branch = "master"
name = "k8s.io/apiextensions-apiserver"
packages = ["pkg/features"]
revision = "099fd227da1f60394aac19c77254374142d01079"
[[projects]]
branch = "release-1.9"
name = "k8s.io/apimachinery"
packages = ["pkg/api/equality","pkg/api/errors","pkg/api/meta","pkg/api/resource","pkg/api/validation","pkg/apimachinery","pkg/apimachinery/announced","pkg/apimachinery/registered","pkg/apis/meta/internalversion","pkg/apis/meta/v1","pkg/apis/meta/v1/unstructured","pkg/apis/meta/v1/validation","pkg/apis/meta/v1alpha1","pkg/conversion","pkg/conversion/queryparams","pkg/fields","pkg/labels","pkg/runtime","pkg/runtime/schema","pkg/runtime/serializer","pkg/runtime/serializer/json","pkg/runtime/serializer/protobuf","pkg/runtime/serializer/recognizer","pkg/runtime/serializer/streaming","pkg/runtime/serializer/versioning","pkg/selection","pkg/types","pkg/util/cache","pkg/util/clock","pkg/util/diff","pkg/util/errors","pkg/util/framer","pkg/util/intstr","pkg/util/json","pkg/util/mergepatch","pkg/util/net","pkg/util/runtime","pkg/util/sets","pkg/util/strategicpatch","pkg/util/validation","pkg/util/validation/field","pkg/util/wait","pkg/util/yaml","pkg/version","pkg/watch","third_party/forked/golang/json","third_party/forked/golang/reflect"]
revision = "180eddb345a5be3a157cea1c624700ad5bd27b8f"
[[projects]]
branch = "master"
name = "k8s.io/apiserver"
packages = ["pkg/authentication/authenticator","pkg/authentication/serviceaccount","pkg/authentication/user","pkg/features","pkg/util/feature"]
revision = "73f85001b53a0adc72b5d3dc68abbcda0837fe3a"
[[projects]]
name = "k8s.io/client-go"
packages = ["discovery","informers","informers/admissionregistration","informers/admissionregistration/v1alpha1","informers/apps","informers/apps/v1beta1","informers/apps/v1beta2","informers/autoscaling","informers/autoscaling/v1","informers/autoscaling/v2beta1","informers/batch","informers/batch/v1","informers/batch/v1beta1","informers/batch/v2alpha1","informers/certificates","informers/certificates/v1beta1","informers/core","informers/core/v1","informers/extensions","informers/extensions/v1beta1","informers/internalinterfaces","informers/networking","informers/networking/v1","informers/policy","informers/policy/v1beta1","informers/rbac","informers/rbac/v1","informers/rbac/v1alpha1","informers/rbac/v1beta1","informers/scheduling","informers/scheduling/v1alpha1","informers/settings","informers/settings/v1alpha1","informers/storage","informers/storage/v1","informers/storage/v1beta1","kubernetes","kubernetes/scheme","kubernetes/typed/admissionregistration/v1alpha1","kubernetes/typed/apps/v1beta1","kubernetes/typed/apps/v1beta2","kubernetes/typed/authentication/v1","kubernetes/typed/authentication/v1beta1","kubernetes/typed/authorization/v1","kubernetes/typed/authorization/v1beta1","kubernetes/typed/autoscaling/v1","kubernetes/typed/autoscaling/v2beta1","kubernetes/typed/batch/v1","kubernetes/typed/batch/v1beta1","kubernetes/typed/batch/v2alpha1","kubernetes/typed/certificates/v1beta1","kubernetes/typed/core/v1","kubernetes/typed/extensions/v1beta1","kubernetes/typed/networking/v1","kubernetes/typed/policy/v1beta1","kubernetes/typed/rbac/v1","kubernetes/typed/rbac/v1alpha1","kubernetes/typed/rbac/v1beta1","kubernetes/typed/scheduling/v1alpha1","kubernetes/typed/settings/v1alpha1","kubernetes/typed/storage/v1","kubernetes/typed/storage/v1beta1","listers/admissionregistration/v1alpha1","listers/apps/v1beta1","listers/apps/v1beta2","listers/autoscaling/v1","listers/autoscaling/v2beta1","listers/batch/v1","listers/batch/v1beta1","listers/batch/v2alpha1","listers/certificates/v1beta1","listers/core/v1","listers/extensions/v1beta1","listers/networking/v1","listers/policy/v1beta1","listers/rbac/v1","listers/rbac/v1alpha1","listers/rbac/v1beta1","listers/scheduling/v1alpha1","listers/settings/v1alpha1","listers/storage/v1","listers/storage/v1beta1","pkg/version","rest","rest/watch","tools/cache","tools/clientcmd/api","tools/metrics","tools/pager","tools/record","tools/reference","transport","util/cert","util/flowcontrol","util/integer","util/retry"]
revision = "59ab1a8387ec5d6273ba6d3d400fb522d6e24c8f"
branch = "master"
[[projects]]
branch = "master"
name = "k8s.io/kube-openapi"
packages = ["pkg/common","pkg/util/proto"]
revision = "39a7bf85c140f972372c2a0d1ee40adbf0c8bfe1"
[[projects]]
name = "k8s.io/kubernetes"
packages = ["pkg/api/legacyscheme","pkg/api/service","pkg/api/v1/pod","pkg/apis/autoscaling","pkg/apis/core","pkg/apis/core/helper","pkg/apis/core/install","pkg/apis/core/pods","pkg/apis/core/v1","pkg/apis/core/v1/helper","pkg/apis/core/validation","pkg/apis/extensions","pkg/apis/networking","pkg/capabilities","pkg/cloudprovider","pkg/controller","pkg/features","pkg/fieldpath","pkg/kubelet/apis","pkg/kubelet/types","pkg/security/apparmor","pkg/serviceaccount","pkg/util/file","pkg/util/hash","pkg/util/io","pkg/util/mount","pkg/util/net/sets","pkg/util/nsenter","pkg/util/parsers","pkg/util/pointer","pkg/util/taints","pkg/volume","pkg/volume/util"]
revision = "3ac6d1f5c03bbc550fe197ae58da9d3f6a91a1f0"
version = "v1.9.0-beta.2"
[[projects]]
branch = "master"
name = "k8s.io/utils"
packages = ["exec"]
revision = "bf963466fd3fea33c428098b12a89d8ecd012f25"
[[projects]]
branch = "master"
name = "k8s.io/api"
packages = ["admissionregistration/v1alpha1","apps/v1beta1","apps/v1beta2","authentication/v1","authentication/v1beta1","authorization/v1","authorization/v1beta1","autoscaling/v1","autoscaling/v2beta1","batch/v1","batch/v1beta1","batch/v2alpha1","certificates/v1beta1","core/v1","extensions/v1beta1","networking/v1","policy/v1beta1","rbac/v1","rbac/v1alpha1","rbac/v1beta1","scheduling/v1alpha1","settings/v1alpha1","storage/v1","storage/v1beta1"]
revision = "5cb170de1f060ebf2dde62cc4b8241fdb3c01203"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "77a9f83151caa2ed28dd48707ab7b3122afd4adbf6699b248cbe3a463dfb4127"
solver-name = "gps-cdcl"
solver-version = 1

46
vendor/github.com/kubernetes-csi/drivers/Gopkg.toml generated vendored Normal file
View File

@ -0,0 +1,46 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
branch = "master"
name = "github.com/container-storage-interface/spec"
[[constraint]]
branch = "master"
name = "github.com/golang/glog"
[[constraint]]
name = "google.golang.org/grpc"
version = "1.7.2"
[[constraint]]
name = "github.com/docker/distribution"
revision = "edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c"
[[constraint]]
name = "k8s.io/kubernetes"
version = "v1.9.0-beta.2"
[[constraint]]
name = "k8s.io/apimachinery"
version = "kubernetes-1.9.0-beta.2"

41
vendor/github.com/kubernetes-csi/drivers/Makefile generated vendored Normal file
View File

@ -0,0 +1,41 @@
# 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.
.PHONY: all flexadapter nfs hostpath iscsi cinder clean
all: flexadapter nfs hostpath iscsi cinder
test:
go test github.com/kubernetes-csi/drivers/pkg/... -cover
go vet github.com/kubernetes-csi/drivers/pkg/...
flexadapter:
if [ ! -d ./vendor ]; then dep ensure; fi
go build -o _output/flexadapter ./app/flexadapter
nfs:
if [ ! -d ./vendor ]; then dep ensure; fi
go build -o _output/nfsplugin ./app/nfsplugin
hostpath:
if [ ! -d ./vendor ]; then dep ensure; fi
go build -i -o _output/hostpathplugin ./app/hostpathplugin
iscsi:
if [ ! -d ./vendor ]; then dep ensure; fi
go build -o _output/iscsiplugin ./app/iscsiplugin
cinder:
if [ ! -d ./vendor ]; then dep ensure; fi
go build -o _output/cinderplugin ./app/cinderplugin
clean:
go clean -r -x
-rm -rf _output

9
vendor/github.com/kubernetes-csi/drivers/README.md generated vendored Normal file
View File

@ -0,0 +1,9 @@
# CSI Drivers
These drivers are provided puerly for illustrative purposes, and should not be used for production workloads.
## Other sample drivers
Please read [Drivers](https://github.com/kubernetes-csi/docs/wiki/Drivers) for more information
## Adding new sample drivers
Please, DO NOT submit PRs to add new drivers here unless they are just examples. Real CSI drivers are to be housed on their own repo separate from this one. You are then welcomed to send a PR to https://github.com/kubernetes-csi/docs to add the [Driver](https://github.com/kubernetes-csi/docs/wiki/Drivers) page.

View File

@ -0,0 +1,72 @@
/*
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 main
import (
"flag"
"fmt"
"os"
"github.com/kubernetes-csi/drivers/pkg/cinder"
"github.com/spf13/cobra"
)
var (
endpoint string
nodeID string
cloudconfig string
)
func init() {
flag.Set("logtostderr", "true")
}
func main() {
flag.CommandLine.Parse([]string{})
cmd := &cobra.Command{
Use: "Cinder",
Short: "CSI based Cinder driver",
Run: func(cmd *cobra.Command, args []string) {
handle()
},
}
cmd.Flags().AddGoFlagSet(flag.CommandLine)
cmd.PersistentFlags().StringVar(&nodeID, "nodeid", "", "node id")
cmd.MarkPersistentFlagRequired("nodeid")
cmd.PersistentFlags().StringVar(&endpoint, "endpoint", "", "CSI endpoint")
cmd.MarkPersistentFlagRequired("endpoint")
cmd.PersistentFlags().StringVar(&cloudconfig, "cloud-config", "", "CSI driver cloud config")
cmd.MarkPersistentFlagRequired("cloud-config")
if err := cmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "%s", err.Error())
os.Exit(1)
}
os.Exit(0)
}
func handle() {
d := cinder.NewDriver(nodeID, endpoint, cloudconfig)
d.Run()
}

View File

@ -0,0 +1,77 @@
/*
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 main
import (
"flag"
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/kubernetes-csi/drivers/pkg/flexadapter"
)
var (
endpoint string
driverName string
driverPath string
nodeID string
)
func init() {
flag.Set("logtostderr", "true")
}
func main() {
flag.CommandLine.Parse([]string{})
cmd := &cobra.Command{
Use: "flexadapter",
Short: "Flex volume adapter for CSI",
Run: func(cmd *cobra.Command, args []string) {
handle()
},
}
cmd.Flags().AddGoFlagSet(flag.CommandLine)
cmd.PersistentFlags().StringVar(&nodeID, "nodeid", "", "node id")
cmd.MarkPersistentFlagRequired("nodeid")
cmd.PersistentFlags().StringVar(&endpoint, "endpoint", "", "CSI endpoint")
cmd.MarkPersistentFlagRequired("endpoint")
cmd.PersistentFlags().StringVar(&driverPath, "driverpath", "", "path to flexvolume driver path")
cmd.MarkPersistentFlagRequired("driverpath")
cmd.PersistentFlags().StringVar(&driverName, "drivername", "", "name of the driver")
cmd.MarkPersistentFlagRequired("drivername")
if err := cmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "%s", err.Error())
os.Exit(1)
}
os.Exit(0)
}
func handle() {
adapter := flexadapter.GetFlexAdapter()
adapter.Run(driverName, driverPath, nodeID, endpoint)
}

View File

@ -0,0 +1,46 @@
/*
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 main
import (
"flag"
"os"
"github.com/kubernetes-csi/drivers/pkg/hostpath"
)
func init() {
flag.Set("logtostderr", "true")
}
var (
endpoint = flag.String("endpoint", "unix://tmp/csi.sock", "CSI endpoint")
driverName = flag.String("drivername", "csi-hostpath", "name of the driver")
nodeID = flag.String("nodeid", "", "node id")
)
func main() {
flag.Parse()
handle()
os.Exit(0)
}
func handle() {
driver := hostpath.GetHostPathDriver()
driver.Run(*driverName, *nodeID, *endpoint)
}

View File

@ -0,0 +1,69 @@
/*
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 main
import (
"flag"
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/kubernetes-csi/drivers/pkg/iscsi"
)
var (
endpoint string
nodeID string
)
func init() {
flag.Set("logtostderr", "true")
}
func main() {
flag.CommandLine.Parse([]string{})
cmd := &cobra.Command{
Use: "ISCSI",
Short: "CSI based ISCSI driver",
Run: func(cmd *cobra.Command, args []string) {
handle()
},
}
cmd.Flags().AddGoFlagSet(flag.CommandLine)
cmd.PersistentFlags().StringVar(&nodeID, "nodeid", "", "node id")
cmd.MarkPersistentFlagRequired("nodeid")
cmd.PersistentFlags().StringVar(&endpoint, "endpoint", "", "CSI endpoint")
cmd.MarkPersistentFlagRequired("endpoint")
if err := cmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "%s", err.Error())
os.Exit(1)
}
os.Exit(0)
}
func handle() {
d := iscsi.NewDriver(nodeID, endpoint)
d.Run()
}

View File

@ -0,0 +1,70 @@
/*
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 main
import (
"flag"
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/kubernetes-csi/drivers/pkg/nfs"
)
var (
endpoint string
nodeID string
)
func init() {
flag.Set("logtostderr", "true")
}
func main() {
flag.CommandLine.Parse([]string{})
cmd := &cobra.Command{
Use: "NFS",
Short: "CSI based NFS driver",
Run: func(cmd *cobra.Command, args []string) {
handle()
},
}
cmd.Flags().AddGoFlagSet(flag.CommandLine)
cmd.PersistentFlags().StringVar(&nodeID, "nodeid", "", "node id")
cmd.MarkPersistentFlagRequired("nodeid")
cmd.PersistentFlags().StringVar(&endpoint, "endpoint", "", "CSI endpoint")
cmd.MarkPersistentFlagRequired("endpoint")
cmd.ParseFlags(os.Args[1:])
if err := cmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "%s", err.Error())
os.Exit(1)
}
os.Exit(0)
}
func handle() {
d := nfs.NewDriver(nodeID, endpoint)
d.Run()
}

View File

@ -0,0 +1,3 @@
# Kubernetes Community Code of Conduct
Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)

View File

@ -0,0 +1,97 @@
# CSI Cinder driver
## Kubernetes
### Requirements
The following feature gates and runtime config have to be enabled to deploy the driver.
```
FEATURE_GATES=CSIPersistentVolume=true,MountPropagation=true
RUNTIME_CONFIG="storage.k8s.io/v1alpha1=true"
```
Mountprogpation requires support for privileged containers. So, make sure privileged containers are enabled in the cluster.
### Example local-up-cluster.sh
```ALLOW_PRIVILEGED=true FEATURE_GATES=CSIPersistentVolume=true,MountPropagation=true RUNTIME_CONFIG="storage.k8s.io/v1alpha1=true" LOG_LEVEL=5 hack/local-up-cluster.sh```
### Deploy
Encode your ```cloud.conf``` file content using base64.
```base64 -w 0 cloud.conf```
Update ```cloud.conf``` configuration in ```deploy/kubernetes/csi-secret-cinderplugin.yaml``` file
by using the result of the above command.
```kubectl -f deploy/kubernetes create```
### Example Nginx application
```kubectl -f examples/kubernetes/nginx.yaml create```
## Using CSC tool
### Start Cinder driver
```
$ sudo ./_output/cinderplugin --endpoint tcp://127.0.0.1:10000 --cloud-config /etc/cloud.conf --nodeid CSINodeID
```
### Test
Get ```csc``` tool from https://github.com/chakri-nelluri/gocsi/tree/master/csc
#### Get plugin info
```
$ csc identity plugin-info --endpoint tcp://127.0.0.1:10000
"csi-cinderplugin" "0.1.0"
```
#### Get supported versions
```
$ csc identity supported-versions --endpoint tcp://127.0.0.1:10000
0.1.0
```
#### Create a volume
```
$ csc controller new --endpoint tcp://127.0.0.1:10000 CSIVolumeName
CSIVolumeID
```
#### Delete a volume
```
$ csc controller del --endpoint tcp://127.0.0.1:10000 CSIVolumeID
CSIVolumeID
```
#### ControllerPublish a volume
```
$ csc controller publish --endpoint tcp://127.0.0.1:10000 --node-id=CSINodeID CSIVolumeID
CSIVolumeID "DevicePath"="/dev/xxx"
```
#### ControllerUnpublish a volume
```
$ csc controller unpublish --endpoint tcp://127.0.0.1:10000 --node-id=CSINodeID CSIVolumeID
CSIVolumeID
```
#### NodePublish a volume
```
$ csc node publish --endpoint tcp://127.0.0.1:10000 --target-path /mnt/cinder --pub-info DevicePath="/dev/xxx" CSIVolumeID
CSIVolumeID
```
#### NodeUnpublish a volume
```
$ csc node unpublish --endpoint tcp://127.0.0.1:10000 --target-path /mnt/cinder CSIVolumeID
CSIVolumeID
```
#### Get NodeID
```
$ csc node get-id --endpoint tcp://127.0.0.1:10000
CSINodeID
```

View File

@ -0,0 +1,172 @@
/*
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 cinder
import (
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/golang/glog"
"github.com/kubernetes-csi/drivers/pkg/cinder/openstack"
csicommon "github.com/kubernetes-csi/drivers/pkg/csi-common"
"github.com/pborman/uuid"
"golang.org/x/net/context"
"k8s.io/kubernetes/pkg/volume"
)
type controllerServer struct {
*csicommon.DefaultControllerServer
}
func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
// Volume Name
volName := req.GetName()
if len(volName) == 0 {
volName = uuid.NewUUID().String()
}
// Volume Size - Default is 1 GiB
volSizeBytes := int64(1 * 1024 * 1024 * 1024)
if req.GetCapacityRange() != nil {
volSizeBytes = int64(req.GetCapacityRange().GetRequiredBytes())
}
volSizeGB := int(volume.RoundUpSize(volSizeBytes, 1024*1024*1024))
// Volume Type
volType := req.GetParameters()["type"]
// Volume Availability - Default is nova
volAvailability := req.GetParameters()["availability"]
// Get OpenStack Provider
cloud, err := openstack.GetOpenStackProvider()
if err != nil {
glog.V(3).Infof("Failed to GetOpenStackProvider: %v", err)
return nil, err
}
// Volume Create
resID, resAvailability, err := cloud.CreateVolume(volName, volSizeGB, volType, volAvailability, nil)
if err != nil {
glog.V(3).Infof("Failed to CreateVolume: %v", err)
return nil, err
}
glog.V(4).Infof("Create volume %s in Availability Zone: %s", resID, resAvailability)
return &csi.CreateVolumeResponse{
VolumeInfo: &csi.VolumeInfo{
Id: resID,
Attributes: map[string]string{
"availability": resAvailability,
},
},
}, nil
}
func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {
// Get OpenStack Provider
cloud, err := openstack.GetOpenStackProvider()
if err != nil {
glog.V(3).Infof("Failed to GetOpenStackProvider: %v", err)
return nil, err
}
// Volume Delete
volID := req.GetVolumeId()
err = cloud.DeleteVolume(volID)
if err != nil {
glog.V(3).Infof("Failed to DeleteVolume: %v", err)
return nil, err
}
glog.V(4).Infof("Delete volume %s", volID)
return &csi.DeleteVolumeResponse{}, nil
}
func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) {
// Get OpenStack Provider
cloud, err := openstack.GetOpenStackProvider()
if err != nil {
glog.V(3).Infof("Failed to GetOpenStackProvider: %v", err)
return nil, err
}
// Volume Attach
instanceID := req.GetNodeId()
volumeID := req.GetVolumeId()
_, err = cloud.AttachVolume(instanceID, volumeID)
if err != nil {
glog.V(3).Infof("Failed to AttachVolume: %v", err)
return nil, err
}
err = cloud.WaitDiskAttached(instanceID, volumeID)
if err != nil {
glog.V(3).Infof("Failed to WaitDiskAttached: %v", err)
return nil, err
}
devicePath, err := cloud.GetAttachmentDiskPath(instanceID, volumeID)
if err != nil {
glog.V(3).Infof("Failed to GetAttachmentDiskPath: %v", err)
return nil, err
}
glog.V(4).Infof("ControllerPublishVolume %s on %s", volumeID, instanceID)
// Publish Volume Info
pvInfo := map[string]string{}
pvInfo["DevicePath"] = devicePath
return &csi.ControllerPublishVolumeResponse{
PublishVolumeInfo: pvInfo,
}, nil
}
func (cs *controllerServer) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) {
// Get OpenStack Provider
cloud, err := openstack.GetOpenStackProvider()
if err != nil {
glog.V(3).Infof("Failed to GetOpenStackProvider: %v", err)
return nil, err
}
// Volume Detach
instanceID := req.GetNodeId()
volumeID := req.GetVolumeId()
err = cloud.DetachVolume(instanceID, volumeID)
if err != nil {
glog.V(3).Infof("Failed to DetachVolume: %v", err)
return nil, err
}
err = cloud.WaitDiskDetached(instanceID, volumeID)
if err != nil {
glog.V(3).Infof("Failed to WaitDiskDetached: %v", err)
return nil, err
}
glog.V(4).Infof("ControllerUnpublishVolume %s on %s", volumeID, instanceID)
return &csi.ControllerUnpublishVolumeResponse{}, nil
}

View File

@ -0,0 +1,176 @@
/*
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 cinder
import (
"testing"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/kubernetes-csi/drivers/pkg/cinder/openstack"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
var fakeCs *controllerServer
// Init Controller Server
func init() {
if fakeCs == nil {
d := NewDriver(fakeNodeID, fakeEndpoint, fakeConfig)
fakeCs = NewControllerServer(d)
}
}
// Test CreateVolume
func TestCreateVolume(t *testing.T) {
// mock OpenStack
osmock := new(openstack.OpenStackMock)
// CreateVolume(name string, size int, vtype, availability string, tags *map[string]string) (string, string, error)
osmock.On("CreateVolume", fakeVolName, mock.AnythingOfType("int"), fakeVolType, fakeAvailability, (*map[string]string)(nil)).Return(fakeVolID, fakeAvailability, nil)
openstack.OsInstance = osmock
// Init assert
assert := assert.New(t)
// Fake request
fakeReq := &csi.CreateVolumeRequest{
Version: &version,
Name: fakeVolName,
VolumeCapabilities: nil,
}
// Invoke CreateVolume
actualRes, err := fakeCs.CreateVolume(fakeCtx, fakeReq)
if err != nil {
t.Errorf("failed to CreateVolume: %v", err)
}
// Assert
assert.NotNil(actualRes.VolumeInfo)
assert.NotEqual(0, len(actualRes.VolumeInfo.Id), "Volume Id is nil")
assert.Equal(fakeAvailability, actualRes.VolumeInfo.Attributes["availability"])
}
// Test DeleteVolume
func TestDeleteVolume(t *testing.T) {
// mock OpenStack
osmock := new(openstack.OpenStackMock)
// DeleteVolume(volumeID string) error
osmock.On("DeleteVolume", fakeVolID).Return(nil)
openstack.OsInstance = osmock
// Init assert
assert := assert.New(t)
// Fake request
fakeReq := &csi.DeleteVolumeRequest{
Version: &version,
VolumeId: fakeVolID,
}
// Expected Result
expectedRes := &csi.DeleteVolumeResponse{}
// Invoke DeleteVolume
actualRes, err := fakeCs.DeleteVolume(fakeCtx, fakeReq)
if err != nil {
t.Errorf("failed to DeleteVolume: %v", err)
}
// Assert
assert.Equal(expectedRes, actualRes)
}
// Test ControllerPublishVolume
func TestControllerPublishVolume(t *testing.T) {
// mock OpenStack
osmock := new(openstack.OpenStackMock)
// AttachVolume(instanceID, volumeID string) (string, error)
osmock.On("AttachVolume", fakeNodeID, fakeVolID).Return(fakeVolID, nil)
// WaitDiskAttached(instanceID string, volumeID string) error
osmock.On("WaitDiskAttached", fakeNodeID, fakeVolID).Return(nil)
// GetAttachmentDiskPath(instanceID, volumeID string) (string, error)
osmock.On("GetAttachmentDiskPath", fakeNodeID, fakeVolID).Return(fakeDevicePath, nil)
openstack.OsInstance = osmock
// Init assert
assert := assert.New(t)
// Fake request
fakeReq := &csi.ControllerPublishVolumeRequest{
Version: &version,
VolumeId: fakeVolID,
NodeId: fakeNodeID,
VolumeCapability: nil,
Readonly: false,
}
// Expected Result
expectedRes := &csi.ControllerPublishVolumeResponse{
PublishVolumeInfo: map[string]string{
"DevicePath": fakeDevicePath,
},
}
// Invoke ControllerPublishVolume
actualRes, err := fakeCs.ControllerPublishVolume(fakeCtx, fakeReq)
if err != nil {
t.Errorf("failed to ControllerPublishVolume: %v", err)
}
// Assert
assert.Equal(expectedRes, actualRes)
}
// Test ControllerUnpublishVolume
func TestControllerUnpublishVolume(t *testing.T) {
// mock OpenStack
osmock := new(openstack.OpenStackMock)
// DetachVolume(instanceID, volumeID string) error
osmock.On("DetachVolume", fakeNodeID, fakeVolID).Return(nil)
// WaitDiskDetached(instanceID string, volumeID string) error
osmock.On("WaitDiskDetached", fakeNodeID, fakeVolID).Return(nil)
openstack.OsInstance = osmock
// Init assert
assert := assert.New(t)
// Fake request
fakeReq := &csi.ControllerUnpublishVolumeRequest{
Version: &version,
VolumeId: fakeVolID,
NodeId: fakeNodeID,
}
// Expected Result
expectedRes := &csi.ControllerUnpublishVolumeResponse{}
// Invoke ControllerUnpublishVolume
actualRes, err := fakeCs.ControllerUnpublishVolume(fakeCtx, fakeReq)
if err != nil {
t.Errorf("failed to ControllerUnpublishVolume: %v", err)
}
// Assert
assert.Equal(expectedRes, actualRes)
}

View File

@ -0,0 +1,71 @@
# This YAML file contains attacher & csi driver API objects,
# which are necessary to run external csi attacher for cinder.
kind: Service
apiVersion: v1
metadata:
name: csi-attacher-cinderplugin
labels:
app: csi-attacher-cinderplugin
spec:
selector:
app: csi-attacher-cinderplugin
ports:
- name: dummy
port: 12345
---
kind: StatefulSet
apiVersion: apps/v1beta1
metadata:
name: csi-attacher-cinderplugin
spec:
serviceName: "csi-attacher-cinderplugin"
replicas: 1
template:
metadata:
labels:
app: csi-attacher-cinderplugin
spec:
serviceAccount: csi-attacher
containers:
- name: csi-attacher
image: docker.io/k8scsi/csi-attacher
args:
- "--v=5"
- "--csi-address=$(ADDRESS)"
env:
- name: ADDRESS
value: /var/lib/csi/sockets/pluginproxy/csi.sock
imagePullPolicy: "IfNotPresent"
volumeMounts:
- name: socket-dir
mountPath: /var/lib/csi/sockets/pluginproxy/
- name: cinder
image: docker.io/k8scsi/cinderplugin
args :
- "--nodeid=$(NODE_ID)"
- "--endpoint=$(CSI_ENDPOINT)"
- "--cloud-config=$(CLOUD_CONFIG)"
env:
- name: NODE_ID
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: CSI_ENDPOINT
value: unix://plugin/csi.sock
- name: CLOUD_CONFIG
value: /etc/config/cloud.conf
imagePullPolicy: "IfNotPresent"
volumeMounts:
- name: socket-dir
mountPath: /plugin
- name: secret-cinderplugin
mountPath: /etc/config
readOnly: true
volumes:
- name: socket-dir
emptyDir:
- name: secret-cinderplugin
secret:
secretName: csi-secret-cinderplugin

View File

@ -0,0 +1,37 @@
# This YAML file contains RBAC API objects,
# which are necessary to run external csi attacher for cinder.
apiVersion: v1
kind: ServiceAccount
metadata:
name: csi-attacher
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: external-attacher-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: ["storage.k8s.io"]
resources: ["volumeattachments"]
verbs: ["get", "list", "watch", "update"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-attacher-role
subjects:
- kind: ServiceAccount
name: csi-attacher
namespace: default
roleRef:
kind: ClusterRole
name: external-attacher-runner
apiGroup: rbac.authorization.k8s.io

View File

@ -0,0 +1,90 @@
# This YAML file contains driver-registrar & csi driver nodeplugin API objects,
# which are necessary to run csi nodeplugin for cinder.
kind: DaemonSet
apiVersion: apps/v1beta2
metadata:
name: csi-nodeplugin-cinderplugin
spec:
selector:
matchLabels:
app: csi-nodeplugin-cinderplugin
template:
metadata:
labels:
app: csi-nodeplugin-cinderplugin
spec:
serviceAccount: csi-nodeplugin
hostNetwork: true
containers:
- name: driver-registrar
image: docker.io/k8scsi/driver-registrar
args:
- "--v=5"
- "--csi-address=$(ADDRESS)"
env:
- name: ADDRESS
value: /plugin/csi.sock
- name: KUBE_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
volumeMounts:
- name: plugin-dir
mountPath: /plugin
- name: cinder
securityContext:
privileged: true
capabilities:
add: ["SYS_ADMIN"]
allowPrivilegeEscalation: true
image: docker.io/k8scsi/cinderplugin
args :
- "--nodeid=$(NODE_ID)"
- "--endpoint=$(CSI_ENDPOINT)"
- "--cloud-config=$(CLOUD_CONFIG)"
env:
- name: NODE_ID
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: CSI_ENDPOINT
value: unix://plugin/csi.sock
- name: CLOUD_CONFIG
value: /etc/config/cloud.conf
imagePullPolicy: "IfNotPresent"
volumeMounts:
- name: plugin-dir
mountPath: /plugin
- name: pods-mount-dir
mountPath: /var/lib/kubelet/pods
mountPropagation: "Bidirectional"
- name: pods-cloud-data
mountPath: /var/lib/cloud/data
readOnly: true
- name: pods-probe-dir
mountPath: /dev
mountPropagation: "HostToContainer"
- name: secret-cinderplugin
mountPath: /etc/config
readOnly: true
volumes:
- name: plugin-dir
hostPath:
path: /var/lib/kubelet/plugins/csi-cinderplugin
type: DirectoryOrCreate
- name: pods-mount-dir
hostPath:
path: /var/lib/kubelet/pods
type: Directory
- name: pods-cloud-data
hostPath:
path: /var/lib/cloud/data
type: Directory
- name: pods-probe-dir
hostPath:
path: /dev
type: Directory
- name: secret-cinderplugin
secret:
secretName: csi-secret-cinderplugin

View File

@ -0,0 +1,35 @@
# This YAML defines all API objects to create RBAC roles for csi node plugin.
apiVersion: v1
kind: ServiceAccount
metadata:
name: csi-nodeplugin
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-nodeplugin
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["volumeattachments"]
verbs: ["get", "list", "watch", "update"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-nodeplugin
subjects:
- kind: ServiceAccount
name: csi-nodeplugin
namespace: default
roleRef:
kind: ClusterRole
name: csi-nodeplugin
apiGroup: rbac.authorization.k8s.io

View File

@ -0,0 +1,71 @@
# This YAML file contains attacher & csi driver API objects,
# which are necessary to run external csi provisioner for cinder.
kind: Service
apiVersion: v1
metadata:
name: csi-provisioner-cinderplugin
labels:
app: csi-provisioner-cinderplugin
spec:
selector:
app: csi-provisioner-cinderplugin
ports:
- name: dummy
port: 12345
---
kind: StatefulSet
apiVersion: apps/v1beta1
metadata:
name: csi-provisioner-cinderplugin
spec:
serviceName: "csi-provisioner-cinderplugin"
replicas: 1
template:
metadata:
labels:
app: csi-provisioner-cinderplugin
spec:
serviceAccount: csi-provisioner
containers:
- name: csi-provisioner
image: docker.io/k8scsi/csi-provisioner
args:
- "--provisioner=csi-cinderplugin"
- "--csi-address=$(ADDRESS)"
env:
- name: ADDRESS
value: /var/lib/csi/sockets/pluginproxy/csi.sock
imagePullPolicy: "IfNotPresent"
volumeMounts:
- name: socket-dir
mountPath: /var/lib/csi/sockets/pluginproxy/
- name: cinder
image: docker.io/k8scsi/cinderplugin
args :
- "--nodeid=$(NODE_ID)"
- "--endpoint=$(CSI_ENDPOINT)"
- "--cloud-config=$(CLOUD_CONFIG)"
env:
- name: NODE_ID
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: CSI_ENDPOINT
value: unix://plugin/csi.sock
- name: CLOUD_CONFIG
value: /etc/config/cloud.conf
imagePullPolicy: "IfNotPresent"
volumeMounts:
- name: socket-dir
mountPath: /plugin
- name: secret-cinderplugin
mountPath: /etc/config
readOnly: true
volumes:
- name: socket-dir
emptyDir:
- name: secret-cinderplugin
secret:
secretName: csi-secret-cinderplugin

View File

@ -0,0 +1,41 @@
# This YAML file contains RBAC API objects,
# which are necessary to run external csi provisioner for cinder.
apiVersion: v1
kind: ServiceAccount
metadata:
name: csi-provisioner
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: external-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["list", "watch", "create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-provisioner-role
subjects:
- kind: ServiceAccount
name: csi-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: external-provisioner-runner
apiGroup: rbac.authorization.k8s.io

View File

@ -0,0 +1,9 @@
# This YAML file contains secret objects,
# which are necessary to run csi cinder plugin.
kind: Secret
apiVersion: v1
metadata:
name: csi-secret-cinderplugin
data:
cloud.conf: W0dsb2JhbF0KdXNlcm5hbWU9dXNlcgpwYXNzd29yZD1wYXNzCmF1dGgtdXJsPWh0dHBzOi8vPGtleXN0b25lX2lwPi9pZGVudGl0eS92Mwp0ZW5hbnQtaWQ9Yzg2OTE2OGE4Mjg4NDdmMzlmN2YwNmVkZDczMDU2MzcKZG9tYWluLWlkPTJhNzNiOGY1OTdjMDQ1NTFhMGZkYzhlOTU1NDRiZThhCg==

View File

@ -0,0 +1,13 @@
# Based on centos
FROM centos:7.4.1708
LABEL maintainers="Kubernetes Authors"
LABEL description="Cinder CSI Plugin"
# Copy cinderplugin from build directory
COPY cinderplugin /cinderplugin
# Install e4fsprogs for format
RUN yum -y install e4fsprogs
# Define default command
ENTRYPOINT ["/cinderplugin"]

View File

@ -0,0 +1,90 @@
/*
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 cinder
import (
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/golang/glog"
"github.com/kubernetes-csi/drivers/pkg/cinder/openstack"
"github.com/kubernetes-csi/drivers/pkg/csi-common"
)
type driver struct {
csiDriver *csicommon.CSIDriver
endpoint string
cloudconfig string
ids *csicommon.DefaultIdentityServer
cs *controllerServer
ns *nodeServer
cap []*csi.VolumeCapability_AccessMode
cscap []*csi.ControllerServiceCapability
}
const (
driverName = "csi-cinderplugin"
)
var (
version = csi.Version{
Minor: 1,
}
)
func GetSupportedVersions() []*csi.Version {
return []*csi.Version{&version}
}
func NewDriver(nodeID, endpoint string, cloudconfig string) *driver {
glog.Infof("Driver: %v version: %v", driverName, csicommon.GetVersionString(&version))
d := &driver{}
d.endpoint = endpoint
d.cloudconfig = cloudconfig
csiDriver := csicommon.NewCSIDriver(driverName, &version, GetSupportedVersions(), nodeID)
csiDriver.AddControllerServiceCapabilities(
[]csi.ControllerServiceCapability_RPC_Type{
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME,
})
csiDriver.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER})
d.csiDriver = csiDriver
return d
}
func NewControllerServer(d *driver) *controllerServer {
return &controllerServer{
DefaultControllerServer: csicommon.NewDefaultControllerServer(d.csiDriver),
}
}
func NewNodeServer(d *driver) *nodeServer {
return &nodeServer{
DefaultNodeServer: csicommon.NewDefaultNodeServer(d.csiDriver),
}
}
func (d *driver) Run() {
openstack.InitOpenStackProvider(d.cloudconfig)
csicommon.RunControllerandNodePublishServer(d.endpoint, d.csiDriver, NewControllerServer(d), NewNodeServer(d))
}

View File

@ -0,0 +1,6 @@
[Global]
username=user
password=pass
auth-url=https://<keystone_ip>/identity/v3
tenant-id=c869168a828847f39f7f06edd7305637
domain-id=2a73b8f597c04551a0fdc8e95544be8a

View File

@ -0,0 +1,44 @@
# This YAML file contains nginx & csi cinder driver objects,
# which are necessary to run nginx with csi cinder driver.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-sc-cinderplugin
provisioner: csi-cinderplugin
parameters:
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: csi-pvc-cinderplugin
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: csi-sc-cinderplugin
---
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: nginx
ports:
- containerPort: 80
protocol: TCP
volumeMounts:
- mountPath: /var/lib/www/html
name: csi-data-cinderplugin
volumes:
- name: csi-data-cinderplugin
persistentVolumeClaim:
claimName: csi-pvc-cinderplugin
readOnly: false

View File

@ -0,0 +1,32 @@
/*
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 cinder
import (
"golang.org/x/net/context"
)
var fakeNodeID = "CSINodeID"
var fakeEndpoint = "tcp://127.0.0.1:10000"
var fakeConfig = "/etc/cloud.conf"
var fakeCtx = context.Background()
var fakeVolName = "CSIVolumeName"
var fakeVolID = "CSIVolumeID"
var fakeVolType = "lvmdriver-1"
var fakeAvailability = "nova"
var fakeDevicePath = "/dev/xxx"
var fakeTargetPath = "/mnt/cinder"

View File

@ -0,0 +1,161 @@
/*
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 mount
import (
"fmt"
"io/ioutil"
"os"
"strings"
"time"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume/util"
utilexec "k8s.io/utils/exec"
"github.com/golang/glog"
)
const (
probeVolumeDuration = 1 * time.Second
probeVolumeTimeout = 60 * time.Second
instanceIDFile = "/var/lib/cloud/data/instance-id"
)
type IMount interface {
ScanForAttach(devicePath string) error
IsLikelyNotMountPointAttach(targetpath string) (bool, error)
FormatAndMount(source string, target string, fstype string, options []string) error
IsLikelyNotMountPointDetach(targetpath string) (bool, error)
UnmountPath(mountPath string) error
GetInstanceID() (string, error)
}
type Mount struct {
}
var MInstance IMount = nil
func GetMountProvider() (IMount, error) {
if MInstance == nil {
MInstance = &Mount{}
}
return MInstance, nil
}
// probeVolume probes volume in compute
func probeVolume() error {
// rescan scsi bus
scsi_path := "/sys/class/scsi_host/"
if dirs, err := ioutil.ReadDir(scsi_path); err == nil {
for _, f := range dirs {
name := scsi_path + f.Name() + "/scan"
data := []byte("- - -")
ioutil.WriteFile(name, data, 0666)
}
}
executor := utilexec.New()
args := []string{"trigger"}
cmd := executor.Command("udevadm", args...)
_, err := cmd.CombinedOutput()
if err != nil {
glog.V(3).Infof("error running udevadm trigger %v\n", err)
return err
}
glog.V(4).Infof("Successfully probed all attachments")
return nil
}
// ScanForAttach
func (m *Mount) ScanForAttach(devicePath string) error {
ticker := time.NewTicker(probeVolumeDuration)
defer ticker.Stop()
timer := time.NewTimer(probeVolumeTimeout)
defer timer.Stop()
for {
select {
case <-ticker.C:
glog.V(5).Infof("Checking Cinder disk %q is attached.", devicePath)
probeVolume()
exists, err := util.PathExists(devicePath)
if exists && err == nil {
return nil
} else {
glog.V(3).Infof("Could not find attached Cinder disk %s", devicePath)
}
case <-timer.C:
return fmt.Errorf("Could not find attached Cinder disk %s. Timeout waiting for mount paths to be created.", devicePath)
}
}
}
// FormatAndMount
func (m *Mount) FormatAndMount(source string, target string, fstype string, options []string) error {
diskMounter := &mount.SafeFormatAndMount{Interface: mount.New(""), Exec: mount.NewOsExec()}
return diskMounter.FormatAndMount(source, target, fstype, options)
}
// IsLikelyNotMountPointAttach
func (m *Mount) IsLikelyNotMountPointAttach(targetpath string) (bool, error) {
notMnt, err := mount.New("").IsLikelyNotMountPoint(targetpath)
if err != nil {
if os.IsNotExist(err) {
err = os.MkdirAll(targetpath, 0750)
if err == nil {
notMnt = true
}
}
}
return notMnt, err
}
// IsLikelyNotMountPointDetach
func (m *Mount) IsLikelyNotMountPointDetach(targetpath string) (bool, error) {
notMnt, err := mount.New("").IsLikelyNotMountPoint(targetpath)
if err != nil {
if os.IsNotExist(err) {
return notMnt, fmt.Errorf("targetpath not found")
} else {
return notMnt, err
}
}
return notMnt, nil
}
// UnmountPath
func (m *Mount) UnmountPath(mountPath string) error {
return util.UnmountPath(mountPath, mount.New(""))
}
// GetInstanceID from file
func (m *Mount) GetInstanceID() (string, error) {
// Try to find instance ID on the local filesystem (created by cloud-init)
idBytes, err := ioutil.ReadFile(instanceIDFile)
if err == nil {
instanceID := string(idBytes)
instanceID = strings.TrimSpace(instanceID)
glog.V(3).Infof("Got instance id from %s: %s", instanceIDFile, instanceID)
if instanceID != "" {
return instanceID, nil
}
}
return "", err
}

View File

@ -0,0 +1,130 @@
/*
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 mount
import mock "github.com/stretchr/testify/mock"
// MountMock is an autogenerated mock type for the IMount type
// ORIGINALLY GENERATED BY mockery with hand edits
type MountMock struct {
mock.Mock
}
// FormatAndMount provides a mock function with given fields: source, target, fstype, options
func (_m *MountMock) FormatAndMount(source string, target string, fstype string, options []string) error {
ret := _m.Called(source, target, fstype, options)
var r0 error
if rf, ok := ret.Get(0).(func(string, string, string, []string) error); ok {
r0 = rf(source, target, fstype, options)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetInstanceID provides a mock function with given fields:
func (_m *MountMock) GetInstanceID() (string, error) {
ret := _m.Called()
var r0 string
if rf, ok := ret.Get(0).(func() string); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// IsLikelyNotMountPointAttach provides a mock function with given fields: targetpath
func (_m *MountMock) IsLikelyNotMountPointAttach(targetpath string) (bool, error) {
ret := _m.Called(targetpath)
var r0 bool
if rf, ok := ret.Get(0).(func(string) bool); ok {
r0 = rf(targetpath)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(targetpath)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// IsLikelyNotMountPointDetach provides a mock function with given fields: targetpath
func (_m *MountMock) IsLikelyNotMountPointDetach(targetpath string) (bool, error) {
ret := _m.Called(targetpath)
var r0 bool
if rf, ok := ret.Get(0).(func(string) bool); ok {
r0 = rf(targetpath)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(targetpath)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ScanForAttach provides a mock function with given fields: devicePath
func (_m *MountMock) ScanForAttach(devicePath string) error {
ret := _m.Called(devicePath)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(devicePath)
} else {
r0 = ret.Error(0)
}
return r0
}
// UnmountPath provides a mock function with given fields: mountPath
func (_m *MountMock) UnmountPath(mountPath string) error {
ret := _m.Called(mountPath)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(mountPath)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@ -0,0 +1,132 @@
/*
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 cinder
import (
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/golang/glog"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/kubernetes-csi/drivers/pkg/cinder/mount"
csicommon "github.com/kubernetes-csi/drivers/pkg/csi-common"
)
type nodeServer struct {
*csicommon.DefaultNodeServer
}
func (ns *nodeServer) GetNodeID(ctx context.Context, req *csi.GetNodeIDRequest) (*csi.GetNodeIDResponse, error) {
// Get Mount Provider
m, err := mount.GetMountProvider()
if err != nil {
glog.V(3).Infof("Failed to GetMountProvider: %v", err)
return nil, err
}
nodeID, err := m.GetInstanceID()
if err != nil {
glog.V(3).Infof("Failed to GetInstanceID: %v", err)
return nil, err
}
if len(nodeID) > 0 {
return &csi.GetNodeIDResponse{
NodeId: nodeID,
}, nil
}
// Using default function
return ns.DefaultNodeServer.GetNodeID(ctx, req)
}
func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
targetPath := req.GetTargetPath()
fsType := req.GetVolumeCapability().GetMount().GetFsType()
devicePath := req.GetPublishVolumeInfo()["DevicePath"]
// Get Mount Provider
m, err := mount.GetMountProvider()
if err != nil {
glog.V(3).Infof("Failed to GetMountProvider: %v", err)
return nil, err
}
// Device Scan
err = m.ScanForAttach(devicePath)
if err != nil {
glog.V(3).Infof("Failed to ScanForAttach: %v", err)
return nil, err
}
// Verify whether mounted
notMnt, err := m.IsLikelyNotMountPointAttach(targetPath)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
// Volume Mount
if notMnt {
// Get Options
var options []string
if req.GetReadonly() {
options = append(options, "ro")
} else {
options = append(options, "rw")
}
mountFlags := req.GetVolumeCapability().GetMount().GetMountFlags()
options = append(options, mountFlags...)
// Mount
err = m.FormatAndMount(devicePath, targetPath, fsType, options)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
}
return &csi.NodePublishVolumeResponse{}, nil
}
func (ns *nodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) {
targetPath := req.GetTargetPath()
// Get Mount Provider
m, err := mount.GetMountProvider()
if err != nil {
glog.V(3).Infof("Failed to GetMountProvider: %v", err)
return nil, err
}
notMnt, err := m.IsLikelyNotMountPointDetach(targetPath)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
if notMnt {
return nil, status.Error(codes.NotFound, "Volume not mounted")
}
err = m.UnmountPath(req.GetTargetPath())
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &csi.NodeUnpublishVolumeResponse{}, nil
}

View File

@ -0,0 +1,142 @@
/*
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 cinder
import (
"testing"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/kubernetes-csi/drivers/pkg/cinder/mount"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
var fakeNs *nodeServer
// Init Node Server
func init() {
if fakeNs == nil {
d := NewDriver(fakeNodeID, fakeEndpoint, fakeConfig)
fakeNs = NewNodeServer(d)
}
}
// Test GetNodeID
func TestGetNodeID(t *testing.T) {
// mock MountMock
mmock := new(mount.MountMock)
// GetInstanceID() (string, error)
mmock.On("GetInstanceID").Return(fakeNodeID, nil)
mount.MInstance = mmock
// Init assert
assert := assert.New(t)
// Expected Result
expectedRes := &csi.GetNodeIDResponse{
NodeId: fakeNodeID,
}
// Fake request
fakeReq := &csi.GetNodeIDRequest{
Version: &version,
}
// Invoke GetNodeID
actualRes, err := fakeNs.GetNodeID(fakeCtx, fakeReq)
if err != nil {
t.Errorf("failed to GetNodeID: %v", err)
}
// Assert
assert.Equal(expectedRes, actualRes)
}
// Test NodePublishVolume
func TestNodePublishVolume(t *testing.T) {
// mock MountMock
mmock := new(mount.MountMock)
// ScanForAttach(devicePath string) error
mmock.On("ScanForAttach", fakeDevicePath).Return(nil)
// IsLikelyNotMountPointAttach(targetpath string) (bool, error)
mmock.On("IsLikelyNotMountPointAttach", fakeTargetPath).Return(true, nil)
// FormatAndMount(source string, target string, fstype string, options []string) error
mmock.On("FormatAndMount", fakeDevicePath, fakeTargetPath, mock.AnythingOfType("string"), []string{"rw"}).Return(nil)
mount.MInstance = mmock
// Init assert
assert := assert.New(t)
// Expected Result
expectedRes := &csi.NodePublishVolumeResponse{}
// Fake request
fakeReq := &csi.NodePublishVolumeRequest{
Version: &version,
VolumeId: fakeVolID,
PublishVolumeInfo: map[string]string{"DevicePath": fakeDevicePath},
TargetPath: fakeTargetPath,
VolumeCapability: nil,
Readonly: false,
}
// Invoke NodePublishVolume
actualRes, err := fakeNs.NodePublishVolume(fakeCtx, fakeReq)
if err != nil {
t.Errorf("failed to NodePublishVolume: %v", err)
}
// Assert
assert.Equal(expectedRes, actualRes)
}
// Test NodeUnpublishVolume
func TestNodeUnpublishVolume(t *testing.T) {
// mock MountMock
mmock := new(mount.MountMock)
// IsLikelyNotMountPointDetach(targetpath string) (bool, error)
mmock.On("IsLikelyNotMountPointDetach", fakeTargetPath).Return(false, nil)
// UnmountPath(mountPath string) error
mmock.On("UnmountPath", fakeTargetPath).Return(nil)
mount.MInstance = mmock
// Init assert
assert := assert.New(t)
// Expected Result
expectedRes := &csi.NodeUnpublishVolumeResponse{}
// Fake request
fakeReq := &csi.NodeUnpublishVolumeRequest{
Version: &version,
VolumeId: fakeVolID,
TargetPath: fakeTargetPath,
}
// Invoke NodeUnpublishVolume
actualRes, err := fakeNs.NodeUnpublishVolume(fakeCtx, fakeReq)
if err != nil {
t.Errorf("failed to NodeUnpublishVolume: %v", err)
}
// Assert
assert.Equal(expectedRes, actualRes)
}

View File

@ -0,0 +1,163 @@
/*
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 openstack
import (
"os"
"github.com/golang/glog"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"gopkg.in/gcfg.v1"
)
type IOpenStack interface {
CreateVolume(name string, size int, vtype, availability string, tags *map[string]string) (string, string, error)
DeleteVolume(volumeID string) error
AttachVolume(instanceID, volumeID string) (string, error)
WaitDiskAttached(instanceID string, volumeID string) error
DetachVolume(instanceID, volumeID string) error
WaitDiskDetached(instanceID string, volumeID string) error
GetAttachmentDiskPath(instanceID, volumeID string) (string, error)
}
type OpenStack struct {
compute *gophercloud.ServiceClient
blockstorage *gophercloud.ServiceClient
}
type Config struct {
Global struct {
AuthUrl string `gcfg:"auth-url"`
Username string
UserId string `gcfg:"user-id"`
Password string
TenantId string `gcfg:"tenant-id"`
TenantName string `gcfg:"tenant-name"`
DomainId string `gcfg:"domain-id"`
DomainName string `gcfg:"domain-name"`
Region string
}
}
func (cfg Config) toAuthOptions() gophercloud.AuthOptions {
return gophercloud.AuthOptions{
IdentityEndpoint: cfg.Global.AuthUrl,
Username: cfg.Global.Username,
UserID: cfg.Global.UserId,
Password: cfg.Global.Password,
TenantID: cfg.Global.TenantId,
TenantName: cfg.Global.TenantName,
DomainID: cfg.Global.DomainId,
DomainName: cfg.Global.DomainName,
// Persistent service, so we need to be able to renew tokens.
AllowReauth: true,
}
}
func GetConfigFromFile(configFilePath string) (gophercloud.AuthOptions, gophercloud.EndpointOpts, error) {
// Get config from file
var authOpts gophercloud.AuthOptions
var epOpts gophercloud.EndpointOpts
config, err := os.Open(configFilePath)
if err != nil {
glog.V(3).Infof("Failed to open OpenStack configuration file: %v", err)
return authOpts, epOpts, err
}
defer config.Close()
// Read configuration
var cfg Config
err = gcfg.ReadInto(&cfg, config)
if err != nil {
glog.V(3).Infof("Failed to read OpenStack configuration file: %v", err)
return authOpts, epOpts, err
}
authOpts = cfg.toAuthOptions()
epOpts = gophercloud.EndpointOpts{
Region: cfg.Global.Region,
}
return authOpts, epOpts, nil
}
func GetConfigFromEnv() (gophercloud.AuthOptions, gophercloud.EndpointOpts, error) {
// Get config from env
authOpts, err := openstack.AuthOptionsFromEnv()
var epOpts gophercloud.EndpointOpts
if err != nil {
glog.V(3).Infof("Failed to read OpenStack configuration from env: %v", err)
return authOpts, epOpts, err
}
epOpts = gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
}
return authOpts, epOpts, nil
}
var OsInstance IOpenStack = nil
var configFile string = "/etc/cloud.conf"
func InitOpenStackProvider(cfg string) {
configFile = cfg
glog.V(2).Infof("InitOpenStackProvider configFile: %s", configFile)
}
func GetOpenStackProvider() (IOpenStack, error) {
if OsInstance == nil {
// Get config from file
authOpts, epOpts, err := GetConfigFromFile(configFile)
if err != nil {
// Get config from env
authOpts, epOpts, err = GetConfigFromEnv()
if err != nil {
return nil, err
}
}
// Authenticate Client
provider, err := openstack.AuthenticatedClient(authOpts)
if err != nil {
return nil, err
}
// Init Nova ServiceClient
computeclient, err := openstack.NewComputeV2(provider, epOpts)
if err != nil {
return nil, err
}
// Init Cinder ServiceClient
blockstorageclient, err := openstack.NewBlockStorageV3(provider, epOpts)
if err != nil {
return nil, err
}
// Init OpenStack
OsInstance = &OpenStack{
compute: computeclient,
blockstorage: blockstorageclient,
}
}
return OsInstance, nil
}

View File

@ -0,0 +1,151 @@
/*
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 openstack
import "github.com/stretchr/testify/mock"
// OpenStackMock is an autogenerated mock type for the IOpenStack type
// ORIGINALLY GENERATED BY mockery with hand edits
type OpenStackMock struct {
mock.Mock
}
// AttachVolume provides a mock function with given fields: instanceID, volumeID
func (_m *OpenStackMock) AttachVolume(instanceID string, volumeID string) (string, error) {
ret := _m.Called(instanceID, volumeID)
var r0 string
if rf, ok := ret.Get(0).(func(string, string) string); ok {
r0 = rf(instanceID, volumeID)
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(instanceID, volumeID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateVolume provides a mock function with given fields: name, size, vtype, availability, tags
func (_m *OpenStackMock) CreateVolume(name string, size int, vtype string, availability string, tags *map[string]string) (string, string, error) {
ret := _m.Called(name, size, vtype, availability, tags)
var r0 string
if rf, ok := ret.Get(0).(func(string, int, string, string, *map[string]string) string); ok {
r0 = rf(name, size, vtype, availability, tags)
} else {
r0 = ret.Get(0).(string)
}
var r1 string
if rf, ok := ret.Get(1).(func(string, int, string, string, *map[string]string) string); ok {
r1 = rf(name, size, vtype, availability, tags)
} else {
r1 = ret.Get(1).(string)
}
var r2 error
if rf, ok := ret.Get(2).(func(string, int, string, string, *map[string]string) error); ok {
r2 = rf(name, size, vtype, availability, tags)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// DeleteVolume provides a mock function with given fields: volumeID
func (_m *OpenStackMock) DeleteVolume(volumeID string) error {
ret := _m.Called(volumeID)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(volumeID)
} else {
r0 = ret.Error(0)
}
return r0
}
// DetachVolume provides a mock function with given fields: instanceID, volumeID
func (_m *OpenStackMock) DetachVolume(instanceID string, volumeID string) error {
ret := _m.Called(instanceID, volumeID)
var r0 error
if rf, ok := ret.Get(0).(func(string, string) error); ok {
r0 = rf(instanceID, volumeID)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetAttachmentDiskPath provides a mock function with given fields: instanceID, volumeID
func (_m *OpenStackMock) GetAttachmentDiskPath(instanceID string, volumeID string) (string, error) {
ret := _m.Called(instanceID, volumeID)
var r0 string
if rf, ok := ret.Get(0).(func(string, string) string); ok {
r0 = rf(instanceID, volumeID)
} else {
r0 = ret.Get(0).(string)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(instanceID, volumeID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// WaitDiskAttached provides a mock function with given fields: instanceID, volumeID
func (_m *OpenStackMock) WaitDiskAttached(instanceID string, volumeID string) error {
ret := _m.Called(instanceID, volumeID)
var r0 error
if rf, ok := ret.Get(0).(func(string, string) error); ok {
r0 = rf(instanceID, volumeID)
} else {
r0 = ret.Error(0)
}
return r0
}
// WaitDiskDetached provides a mock function with given fields: instanceID, volumeID
func (_m *OpenStackMock) WaitDiskDetached(instanceID string, volumeID string) error {
ret := _m.Called(instanceID, volumeID)
var r0 error
if rf, ok := ret.Get(0).(func(string, string) error); ok {
r0 = rf(instanceID, volumeID)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@ -0,0 +1,119 @@
/*
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 openstack
import (
"os"
"testing"
"github.com/gophercloud/gophercloud"
"github.com/stretchr/testify/assert"
)
var fakeFileName = "cloud.conf"
var fakeUserName = "user"
var fakePassword = "pass"
var fakeAuthUrl = "https://169.254.169.254/identity/v3"
var fakeTenantID = "c869168a828847f39f7f06edd7305637"
var fakeDomainID = "2a73b8f597c04551a0fdc8e95544be8a"
var fakeRegion = "RegionOne"
// Test GetConfigFromFile
func TestGetConfigFromFile(t *testing.T) {
// init file
var fakeFileContent = `
[Global]
username=` + fakeUserName + `
password=` + fakePassword + `
auth-url=` + fakeAuthUrl + `
tenant-id=` + fakeTenantID + `
domain-id=` + fakeDomainID + `
region=` + fakeRegion + `
`
f, err := os.Create(fakeFileName)
if err != nil {
t.Errorf("failed to create file: %v", err)
}
_, err = f.WriteString(fakeFileContent)
f.Close()
if err != nil {
t.Errorf("failed to write file: %v", err)
}
defer os.Remove(fakeFileName)
// Init assert
assert := assert.New(t)
expectedAuthOpts := gophercloud.AuthOptions{
IdentityEndpoint: fakeAuthUrl,
Username: fakeUserName,
Password: fakePassword,
TenantID: fakeTenantID,
DomainID: fakeDomainID,
AllowReauth: true,
}
expectedEpOpts := gophercloud.EndpointOpts{
Region: fakeRegion,
}
// Invoke GetConfigFromFile
actualAuthOpts, actualEpOpts, err := GetConfigFromFile(fakeFileName)
if err != nil {
t.Errorf("failed to GetConfigFromFile: %v", err)
}
// Assert
assert.Equal(expectedAuthOpts, actualAuthOpts)
assert.Equal(expectedEpOpts, actualEpOpts)
}
// Test GetConfigFromEnv
func TestGetConfigFromEnv(t *testing.T) {
// init env
os.Setenv("OS_AUTH_URL", fakeAuthUrl)
os.Setenv("OS_USERNAME", fakeUserName)
os.Setenv("OS_PASSWORD", fakePassword)
os.Setenv("OS_TENANT_ID", fakeTenantID)
os.Setenv("OS_DOMAIN_ID", fakeDomainID)
os.Setenv("OS_REGION_NAME", fakeRegion)
// Init assert
assert := assert.New(t)
expectedAuthOpts := gophercloud.AuthOptions{
IdentityEndpoint: fakeAuthUrl,
Username: fakeUserName,
Password: fakePassword,
TenantID: fakeTenantID,
DomainID: fakeDomainID,
}
expectedEpOpts := gophercloud.EndpointOpts{
Region: fakeRegion,
}
// Invoke GetConfigFromEnv
actualAuthOpts, actualEpOpts, err := GetConfigFromEnv()
if err != nil {
t.Errorf("failed to GetConfigFromEnv: %v", err)
}
// Assert
assert.Equal(expectedAuthOpts, actualAuthOpts)
assert.Equal(expectedEpOpts, actualEpOpts)
}

View File

@ -0,0 +1,253 @@
/*
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 openstack
import (
"fmt"
"time"
"github.com/gophercloud/gophercloud/openstack/blockstorage/v3/volumes"
"github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/volumeattach"
"k8s.io/apimachinery/pkg/util/wait"
"github.com/golang/glog"
)
const (
VolumeAvailableStatus = "available"
VolumeInUseStatus = "in-use"
VolumeDeletedStatus = "deleted"
VolumeErrorStatus = "error"
operationFinishInitDelay = 1 * time.Second
operationFinishFactor = 1.1
operationFinishSteps = 10
diskAttachInitDelay = 1 * time.Second
diskAttachFactor = 1.2
diskAttachSteps = 15
diskDetachInitDelay = 1 * time.Second
diskDetachFactor = 1.2
diskDetachSteps = 13
)
type Volume struct {
// ID of the instance, to which this volume is attached. "" if not attached
AttachedServerId string
// Device file path
AttachedDevice string
// Unique identifier for the volume.
ID string
// Human-readable display name for the volume.
Name string
// Current status of the volume.
Status string
// Volume size in GB
Size int
}
// CreateVolume creates a volume of given size
func (os *OpenStack) CreateVolume(name string, size int, vtype, availability string, tags *map[string]string) (string, string, error) {
opts := &volumes.CreateOpts{
Name: name,
Size: size,
VolumeType: vtype,
AvailabilityZone: availability,
}
if tags != nil {
opts.Metadata = *tags
}
vol, err := volumes.Create(os.blockstorage, opts).Extract()
if err != nil {
return "", "", err
}
return vol.ID, vol.AvailabilityZone, nil
}
// DeleteVolume delete a volume
func (os *OpenStack) DeleteVolume(volumeID string) error {
used, err := os.diskIsUsed(volumeID)
if err != nil {
return err
}
if used {
return fmt.Errorf("Cannot delete the volume %q, it's still attached to a node", volumeID)
}
err = volumes.Delete(os.blockstorage, volumeID).ExtractErr()
return err
}
// GetVolume retrieves Volume by its ID.
func (os *OpenStack) GetVolume(volumeID string) (Volume, error) {
vol, err := volumes.Get(os.blockstorage, volumeID).Extract()
if err != nil {
return Volume{}, err
}
volume := Volume{
ID: vol.ID,
Name: vol.Name,
Status: vol.Status,
}
if len(vol.Attachments) > 0 {
volume.AttachedServerId = vol.Attachments[0].ServerID
volume.AttachedDevice = vol.Attachments[0].Device
}
return volume, nil
}
// AttachVolume attaches given cinder volume to the compute
func (os *OpenStack) AttachVolume(instanceID, volumeID string) (string, error) {
volume, err := os.GetVolume(volumeID)
if err != nil {
return "", err
}
if volume.AttachedServerId != "" {
if instanceID == volume.AttachedServerId {
glog.V(4).Infof("Disk %s is already attached to instance %s", volumeID, instanceID)
return volume.ID, nil
}
return "", fmt.Errorf("disk %s is attached to a different instance (%s)", volumeID, volume.AttachedServerId)
}
_, err = volumeattach.Create(os.compute, instanceID, &volumeattach.CreateOpts{
VolumeID: volume.ID,
}).Extract()
if err != nil {
return "", fmt.Errorf("failed to attach %s volume to %s compute: %v", volumeID, instanceID, err)
}
glog.V(2).Infof("Successfully attached %s volume to %s compute", volumeID, instanceID)
return volume.ID, nil
}
// WaitDiskAttached waits for attched
func (os *OpenStack) WaitDiskAttached(instanceID string, volumeID string) error {
backoff := wait.Backoff{
Duration: diskAttachInitDelay,
Factor: diskAttachFactor,
Steps: diskAttachSteps,
}
err := wait.ExponentialBackoff(backoff, func() (bool, error) {
attached, err := os.diskIsAttached(instanceID, volumeID)
if err != nil {
return false, err
}
return attached, nil
})
if err == wait.ErrWaitTimeout {
err = fmt.Errorf("Volume %q failed to be attached within the alloted time", volumeID)
}
return err
}
// DetachVolume detaches given cinder volume from the compute
func (os *OpenStack) DetachVolume(instanceID, volumeID string) error {
volume, err := os.GetVolume(volumeID)
if err != nil {
return err
}
if volume.Status == VolumeAvailableStatus {
glog.V(2).Infof("volume: %s has been detached from compute: %s ", volume.ID, instanceID)
return nil
}
if volume.Status != VolumeInUseStatus {
return fmt.Errorf("can not detach volume %s, its status is %s", volume.Name, volume.Status)
}
if volume.AttachedServerId != instanceID {
return fmt.Errorf("disk: %s has no attachments or is not attached to compute: %s", volume.Name, instanceID)
} else {
err = volumeattach.Delete(os.compute, instanceID, volume.ID).ExtractErr()
if err != nil {
return fmt.Errorf("failed to delete volume %s from compute %s attached %v", volume.ID, instanceID, err)
}
glog.V(2).Infof("Successfully detached volume: %s from compute: %s", volume.ID, instanceID)
}
return nil
}
// WaitDiskDetached waits for detached
func (os *OpenStack) WaitDiskDetached(instanceID string, volumeID string) error {
backoff := wait.Backoff{
Duration: diskDetachInitDelay,
Factor: diskDetachFactor,
Steps: diskDetachSteps,
}
err := wait.ExponentialBackoff(backoff, func() (bool, error) {
attached, err := os.diskIsAttached(instanceID, volumeID)
if err != nil {
return false, err
}
return !attached, nil
})
if err == wait.ErrWaitTimeout {
err = fmt.Errorf("Volume %q failed to detach within the alloted time", volumeID)
}
return err
}
// GetAttachmentDiskPath gets device path of attached volume to the compute
func (os *OpenStack) GetAttachmentDiskPath(instanceID, volumeID string) (string, error) {
volume, err := os.GetVolume(volumeID)
if err != nil {
return "", err
}
if volume.Status != VolumeInUseStatus {
return "", fmt.Errorf("can not get device path of volume %s, its status is %s ", volume.Name, volume.Status)
}
if volume.AttachedServerId != "" {
if instanceID == volume.AttachedServerId {
return volume.AttachedDevice, nil
} else {
return "", fmt.Errorf("disk %q is attached to a different compute: %q, should be detached before proceeding", volumeID, volume.AttachedServerId)
}
}
return "", fmt.Errorf("volume %s has no ServerId", volumeID)
}
// diskIsAttached queries if a volume is attached to a compute instance
func (os *OpenStack) diskIsAttached(instanceID, volumeID string) (bool, error) {
volume, err := os.GetVolume(volumeID)
if err != nil {
return false, err
}
return instanceID == volume.AttachedServerId, nil
}
// diskIsUsed returns true a disk is attached to any node.
func (os *OpenStack) diskIsUsed(volumeID string) (bool, error) {
volume, err := os.GetVolume(volumeID)
if err != nil {
return false, err
}
return volume.AttachedServerId != "", nil
}

View File

@ -0,0 +1,96 @@
/*
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 csicommon
import (
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/golang/glog"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type DefaultControllerServer struct {
Driver *CSIDriver
}
func (cs *DefaultControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
return nil, status.Error(codes.Unimplemented, "")
}
func (cs *DefaultControllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {
return nil, status.Error(codes.Unimplemented, "")
}
func (cs *DefaultControllerServer) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) {
return nil, status.Error(codes.Unimplemented, "")
}
func (cs *DefaultControllerServer) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) {
return nil, status.Error(codes.Unimplemented, "")
}
func (cs *DefaultControllerServer) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) {
glog.V(5).Infof("Using default ValidateVolumeCapabilities")
for _, c := range req.GetVolumeCapabilities() {
found := false
for _, c1 := range cs.Driver.vc {
if c1.GetMode() == c.GetAccessMode().GetMode() {
found = true
}
}
if !found {
return &csi.ValidateVolumeCapabilitiesResponse{
Supported: false,
Message: "Driver doesnot support mode:" + c.GetAccessMode().GetMode().String(),
}, status.Error(codes.InvalidArgument, "Driver doesnot support mode:"+c.GetAccessMode().GetMode().String())
}
// TODO: Ignoring mount & block tyeps for now.
}
return &csi.ValidateVolumeCapabilitiesResponse{
Supported: true,
}, nil
}
func (cs *DefaultControllerServer) ListVolumes(ctx context.Context, req *csi.ListVolumesRequest) (*csi.ListVolumesResponse, error) {
return nil, status.Error(codes.Unimplemented, "")
}
func (cs *DefaultControllerServer) GetCapacity(ctx context.Context, req *csi.GetCapacityRequest) (*csi.GetCapacityResponse, error) {
return nil, status.Error(codes.Unimplemented, "")
}
func (cs *DefaultControllerServer) ControllerProbe(ctx context.Context, req *csi.ControllerProbeRequest) (*csi.ControllerProbeResponse, error) {
glog.V(5).Infof("Using default ControllerProbe")
if err := cs.Driver.ValidateControllerServiceRequest(req.Version, csi.ControllerServiceCapability_RPC_UNKNOWN); err != nil {
return nil, err
}
return &csi.ControllerProbeResponse{}, nil
}
// ControllerGetCapabilities implements the default GRPC callout.
// Default supports all capabilities
func (cs *DefaultControllerServer) ControllerGetCapabilities(ctx context.Context, req *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) {
glog.V(5).Infof("Using default ControllerGetCapabilities")
return &csi.ControllerGetCapabilitiesResponse{
Capabilities: cs.Driver.cap,
}, nil
}

View File

@ -0,0 +1,137 @@
/*
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 csicommon
import (
"github.com/golang/glog"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/container-storage-interface/spec/lib/go/csi"
)
type CSIDriver struct {
name string
nodeID string
version *csi.Version
supVers []*csi.Version
cap []*csi.ControllerServiceCapability
vc []*csi.VolumeCapability_AccessMode
}
// Creates a NewCSIDriver object. Assumes vendor version is equal to driver version &
// does not support optional driver plugin info manifest field. Refer to CSI spec for more details.
func NewCSIDriver(name string, v *csi.Version, supVers []*csi.Version, nodeID string) *CSIDriver {
if name == "" {
glog.Errorf("Driver name missing")
return nil
}
if nodeID == "" {
glog.Errorf("NodeID missing")
return nil
}
if v == nil {
glog.Errorf("Version argument missing")
return nil
}
found := false
for _, sv := range supVers {
if sv.GetMajor() == v.GetMajor() && sv.GetMinor() == v.GetMinor() && sv.GetPatch() == v.GetPatch() {
found = true
}
}
if !found {
supVers = append(supVers, v)
}
driver := CSIDriver{
name: name,
version: v,
supVers: supVers,
nodeID: nodeID,
}
return &driver
}
func (d *CSIDriver) CheckVersion(v *csi.Version) error {
if v == nil {
return status.Error(codes.InvalidArgument, "Version missing")
}
// Assumes always backward compatible
for _, sv := range d.supVers {
if v.Major == sv.Major && v.Minor <= sv.Minor {
return nil
}
}
return status.Error(codes.InvalidArgument, "Unsupported version: "+GetVersionString(v))
}
func (d *CSIDriver) ValidateControllerServiceRequest(v *csi.Version, c csi.ControllerServiceCapability_RPC_Type) error {
if v == nil {
return status.Error(codes.InvalidArgument, "Version not specified")
}
if err := d.CheckVersion(v); err != nil {
return status.Error(codes.InvalidArgument, "Unsupported version")
}
if c == csi.ControllerServiceCapability_RPC_UNKNOWN {
return nil
}
for _, cap := range d.cap {
if c == cap.GetRpc().GetType() {
return nil
}
}
return status.Error(codes.InvalidArgument, "Unsupported version: "+GetVersionString(v))
}
func (d *CSIDriver) AddControllerServiceCapabilities(cl []csi.ControllerServiceCapability_RPC_Type) {
var csc []*csi.ControllerServiceCapability
for _, c := range cl {
glog.Infof("Enabling controller service capability: %v", c.String())
csc = append(csc, NewControllerServiceCapability(c))
}
d.cap = csc
return
}
func (d *CSIDriver) AddVolumeCapabilityAccessModes(vc []csi.VolumeCapability_AccessMode_Mode) []*csi.VolumeCapability_AccessMode {
var vca []*csi.VolumeCapability_AccessMode
for _, c := range vc {
glog.Infof("Enabling volume access mode: %v", c.String())
vca = append(vca, NewVolumeCapabilityAccessMode(c))
}
d.vc = vca
return vca
}
func (d *CSIDriver) GetVolumeCapabilityAccessModes() []*csi.VolumeCapability_AccessMode {
return d.vc
}

View File

@ -0,0 +1,181 @@
/*
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 csicommon
import (
"testing"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const (
fakeDriverName = "fake"
fakeNodeID = "fakeNodeID"
)
var (
fakeVersion = csi.Version{Major: 5, Minor: 2, Patch: 0}
fakeVersionsSupported = []*csi.Version{
&csi.Version{
Major: 4, Minor: 0, Patch: 0,
},
&csi.Version{
Major: 4, Minor: 1, Patch: 0,
},
}
)
func NewFakeDriver() *CSIDriver {
fakeVersion = csi.Version{Major: 5, Minor: 2, Patch: 0}
fakeVersionsSupported = []*csi.Version{
&csi.Version{
Major: 4, Minor: 0, Patch: 0,
},
&csi.Version{
Major: 4, Minor: 1, Patch: 0,
},
}
driver := NewCSIDriver(fakeDriverName, &fakeVersion, fakeVersionsSupported, fakeNodeID)
return driver
}
func TestNewFakeDriver(t *testing.T) {
// Test New fake driver with invalid arguments.
d := NewCSIDriver("", &fakeVersion, fakeVersionsSupported, fakeNodeID)
assert.Nil(t, d)
}
func TestCheckVersion(t *testing.T) {
driver := NewFakeDriver()
// Exact version
v := csi.Version{
Major: 5,
Minor: 1,
Patch: 0,
}
err := driver.CheckVersion(&v)
assert.NoError(t, err)
//Supported version
v = csi.Version{
Major: 4,
Minor: 0,
Patch: 0,
}
err = driver.CheckVersion(&v)
assert.NoError(t, err)
// Unsupported version
v = csi.Version{
Major: 6,
Minor: 0,
Patch: 0,
}
err = driver.CheckVersion(&v)
s, ok := status.FromError(err)
assert.True(t, ok)
assert.Equal(t, s.Code(), codes.InvalidArgument)
// Supported minor version
v = csi.Version{
Major: 5,
Minor: 1,
Patch: 0,
}
err = driver.CheckVersion(&v)
assert.NoError(t, err)
// Unsupported minor version
v = csi.Version{
Major: 5,
Minor: 3,
Patch: 0,
}
err = driver.CheckVersion(&v)
s, ok = status.FromError(err)
assert.True(t, ok)
assert.Equal(t, s.Code(), codes.InvalidArgument)
}
func TestGetVolumeCapabilityAccessModes(t *testing.T) {
d := NewFakeDriver()
// Test no volume access modes.
// REVISIT: Do we need to support any default access modes.
c := d.GetVolumeCapabilityAccessModes()
assert.Zero(t, len(c))
// Test driver with access modes.
d.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER})
modes := d.GetVolumeCapabilityAccessModes()
assert.Equal(t, 1, len(modes))
assert.Equal(t, modes[0].GetMode(), csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER)
}
func TestValidateControllerServiceRequest(t *testing.T) {
d := NewFakeDriver()
v := csi.Version{
Major: 5,
Minor: 0,
Patch: 0,
}
// Valid requests which require no capabilities
err := d.ValidateControllerServiceRequest(&v, csi.ControllerServiceCapability_RPC_UNKNOWN)
assert.NoError(t, err)
// Test controller service publish/unpublish not supported
err = d.ValidateControllerServiceRequest(&v, csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME)
s, ok := status.FromError(err)
assert.True(t, ok)
assert.Equal(t, s.Code(), codes.InvalidArgument)
// Add controller service publish & unpublish request
d.AddControllerServiceCapabilities(
[]csi.ControllerServiceCapability_RPC_Type{
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME,
csi.ControllerServiceCapability_RPC_GET_CAPACITY,
csi.ControllerServiceCapability_RPC_LIST_VOLUMES,
})
// Test controller service publish/unpublish is supported
err = d.ValidateControllerServiceRequest(&v, csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME)
assert.NoError(t, err)
// Test controller service create/delete is supported
err = d.ValidateControllerServiceRequest(&v, csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME)
assert.NoError(t, err)
// Test controller service list volumes is supported
err = d.ValidateControllerServiceRequest(&v, csi.ControllerServiceCapability_RPC_LIST_VOLUMES)
assert.NoError(t, err)
// Test controller service get capacity is supported
err = d.ValidateControllerServiceRequest(&v, csi.ControllerServiceCapability_RPC_GET_CAPACITY)
assert.NoError(t, err)
}

View File

@ -0,0 +1,58 @@
/*
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 csicommon
import (
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/golang/glog"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type DefaultIdentityServer struct {
Driver *CSIDriver
}
//GetSupportedVersions(context.Context, *GetSupportedVersionsRequest) (*GetSupportedVersionsResponse, error)
//GetPluginInfo(context.Context, *GetPluginInfoRequest) (*GetPluginInfoResponse, error)
func (ids *DefaultIdentityServer) GetSupportedVersions(ctx context.Context, req *csi.GetSupportedVersionsRequest) (*csi.GetSupportedVersionsResponse, error) {
glog.V(5).Infof("Using default GetSupportedVersions")
return &csi.GetSupportedVersionsResponse{
SupportedVersions: ids.Driver.supVers,
}, nil
}
func (ids *DefaultIdentityServer) GetPluginInfo(ctx context.Context, req *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) {
glog.V(5).Infof("Using default GetPluginInnfo")
if ids.Driver.name == "" {
return nil, status.Error(codes.Unavailable, "Driver name not configured")
}
err := ids.Driver.CheckVersion(req.GetVersion())
if err != nil {
return nil, err
}
version := GetVersionString(ids.Driver.version)
return &csi.GetPluginInfoResponse{
Name: ids.Driver.name,
VendorVersion: version,
}, nil
}

View File

@ -0,0 +1,68 @@
/*
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 csicommon
import (
"context"
"testing"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestGetSupportedVersions(t *testing.T) {
d := NewFakeDriver()
ids := NewDefaultIdentityServer(d)
req := csi.GetSupportedVersionsRequest{}
// Test Get supported versions are valid.
resp, err := ids.GetSupportedVersions(context.Background(), &req)
assert.NoError(t, err)
for _, fv := range fakeVersionsSupported {
found := false
for _, rv := range resp.GetSupportedVersions() {
if fv.GetMajor() == rv.GetMajor() && fv.GetMinor() == rv.GetMinor() && fv.GetPatch() == rv.GetPatch() {
found = true
}
}
assert.True(t, found)
}
}
func TestGetPluginInfo(t *testing.T) {
d := NewFakeDriver()
ids := NewDefaultIdentityServer(d)
// Test invalid request
req := csi.GetPluginInfoRequest{}
resp, err := ids.GetPluginInfo(context.Background(), &req)
s, ok := status.FromError(err)
assert.True(t, ok)
assert.Equal(t, s.Code(), codes.InvalidArgument)
// Test valid request
req.Version = &fakeVersion
resp, err = ids.GetPluginInfo(context.Background(), &req)
assert.NoError(t, err)
assert.Equal(t, resp.GetName(), fakeDriverName)
}

View File

@ -0,0 +1,80 @@
/*
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 csicommon
import (
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/golang/glog"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type DefaultNodeServer struct {
Driver *CSIDriver
}
func (ns *DefaultNodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
err := ns.Driver.CheckVersion(req.GetVersion())
if err != nil {
return nil, err
}
return nil, status.Error(codes.Unimplemented, "")
}
func (ns *DefaultNodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) {
err := ns.Driver.CheckVersion(req.GetVersion())
if err != nil {
return nil, err
}
return nil, status.Error(codes.Unimplemented, "")
}
func (ns *DefaultNodeServer) GetNodeID(ctx context.Context, req *csi.GetNodeIDRequest) (*csi.GetNodeIDResponse, error) {
glog.V(5).Infof("Using default GetNodeID")
err := ns.Driver.CheckVersion(req.GetVersion())
if err != nil {
return nil, err
}
return &csi.GetNodeIDResponse{
NodeId: ns.Driver.nodeID,
}, nil
}
func (ns *DefaultNodeServer) NodeProbe(ctx context.Context, req *csi.NodeProbeRequest) (*csi.NodeProbeResponse, error) {
glog.V(5).Infof("Using default NodeProbe")
err := ns.Driver.CheckVersion(req.GetVersion())
if err != nil {
return nil, err
}
return &csi.NodeProbeResponse{}, nil
}
func (ns *DefaultNodeServer) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) {
glog.V(5).Infof("Using default NodeGetCapabilities")
err := ns.Driver.CheckVersion(req.GetVersion())
if err != nil {
return nil, err
}
return &csi.NodeGetCapabilitiesResponse{}, nil
}

View File

@ -0,0 +1,122 @@
/*
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 csicommon
import (
"context"
"testing"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func TestGetNodeID(t *testing.T) {
d := NewFakeDriver()
ns := NewDefaultNodeServer(d)
// Test invalid request
req := csi.GetNodeIDRequest{}
_, err := ns.GetNodeID(context.Background(), &req)
s, ok := status.FromError(err)
assert.True(t, ok)
assert.Equal(t, s.Code(), codes.InvalidArgument)
// Test valid request
req.Version = &fakeVersion
resp, err := ns.GetNodeID(context.Background(), &req)
assert.NoError(t, err)
assert.Equal(t, resp.GetNodeId(), fakeNodeID)
}
func TestNodeGetCapabilities(t *testing.T) {
d := NewFakeDriver()
ns := NewDefaultNodeServer(d)
// Test invalid request
req := csi.NodeGetCapabilitiesRequest{}
_, err := ns.NodeGetCapabilities(context.Background(), &req)
s, ok := status.FromError(err)
assert.True(t, ok)
assert.Equal(t, s.Code(), codes.InvalidArgument)
// Test valid request
req.Version = &fakeVersion
_, err = ns.NodeGetCapabilities(context.Background(), &req)
assert.NoError(t, err)
}
func TestNodeProbe(t *testing.T) {
d := NewFakeDriver()
ns := NewDefaultNodeServer(d)
// Test invalid request
req := csi.NodeProbeRequest{}
_, err := ns.NodeProbe(context.Background(), &req)
s, ok := status.FromError(err)
assert.True(t, ok)
assert.Equal(t, s.Code(), codes.InvalidArgument)
// Test valid request
req.Version = &fakeVersion
_, err = ns.NodeProbe(context.Background(), &req)
assert.NoError(t, err)
}
func TestNodePublishVolume(t *testing.T) {
d := NewFakeDriver()
ns := NewDefaultNodeServer(d)
// Test invalid request
req := csi.NodePublishVolumeRequest{}
_, err := ns.NodePublishVolume(context.Background(), &req)
s, ok := status.FromError(err)
assert.True(t, ok)
assert.Equal(t, s.Code(), codes.InvalidArgument)
// Test valid node publish request
req.Version = &fakeVersion
_, err = ns.NodePublishVolume(context.Background(), &req)
s, ok = status.FromError(err)
assert.True(t, ok)
assert.Equal(t, s.Code(), codes.Unimplemented)
}
func TestNodeUnpublishVolume(t *testing.T) {
d := NewFakeDriver()
ns := NewDefaultNodeServer(d)
// Test invalid request
req := csi.NodeUnpublishVolumeRequest{}
_, err := ns.NodeUnpublishVolume(context.Background(), &req)
s, ok := status.FromError(err)
assert.True(t, ok)
assert.Equal(t, s.Code(), codes.InvalidArgument)
// Test valid node publish request
req.Version = &fakeVersion
_, err = ns.NodeUnpublishVolume(context.Background(), &req)
s, ok = status.FromError(err)
assert.True(t, ok)
assert.Equal(t, s.Code(), codes.Unimplemented)
}

View File

@ -0,0 +1,112 @@
/*
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 csicommon
import (
"net"
"os"
"sync"
"github.com/golang/glog"
"google.golang.org/grpc"
"github.com/container-storage-interface/spec/lib/go/csi"
)
// Defines Non blocking GRPC server interfaces
type NonBlockingGRPCServer interface {
// Start services at the endpoint
Start(endpoint string, ids csi.IdentityServer, cs csi.ControllerServer, ns csi.NodeServer)
// Waits for the service to stop
Wait()
// Stops the service gracefully
Stop()
// Stops the service forcefully
ForceStop()
}
func NewNonBlockingGRPCServer() NonBlockingGRPCServer {
return &nonBlockingGRPCServer{}
}
// NonBlocking server
type nonBlockingGRPCServer struct {
wg sync.WaitGroup
server *grpc.Server
}
func (s *nonBlockingGRPCServer) Start(endpoint string, ids csi.IdentityServer, cs csi.ControllerServer, ns csi.NodeServer) {
s.wg.Add(1)
go s.serve(endpoint, ids, cs, ns)
return
}
func (s *nonBlockingGRPCServer) Wait() {
s.wg.Wait()
}
func (s *nonBlockingGRPCServer) Stop() {
s.server.GracefulStop()
}
func (s *nonBlockingGRPCServer) ForceStop() {
s.server.Stop()
}
func (s *nonBlockingGRPCServer) serve(endpoint string, ids csi.IdentityServer, cs csi.ControllerServer, ns csi.NodeServer) {
proto, addr, err := ParseEndpoint(endpoint)
if err != nil {
glog.Fatal(err.Error())
}
if proto == "unix" {
addr = "/" + addr
if err := os.Remove(addr); err != nil && !os.IsNotExist(err) {
glog.Fatalf("Failed to remove %s, error: %s", addr, err.Error())
}
}
listener, err := net.Listen(proto, addr)
if err != nil {
glog.Fatalf("Failed to listen: %v", err)
}
opts := []grpc.ServerOption{
grpc.UnaryInterceptor(logGRPC),
}
server := grpc.NewServer(opts...)
s.server = server
if ids != nil {
csi.RegisterIdentityServer(server, ids)
}
if cs != nil {
csi.RegisterControllerServer(server, cs)
}
if ns != nil {
csi.RegisterNodeServer(server, ns)
}
glog.Infof("Listening for connections on address: %#v", listener.Addr())
server.Serve(listener)
}

View File

@ -0,0 +1,127 @@
/*
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 csicommon
import (
"fmt"
"strings"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/golang/glog"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
func ParseEndpoint(ep string) (string, string, error) {
if strings.HasPrefix(strings.ToLower(ep), "unix://") || strings.HasPrefix(strings.ToLower(ep), "tcp://") {
s := strings.SplitN(ep, "://", 2)
if s[1] != "" {
return s[0], s[1], nil
}
}
return "", "", fmt.Errorf("Invalid endpoint: %v", ep)
}
func GetVersionString(v *csi.Version) string {
return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
}
func GetVersionFromString(v string) (*csi.Version, error) {
var major, minor, patch uint32
n, err := fmt.Sscanf(v, "%d.%d.%d", &major, &minor, &patch)
if err != nil {
return nil, err
}
if n != 3 {
return nil, fmt.Errorf("Invalid format. Specify version in x.y.z format")
}
return &csi.Version{
Major: major,
Minor: minor,
Patch: patch,
}, nil
}
func NewVolumeCapabilityAccessMode(mode csi.VolumeCapability_AccessMode_Mode) *csi.VolumeCapability_AccessMode {
return &csi.VolumeCapability_AccessMode{Mode: mode}
}
func NewDefaultNodeServer(d *CSIDriver) *DefaultNodeServer {
return &DefaultNodeServer{
Driver: d,
}
}
func NewDefaultIdentityServer(d *CSIDriver) *DefaultIdentityServer {
return &DefaultIdentityServer{
Driver: d,
}
}
func NewDefaultControllerServer(d *CSIDriver) *DefaultControllerServer {
return &DefaultControllerServer{
Driver: d,
}
}
func NewControllerServiceCapability(cap csi.ControllerServiceCapability_RPC_Type) *csi.ControllerServiceCapability {
return &csi.ControllerServiceCapability{
Type: &csi.ControllerServiceCapability_Rpc{
Rpc: &csi.ControllerServiceCapability_RPC{
Type: cap,
},
},
}
}
func RunNodePublishServer(endpoint string, d *CSIDriver, ns csi.NodeServer) {
ids := NewDefaultIdentityServer(d)
s := NewNonBlockingGRPCServer()
s.Start(endpoint, ids, nil, ns)
s.Wait()
}
func RunControllerPublishServer(endpoint string, d *CSIDriver, cs csi.ControllerServer) {
ids := NewDefaultIdentityServer(d)
s := NewNonBlockingGRPCServer()
s.Start(endpoint, ids, cs, nil)
s.Wait()
}
func RunControllerandNodePublishServer(endpoint string, d *CSIDriver, cs csi.ControllerServer, ns csi.NodeServer) {
ids := NewDefaultIdentityServer(d)
s := NewNonBlockingGRPCServer()
s.Start(endpoint, ids, cs, ns)
s.Wait()
}
func logGRPC(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
glog.V(3).Infof("GRPC call: %s", info.FullMethod)
glog.V(5).Infof("GRPC request: %+v", req)
resp, err := handler(ctx, req)
if err != nil {
glog.Errorf("GRPC error: %v", err)
} else {
glog.V(5).Infof("GRPC response: %+v", resp)
}
return resp, err
}

View File

@ -0,0 +1,106 @@
/*
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 csicommon
import (
"testing"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/stretchr/testify/assert"
)
func TestGetVersionFromString(t *testing.T) {
//Invalid version
_, err := GetVersionFromString("")
assert.NotNil(t, err)
v, err := GetVersionFromString("1.2.3")
assert.NoError(t, err)
assert.Equal(t, v.GetMajor(), uint32(1))
assert.Equal(t, v.GetMinor(), uint32(2))
assert.Equal(t, v.GetPatch(), uint32(3))
// Invalid version
_, err = GetVersionFromString("1.2")
assert.NotNil(t, err)
}
func TestGetVersionString(t *testing.T) {
v := &csi.Version{
Major: 1,
Minor: 0,
Patch: 0,
}
//Invalid version
vStr := GetVersionString(v)
assert.Equal(t, vStr, "1.0.0")
}
func TestParseEndpoint(t *testing.T) {
//Valid unix domain socket endpoint
sockType, addr, err := ParseEndpoint("unix://fake.sock")
assert.NoError(t, err)
assert.Equal(t, sockType, "unix")
assert.Equal(t, addr, "fake.sock")
sockType, addr, err = ParseEndpoint("unix:///fakedir/fakedir/fake.sock")
assert.NoError(t, err)
assert.Equal(t, sockType, "unix")
assert.Equal(t, addr, "/fakedir/fakedir/fake.sock")
//Valid unix domain socket with uppercase
sockType, addr, err = ParseEndpoint("UNIX://fake.sock")
assert.NoError(t, err)
assert.Equal(t, sockType, "UNIX")
assert.Equal(t, addr, "fake.sock")
//Valid TCP endpoint with ip
sockType, addr, err = ParseEndpoint("tcp://127.0.0.1:80")
assert.NoError(t, err)
assert.Equal(t, sockType, "tcp")
assert.Equal(t, addr, "127.0.0.1:80")
//Valid TCP endpoint with uppercase
sockType, addr, err = ParseEndpoint("TCP://127.0.0.1:80")
assert.NoError(t, err)
assert.Equal(t, sockType, "TCP")
assert.Equal(t, addr, "127.0.0.1:80")
//Valid TCP endpoint with hostname
sockType, addr, err = ParseEndpoint("tcp://fakehost:80")
assert.NoError(t, err)
assert.Equal(t, sockType, "tcp")
assert.Equal(t, addr, "fakehost:80")
_, _, err = ParseEndpoint("unix:/fake.sock/")
assert.NotNil(t, err)
_, _, err = ParseEndpoint("fake.sock")
assert.NotNil(t, err)
_, _, err = ParseEndpoint("unix://")
assert.NotNil(t, err)
_, _, err = ParseEndpoint("://")
assert.NotNil(t, err)
_, _, err = ParseEndpoint("")
assert.NotNil(t, err)
}

View File

@ -0,0 +1,42 @@
# CSI to Flexvolume adapter
## Usage:
### Start Flexvolume adapter for simple nfs flexvolume driver
```
$ sudo ../_output/flexadapter --endpoint tcp://127.0.0.1:10000 --drivername simplenfs --driverpath ./examples/simple-nfs-flexdriver/nfs --nodeid CSINode
```
### Test using csc
Get ```csc``` tool from https://github.com/chakri-nelluri/gocsi/tree/master/csc
#### Get plugin info
```
$ csc identity plugininfo --endpoint tcp://127.0.0.1:10000
"simplenfs" "0.1.0"
```
### Get supported versions
```
$ csc identity supportedversions --endpoint tcp://127.0.0.1:10000
0.1.0
```
#### NodePublish a volume
```
$ csc node publishvolume --endpoint tcp://127.0.0.1:10000 --target-path /mnt/nfs --attrib server=a.b.c.d --attrib share=nfs_share nfstestvol
nfstestvol
```
#### NodeUnpublish a volume
```
$ csc node unpublishvolume --endpoint tcp://127.0.0.1:10000 --target-path /mnt/nfs nfstestvol
nfstestvol
```
#### Get NodeID
```
$ csc node getid --endpoint tcp://127.0.0.1:10000
CSINode
```

View File

@ -0,0 +1,95 @@
/*
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 flexadapter
import (
"fmt"
"github.com/container-storage-interface/spec/lib/go/csi"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/kubernetes-csi/drivers/pkg/csi-common"
)
const (
deviceID = "deviceID"
)
type controllerServer struct {
*csicommon.DefaultControllerServer
}
func GetVersionString(ver *csi.Version) string {
return fmt.Sprintf("%d.%d.%d", ver.Major, ver.Minor, ver.Patch)
}
func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) {
if err := cs.Driver.ValidateControllerServiceRequest(req.Version, csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME); err != nil {
return nil, err
}
call := GetFlexAdapter().flexDriver.NewDriverCall(attachCmd)
fsType := req.GetVolumeCapability().GetMount().FsType
call.AppendSpec(req.GetVolumeId(), fsType, req.GetReadonly(), req.GetVolumeAttributes())
call.Append(req.GetNodeId())
callStatus, err := call.Run()
if isCmdNotSupportedErr(err) {
return nil, status.Error(codes.Unimplemented, "")
} else if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
pvInfo := map[string]string{}
pvInfo[deviceID] = callStatus.DevicePath
return &csi.ControllerPublishVolumeResponse{
PublishVolumeInfo: pvInfo,
}, nil
}
func (cs *controllerServer) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) {
if err := cs.Driver.ValidateControllerServiceRequest(req.Version, csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME); err != nil {
return nil, err
}
call := GetFlexAdapter().flexDriver.NewDriverCall(detachCmd)
call.Append(req.GetVolumeId())
call.Append(req.GetNodeId())
_, err := call.Run()
if isCmdNotSupportedErr(err) {
return nil, status.Error(codes.Unimplemented, "")
} else if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &csi.ControllerUnpublishVolumeResponse{}, nil
}
func (cs *controllerServer) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) {
for _, cap := range req.VolumeCapabilities {
if cap.GetAccessMode().GetMode() != csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER {
return &csi.ValidateVolumeCapabilitiesResponse{Supported: false, Message: ""}, nil
}
}
return &csi.ValidateVolumeCapabilitiesResponse{Supported: true, Message: ""}, nil
}

View File

@ -0,0 +1,239 @@
/*
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 flexadapter
import (
"encoding/json"
"errors"
"fmt"
"os/exec"
"time"
"github.com/golang/glog"
)
const (
// Driver calls
initCmd = "init"
getVolumeNameCmd = "getvolumename"
isAttached = "isattached"
attachCmd = "attach"
waitForAttachCmd = "waitforattach"
mountDeviceCmd = "mountdevice"
detachCmd = "detach"
waitForDetachCmd = "waitfordetach"
unmountDeviceCmd = "unmountdevice"
mountCmd = "mount"
unmountCmd = "unmount"
// Option keys
optionFSType = "kubernetes.io/fsType"
optionReadWrite = "kubernetes.io/readwrite"
optionKeySecret = "kubernetes.io/secret"
optionFSGroup = "kubernetes.io/fsGroup"
optionMountsDir = "kubernetes.io/mountsDir"
optionPVorVolumeName = "kubernetes.io/pvOrVolumeName"
optionKeyPodName = "kubernetes.io/pod.name"
optionKeyPodNamespace = "kubernetes.io/pod.namespace"
optionKeyPodUID = "kubernetes.io/pod.uid"
optionKeyServiceAccountName = "kubernetes.io/serviceAccount.name"
)
const (
// StatusSuccess represents the successful completion of command.
StatusSuccess = "Success"
// StatusNotSupported represents that the command is not supported.
StatusNotSupported = "Not supported"
)
var (
TimeoutError = fmt.Errorf("Timeout")
)
// DriverCall implements the basic contract between FlexVolume and its driver.
// The caller is responsible for providing the required args.
type DriverCall struct {
driver *flexVolumeDriver
Command string
Timeout time.Duration
args []string
}
func (d *flexVolumeDriver) NewDriverCall(command string) *DriverCall {
return d.NewDriverCallWithTimeout(command, 0)
}
func (d *flexVolumeDriver) NewDriverCallWithTimeout(command string, timeout time.Duration) *DriverCall {
return &DriverCall{
driver: d,
Command: command,
Timeout: timeout,
args: []string{command},
}
}
func (dc *DriverCall) Append(arg string) {
dc.args = append(dc.args, arg)
}
func (dc *DriverCall) AppendSpec(volumeID, fsType string, readOnly bool, volumeAttributes map[string]string) error {
optionsForDriver := NewOptionsForDriver(volumeID, fsType, readOnly, volumeAttributes)
jsonBytes, err := json.Marshal(optionsForDriver)
if err != nil {
return fmt.Errorf("Failed to marshal spec, error: %s", err.Error())
}
dc.Append(string(jsonBytes))
return nil
}
func (dc *DriverCall) Run() (*DriverStatus, error) {
if dc.driver.isUnsupported(dc.Command) {
return nil, errors.New(StatusNotSupported)
}
execPath := dc.driver.getExecutable()
cmd := exec.Command(execPath, dc.args...)
timeout := false
if dc.Timeout > 0 {
timer := time.AfterFunc(dc.Timeout, func() {
timeout = true
//TODO: cmd.Stop()
})
defer timer.Stop()
}
output, execErr := cmd.CombinedOutput()
if execErr != nil {
if timeout {
return nil, TimeoutError
}
_, err := handleCmdResponse(dc.Command, output)
if err == nil {
glog.Errorf("FlexVolume: driver bug: %s: exec error (%s) but no error in response.", execPath, execErr)
return nil, execErr
}
if isCmdNotSupportedErr(err) {
dc.driver.unsupported(dc.Command)
} else {
glog.Warningf("FlexVolume: driver call failed: executable: %s, args: %s, error: %s, output: %q", execPath, dc.args, execErr.Error(), output)
}
return nil, err
}
status, err := handleCmdResponse(dc.Command, output)
if err != nil {
if isCmdNotSupportedErr(err) {
dc.driver.unsupported(dc.Command)
}
return nil, err
}
return status, nil
}
// OptionsForDriver represents the spec given to the driver.
type OptionsForDriver map[string]string
func NewOptionsForDriver(volumeID, fsType string, readOnly bool, volumeAttributes map[string]string) OptionsForDriver {
options := map[string]string{}
if readOnly {
options[optionReadWrite] = "ro"
} else {
options[optionReadWrite] = "rw"
}
options[optionFSType] = fsType
options[optionPVorVolumeName] = volumeID
for key, value := range volumeAttributes {
options[key] = value
}
return OptionsForDriver(options)
}
// DriverStatus represents the return value of the driver callout.
type DriverStatus struct {
// Status of the callout. One of "Success", "Failure" or "Not supported".
Status string `json:"status"`
// Reason for success/failure.
Message string `json:"message,omitempty"`
// Path to the device attached. This field is valid only for attach calls.
// ie: /dev/sdx
DevicePath string `json:"device,omitempty"`
// Cluster wide unique name of the volume.
VolumeName string `json:"volumeName,omitempty"`
// Represents volume is attached on the node
Attached bool `json:"attached,omitempty"`
// Returns capabilities of the driver.
// By default we assume all the capabilities are supported.
// If the plugin does not support a capability, it can return false for that capability.
Capabilities *DriverCapabilities `json:",omitempty"`
}
type DriverCapabilities struct {
Attach bool `json:"attach"`
SELinuxRelabel bool `json:"selinuxRelabel"`
}
func defaultCapabilities() *DriverCapabilities {
return &DriverCapabilities{
Attach: true,
SELinuxRelabel: true,
}
}
// isCmdNotSupportedErr checks if the error corresponds to command not supported by
// driver.
func isCmdNotSupportedErr(err error) bool {
if err != nil && err.Error() == StatusNotSupported {
return true
}
return false
}
// handleCmdResponse processes the command output and returns the appropriate
// error code or message.
func handleCmdResponse(cmd string, output []byte) (*DriverStatus, error) {
status := DriverStatus{
Capabilities: defaultCapabilities(),
}
if err := json.Unmarshal(output, &status); err != nil {
glog.Errorf("Failed to unmarshal output for command: %s, output: %q, error: %s", cmd, string(output), err.Error())
return nil, err
} else if status.Status == StatusNotSupported {
glog.V(5).Infof("%s command is not supported by the driver", cmd)
return nil, errors.New(status.Status)
} else if status.Status != StatusSuccess {
errMsg := fmt.Sprintf("%s command failed, status: %s, reason: %s", cmd, status.Status, status.Message)
glog.Errorf(errMsg)
return nil, fmt.Errorf("%s", errMsg)
}
return &status, nil
}

View File

@ -0,0 +1,25 @@
# CSI-Flex NFS driver
# Requirements
The folllowing feature gates and runtime config have to be enabled to deploy the driver
```
FEATURE_GATES=CSIPersistentVolume=true,MountPropagation=true
RUNTIME_CONFIG="storage.k8s.io/v1alpha1=true"
```
Mountprogpation requries support for privileged containers. So, make sure privileged containers are enabled in the cluster.
## Example local-up-cluster.sh
```ALLOW_PRIVILEGED=true FEATURE_GATES=CSIPersistentVolume=true,MountPropagation=true RUNTIME_CONFIG="storage.k8s.io/v1alpha1=true" LOG_LEVEL=5 hack/local-up-cluster.sh```
# Deploy in Kubernetes
```kubectl -f deploy/kubernetes create```
# Example Nginx application
``` kubectl -f examples/kubernetes/nginx.yaml create```

View File

@ -0,0 +1,107 @@
# This YAML file contains all API objects that are necessary to run external
# CSI attacher for nfs flex adapter
#
# In production, this needs to be in separate files, e.g. service account and
# role and role binding needs to be created once, while stateful set may
# require some tuning.
#
# In addition, mock CSI driver is hardcoded as the CSI driver.
apiVersion: v1
kind: ServiceAccount
metadata:
name: csi-attacher
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: external-attacher-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: ["storage.k8s.io"]
resources: ["volumeattachments"]
verbs: ["get", "list", "watch", "update"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-attacher-role
subjects:
- kind: ServiceAccount
name: csi-attacher
namespace: default
roleRef:
kind: ClusterRole
name: external-attacher-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Service
apiVersion: v1
metadata:
name: csi-attacher-simplenfs-flexdriver
labels:
app: csi-attacher-simplenfs-flexdriver
spec:
selector:
app: csi-attacher-simplenfs-flexdriver
ports:
- name: dummy
port: 12345
---
kind: StatefulSet
apiVersion: apps/v1beta1
metadata:
name: csi-attacher-simplenfs-flexdriver
spec:
serviceName: "csi-attacher"
replicas: 1
template:
metadata:
labels:
app: csi-attacher-simplenfs-flexdriver
spec:
serviceAccount: csi-attacher
containers:
- name: csi-attacher
image: docker.io/k8scsi/csi-attacher
args:
- "--v=5"
- "--csi-address=$(ADDRESS)"
env:
- name: ADDRESS
value: /var/lib/csi/sockets/pluginproxy/csi.sock
imagePullPolicy: "IfNotPresent"
volumeMounts:
- name: socket-dir
mountPath: /var/lib/csi/sockets/pluginproxy/
- name: simplenfs
image: docker.io/k8scsi/csi-simplenfs-flexdriver:v0.1
args :
- "--nodeid=$(NODE_ID)"
- "--drivername=csi-simplenfs-flexdriver"
- "--endpoint=$(CSI_ENDPOINT)"
env:
- name: NODE_ID
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: CSI_ENDPOINT
value: unix://plugin/csi.sock
imagePullPolicy: "IfNotPresent"
volumeMounts:
- name: socket-dir
mountPath: /plugin
volumes:
- name: socket-dir
emptyDir:

View File

@ -0,0 +1,34 @@
# This YAML defines all API objects to create RBAC roles for CSI node plugin
apiVersion: v1
kind: ServiceAccount
metadata:
name: csi-nodeplugin
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-nodeplugin
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["volumeattachments"]
verbs: ["get", "list", "watch", "update"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-nodeplugin
subjects:
- kind: ServiceAccount
name: csi-nodeplugin
namespace: default
roleRef:
kind: ClusterRole
name: csi-nodeplugin
apiGroup: rbac.authorization.k8s.io

View File

@ -0,0 +1,65 @@
kind: DaemonSet
apiVersion: apps/v1beta2
metadata:
name: csi-nodeplugin-simplenfs
spec:
selector:
matchLabels:
app: csi-nodeplugin-simplenfs
template:
metadata:
labels:
app: csi-nodeplugin-simplenfs
spec:
serviceAccount: csi-nodeplugin
hostNetwork: true
containers:
- name: driver-registrar
image: docker.io/k8scsi/driver-registrar
args:
- "--v=5"
- "--csi-address=$(ADDRESS)"
env:
- name: ADDRESS
value: /plugin/csi.sock
- name: KUBE_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
volumeMounts:
- name: plugin-dir
mountPath: /plugin
- name: simplenfs
securityContext:
privileged: true
capabilities:
add: ["SYS_ADMIN"]
allowPrivilegeEscalation: true
image: docker.io/k8scsi/csi-simplenfs-flexdriver:v0.1
args :
- "--nodeid=$(NODE_ID)"
- "--drivername=csi-simplenfs-flexdriver"
- "--endpoint=$(CSI_ENDPOINT)"
env:
- name: NODE_ID
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: CSI_ENDPOINT
value: unix://plugin/csi.sock
imagePullPolicy: "IfNotPresent"
volumeMounts:
- name: plugin-dir
mountPath: /plugin
- name: pods-mount-dir
mountPath: /var/lib/kubelet/pods
mountPropagation: "Bidirectional"
volumes:
- name: plugin-dir
hostPath:
path: /var/lib/kubelet/plugins/csi-simplenfs-flexdriver
type: Directory
- name: pods-mount-dir
hostPath:
path: /var/lib/kubelet/pods
type: Directory

View File

@ -0,0 +1,10 @@
FROM centos:7.4.1708
# Copy flexadapter from build _output directory
COPY flexadapter /flexadapter
# Copy nfs from driver directory
COPY nfs /drivers/nfs
RUN yum -y install nfs-utils && yum -y install epel-release && yum -y install jq && yum clean all
ENTRYPOINT ["/flexadapter", "--driverpath=/drivers/nfs"]

View File

@ -0,0 +1,113 @@
#!/bin/bash
# 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.
# Notes:
# - Please install "jq" package before using this driver.
usage() {
err "Invalid usage. Usage: "
err "\t$0 init"
err "\t$0 mount <mount dir> <json params>"
err "\t$0 unmount <mount dir>"
exit 1
}
err() {
echo -ne $* 1>&2
}
log() {
echo -ne $* >&1
}
ismounted() {
MOUNT=`findmnt -n ${MNTPATH} 2>/dev/null | cut -d' ' -f1`
if [ "${MOUNT}" == "${MNTPATH}" ]; then
echo "1"
else
echo "0"
fi
}
domount() {
MNTPATH=$1
NFS_SERVER=$(echo $2 | jq -r '.server')
SHARE=$(echo $2 | jq -r '.share')
if [ $(ismounted) -eq 1 ] ; then
log '{"status": "Success"}'
exit 0
fi
mkdir -p ${MNTPATH} &> /dev/null
mount -t nfs ${NFS_SERVER}:/${SHARE} ${MNTPATH} &> /dev/null
if [ $? -ne 0 ]; then
err "{ \"status\": \"Failure\", \"message\": \"Failed to mount ${NFS_SERVER}:${SHARE} at ${MNTPATH}\"}"
exit 1
fi
log '{"status": "Success"}'
exit 0
}
unmount() {
MNTPATH=$1
if [ $(ismounted) -eq 0 ] ; then
log '{"status": "Success"}'
exit 0
fi
umount ${MNTPATH} &> /dev/null
if [ $? -ne 0 ]; then
err "{ \"status\": \"Failed\", \"message\": \"Failed to unmount volume at ${MNTPATH}\"}"
exit 1
fi
log '{"status": "Success"}'
exit 0
}
op=$1
if ! command -v jq >/dev/null 2>&1; then
err "{ \"status\": \"Failure\", \"message\": \"'jq' binary not found. Please install jq package before using this driver\"}"
exit 1
fi
if [ "$op" = "init" ]; then
log '{"status": "Success", "capabilities": {"attach": false}}'
exit 0
fi
if [ $# -lt 2 ]; then
usage
fi
shift
case "$op" in
mount)
domount $*
;;
unmount)
unmount $*
;;
*)
log '{"status": "Not supported"}'
exit 0
esac
exit 1

View File

@ -0,0 +1,52 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: data
labels:
name: data
annotations:
csi.volume.kubernetes.io/volume-attributes: '{"server": "10.10.10.10", "share": "my_export"}'
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 100Gi
csi:
driver: csi-simplenfs-flexdriver
volumeHandle: data-id
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
selector:
matchExpressions:
- key: name
operator: In
values: ["data"]
---
apiVersion: v1
kind: Pod
metadata:
name: data
spec:
containers:
- image: maersk/nginx
imagePullPolicy: Always
name: nginx
ports:
- containerPort: 80
protocol: TCP
volumeMounts:
- mountPath: /var/www
name: data
volumes:
- name: data
persistentVolumeClaim:
claimName: data

View File

@ -0,0 +1,97 @@
/*
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 flexadapter
import (
"os"
"sync"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/golang/glog"
"github.com/kubernetes-csi/drivers/pkg/csi-common"
)
type flexAdapter struct {
driver *csicommon.CSIDriver
flexDriver *flexVolumeDriver
ns *nodeServer
cs *controllerServer
cap []*csi.VolumeCapability_AccessMode
cscap []*csi.ControllerServiceCapability
}
var (
adapter *flexAdapter
runOnce sync.Once
version = csi.Version{
Minor: 1,
}
)
func GetSupportedVersions() []*csi.Version {
return []*csi.Version{&version}
}
func GetFlexAdapter() *flexAdapter {
runOnce.Do(func() {
adapter = &flexAdapter{}
})
return adapter
}
func NewControllerServer(d *csicommon.CSIDriver) *controllerServer {
return &controllerServer{
DefaultControllerServer: csicommon.NewDefaultControllerServer(d),
}
}
func NewNodeServer(d *csicommon.CSIDriver, f *flexVolumeDriver) *nodeServer {
return &nodeServer{
flexDriver: f,
DefaultNodeServer: csicommon.NewDefaultNodeServer(d),
}
}
func (f *flexAdapter) Run(driverName, driverPath, nodeID, endpoint string) {
var err error
glog.Infof("Driver: %v version: %v", driverName, GetVersionString(&version))
// Create flex volume driver
adapter.flexDriver, err = NewFlexVolumeDriver(driverName, driverPath)
if err != nil {
glog.Errorf("Failed to initialize flex volume driver, error: %v", err.Error())
os.Exit(1)
}
// Initialize default library driver
adapter.driver = csicommon.NewCSIDriver(driverName, &version, GetSupportedVersions(), nodeID)
if adapter.flexDriver.capabilities.Attach {
adapter.driver.AddControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME})
}
adapter.driver.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER})
// Create GRPC servers
f.ns = NewNodeServer(adapter.driver, adapter.flexDriver)
f.cs = NewControllerServer(adapter.driver)
csicommon.RunControllerandNodePublishServer(endpoint, adapter.driver, f.cs, f.ns)
}

View File

@ -0,0 +1,73 @@
/*
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 flexadapter
import (
"sync"
)
type flexVolumeDriver struct {
sync.Mutex
driverName string
execPath string
unsupportedCommands []string
capabilities DriverCapabilities
}
// Returns true iff the given command is known to be unsupported.
func (d *flexVolumeDriver) isUnsupported(command string) bool {
d.Lock()
defer d.Unlock()
for _, unsupportedCommand := range d.unsupportedCommands {
if command == unsupportedCommand {
return true
}
}
return false
}
func (d *flexVolumeDriver) getExecutable() string {
return d.execPath
}
// Mark the given commands as unsupported.
func (d *flexVolumeDriver) unsupported(commands ...string) {
d.Lock()
defer d.Unlock()
d.unsupportedCommands = append(d.unsupportedCommands, commands...)
}
func NewFlexVolumeDriver(driverName, driverPath string) (*flexVolumeDriver, error) {
flexDriver := &flexVolumeDriver{
driverName: driverName,
execPath: driverPath,
}
adapter.flexDriver = flexDriver
// Initialize the plugin and probe the capabilities
call := flexDriver.NewDriverCall(initCmd)
ds, err := call.Run()
if err != nil {
return nil, err
}
flexDriver.capabilities = *ds.Capabilities
return flexDriver, nil
}

View File

@ -0,0 +1,113 @@
/*
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 flexadapter
import (
"os"
"github.com/container-storage-interface/spec/lib/go/csi"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume/util"
"github.com/kubernetes-csi/drivers/pkg/csi-common"
)
type nodeServer struct {
flexDriver *flexVolumeDriver
*csicommon.DefaultNodeServer
}
func mountDevice(devicePath, targetPath, fsType string, readOnly bool, mountOptions []string) error {
var options []string
if readOnly {
options = append(options, "ro")
} else {
options = append(options, "rw")
}
options = append(options, mountOptions...)
diskMounter := &mount.SafeFormatAndMount{Interface: mount.New(""), Exec: mount.NewOsExec()}
return diskMounter.FormatAndMount(deviceID, targetPath, fsType, options)
}
func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
targetPath := req.GetTargetPath()
fsType := req.GetVolumeCapability().GetMount().GetFsType()
notMnt, err := mount.New("").IsLikelyNotMountPoint(targetPath)
if err != nil {
if os.IsNotExist(err) {
if err := os.MkdirAll(targetPath, 0750); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
notMnt = true
} else {
return nil, status.Error(codes.Internal, err.Error())
}
}
if !notMnt {
return &csi.NodePublishVolumeResponse{}, nil
}
call := GetFlexAdapter().flexDriver.NewDriverCall(mountCmd)
call.Append(req.GetTargetPath())
if req.GetPublishVolumeInfo() != nil {
call.Append(req.GetPublishVolumeInfo()[deviceID])
}
call.AppendSpec(req.GetVolumeId(), fsType, req.GetReadonly(), req.GetVolumeAttributes())
_, err = call.Run()
if isCmdNotSupportedErr(err) {
mountFlags := req.GetVolumeCapability().GetMount().GetMountFlags()
err := mountDevice(req.VolumeAttributes[deviceID], targetPath, fsType, req.GetReadonly(), mountFlags)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
} else if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &csi.NodePublishVolumeResponse{}, nil
}
func unmountDevice(path string) error {
return util.UnmountPath(path, mount.New(""))
}
func (ns *nodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) {
call := GetFlexAdapter().flexDriver.NewDriverCall(unmountCmd)
call.Append(req.GetTargetPath())
_, err := call.Run()
if isCmdNotSupportedErr(err) {
err := unmountDevice(req.GetTargetPath())
return nil, status.Error(codes.Internal, err.Error())
} else if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &csi.NodeUnpublishVolumeResponse{}, nil
}

View File

@ -0,0 +1,84 @@
/*
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 hostpath
import (
"fmt"
"os"
"github.com/golang/glog"
"github.com/pborman/uuid"
"golang.org/x/net/context"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/kubernetes-csi/drivers/pkg/csi-common"
)
const (
deviceID = "deviceID"
provisionRoot = "/tmp/"
)
type controllerServer struct {
*csicommon.DefaultControllerServer
}
func GetVersionString(ver *csi.Version) string {
return fmt.Sprintf("%d.%d.%d", ver.Major, ver.Minor, ver.Patch)
}
func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
if err := cs.Driver.ValidateControllerServiceRequest(req.Version, csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME); err != nil {
glog.V(3).Infof("invalid create volume req: %v", req)
return nil, err
}
volumeId := uuid.NewUUID().String()
path := provisionRoot + volumeId
err := os.MkdirAll(path, 0777)
if err != nil {
glog.V(3).Infof("failed to create volume: %v", err)
return nil, err
}
glog.V(4).Infof("create volume %s", path)
return &csi.CreateVolumeResponse{
VolumeInfo: &csi.VolumeInfo{
Id: volumeId,
},
}, nil
}
func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {
if err := cs.Driver.ValidateControllerServiceRequest(req.Version, csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME); err != nil {
glog.V(3).Infof("invalid delete volume req: %v", req)
return nil, err
}
volumeId := req.VolumeId
glog.V(4).Infof("deleting volume %s", volumeId)
path := provisionRoot + volumeId
os.RemoveAll(path)
return &csi.DeleteVolumeResponse{}, nil
}
func (cs *controllerServer) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) {
for _, cap := range req.VolumeCapabilities {
if cap.GetAccessMode().GetMode() != csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER {
return &csi.ValidateVolumeCapabilitiesResponse{Supported: false, Message: ""}, nil
}
}
return &csi.ValidateVolumeCapabilitiesResponse{Supported: true, Message: ""}, nil
}

View File

@ -0,0 +1,6 @@
FROM alpine
LABEL maintainers="Kubernetes Authors"
LABEL description="HostPath CSI Plugin"
COPY hostpathplugin /hostpathplugin
ENTRYPOINT ["/hostpathplugin"]

View File

@ -0,0 +1,13 @@
FROM golang:alpine
LABEL maintainers="Kubernetes Authors"
LABEL description="HostPath CSI Plugin"
RUN apk add --no-cache git make wget
RUN wget https://github.com/golang/dep/releases/download/v0.3.2/dep-linux-amd64 && \
chmod +x dep-linux-amd64 && \
mv dep-linux-amd64 /usr/bin/dep
RUN go get -d github.com/kubernetes-csi/drivers/app/hostpathplugin
RUN cd /go/src/github.com/kubernetes-csi/drivers && \
dep ensure && \
make hostpath && \
cp _output/hostpathplugin /hostpathplugin

View File

@ -0,0 +1,10 @@
#!/bin/sh
PROG=hostpathplugin
docker build --rm -f Dockerfile.builder -t ${PROG}:builder .
docker run --rm --privileged -v $PWD:/host ${PROG}:builder cp /${PROG} /host/${PROG}
sudo chown $USER ${PROG}
docker build --rm -t docker.io/k8scsi/${PROG} .
docker rmi ${PROG}:builder
rm -f ${PROG}

View File

@ -0,0 +1,86 @@
/*
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 hostpath
import (
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/golang/glog"
"github.com/kubernetes-csi/drivers/pkg/csi-common"
)
type hostPath struct {
driver *csicommon.CSIDriver
ids *identityServer
ns *nodeServer
cs *controllerServer
cap []*csi.VolumeCapability_AccessMode
cscap []*csi.ControllerServiceCapability
}
var (
hostPathDriver *hostPath
version = csi.Version{
Minor: 1,
}
)
func GetSupportedVersions() []*csi.Version {
return []*csi.Version{&version}
}
func GetHostPathDriver() *hostPath {
return &hostPath{}
}
func NewIdentityServer(d *csicommon.CSIDriver) *identityServer {
return &identityServer{
DefaultIdentityServer: csicommon.NewDefaultIdentityServer(d),
}
}
func NewControllerServer(d *csicommon.CSIDriver) *controllerServer {
return &controllerServer{
DefaultControllerServer: csicommon.NewDefaultControllerServer(d),
}
}
func NewNodeServer(d *csicommon.CSIDriver) *nodeServer {
return &nodeServer{
DefaultNodeServer: csicommon.NewDefaultNodeServer(d),
}
}
func (hp *hostPath) Run(driverName, nodeID, endpoint string) {
glog.Infof("Driver: %v version: %v", driverName, GetVersionString(&version))
// Initialize default library driver
hp.driver = csicommon.NewCSIDriver(driverName, &version, GetSupportedVersions(), nodeID)
hp.driver.AddControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME})
hp.driver.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER})
// Create GRPC servers
hp.ids = NewIdentityServer(hp.driver)
hp.ns = NewNodeServer(hp.driver)
hp.cs = NewControllerServer(hp.driver)
s := csicommon.NewNonBlockingGRPCServer()
s.Start(endpoint, hp.ids, hp.cs, hp.ns)
s.Wait()
}

View File

@ -0,0 +1,25 @@
/*
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 hostpath
import (
"github.com/kubernetes-csi/drivers/pkg/csi-common"
)
type identityServer struct {
*csicommon.DefaultIdentityServer
}

View File

@ -0,0 +1,85 @@
/*
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 hostpath
import (
"os"
"github.com/golang/glog"
"golang.org/x/net/context"
"github.com/container-storage-interface/spec/lib/go/csi"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/kubernetes/pkg/util/mount"
"github.com/kubernetes-csi/drivers/pkg/csi-common"
)
type nodeServer struct {
*csicommon.DefaultNodeServer
}
func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
targetPath := req.GetTargetPath()
notMnt, err := mount.New("").IsLikelyNotMountPoint(targetPath)
if err != nil {
if os.IsNotExist(err) {
if err = os.MkdirAll(targetPath, 0750); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
notMnt = true
} else {
return nil, status.Error(codes.Internal, err.Error())
}
}
if !notMnt {
return &csi.NodePublishVolumeResponse{}, nil
}
fsType := req.GetVolumeCapability().GetMount().GetFsType()
deviceId := ""
if req.GetPublishVolumeInfo() != nil {
deviceId = req.GetPublishVolumeInfo()[deviceID]
}
readOnly := req.GetReadonly()
volumeId := req.GetVolumeId()
attrib := req.GetVolumeAttributes()
mountFlags := req.GetVolumeCapability().GetMount().GetMountFlags()
glog.V(4).Infof("target %v\nfstype %v\ndevice %v\nreadonly %v\nattributes %v\n mountflags %v\n",
targetPath, fsType, deviceId, readOnly, volumeId, attrib, mountFlags)
options := []string{"bind"}
if readOnly {
options = append(options, "ro")
}
mounter := mount.New("")
path := provisionRoot + volumeId
if err := mounter.Mount(path, targetPath, "", options); err != nil {
return nil, err
}
return &csi.NodePublishVolumeResponse{}, nil
}
func (ns *nodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) {
return &csi.NodeUnpublishVolumeResponse{}, nil
}

View File

@ -0,0 +1,43 @@
# CSI ISCSI driver
## Usage:
### Start ISCSI driver
```
$ sudo ../_output/iscsidriver --endpoint tcp://127.0.0.1:10000 --nodeid CSINode
```
### Test using csc
Get ```csc``` tool from https://github.com/chakri-nelluri/gocsi/tree/master/csc
#### Get plugin info
```
$ csc identity plugin-info --endpoint tcp://127.0.0.1:10000
"ISCSI" "0.1.0"
```
### Get supported versions
```
$ csc identity supported-versions --endpoint tcp://127.0.0.1:10000
0.1.0
```
#### NodePublish a volume
```
$ export ISCSI_TARGET="iSCSI Target Server IP (Ex: 10.10.10.10)"
$ export IQN="Target IQN"
$ csc node publish --endpoint tcp://127.0.0.1:10000 --target-path /mnt/iscsi --attrib targetPortal=$ISCSI_TARGET --attrib iqn=$IQN --attrib lun=<lun-id> iscsitestvol
iscsitestvol
```
#### NodeUnpublish a volume
```
$ csc node unpublish --endpoint tcp://127.0.0.1:10000 --target-path /mnt/iscsi iscsitestvol
iscsitestvol
```
#### Get NodeID
```
$ csc node get-id --endpoint tcp://127.0.0.1:10000
CSINode
```

View File

@ -0,0 +1,74 @@
/*
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 iscsi
import (
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/golang/glog"
"github.com/kubernetes-csi/drivers/pkg/csi-common"
)
type driver struct {
csiDriver *csicommon.CSIDriver
endpoint string
ids *csicommon.DefaultIdentityServer
ns *nodeServer
cap []*csi.VolumeCapability_AccessMode
cscap []*csi.ControllerServiceCapability
}
const (
driverName = "ISCSI"
)
var (
version = csi.Version{
Minor: 1,
}
)
func GetSupportedVersions() []*csi.Version {
return []*csi.Version{&version}
}
func NewDriver(nodeID, endpoint string) *driver {
glog.Infof("Driver: %v version: %v", driverName, csicommon.GetVersionString(&version))
d := &driver{}
d.endpoint = endpoint
csiDriver := csicommon.NewCSIDriver(driverName, &version, GetSupportedVersions(), nodeID)
csiDriver.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER})
d.csiDriver = csiDriver
return d
}
func NewNodeServer(d *driver) *nodeServer {
return &nodeServer{
DefaultNodeServer: csicommon.NewDefaultNodeServer(d.csiDriver),
}
}
func (d *driver) Run() {
csicommon.RunNodePublishServer(d.endpoint, d.csiDriver, NewNodeServer(d))
}

View File

@ -0,0 +1,148 @@
/*
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 iscsi
import (
"encoding/json"
"fmt"
"strings"
"github.com/container-storage-interface/spec/lib/go/csi"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume/util"
)
func getISCSIInfo(req *csi.NodePublishVolumeRequest) (*iscsiDisk, error) {
volName := req.GetVolumeId()
tp := req.GetVolumeAttributes()["targetPortal"]
iqn := req.GetVolumeAttributes()["iqn"]
lun := req.GetVolumeAttributes()["lun"]
if tp == "" || iqn == "" || lun == "" {
return nil, fmt.Errorf("iSCSI target information is missing")
}
portalList := req.GetVolumeAttributes()["portals"]
secretParams := req.GetVolumeAttributes()["secret"]
secret := parseSecret(secretParams)
portal := portalMounter(tp)
var bkportal []string
bkportal = append(bkportal, portal)
portals := []string{}
if err := json.Unmarshal([]byte(portalList), &portals); err != nil {
return nil, err
}
for _, portal := range portals {
bkportal = append(bkportal, portalMounter(string(portal)))
}
iface := req.GetVolumeAttributes()["iscsiInterface"]
initiatorName := req.GetVolumeAttributes()["initiatorName"]
chapDiscovery := false
if req.GetVolumeAttributes()["discoveryCHAPAuth"] == "true" {
chapDiscovery = true
}
chapSession := false
if req.GetVolumeAttributes()["sessionCHAPAuth"] == "true" {
chapSession = true
}
return &iscsiDisk{
VolName: volName,
Portals: bkportal,
Iqn: iqn,
lun: lun,
Iface: iface,
chap_discovery: chapDiscovery,
chap_session: chapSession,
secret: secret,
InitiatorName: initiatorName}, nil
}
func getISCSIDiskMounter(iscsiInfo *iscsiDisk, req *csi.NodePublishVolumeRequest) *iscsiDiskMounter {
readOnly := req.GetReadonly()
fsType := req.GetVolumeCapability().GetMount().GetFsType()
mountOptions := req.GetVolumeCapability().GetMount().GetMountFlags()
return &iscsiDiskMounter{
iscsiDisk: iscsiInfo,
fsType: fsType,
readOnly: readOnly,
mountOptions: mountOptions,
mounter: &mount.SafeFormatAndMount{Interface: mount.New(""), Exec: mount.NewOsExec()},
exec: mount.NewOsExec(),
targetPath: req.GetTargetPath(),
deviceUtil: util.NewDeviceHandler(util.NewIOHandler()),
}
}
func getISCSIDiskUnmounter(req *csi.NodeUnpublishVolumeRequest) *iscsiDiskUnmounter {
return &iscsiDiskUnmounter{
iscsiDisk: &iscsiDisk{
VolName: req.GetVolumeId(),
},
mounter: mount.New(""),
exec: mount.NewOsExec(),
}
}
func portalMounter(portal string) string {
if !strings.Contains(portal, ":") {
portal = portal + ":3260"
}
return portal
}
func parseSecret(secretParams string) map[string]string {
var secret map[string]string
if err := json.Unmarshal([]byte(secretParams), &secret); err != nil {
return nil
}
return secret
}
type iscsiDisk struct {
Portals []string
Iqn string
lun string
Iface string
chap_discovery bool
chap_session bool
secret map[string]string
InitiatorName string
VolName string
}
type iscsiDiskMounter struct {
*iscsiDisk
readOnly bool
fsType string
mountOptions []string
mounter *mount.SafeFormatAndMount
exec mount.Exec
deviceUtil util.DeviceUtil
targetPath string
}
type iscsiDiskUnmounter struct {
*iscsiDisk
mounter mount.Interface
exec mount.Exec
}

View File

@ -0,0 +1,478 @@
/*
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 iscsi
import (
"encoding/json"
"fmt"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/util/mount"
volumeutil "k8s.io/kubernetes/pkg/volume/util"
)
var (
chap_st = []string{
"discovery.sendtargets.auth.username",
"discovery.sendtargets.auth.password",
"discovery.sendtargets.auth.username_in",
"discovery.sendtargets.auth.password_in"}
chap_sess = []string{
"node.session.auth.username",
"node.session.auth.password",
"node.session.auth.username_in",
"node.session.auth.password_in"}
ifaceTransportNameRe = regexp.MustCompile(`iface.transport_name = (.*)\n`)
)
func updateISCSIDiscoverydb(b iscsiDiskMounter, tp string) error {
if !b.chap_discovery {
return nil
}
out, err := b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "update", "-n", "discovery.sendtargets.auth.authmethod", "-v", "CHAP")
if err != nil {
return fmt.Errorf("iscsi: failed to update discoverydb with CHAP, output: %v", string(out))
}
for _, k := range chap_st {
v := b.secret[k]
if len(v) > 0 {
out, err := b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "update", "-n", k, "-v", v)
if err != nil {
return fmt.Errorf("iscsi: failed to update discoverydb key %q with value %q error: %v", k, v, string(out))
}
}
}
return nil
}
func updateISCSINode(b iscsiDiskMounter, tp string) error {
if !b.chap_session {
return nil
}
out, err := b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-I", b.Iface, "-o", "update", "-n", "node.session.auth.authmethod", "-v", "CHAP")
if err != nil {
return fmt.Errorf("iscsi: failed to update node with CHAP, output: %v", string(out))
}
for _, k := range chap_sess {
v := b.secret[k]
if len(v) > 0 {
out, err := b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-I", b.Iface, "-o", "update", "-n", k, "-v", v)
if err != nil {
return fmt.Errorf("iscsi: failed to update node session key %q with value %q error: %v", k, v, string(out))
}
}
}
return nil
}
// stat a path, if not exists, retry maxRetries times
// when iscsi transports other than default are used, use glob instead as pci id of device is unknown
type StatFunc func(string) (os.FileInfo, error)
type GlobFunc func(string) ([]string, error)
func waitForPathToExist(devicePath *string, maxRetries int, deviceTransport string) bool {
// This makes unit testing a lot easier
return waitForPathToExistInternal(devicePath, maxRetries, deviceTransport, os.Stat, filepath.Glob)
}
func waitForPathToExistInternal(devicePath *string, maxRetries int, deviceTransport string, osStat StatFunc, filepathGlob GlobFunc) bool {
if devicePath == nil {
return false
}
for i := 0; i < maxRetries; i++ {
var err error
if deviceTransport == "tcp" {
_, err = osStat(*devicePath)
} else {
fpath, _ := filepathGlob(*devicePath)
if fpath == nil {
err = os.ErrNotExist
} else {
// There might be a case that fpath contains multiple device paths if
// multiple PCI devices connect to same iscsi target. We handle this
// case at subsequent logic. Pick up only first path here.
*devicePath = fpath[0]
}
}
if err == nil {
return true
}
if !os.IsNotExist(err) {
return false
}
if i == maxRetries-1 {
break
}
time.Sleep(time.Second)
}
return false
}
type ISCSIUtil struct{}
func (util *ISCSIUtil) persistISCSI(conf iscsiDisk, mnt string) error {
file := path.Join(mnt, conf.VolName+".json")
fp, err := os.Create(file)
if err != nil {
return fmt.Errorf("iscsi: create %s err %s", file, err)
}
defer fp.Close()
encoder := json.NewEncoder(fp)
if err = encoder.Encode(conf); err != nil {
return fmt.Errorf("iscsi: encode err: %v.", err)
}
return nil
}
func (util *ISCSIUtil) loadISCSI(conf *iscsiDisk, mnt string) error {
// NOTE: The iscsi config json is not deleted after logging out from target portals.
file := path.Join(mnt, conf.VolName+".json")
fp, err := os.Open(file)
if err != nil {
return fmt.Errorf("iscsi: open %s err %s", file, err)
}
defer fp.Close()
decoder := json.NewDecoder(fp)
if err = decoder.Decode(conf); err != nil {
return fmt.Errorf("iscsi: decode err: %v.", err)
}
return nil
}
func (util *ISCSIUtil) AttachDisk(b iscsiDiskMounter) (string, error) {
var devicePath string
var devicePaths []string
var iscsiTransport string
var lastErr error
out, err := b.exec.Run("iscsiadm", "-m", "iface", "-I", b.Iface, "-o", "show")
if err != nil {
glog.Errorf("iscsi: could not read iface %s error: %s", b.Iface, string(out))
return "", err
}
iscsiTransport = extractTransportname(string(out))
bkpPortal := b.Portals
// create new iface and copy parameters from pre-configured iface to the created iface
if b.InitiatorName != "" {
// new iface name is <target portal>:<volume name>
newIface := bkpPortal[0] + ":" + b.VolName
err = cloneIface(b, newIface)
if err != nil {
glog.Errorf("iscsi: failed to clone iface: %s error: %v", b.Iface, err)
return "", err
}
// update iface name
b.Iface = newIface
}
for _, tp := range bkpPortal {
// Rescan sessions to discover newly mapped LUNs. Do not specify the interface when rescanning
// to avoid establishing additional sessions to the same target.
out, err := b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-R")
if err != nil {
glog.Errorf("iscsi: failed to rescan session with error: %s (%v)", string(out), err)
}
if iscsiTransport == "" {
glog.Errorf("iscsi: could not find transport name in iface %s", b.Iface)
return "", fmt.Errorf("Could not parse iface file for %s", b.Iface)
}
if iscsiTransport == "tcp" {
devicePath = strings.Join([]string{"/dev/disk/by-path/ip", tp, "iscsi", b.Iqn, "lun", b.lun}, "-")
} else {
devicePath = strings.Join([]string{"/dev/disk/by-path/pci", "*", "ip", tp, "iscsi", b.Iqn, "lun", b.lun}, "-")
}
if exist := waitForPathToExist(&devicePath, 1, iscsiTransport); exist {
glog.V(4).Infof("iscsi: devicepath (%s) exists", devicePath)
devicePaths = append(devicePaths, devicePath)
continue
}
// build discoverydb and discover iscsi target
b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "new")
// update discoverydb with CHAP secret
err = updateISCSIDiscoverydb(b, tp)
if err != nil {
lastErr = fmt.Errorf("iscsi: failed to update discoverydb to portal %s error: %v", tp, err)
continue
}
out, err = b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "--discover")
if err != nil {
// delete discoverydb record
b.exec.Run("iscsiadm", "-m", "discoverydb", "-t", "sendtargets", "-p", tp, "-I", b.Iface, "-o", "delete")
lastErr = fmt.Errorf("iscsi: failed to sendtargets to portal %s output: %s, err %v", tp, string(out), err)
continue
}
err = updateISCSINode(b, tp)
if err != nil {
// failure to update node db is rare. But deleting record will likely impact those who already start using it.
lastErr = fmt.Errorf("iscsi: failed to update iscsi node to portal %s error: %v", tp, err)
continue
}
// login to iscsi target
out, err = b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-T", b.Iqn, "-I", b.Iface, "--login")
if err != nil {
// delete the node record from database
b.exec.Run("iscsiadm", "-m", "node", "-p", tp, "-I", b.Iface, "-T", b.Iqn, "-o", "delete")
lastErr = fmt.Errorf("iscsi: failed to attach disk: Error: %s (%v)", string(out), err)
continue
}
if exist := waitForPathToExist(&devicePath, 10, iscsiTransport); !exist {
glog.Errorf("Could not attach disk: Timeout after 10s")
// update last error
lastErr = fmt.Errorf("Could not attach disk: Timeout after 10s")
continue
} else {
devicePaths = append(devicePaths, devicePath)
}
}
if len(devicePaths) == 0 {
// delete cloned iface
b.exec.Run("iscsiadm", "-m", "iface", "-I", b.Iface, "-o", "delete")
glog.Errorf("iscsi: failed to get any path for iscsi disk, last err seen:\n%v", lastErr)
return "", fmt.Errorf("failed to get any path for iscsi disk, last err seen:\n%v", lastErr)
}
if lastErr != nil {
glog.Errorf("iscsi: last error occurred during iscsi init:\n%v", lastErr)
}
// Make sure we use a valid devicepath to find mpio device.
devicePath = devicePaths[0]
// Mount device
mntPath := path.Join(b.targetPath, b.VolName)
notMnt, err := b.mounter.IsLikelyNotMountPoint(mntPath)
if err != nil && !os.IsNotExist(err) {
return "", fmt.Errorf("Heuristic determination of mount point failed:%v", err)
}
if !notMnt {
glog.Infof("iscsi: %s already mounted", mntPath)
return "", nil
}
if err := os.MkdirAll(mntPath, 0750); err != nil {
glog.Errorf("iscsi: failed to mkdir %s, error", mntPath)
return "", err
}
// Persist iscsi disk config to json file for DetachDisk path
if err := util.persistISCSI(*(b.iscsiDisk), b.targetPath); err != nil {
glog.Errorf("iscsi: failed to save iscsi config with error: %v", err)
return "", err
}
for _, path := range devicePaths {
// There shouldnt be any empty device paths. However adding this check
// for safer side to avoid the possibility of an empty entry.
if path == "" {
continue
}
// check if the dev is using mpio and if so mount it via the dm-XX device
if mappedDevicePath := b.deviceUtil.FindMultipathDeviceForDevice(path); mappedDevicePath != "" {
devicePath = mappedDevicePath
break
}
}
var options []string
if b.readOnly {
options = append(options, "ro")
} else {
options = append(options, "rw")
}
options = append(options, b.mountOptions...)
err = b.mounter.FormatAndMount(devicePath, mntPath, b.fsType, options)
if err != nil {
glog.Errorf("iscsi: failed to mount iscsi volume %s [%s] to %s, error %v", devicePath, b.fsType, mntPath, err)
}
return devicePath, err
}
func (util *ISCSIUtil) DetachDisk(c iscsiDiskUnmounter, targetPath string) error {
mntPath := path.Join(targetPath, c.VolName)
_, cnt, err := mount.GetDeviceNameFromMount(c.mounter, mntPath)
if err != nil {
glog.Errorf("iscsi detach disk: failed to get device from mnt: %s\nError: %v", mntPath, err)
return err
}
if pathExists, pathErr := volumeutil.PathExists(mntPath); pathErr != nil {
return fmt.Errorf("Error checking if path exists: %v", pathErr)
} else if !pathExists {
glog.Warningf("Warning: Unmount skipped because path does not exist: %v", mntPath)
return nil
}
if err = c.mounter.Unmount(mntPath); err != nil {
glog.Errorf("iscsi detach disk: failed to unmount: %s\nError: %v", mntPath, err)
return err
}
cnt--
if cnt != 0 {
return nil
}
var bkpPortal []string
var volName, iqn, iface, initiatorName string
found := true
// load iscsi disk config from json file
if err := util.loadISCSI(c.iscsiDisk, targetPath); err == nil {
bkpPortal, iqn, iface, volName = c.iscsiDisk.Portals, c.iscsiDisk.Iqn, c.iscsiDisk.Iface, c.iscsiDisk.VolName
initiatorName = c.iscsiDisk.InitiatorName
} else {
glog.Errorf("iscsi detach disk: failed to get iscsi config from path %s Error: %v", targetPath, err)
return err
}
portals := removeDuplicate(bkpPortal)
if len(portals) == 0 {
return fmt.Errorf("iscsi detach disk: failed to detach iscsi disk. Couldn't get connected portals from configurations.")
}
for _, portal := range portals {
logoutArgs := []string{"-m", "node", "-p", portal, "-T", iqn, "--logout"}
deleteArgs := []string{"-m", "node", "-p", portal, "-T", iqn, "-o", "delete"}
if found {
logoutArgs = append(logoutArgs, []string{"-I", iface}...)
deleteArgs = append(deleteArgs, []string{"-I", iface}...)
}
glog.Infof("iscsi: log out target %s iqn %s iface %s", portal, iqn, iface)
out, err := c.exec.Run("iscsiadm", logoutArgs...)
if err != nil {
glog.Errorf("iscsi: failed to detach disk Error: %s", string(out))
}
// Delete the node record
glog.Infof("iscsi: delete node record target %s iqn %s", portal, iqn)
out, err = c.exec.Run("iscsiadm", deleteArgs...)
if err != nil {
glog.Errorf("iscsi: failed to delete node record Error: %s", string(out))
}
}
// Delete the iface after all sessions have logged out
// If the iface is not created via iscsi plugin, skip to delete
if initiatorName != "" && found && iface == (portals[0]+":"+volName) {
deleteArgs := []string{"-m", "iface", "-I", iface, "-o", "delete"}
out, err := c.exec.Run("iscsiadm", deleteArgs...)
if err != nil {
glog.Errorf("iscsi: failed to delete iface Error: %s", string(out))
}
}
if err := os.RemoveAll(targetPath); err != nil {
glog.Errorf("iscsi: failed to remove mount path Error: %v", err)
return err
}
return nil
}
func extractTransportname(ifaceOutput string) (iscsiTransport string) {
rexOutput := ifaceTransportNameRe.FindStringSubmatch(ifaceOutput)
if rexOutput == nil {
return ""
}
iscsiTransport = rexOutput[1]
// While iface.transport_name is a required parameter, handle it being unspecified anyways
if iscsiTransport == "<empty>" {
iscsiTransport = "tcp"
}
return iscsiTransport
}
// Remove duplicates or string
func removeDuplicate(s []string) []string {
m := map[string]bool{}
for _, v := range s {
if v != "" && !m[v] {
s[len(m)] = v
m[v] = true
}
}
s = s[:len(m)]
return s
}
func parseIscsiadmShow(output string) (map[string]string, error) {
params := make(map[string]string)
slice := strings.Split(output, "\n")
for _, line := range slice {
if !strings.HasPrefix(line, "iface.") || strings.Contains(line, "<empty>") {
continue
}
iface := strings.Fields(line)
if len(iface) != 3 || iface[1] != "=" {
return nil, fmt.Errorf("Error: invalid iface setting: %v", iface)
}
// iscsi_ifacename is immutable once the iface is created
if iface[0] == "iface.iscsi_ifacename" {
continue
}
params[iface[0]] = iface[2]
}
return params, nil
}
func cloneIface(b iscsiDiskMounter, newIface string) error {
var lastErr error
// get pre-configured iface records
out, err := b.exec.Run("iscsiadm", "-m", "iface", "-I", b.Iface, "-o", "show")
if err != nil {
lastErr = fmt.Errorf("iscsi: failed to show iface records: %s (%v)", string(out), err)
return lastErr
}
// parse obtained records
params, err := parseIscsiadmShow(string(out))
if err != nil {
lastErr = fmt.Errorf("iscsi: failed to parse iface records: %s (%v)", string(out), err)
return lastErr
}
// update initiatorname
params["iface.initiatorname"] = b.InitiatorName
// create new iface
out, err = b.exec.Run("iscsiadm", "-m", "iface", "-I", newIface, "-o", "new")
if err != nil {
lastErr = fmt.Errorf("iscsi: failed to create new iface: %s (%v)", string(out), err)
return lastErr
}
// update new iface records
for key, val := range params {
_, err = b.exec.Run("iscsiadm", "-m", "iface", "-I", newIface, "-o", "update", "-n", key, "-v", val)
if err != nil {
b.exec.Run("iscsiadm", "-m", "iface", "-I", newIface, "-o", "delete")
lastErr = fmt.Errorf("iscsi: failed to update iface records: %s (%v). iface(%s) will be used", string(out), err, b.Iface)
break
}
}
return lastErr
}

View File

@ -0,0 +1,59 @@
/*
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 iscsi
import (
"github.com/container-storage-interface/spec/lib/go/csi"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/kubernetes-csi/drivers/pkg/csi-common"
)
type nodeServer struct {
*csicommon.DefaultNodeServer
}
func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
iscsiInfo, err := getISCSIInfo(req)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
diskMounter := getISCSIDiskMounter(iscsiInfo, req)
util := &ISCSIUtil{}
_, err = util.AttachDisk(*diskMounter)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &csi.NodePublishVolumeResponse{}, nil
}
func (ns *nodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) {
diskUnmounter := getISCSIDiskUnmounter(req)
targetPath := req.GetTargetPath()
iscsiutil := &ISCSIUtil{}
err := iscsiutil.DetachDisk(*diskUnmounter, targetPath)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &csi.NodeUnpublishVolumeResponse{}, nil
}

View File

@ -0,0 +1,70 @@
# CSI NFS driver
## Kubernetes
### Requirements
The folllowing feature gates and runtime config have to be enabled to deploy the driver
```
FEATURE_GATES=CSIPersistentVolume=true,MountPropagation=true
RUNTIME_CONFIG="storage.k8s.io/v1alpha1=true"
```
Mountprogpation requries support for privileged containers. So, make sure privileged containers are enabled in the cluster.
### Example local-up-cluster.sh
```ALLOW_PRIVILEGED=true FEATURE_GATES=CSIPersistentVolume=true,MountPropagation=true RUNTIME_CONFIG="storage.k8s.io/v1alpha1=true" LOG_LEVEL=5 hack/local-up-cluster.sh```
### Deploy
```kubectl -f deploy/kubernetes create```
### Example Nginx application
Please update the NFS Server & share information in nginx.yaml file.
```kubectl -f examples/kubernetes/nginx.yaml create```
## Using CSC tool
### Start NFS driver
```
$ sudo ../../_output/flexadapter --endpoint tcp://127.0.0.1:10000 --drivername simplenfs --driverpath ./examples/simplenfs-flexdriver/driver/nfs --nodeid CSINode -v=3
```
## Test
Get ```csc``` tool from https://github.com/thecodeteam/gocsi/tree/master/csc
#### Get plugin info
```
$ csc identity plugininfo --endpoint tcp://127.0.0.1:10000
"NFS" "0.1.0"
```
### Get supported versions
```
$ csc identity supportedversions --endpoint tcp://127.0.0.1:10000
0.1.0
```
#### NodePublish a volume
```
$ export NFS_SERVER="Your Server IP (Ex: 10.10.10.10)"
$ export NFS_SHARE="Your NFS share"
$ csc node publishvolume --endpoint tcp://127.0.0.1:10000 --target-path /mnt/nfs --attrib server=$NFS_SERVER --attrib share=$NFS_SHARE nfstestvol
nfstestvol
```
#### NodeUnpublish a volume
```
$ csc node unpublishvolume --endpoint tcp://127.0.0.1:10000 --target-path /mnt/nfs nfstestvol
nfstestvol
```
#### Get NodeID
```
$ csc node getid --endpoint tcp://127.0.0.1:10000
CSINode
```

View File

@ -0,0 +1,64 @@
# This YAML file contains attacher & csi driver API objects that are necessary
# to run external CSI attacher for nfs
kind: Service
apiVersion: v1
metadata:
name: csi-attacher-nfsplugin
labels:
app: csi-attacher-nfsplugin
spec:
selector:
app: csi-attacher-nfsplugin
ports:
- name: dummy
port: 12345
---
kind: StatefulSet
apiVersion: apps/v1beta1
metadata:
name: csi-attacher-nfsplugin
spec:
serviceName: "csi-attacher"
replicas: 1
template:
metadata:
labels:
app: csi-attacher-nfsplugin
spec:
serviceAccount: csi-attacher
containers:
- name: csi-attacher
image: docker.io/k8scsi/csi-attacher
args:
- "--v=5"
- "--csi-address=$(ADDRESS)"
env:
- name: ADDRESS
value: /var/lib/csi/sockets/pluginproxy/csi.sock
imagePullPolicy: "IfNotPresent"
volumeMounts:
- name: socket-dir
mountPath: /var/lib/csi/sockets/pluginproxy/
- name: nfs
image: docker.io/k8s/nfsplugin:v0.1
args :
- "--nodeid=$(NODE_ID)"
- "--endpoint=$(CSI_ENDPOINT)"
env:
- name: NODE_ID
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: CSI_ENDPOINT
value: unix://plugin/csi.sock
imagePullPolicy: "IfNotPresent"
volumeMounts:
- name: socket-dir
mountPath: /plugin
volumes:
- name: socket-dir
emptyDir:

View File

@ -0,0 +1,37 @@
# This YAML file contains RBAC API objects that are necessary to run external
# CSI attacher for nfs flex adapter
apiVersion: v1
kind: ServiceAccount
metadata:
name: csi-attacher
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: external-attacher-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: ["storage.k8s.io"]
resources: ["volumeattachments"]
verbs: ["get", "list", "watch", "update"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-attacher-role
subjects:
- kind: ServiceAccount
name: csi-attacher
namespace: default
roleRef:
kind: ClusterRole
name: external-attacher-runner
apiGroup: rbac.authorization.k8s.io

View File

@ -0,0 +1,66 @@
# This YAML file contains driver-registrar & csi driver nodeplugin API objects
# that are necessary to run CSI nodeplugin for nfs
kind: DaemonSet
apiVersion: apps/v1beta2
metadata:
name: csi-nodeplugin-nfsplugin
spec:
selector:
matchLabels:
app: csi-nodeplugin-nfsplugin
template:
metadata:
labels:
app: csi-nodeplugin-nfsplugin
spec:
serviceAccount: csi-nodeplugin
hostNetwork: true
containers:
- name: driver-registrar
image: docker.io/k8scsi/driver-registrar
args:
- "--v=5"
- "--csi-address=$(ADDRESS)"
env:
- name: ADDRESS
value: /plugin/csi.sock
- name: KUBE_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
volumeMounts:
- name: plugin-dir
mountPath: /plugin
- name: nfs
securityContext:
privileged: true
capabilities:
add: ["SYS_ADMIN"]
allowPrivilegeEscalation: true
image: docker.io/k8s/nfsplugin:v0.1
args :
- "--nodeid=$(NODE_ID)"
- "--endpoint=$(CSI_ENDPOINT)"
env:
- name: NODE_ID
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: CSI_ENDPOINT
value: unix://plugin/csi.sock
imagePullPolicy: "IfNotPresent"
volumeMounts:
- name: plugin-dir
mountPath: /plugin
- name: pods-mount-dir
mountPath: /var/lib/kubelet/pods
mountPropagation: "Bidirectional"
volumes:
- name: plugin-dir
hostPath:
path: /var/lib/kubelet/plugins/csi-nfsplugin
type: DirectoryOrCreate
- name: pods-mount-dir
hostPath:
path: /var/lib/kubelet/pods
type: Directory

View File

@ -0,0 +1,34 @@
# This YAML defines all API objects to create RBAC roles for CSI node plugin
apiVersion: v1
kind: ServiceAccount
metadata:
name: csi-nodeplugin
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-nodeplugin
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["volumeattachments"]
verbs: ["get", "list", "watch", "update"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-nodeplugin
subjects:
- kind: ServiceAccount
name: csi-nodeplugin
namespace: default
roleRef:
kind: ClusterRole
name: csi-nodeplugin
apiGroup: rbac.authorization.k8s.io

View File

@ -0,0 +1,8 @@
FROM centos:7.4.1708
# Copy nfsplugin from build _output directory
COPY nfsplugin /nfsplugin
RUN yum -y install nfs-utils && yum -y install epel-release && yum -y install jq && yum clean all
ENTRYPOINT ["/nfsplugin"]

View File

@ -0,0 +1,79 @@
/*
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 nfs
import (
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/golang/glog"
"github.com/kubernetes-csi/drivers/pkg/csi-common"
)
type driver struct {
csiDriver *csicommon.CSIDriver
endpoint string
ids *csicommon.DefaultIdentityServer
ns *nodeServer
cap []*csi.VolumeCapability_AccessMode
cscap []*csi.ControllerServiceCapability
}
const (
driverName = "csi-nfsplugin"
)
var (
version = csi.Version{
Minor: 1,
}
)
func GetSupportedVersions() []*csi.Version {
return []*csi.Version{&version}
}
func NewDriver(nodeID, endpoint string) *driver {
glog.Infof("Driver: %v version: %v", driverName, csicommon.GetVersionString(&version))
d := &driver{}
d.endpoint = endpoint
csiDriver := csicommon.NewCSIDriver(driverName, &version, GetSupportedVersions(), nodeID)
csiDriver.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER})
d.csiDriver = csiDriver
return d
}
func NewNodeServer(d *driver) *nodeServer {
return &nodeServer{
DefaultNodeServer: csicommon.NewDefaultNodeServer(d.csiDriver),
}
}
func (d *driver) Run() {
s := csicommon.NewNonBlockingGRPCServer()
s.Start(d.endpoint,
csicommon.NewDefaultIdentityServer(d.csiDriver),
csicommon.NewDefaultControllerServer(d.csiDriver),
NewNodeServer(d))
s.Wait()
}

View File

@ -0,0 +1,52 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: data-nfsplugin
labels:
name: data-nfsplugin
annotations:
csi.volume.kubernetes.io/volume-attributes: '{"server": "10.10.10.10", "share": "share"}'
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 100Gi
csi:
driver: csi-nfsplugin
volumeHandle: data-id
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-nfsplugin
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
selector:
matchExpressions:
- key: name
operator: In
values: ["data-nfsplugin"]
---
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- image: maersk/nginx
imagePullPolicy: Always
name: nginx
ports:
- containerPort: 80
protocol: TCP
volumeMounts:
- mountPath: /var/www
name: data-nfsplugin
volumes:
- name: data-nfsplugin
persistentVolumeClaim:
claimName: data-nfsplugin

View File

@ -0,0 +1,101 @@
/*
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 nfs
import (
"fmt"
"os"
"strings"
"github.com/container-storage-interface/spec/lib/go/csi"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume/util"
"github.com/kubernetes-csi/drivers/pkg/csi-common"
)
type nodeServer struct {
*csicommon.DefaultNodeServer
}
func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
targetPath := req.GetTargetPath()
notMnt, err := mount.New("").IsLikelyNotMountPoint(targetPath)
if err != nil {
if os.IsNotExist(err) {
if err := os.MkdirAll(targetPath, 0750); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
notMnt = true
} else {
return nil, status.Error(codes.Internal, err.Error())
}
}
if !notMnt {
return &csi.NodePublishVolumeResponse{}, nil
}
mo := req.GetVolumeCapability().GetMount().GetMountFlags()
if req.GetReadonly() {
mo = append(mo, "ro")
}
s := req.GetVolumeAttributes()["server"]
ep := req.GetVolumeAttributes()["exportPath"]
source := fmt.Sprintf("%s:%s", s, ep)
mounter := mount.New("")
err = mounter.Mount(source, targetPath, "nfs", mo)
if err != nil {
if os.IsPermission(err) {
return nil, status.Error(codes.PermissionDenied, err.Error())
}
if strings.Contains(err.Error(), "invalid argument") {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
return nil, status.Error(codes.Internal, err.Error())
}
return &csi.NodePublishVolumeResponse{}, nil
}
func (ns *nodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) {
targetPath := req.GetTargetPath()
notMnt, err := mount.New("").IsLikelyNotMountPoint(targetPath)
if err != nil {
if os.IsNotExist(err) {
return nil, status.Error(codes.NotFound, "Targetpath not found")
} else {
return nil, status.Error(codes.Internal, err.Error())
}
}
if notMnt {
return nil, status.Error(codes.NotFound, "Volume not mounted")
}
err = util.UnmountPath(req.GetTargetPath(), mount.New(""))
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &csi.NodeUnpublishVolumeResponse{}, nil
}