quobyte.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package quobyte
  14. import (
  15. "fmt"
  16. "os"
  17. "path/filepath"
  18. gostrings "strings"
  19. "github.com/pborman/uuid"
  20. "k8s.io/api/core/v1"
  21. "k8s.io/apimachinery/pkg/api/resource"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/types"
  24. "k8s.io/klog"
  25. "k8s.io/kubernetes/pkg/util/mount"
  26. "k8s.io/kubernetes/pkg/volume"
  27. "k8s.io/kubernetes/pkg/volume/util"
  28. utilstrings "k8s.io/utils/strings"
  29. )
  30. // ProbeVolumePlugins is the primary entrypoint for volume plugins.
  31. func ProbeVolumePlugins() []volume.VolumePlugin {
  32. return []volume.VolumePlugin{&quobytePlugin{nil}}
  33. }
  34. type quobytePlugin struct {
  35. host volume.VolumeHost
  36. }
  37. // This user is used to authenticate against the
  38. // Quobyte API server and holds all information
  39. type quobyteAPIConfig struct {
  40. quobyteUser string
  41. quobytePassword string
  42. quobyteAPIServer string
  43. }
  44. var _ volume.VolumePlugin = &quobytePlugin{}
  45. var _ volume.PersistentVolumePlugin = &quobytePlugin{}
  46. var _ volume.DeletableVolumePlugin = &quobytePlugin{}
  47. var _ volume.ProvisionableVolumePlugin = &quobytePlugin{}
  48. var _ volume.Provisioner = &quobyteVolumeProvisioner{}
  49. var _ volume.Deleter = &quobyteVolumeDeleter{}
  50. const (
  51. quobytePluginName = "kubernetes.io/quobyte"
  52. )
  53. func (plugin *quobytePlugin) Init(host volume.VolumeHost) error {
  54. plugin.host = host
  55. return nil
  56. }
  57. func (plugin *quobytePlugin) GetPluginName() string {
  58. return quobytePluginName
  59. }
  60. func (plugin *quobytePlugin) GetVolumeName(spec *volume.Spec) (string, error) {
  61. volumeSource, _, err := getVolumeSource(spec)
  62. if err != nil {
  63. return "", err
  64. }
  65. return fmt.Sprintf(
  66. "%v:%v",
  67. volumeSource.Registry,
  68. volumeSource.Volume), nil
  69. }
  70. func (plugin *quobytePlugin) CanSupport(spec *volume.Spec) bool {
  71. if (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Quobyte == nil) ||
  72. (spec.Volume != nil && spec.Volume.Quobyte == nil) {
  73. return false
  74. }
  75. // If Quobyte is already mounted we don't need to check if the binary is installed
  76. if mounter, err := plugin.newMounterInternal(spec, nil, plugin.host.GetMounter(plugin.GetPluginName())); err == nil {
  77. qm, _ := mounter.(*quobyteMounter)
  78. pluginDir := plugin.host.GetPluginDir(utilstrings.EscapeQualifiedName(quobytePluginName))
  79. if mounted, err := qm.pluginDirIsMounted(pluginDir); mounted && err == nil {
  80. klog.V(4).Infof("quobyte: can support")
  81. return true
  82. }
  83. } else {
  84. klog.V(4).Infof("quobyte: Error: %v", err)
  85. }
  86. exec := plugin.host.GetExec(plugin.GetPluginName())
  87. if out, err := exec.Run("ls", "/sbin/mount.quobyte"); err == nil {
  88. klog.V(4).Infof("quobyte: can support: %s", string(out))
  89. return true
  90. }
  91. return false
  92. }
  93. func (plugin *quobytePlugin) IsMigratedToCSI() bool {
  94. return false
  95. }
  96. func (plugin *quobytePlugin) RequiresRemount() bool {
  97. return false
  98. }
  99. func (plugin *quobytePlugin) SupportsMountOption() bool {
  100. return true
  101. }
  102. func (plugin *quobytePlugin) SupportsBulkVolumeVerification() bool {
  103. return false
  104. }
  105. func (plugin *quobytePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
  106. return []v1.PersistentVolumeAccessMode{
  107. v1.ReadWriteOnce,
  108. v1.ReadOnlyMany,
  109. v1.ReadWriteMany,
  110. }
  111. }
  112. func getVolumeSource(spec *volume.Spec) (*v1.QuobyteVolumeSource, bool, error) {
  113. if spec.Volume != nil && spec.Volume.Quobyte != nil {
  114. return spec.Volume.Quobyte, spec.Volume.Quobyte.ReadOnly, nil
  115. } else if spec.PersistentVolume != nil &&
  116. spec.PersistentVolume.Spec.Quobyte != nil {
  117. return spec.PersistentVolume.Spec.Quobyte, spec.ReadOnly, nil
  118. }
  119. return nil, false, fmt.Errorf("Spec does not reference a Quobyte volume type")
  120. }
  121. func (plugin *quobytePlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
  122. quobyteVolume := &v1.Volume{
  123. Name: volumeName,
  124. VolumeSource: v1.VolumeSource{
  125. Quobyte: &v1.QuobyteVolumeSource{
  126. Volume: volumeName,
  127. },
  128. },
  129. }
  130. return volume.NewSpecFromVolume(quobyteVolume), nil
  131. }
  132. func (plugin *quobytePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
  133. return plugin.newMounterInternal(spec, pod, plugin.host.GetMounter(plugin.GetPluginName()))
  134. }
  135. func (plugin *quobytePlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, mounter mount.Interface) (volume.Mounter, error) {
  136. source, readOnly, err := getVolumeSource(spec)
  137. if err != nil {
  138. return nil, err
  139. }
  140. return &quobyteMounter{
  141. quobyte: &quobyte{
  142. volName: spec.Name(),
  143. user: source.User,
  144. group: source.Group,
  145. mounter: mounter,
  146. pod: pod,
  147. volume: source.Volume,
  148. plugin: plugin,
  149. },
  150. registry: source.Registry,
  151. readOnly: readOnly,
  152. mountOptions: util.MountOptionFromSpec(spec),
  153. }, nil
  154. }
  155. func (plugin *quobytePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
  156. return plugin.newUnmounterInternal(volName, podUID, plugin.host.GetMounter(plugin.GetPluginName()))
  157. }
  158. func (plugin *quobytePlugin) newUnmounterInternal(volName string, podUID types.UID, mounter mount.Interface) (volume.Unmounter, error) {
  159. return &quobyteUnmounter{
  160. &quobyte{
  161. volName: volName,
  162. mounter: mounter,
  163. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: podUID}},
  164. plugin: plugin,
  165. },
  166. }, nil
  167. }
  168. // Quobyte volumes represent a bare host directory mount of an quobyte export.
  169. type quobyte struct {
  170. volName string
  171. pod *v1.Pod
  172. user string
  173. group string
  174. volume string
  175. tenant string
  176. config string
  177. mounter mount.Interface
  178. plugin *quobytePlugin
  179. volume.MetricsNil
  180. }
  181. type quobyteMounter struct {
  182. *quobyte
  183. registry string
  184. readOnly bool
  185. mountOptions []string
  186. }
  187. var _ volume.Mounter = &quobyteMounter{}
  188. func (mounter *quobyteMounter) GetAttributes() volume.Attributes {
  189. return volume.Attributes{
  190. ReadOnly: mounter.readOnly,
  191. Managed: false,
  192. SupportsSELinux: false,
  193. }
  194. }
  195. // Checks prior to mount operations to verify that the required components (binaries, etc.)
  196. // to mount the volume are available on the underlying node.
  197. // If not, it returns an error
  198. func (mounter *quobyteMounter) CanMount() error {
  199. return nil
  200. }
  201. // SetUp attaches the disk and bind mounts to the volume path.
  202. func (mounter *quobyteMounter) SetUp(mounterArgs volume.MounterArgs) error {
  203. pluginDir := mounter.plugin.host.GetPluginDir(utilstrings.EscapeQualifiedName(quobytePluginName))
  204. return mounter.SetUpAt(pluginDir, mounterArgs)
  205. }
  206. func (mounter *quobyteMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
  207. // Check if Quobyte is already mounted on the host in the Plugin Dir
  208. // if so we can use this mountpoint instead of creating a new one
  209. // IsLikelyNotMountPoint wouldn't check the mount type
  210. if mounted, err := mounter.pluginDirIsMounted(dir); err != nil {
  211. return err
  212. } else if mounted {
  213. return nil
  214. }
  215. os.MkdirAll(dir, 0750)
  216. var options []string
  217. options = append(options, "allow-usermapping-in-volumename")
  218. if mounter.readOnly {
  219. options = append(options, "ro")
  220. }
  221. //if a trailing slash is missing we add it here
  222. mountOptions := util.JoinMountOptions(mounter.mountOptions, options)
  223. if err := mounter.mounter.Mount(mounter.correctTraillingSlash(mounter.registry), dir, "quobyte", mountOptions); err != nil {
  224. return fmt.Errorf("quobyte: mount failed: %v", err)
  225. }
  226. klog.V(4).Infof("quobyte: mount set up: %s", dir)
  227. return nil
  228. }
  229. // GetPath returns the path to the user specific mount of a Quobyte volume
  230. // Returns a path in the format ../user#group@volume
  231. func (quobyteVolume *quobyte) GetPath() string {
  232. user := quobyteVolume.user
  233. if len(user) == 0 {
  234. user = "root"
  235. }
  236. group := quobyteVolume.group
  237. if len(group) == 0 {
  238. group = "nfsnobody"
  239. }
  240. // Quobyte has only one mount in the PluginDir where all Volumes are mounted
  241. // The Quobyte client does a fixed-user mapping
  242. pluginDir := quobyteVolume.plugin.host.GetPluginDir(utilstrings.EscapeQualifiedName(quobytePluginName))
  243. return filepath.Join(pluginDir, fmt.Sprintf("%s#%s@%s", user, group, quobyteVolume.volume))
  244. }
  245. type quobyteUnmounter struct {
  246. *quobyte
  247. }
  248. var _ volume.Unmounter = &quobyteUnmounter{}
  249. func (unmounter *quobyteUnmounter) TearDown() error {
  250. return unmounter.TearDownAt(unmounter.GetPath())
  251. }
  252. // We don't need to unmount on the host because only one mount exists
  253. func (unmounter *quobyteUnmounter) TearDownAt(dir string) error {
  254. return nil
  255. }
  256. type quobyteVolumeDeleter struct {
  257. *quobyteMounter
  258. pv *v1.PersistentVolume
  259. }
  260. func (plugin *quobytePlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) {
  261. if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Quobyte == nil {
  262. return nil, fmt.Errorf("spec.PersistentVolume.Spec.Quobyte is nil")
  263. }
  264. return plugin.newDeleterInternal(spec)
  265. }
  266. func (plugin *quobytePlugin) newDeleterInternal(spec *volume.Spec) (volume.Deleter, error) {
  267. source, readOnly, err := getVolumeSource(spec)
  268. if err != nil {
  269. return nil, err
  270. }
  271. return &quobyteVolumeDeleter{
  272. quobyteMounter: &quobyteMounter{
  273. quobyte: &quobyte{
  274. volName: spec.Name(),
  275. user: source.User,
  276. group: source.Group,
  277. volume: source.Volume,
  278. plugin: plugin,
  279. tenant: source.Tenant,
  280. },
  281. registry: source.Registry,
  282. readOnly: readOnly,
  283. },
  284. pv: spec.PersistentVolume,
  285. }, nil
  286. }
  287. func (plugin *quobytePlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
  288. return plugin.newProvisionerInternal(options)
  289. }
  290. func (plugin *quobytePlugin) newProvisionerInternal(options volume.VolumeOptions) (volume.Provisioner, error) {
  291. return &quobyteVolumeProvisioner{
  292. quobyteMounter: &quobyteMounter{
  293. quobyte: &quobyte{
  294. plugin: plugin,
  295. },
  296. },
  297. options: options,
  298. }, nil
  299. }
  300. type quobyteVolumeProvisioner struct {
  301. *quobyteMounter
  302. options volume.VolumeOptions
  303. }
  304. func (provisioner *quobyteVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
  305. if !util.AccessModesContainedInAll(provisioner.plugin.GetAccessModes(), provisioner.options.PVC.Spec.AccessModes) {
  306. return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", provisioner.options.PVC.Spec.AccessModes, provisioner.plugin.GetAccessModes())
  307. }
  308. if util.CheckPersistentVolumeClaimModeBlock(provisioner.options.PVC) {
  309. return nil, fmt.Errorf("%s does not support block volume provisioning", provisioner.plugin.GetPluginName())
  310. }
  311. if provisioner.options.PVC.Spec.Selector != nil {
  312. return nil, fmt.Errorf("claim Selector is not supported")
  313. }
  314. provisioner.config = "BASE"
  315. provisioner.tenant = "DEFAULT"
  316. createQuota := false
  317. cfg, err := parseAPIConfig(provisioner.plugin, provisioner.options.Parameters)
  318. if err != nil {
  319. return nil, err
  320. }
  321. for k, v := range provisioner.options.Parameters {
  322. switch gostrings.ToLower(k) {
  323. case "registry":
  324. provisioner.registry = v
  325. case "user":
  326. provisioner.user = v
  327. case "group":
  328. provisioner.group = v
  329. case "quobytetenant":
  330. provisioner.tenant = v
  331. case "quobyteconfig":
  332. provisioner.config = v
  333. case "createquota":
  334. createQuota = gostrings.ToLower(v) == "true"
  335. case "adminsecretname",
  336. "adminsecretnamespace",
  337. "quobyteapiserver":
  338. continue
  339. default:
  340. return nil, fmt.Errorf("invalid option %q for volume plugin %s", k, provisioner.plugin.GetPluginName())
  341. }
  342. }
  343. if !validateRegistry(provisioner.registry) {
  344. return nil, fmt.Errorf("Quobyte registry missing or malformed: must be a host:port pair or multiple pairs separated by commas")
  345. }
  346. // create random image name
  347. provisioner.volume = fmt.Sprintf("kubernetes-dynamic-pvc-%s", uuid.NewUUID())
  348. manager := &quobyteVolumeManager{
  349. config: cfg,
  350. }
  351. vol, sizeGB, err := manager.createVolume(provisioner, createQuota)
  352. if err != nil {
  353. return nil, err
  354. }
  355. pv := new(v1.PersistentVolume)
  356. metav1.SetMetaDataAnnotation(&pv.ObjectMeta, util.VolumeDynamicallyCreatedByKey, "quobyte-dynamic-provisioner")
  357. pv.Spec.PersistentVolumeSource.Quobyte = vol
  358. pv.Spec.PersistentVolumeReclaimPolicy = provisioner.options.PersistentVolumeReclaimPolicy
  359. pv.Spec.AccessModes = provisioner.options.PVC.Spec.AccessModes
  360. if len(pv.Spec.AccessModes) == 0 {
  361. pv.Spec.AccessModes = provisioner.plugin.GetAccessModes()
  362. }
  363. pv.Spec.Capacity = v1.ResourceList{
  364. v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGB)),
  365. }
  366. pv.Spec.MountOptions = provisioner.options.MountOptions
  367. pv.Spec.PersistentVolumeSource.Quobyte.Tenant = provisioner.tenant
  368. return pv, nil
  369. }
  370. func (deleter *quobyteVolumeDeleter) GetPath() string {
  371. return deleter.quobyte.GetPath()
  372. }
  373. func (deleter *quobyteVolumeDeleter) Delete() error {
  374. class, err := util.GetClassForVolume(deleter.plugin.host.GetKubeClient(), deleter.pv)
  375. if err != nil {
  376. return err
  377. }
  378. cfg, err := parseAPIConfig(deleter.plugin, class.Parameters)
  379. if err != nil {
  380. return err
  381. }
  382. manager := &quobyteVolumeManager{
  383. config: cfg,
  384. }
  385. return manager.deleteVolume(deleter)
  386. }
  387. // Parse API configuration (url, username and password) out of class.Parameters.
  388. func parseAPIConfig(plugin *quobytePlugin, params map[string]string) (*quobyteAPIConfig, error) {
  389. var apiServer, secretName string
  390. secretNamespace := "default"
  391. deleteKeys := []string{}
  392. for k, v := range params {
  393. switch gostrings.ToLower(k) {
  394. case "adminsecretname":
  395. secretName = v
  396. deleteKeys = append(deleteKeys, k)
  397. case "adminsecretnamespace":
  398. secretNamespace = v
  399. deleteKeys = append(deleteKeys, k)
  400. case "quobyteapiserver":
  401. apiServer = v
  402. deleteKeys = append(deleteKeys, k)
  403. }
  404. }
  405. if len(apiServer) == 0 {
  406. return nil, fmt.Errorf("Quobyte API server missing or malformed: must be a http(s)://host:port pair or multiple pairs separated by commas")
  407. }
  408. secretMap, err := util.GetSecretForPV(secretNamespace, secretName, quobytePluginName, plugin.host.GetKubeClient())
  409. if err != nil {
  410. return nil, err
  411. }
  412. cfg := &quobyteAPIConfig{
  413. quobyteAPIServer: apiServer,
  414. }
  415. var ok bool
  416. if cfg.quobyteUser, ok = secretMap["user"]; !ok {
  417. return nil, fmt.Errorf("Missing \"user\" in secret %s/%s", secretNamespace, secretName)
  418. }
  419. if cfg.quobytePassword, ok = secretMap["password"]; !ok {
  420. return nil, fmt.Errorf("Missing \"password\" in secret %s/%s", secretNamespace, secretName)
  421. }
  422. return cfg, nil
  423. }