azure_file.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. // +build !providerless
  2. /*
  3. Copyright 2016 The Kubernetes Authors.
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package azure_file
  15. import (
  16. "fmt"
  17. "io/ioutil"
  18. "os"
  19. "runtime"
  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. cloudprovider "k8s.io/cloud-provider"
  28. volumehelpers "k8s.io/cloud-provider/volume/helpers"
  29. "k8s.io/kubernetes/pkg/volume"
  30. volutil "k8s.io/kubernetes/pkg/volume/util"
  31. "k8s.io/legacy-cloud-providers/azure"
  32. )
  33. // ProbeVolumePlugins is the primary endpoint for volume plugins
  34. func ProbeVolumePlugins() []volume.VolumePlugin {
  35. return []volume.VolumePlugin{&azureFilePlugin{nil}}
  36. }
  37. type azureFilePlugin struct {
  38. host volume.VolumeHost
  39. }
  40. var _ volume.VolumePlugin = &azureFilePlugin{}
  41. var _ volume.PersistentVolumePlugin = &azureFilePlugin{}
  42. var _ volume.ExpandableVolumePlugin = &azureFilePlugin{}
  43. const (
  44. azureFilePluginName = "kubernetes.io/azure-file"
  45. )
  46. func getPath(uid types.UID, volName string, host volume.VolumeHost) string {
  47. return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(azureFilePluginName), volName)
  48. }
  49. func (plugin *azureFilePlugin) Init(host volume.VolumeHost) error {
  50. plugin.host = host
  51. return nil
  52. }
  53. func (plugin *azureFilePlugin) GetPluginName() string {
  54. return azureFilePluginName
  55. }
  56. func (plugin *azureFilePlugin) GetVolumeName(spec *volume.Spec) (string, error) {
  57. share, _, err := getVolumeSource(spec)
  58. if err != nil {
  59. return "", err
  60. }
  61. return share, nil
  62. }
  63. func (plugin *azureFilePlugin) CanSupport(spec *volume.Spec) bool {
  64. //TODO: check if mount.cifs is there
  65. return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.AzureFile != nil) ||
  66. (spec.Volume != nil && spec.Volume.AzureFile != nil)
  67. }
  68. func (plugin *azureFilePlugin) RequiresRemount() bool {
  69. return false
  70. }
  71. func (plugin *azureFilePlugin) SupportsMountOption() bool {
  72. return true
  73. }
  74. func (plugin *azureFilePlugin) SupportsBulkVolumeVerification() bool {
  75. return false
  76. }
  77. func (plugin *azureFilePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
  78. return []v1.PersistentVolumeAccessMode{
  79. v1.ReadWriteOnce,
  80. v1.ReadOnlyMany,
  81. v1.ReadWriteMany,
  82. }
  83. }
  84. func (plugin *azureFilePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
  85. return plugin.newMounterInternal(spec, pod, &azureSvc{}, plugin.host.GetMounter(plugin.GetPluginName()))
  86. }
  87. func (plugin *azureFilePlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, util azureUtil, mounter mount.Interface) (volume.Mounter, error) {
  88. share, readOnly, err := getVolumeSource(spec)
  89. if err != nil {
  90. return nil, err
  91. }
  92. secretName, secretNamespace, err := getSecretNameAndNamespace(spec, pod.Namespace)
  93. if err != nil {
  94. // Log-and-continue instead of returning an error for now
  95. // due to unspecified backwards compatibility concerns (a subject to revise)
  96. klog.Errorf("get secret name and namespace from pod(%s) return with error: %v", pod.Name, err)
  97. }
  98. return &azureFileMounter{
  99. azureFile: &azureFile{
  100. volName: spec.Name(),
  101. mounter: mounter,
  102. pod: pod,
  103. plugin: plugin,
  104. MetricsProvider: volume.NewMetricsStatFS(getPath(pod.UID, spec.Name(), plugin.host)),
  105. },
  106. util: util,
  107. secretNamespace: secretNamespace,
  108. secretName: secretName,
  109. shareName: share,
  110. readOnly: readOnly,
  111. mountOptions: volutil.MountOptionFromSpec(spec),
  112. }, nil
  113. }
  114. func (plugin *azureFilePlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
  115. return plugin.newUnmounterInternal(volName, podUID, plugin.host.GetMounter(plugin.GetPluginName()))
  116. }
  117. func (plugin *azureFilePlugin) newUnmounterInternal(volName string, podUID types.UID, mounter mount.Interface) (volume.Unmounter, error) {
  118. return &azureFileUnmounter{&azureFile{
  119. volName: volName,
  120. mounter: mounter,
  121. pod: &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: podUID}},
  122. plugin: plugin,
  123. MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volName, plugin.host)),
  124. }}, nil
  125. }
  126. func (plugin *azureFilePlugin) RequiresFSResize() bool {
  127. return false
  128. }
  129. func (plugin *azureFilePlugin) ExpandVolumeDevice(
  130. spec *volume.Spec,
  131. newSize resource.Quantity,
  132. oldSize resource.Quantity) (resource.Quantity, error) {
  133. if spec.PersistentVolume == nil || spec.PersistentVolume.Spec.AzureFile == nil {
  134. return oldSize, fmt.Errorf("invalid PV spec")
  135. }
  136. shareName := spec.PersistentVolume.Spec.AzureFile.ShareName
  137. azure, err := getAzureCloudProvider(plugin.host.GetCloudProvider())
  138. if err != nil {
  139. return oldSize, err
  140. }
  141. secretName, secretNamespace, err := getSecretNameAndNamespace(spec, spec.PersistentVolume.Spec.ClaimRef.Namespace)
  142. if err != nil {
  143. return oldSize, err
  144. }
  145. accountName, accountKey, err := (&azureSvc{}).GetAzureCredentials(plugin.host, secretNamespace, secretName)
  146. if err != nil {
  147. return oldSize, err
  148. }
  149. if err := azure.ResizeFileShare(accountName, accountKey, shareName, int(volumehelpers.RoundUpToGiB(newSize))); err != nil {
  150. return oldSize, err
  151. }
  152. return newSize, nil
  153. }
  154. func (plugin *azureFilePlugin) ConstructVolumeSpec(volName, mountPath string) (*volume.Spec, error) {
  155. azureVolume := &v1.Volume{
  156. Name: volName,
  157. VolumeSource: v1.VolumeSource{
  158. AzureFile: &v1.AzureFileVolumeSource{
  159. SecretName: volName,
  160. ShareName: volName,
  161. },
  162. },
  163. }
  164. return volume.NewSpecFromVolume(azureVolume), nil
  165. }
  166. // azureFile volumes represent mount of an AzureFile share.
  167. type azureFile struct {
  168. volName string
  169. podUID types.UID
  170. pod *v1.Pod
  171. mounter mount.Interface
  172. plugin *azureFilePlugin
  173. volume.MetricsProvider
  174. }
  175. func (azureFileVolume *azureFile) GetPath() string {
  176. return getPath(azureFileVolume.pod.UID, azureFileVolume.volName, azureFileVolume.plugin.host)
  177. }
  178. type azureFileMounter struct {
  179. *azureFile
  180. util azureUtil
  181. secretName string
  182. secretNamespace string
  183. shareName string
  184. readOnly bool
  185. mountOptions []string
  186. }
  187. var _ volume.Mounter = &azureFileMounter{}
  188. func (b *azureFileMounter) GetAttributes() volume.Attributes {
  189. return volume.Attributes{
  190. ReadOnly: b.readOnly,
  191. Managed: !b.readOnly,
  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 (b *azureFileMounter) CanMount() error {
  199. return nil
  200. }
  201. // SetUp attaches the disk and bind mounts to the volume path.
  202. func (b *azureFileMounter) SetUp(mounterArgs volume.MounterArgs) error {
  203. return b.SetUpAt(b.GetPath(), mounterArgs)
  204. }
  205. func (b *azureFileMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
  206. notMnt, err := b.mounter.IsLikelyNotMountPoint(dir)
  207. klog.V(4).Infof("AzureFile mount set up: %s %v %v", dir, !notMnt, err)
  208. if err != nil && !os.IsNotExist(err) {
  209. return err
  210. }
  211. if !notMnt {
  212. // testing original mount point, make sure the mount link is valid
  213. if _, err := ioutil.ReadDir(dir); err == nil {
  214. klog.V(4).Infof("azureFile - already mounted to target %s", dir)
  215. return nil
  216. }
  217. // mount link is invalid, now unmount and remount later
  218. klog.Warningf("azureFile - ReadDir %s failed with %v, unmount this directory", dir, err)
  219. if err := b.mounter.Unmount(dir); err != nil {
  220. klog.Errorf("azureFile - Unmount directory %s failed with %v", dir, err)
  221. return err
  222. }
  223. }
  224. var accountKey, accountName string
  225. if accountName, accountKey, err = b.util.GetAzureCredentials(b.plugin.host, b.secretNamespace, b.secretName); err != nil {
  226. return err
  227. }
  228. var mountOptions []string
  229. source := ""
  230. osSeparator := string(os.PathSeparator)
  231. source = fmt.Sprintf("%s%s%s.file.%s%s%s", osSeparator, osSeparator, accountName, getStorageEndpointSuffix(b.plugin.host.GetCloudProvider()), osSeparator, b.shareName)
  232. if runtime.GOOS == "windows" {
  233. mountOptions = []string{fmt.Sprintf("AZURE\\%s", accountName), accountKey}
  234. } else {
  235. if err := os.MkdirAll(dir, 0700); err != nil {
  236. return err
  237. }
  238. // parameters suggested by https://azure.microsoft.com/en-us/documentation/articles/storage-how-to-use-files-linux/
  239. options := []string{fmt.Sprintf("username=%s,password=%s", accountName, accountKey)}
  240. if b.readOnly {
  241. options = append(options, "ro")
  242. }
  243. mountOptions = volutil.JoinMountOptions(b.mountOptions, options)
  244. mountOptions = appendDefaultMountOptions(mountOptions, mounterArgs.FsGroup)
  245. }
  246. err = b.mounter.Mount(source, dir, "cifs", mountOptions)
  247. if err != nil {
  248. notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
  249. if mntErr != nil {
  250. klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
  251. return err
  252. }
  253. if !notMnt {
  254. if mntErr = b.mounter.Unmount(dir); mntErr != nil {
  255. klog.Errorf("Failed to unmount: %v", mntErr)
  256. return err
  257. }
  258. notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
  259. if mntErr != nil {
  260. klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
  261. return err
  262. }
  263. if !notMnt {
  264. // This is very odd, we don't expect it. We'll try again next sync loop.
  265. klog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", dir)
  266. return err
  267. }
  268. }
  269. os.Remove(dir)
  270. return err
  271. }
  272. return nil
  273. }
  274. var _ volume.Unmounter = &azureFileUnmounter{}
  275. type azureFileUnmounter struct {
  276. *azureFile
  277. }
  278. func (c *azureFileUnmounter) TearDown() error {
  279. return c.TearDownAt(c.GetPath())
  280. }
  281. func (c *azureFileUnmounter) TearDownAt(dir string) error {
  282. return mount.CleanupMountPoint(dir, c.mounter, false)
  283. }
  284. func getVolumeSource(spec *volume.Spec) (string, bool, error) {
  285. if spec.Volume != nil && spec.Volume.AzureFile != nil {
  286. share := spec.Volume.AzureFile.ShareName
  287. readOnly := spec.Volume.AzureFile.ReadOnly
  288. return share, readOnly, nil
  289. } else if spec.PersistentVolume != nil &&
  290. spec.PersistentVolume.Spec.AzureFile != nil {
  291. share := spec.PersistentVolume.Spec.AzureFile.ShareName
  292. readOnly := spec.ReadOnly
  293. return share, readOnly, nil
  294. }
  295. return "", false, fmt.Errorf("Spec does not reference an AzureFile volume type")
  296. }
  297. func getSecretNameAndNamespace(spec *volume.Spec, defaultNamespace string) (string, string, error) {
  298. secretName := ""
  299. secretNamespace := ""
  300. if spec.Volume != nil && spec.Volume.AzureFile != nil {
  301. secretName = spec.Volume.AzureFile.SecretName
  302. secretNamespace = defaultNamespace
  303. } else if spec.PersistentVolume != nil &&
  304. spec.PersistentVolume.Spec.AzureFile != nil {
  305. secretNamespace = defaultNamespace
  306. if spec.PersistentVolume.Spec.AzureFile.SecretNamespace != nil {
  307. secretNamespace = *spec.PersistentVolume.Spec.AzureFile.SecretNamespace
  308. }
  309. secretName = spec.PersistentVolume.Spec.AzureFile.SecretName
  310. } else {
  311. return "", "", fmt.Errorf("Spec does not reference an AzureFile volume type")
  312. }
  313. if len(secretNamespace) == 0 {
  314. return "", "", fmt.Errorf("invalid Azure volume: nil namespace")
  315. }
  316. return secretName, secretNamespace, nil
  317. }
  318. func getAzureCloud(cloudProvider cloudprovider.Interface) (*azure.Cloud, error) {
  319. azure, ok := cloudProvider.(*azure.Cloud)
  320. if !ok || azure == nil {
  321. return nil, fmt.Errorf("Failed to get Azure Cloud Provider. GetCloudProvider returned %v instead", cloudProvider)
  322. }
  323. return azure, nil
  324. }
  325. func getStorageEndpointSuffix(cloudprovider cloudprovider.Interface) string {
  326. const publicCloudStorageEndpointSuffix = "core.windows.net"
  327. azure, err := getAzureCloud(cloudprovider)
  328. if err != nil {
  329. klog.Warningf("No Azure cloud provider found. Using the Azure public cloud endpoint: %s", publicCloudStorageEndpointSuffix)
  330. return publicCloudStorageEndpointSuffix
  331. }
  332. return azure.Environment.StorageEndpointSuffix
  333. }