quobyte.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  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/google/uuid"
  20. "k8s.io/klog"
  21. "k8s.io/utils/mount"
  22. utilstrings "k8s.io/utils/strings"
  23. v1 "k8s.io/api/core/v1"
  24. "k8s.io/apimachinery/pkg/api/resource"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. "k8s.io/apimachinery/pkg/types"
  27. "k8s.io/kubernetes/pkg/volume"
  28. "k8s.io/kubernetes/pkg/volume/util"
  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.Command("ls", "/sbin/mount.quobyte").CombinedOutput(); 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) RequiresRemount() bool {
  94. return false
  95. }
  96. func (plugin *quobytePlugin) SupportsMountOption() bool {
  97. return true
  98. }
  99. func (plugin *quobytePlugin) SupportsBulkVolumeVerification() bool {
  100. return false
  101. }
  102. func (plugin *quobytePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
  103. return []v1.PersistentVolumeAccessMode{
  104. v1.ReadWriteOnce,
  105. v1.ReadOnlyMany,
  106. v1.ReadWriteMany,
  107. }
  108. }
  109. func getVolumeSource(spec *volume.Spec) (*v1.QuobyteVolumeSource, bool, error) {
  110. if spec.Volume != nil && spec.Volume.Quobyte != nil {
  111. return spec.Volume.Quobyte, spec.Volume.Quobyte.ReadOnly, nil
  112. } else if spec.PersistentVolume != nil &&
  113. spec.PersistentVolume.Spec.Quobyte != nil {
  114. return spec.PersistentVolume.Spec.Quobyte, spec.ReadOnly, nil
  115. }
  116. return nil, false, fmt.Errorf("Spec does not reference a Quobyte volume type")
  117. }
  118. func (plugin *quobytePlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
  119. quobyteVolume := &v1.Volume{
  120. Name: volumeName,
  121. VolumeSource: v1.VolumeSource{
  122. Quobyte: &v1.QuobyteVolumeSource{
  123. Volume: volumeName,
  124. },
  125. },
  126. }
  127. return volume.NewSpecFromVolume(quobyteVolume), nil
  128. }
  129. func (plugin *quobytePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
  130. return plugin.newMounterInternal(spec, pod, plugin.host.GetMounter(plugin.GetPluginName()))
  131. }
  132. func (plugin *quobytePlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, mounter mount.Interface) (volume.Mounter, error) {
  133. source, readOnly, err := getVolumeSource(spec)
  134. if err != nil {
  135. return nil, err
  136. }
  137. return &quobyteMounter{
  138. quobyte: &quobyte{
  139. volName: spec.Name(),
  140. user: source.User,
  141. group: source.Group,
  142. mounter: mounter,
  143. pod: pod,
  144. volume: source.Volume,
  145. plugin: plugin,
  146. },
  147. registry: source.Registry,
  148. readOnly: readOnly,
  149. mountOptions: util.MountOptionFromSpec(spec),
  150. }, nil
  151. }
  152. func (plugin *quobytePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
  153. return plugin.newUnmounterInternal(volName, podUID, plugin.host.GetMounter(plugin.GetPluginName()))
  154. }
  155. func (plugin *quobytePlugin) newUnmounterInternal(volName string, podUID types.UID, mounter mount.Interface) (volume.Unmounter, error) {
  156. return &quobyteUnmounter{
  157. &quobyte{
  158. volName: volName,
  159. mounter: mounter,
  160. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: podUID}},
  161. plugin: plugin,
  162. },
  163. }, nil
  164. }
  165. // Quobyte volumes represent a bare host directory mount of an quobyte export.
  166. type quobyte struct {
  167. volName string
  168. pod *v1.Pod
  169. user string
  170. group string
  171. volume string
  172. tenant string
  173. config string
  174. mounter mount.Interface
  175. plugin *quobytePlugin
  176. volume.MetricsNil
  177. }
  178. type quobyteMounter struct {
  179. *quobyte
  180. registry string
  181. readOnly bool
  182. mountOptions []string
  183. }
  184. var _ volume.Mounter = &quobyteMounter{}
  185. func (mounter *quobyteMounter) GetAttributes() volume.Attributes {
  186. return volume.Attributes{
  187. ReadOnly: mounter.readOnly,
  188. Managed: false,
  189. SupportsSELinux: false,
  190. }
  191. }
  192. // Checks prior to mount operations to verify that the required components (binaries, etc.)
  193. // to mount the volume are available on the underlying node.
  194. // If not, it returns an error
  195. func (mounter *quobyteMounter) CanMount() error {
  196. return nil
  197. }
  198. // SetUp attaches the disk and bind mounts to the volume path.
  199. func (mounter *quobyteMounter) SetUp(mounterArgs volume.MounterArgs) error {
  200. pluginDir := mounter.plugin.host.GetPluginDir(utilstrings.EscapeQualifiedName(quobytePluginName))
  201. return mounter.SetUpAt(pluginDir, mounterArgs)
  202. }
  203. func (mounter *quobyteMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
  204. // Check if Quobyte is already mounted on the host in the Plugin Dir
  205. // if so we can use this mountpoint instead of creating a new one
  206. // IsLikelyNotMountPoint wouldn't check the mount type
  207. if mounted, err := mounter.pluginDirIsMounted(dir); err != nil {
  208. return err
  209. } else if mounted {
  210. return nil
  211. }
  212. os.MkdirAll(dir, 0750)
  213. var options []string
  214. options = append(options, "allow-usermapping-in-volumename")
  215. if mounter.readOnly {
  216. options = append(options, "ro")
  217. }
  218. //if a trailing slash is missing we add it here
  219. mountOptions := util.JoinMountOptions(mounter.mountOptions, options)
  220. if err := mounter.mounter.Mount(mounter.correctTraillingSlash(mounter.registry), dir, "quobyte", mountOptions); err != nil {
  221. return fmt.Errorf("quobyte: mount failed: %v", err)
  222. }
  223. klog.V(4).Infof("quobyte: mount set up: %s", dir)
  224. return nil
  225. }
  226. // GetPath returns the path to the user specific mount of a Quobyte volume
  227. // Returns a path in the format ../user#group@volume
  228. func (quobyteVolume *quobyte) GetPath() string {
  229. user := quobyteVolume.user
  230. if len(user) == 0 {
  231. user = "root"
  232. }
  233. group := quobyteVolume.group
  234. if len(group) == 0 {
  235. group = "nfsnobody"
  236. }
  237. // Quobyte has only one mount in the PluginDir where all Volumes are mounted
  238. // The Quobyte client does a fixed-user mapping
  239. pluginDir := quobyteVolume.plugin.host.GetPluginDir(utilstrings.EscapeQualifiedName(quobytePluginName))
  240. return filepath.Join(pluginDir, fmt.Sprintf("%s#%s@%s", user, group, quobyteVolume.volume))
  241. }
  242. type quobyteUnmounter struct {
  243. *quobyte
  244. }
  245. var _ volume.Unmounter = &quobyteUnmounter{}
  246. func (unmounter *quobyteUnmounter) TearDown() error {
  247. return unmounter.TearDownAt(unmounter.GetPath())
  248. }
  249. // We don't need to unmount on the host because only one mount exists
  250. func (unmounter *quobyteUnmounter) TearDownAt(dir string) error {
  251. return nil
  252. }
  253. type quobyteVolumeDeleter struct {
  254. *quobyteMounter
  255. pv *v1.PersistentVolume
  256. }
  257. func (plugin *quobytePlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) {
  258. if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.Quobyte == nil {
  259. return nil, fmt.Errorf("spec.PersistentVolume.Spec.Quobyte is nil")
  260. }
  261. return plugin.newDeleterInternal(spec)
  262. }
  263. func (plugin *quobytePlugin) newDeleterInternal(spec *volume.Spec) (volume.Deleter, error) {
  264. source, readOnly, err := getVolumeSource(spec)
  265. if err != nil {
  266. return nil, err
  267. }
  268. return &quobyteVolumeDeleter{
  269. quobyteMounter: &quobyteMounter{
  270. quobyte: &quobyte{
  271. volName: spec.Name(),
  272. user: source.User,
  273. group: source.Group,
  274. volume: source.Volume,
  275. plugin: plugin,
  276. tenant: source.Tenant,
  277. },
  278. registry: source.Registry,
  279. readOnly: readOnly,
  280. },
  281. pv: spec.PersistentVolume,
  282. }, nil
  283. }
  284. func (plugin *quobytePlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) {
  285. return plugin.newProvisionerInternal(options)
  286. }
  287. func (plugin *quobytePlugin) newProvisionerInternal(options volume.VolumeOptions) (volume.Provisioner, error) {
  288. return &quobyteVolumeProvisioner{
  289. quobyteMounter: &quobyteMounter{
  290. quobyte: &quobyte{
  291. plugin: plugin,
  292. },
  293. },
  294. options: options,
  295. }, nil
  296. }
  297. type quobyteVolumeProvisioner struct {
  298. *quobyteMounter
  299. options volume.VolumeOptions
  300. }
  301. func (provisioner *quobyteVolumeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
  302. if !util.AccessModesContainedInAll(provisioner.plugin.GetAccessModes(), provisioner.options.PVC.Spec.AccessModes) {
  303. return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", provisioner.options.PVC.Spec.AccessModes, provisioner.plugin.GetAccessModes())
  304. }
  305. if util.CheckPersistentVolumeClaimModeBlock(provisioner.options.PVC) {
  306. return nil, fmt.Errorf("%s does not support block volume provisioning", provisioner.plugin.GetPluginName())
  307. }
  308. if provisioner.options.PVC.Spec.Selector != nil {
  309. return nil, fmt.Errorf("claim Selector is not supported")
  310. }
  311. provisioner.config = "BASE"
  312. provisioner.tenant = "DEFAULT"
  313. createQuota := false
  314. cfg, err := parseAPIConfig(provisioner.plugin, provisioner.options.Parameters)
  315. if err != nil {
  316. return nil, err
  317. }
  318. for k, v := range provisioner.options.Parameters {
  319. switch gostrings.ToLower(k) {
  320. case "registry":
  321. provisioner.registry = v
  322. case "user":
  323. provisioner.user = v
  324. case "group":
  325. provisioner.group = v
  326. case "quobytetenant":
  327. provisioner.tenant = v
  328. case "quobyteconfig":
  329. provisioner.config = v
  330. case "createquota":
  331. createQuota = gostrings.ToLower(v) == "true"
  332. case "adminsecretname",
  333. "adminsecretnamespace",
  334. "quobyteapiserver":
  335. continue
  336. default:
  337. return nil, fmt.Errorf("invalid option %q for volume plugin %s", k, provisioner.plugin.GetPluginName())
  338. }
  339. }
  340. if !validateRegistry(provisioner.registry) {
  341. return nil, fmt.Errorf("Quobyte registry missing or malformed: must be a host:port pair or multiple pairs separated by commas")
  342. }
  343. // create random image name
  344. provisioner.volume = fmt.Sprintf("kubernetes-dynamic-pvc-%s", uuid.New().String())
  345. manager := &quobyteVolumeManager{
  346. config: cfg,
  347. }
  348. vol, sizeGB, err := manager.createVolume(provisioner, createQuota)
  349. if err != nil {
  350. return nil, err
  351. }
  352. pv := new(v1.PersistentVolume)
  353. metav1.SetMetaDataAnnotation(&pv.ObjectMeta, util.VolumeDynamicallyCreatedByKey, "quobyte-dynamic-provisioner")
  354. pv.Spec.PersistentVolumeSource.Quobyte = vol
  355. pv.Spec.PersistentVolumeReclaimPolicy = provisioner.options.PersistentVolumeReclaimPolicy
  356. pv.Spec.AccessModes = provisioner.options.PVC.Spec.AccessModes
  357. if len(pv.Spec.AccessModes) == 0 {
  358. pv.Spec.AccessModes = provisioner.plugin.GetAccessModes()
  359. }
  360. pv.Spec.Capacity = v1.ResourceList{
  361. v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", sizeGB)),
  362. }
  363. pv.Spec.MountOptions = provisioner.options.MountOptions
  364. pv.Spec.PersistentVolumeSource.Quobyte.Tenant = provisioner.tenant
  365. return pv, nil
  366. }
  367. func (deleter *quobyteVolumeDeleter) GetPath() string {
  368. return deleter.quobyte.GetPath()
  369. }
  370. func (deleter *quobyteVolumeDeleter) Delete() error {
  371. class, err := util.GetClassForVolume(deleter.plugin.host.GetKubeClient(), deleter.pv)
  372. if err != nil {
  373. return err
  374. }
  375. cfg, err := parseAPIConfig(deleter.plugin, class.Parameters)
  376. if err != nil {
  377. return err
  378. }
  379. manager := &quobyteVolumeManager{
  380. config: cfg,
  381. }
  382. return manager.deleteVolume(deleter)
  383. }
  384. // Parse API configuration (url, username and password) out of class.Parameters.
  385. func parseAPIConfig(plugin *quobytePlugin, params map[string]string) (*quobyteAPIConfig, error) {
  386. var apiServer, secretName string
  387. secretNamespace := "default"
  388. deleteKeys := []string{}
  389. for k, v := range params {
  390. switch gostrings.ToLower(k) {
  391. case "adminsecretname":
  392. secretName = v
  393. deleteKeys = append(deleteKeys, k)
  394. case "adminsecretnamespace":
  395. secretNamespace = v
  396. deleteKeys = append(deleteKeys, k)
  397. case "quobyteapiserver":
  398. apiServer = v
  399. deleteKeys = append(deleteKeys, k)
  400. }
  401. }
  402. if len(apiServer) == 0 {
  403. return nil, fmt.Errorf("Quobyte API server missing or malformed: must be a http(s)://host:port pair or multiple pairs separated by commas")
  404. }
  405. secretMap, err := util.GetSecretForPV(secretNamespace, secretName, quobytePluginName, plugin.host.GetKubeClient())
  406. if err != nil {
  407. return nil, err
  408. }
  409. cfg := &quobyteAPIConfig{
  410. quobyteAPIServer: apiServer,
  411. }
  412. var ok bool
  413. if cfg.quobyteUser, ok = secretMap["user"]; !ok {
  414. return nil, fmt.Errorf("Missing \"user\" in secret %s/%s", secretNamespace, secretName)
  415. }
  416. if cfg.quobytePassword, ok = secretMap["password"]; !ok {
  417. return nil, fmt.Errorf("Missing \"password\" in secret %s/%s", secretNamespace, secretName)
  418. }
  419. return cfg, nil
  420. }