azure_file.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  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 azure_file
  14. import (
  15. "fmt"
  16. "io/ioutil"
  17. "os"
  18. "runtime"
  19. v1 "k8s.io/api/core/v1"
  20. "k8s.io/apimachinery/pkg/api/resource"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/types"
  23. utilfeature "k8s.io/apiserver/pkg/util/feature"
  24. cloudprovider "k8s.io/cloud-provider"
  25. volumehelpers "k8s.io/cloud-provider/volume/helpers"
  26. "k8s.io/klog"
  27. "k8s.io/kubernetes/pkg/features"
  28. "k8s.io/kubernetes/pkg/util/mount"
  29. "k8s.io/kubernetes/pkg/volume"
  30. volutil "k8s.io/kubernetes/pkg/volume/util"
  31. "k8s.io/legacy-cloud-providers/azure"
  32. utilstrings "k8s.io/utils/strings"
  33. )
  34. // ProbeVolumePlugins is the primary endpoint for volume plugins
  35. func ProbeVolumePlugins() []volume.VolumePlugin {
  36. return []volume.VolumePlugin{&azureFilePlugin{nil}}
  37. }
  38. type azureFilePlugin struct {
  39. host volume.VolumeHost
  40. }
  41. var _ volume.VolumePlugin = &azureFilePlugin{}
  42. var _ volume.PersistentVolumePlugin = &azureFilePlugin{}
  43. var _ volume.ExpandableVolumePlugin = &azureFilePlugin{}
  44. const (
  45. azureFilePluginName = "kubernetes.io/azure-file"
  46. )
  47. func getPath(uid types.UID, volName string, host volume.VolumeHost) string {
  48. return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(azureFilePluginName), volName)
  49. }
  50. func (plugin *azureFilePlugin) Init(host volume.VolumeHost) error {
  51. plugin.host = host
  52. return nil
  53. }
  54. func (plugin *azureFilePlugin) GetPluginName() string {
  55. return azureFilePluginName
  56. }
  57. func (plugin *azureFilePlugin) GetVolumeName(spec *volume.Spec) (string, error) {
  58. share, _, err := getVolumeSource(spec)
  59. if err != nil {
  60. return "", err
  61. }
  62. return share, nil
  63. }
  64. func (plugin *azureFilePlugin) CanSupport(spec *volume.Spec) bool {
  65. //TODO: check if mount.cifs is there
  66. return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.AzureFile != nil) ||
  67. (spec.Volume != nil && spec.Volume.AzureFile != nil)
  68. }
  69. func (plugin *azureFilePlugin) IsMigratedToCSI() bool {
  70. return utilfeature.DefaultFeatureGate.Enabled(features.CSIMigration) &&
  71. utilfeature.DefaultFeatureGate.Enabled(features.CSIMigrationAzureFile)
  72. }
  73. func (plugin *azureFilePlugin) RequiresRemount() bool {
  74. return false
  75. }
  76. func (plugin *azureFilePlugin) SupportsMountOption() bool {
  77. return true
  78. }
  79. func (plugin *azureFilePlugin) SupportsBulkVolumeVerification() bool {
  80. return false
  81. }
  82. func (plugin *azureFilePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
  83. return []v1.PersistentVolumeAccessMode{
  84. v1.ReadWriteOnce,
  85. v1.ReadOnlyMany,
  86. v1.ReadWriteMany,
  87. }
  88. }
  89. func (plugin *azureFilePlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
  90. return plugin.newMounterInternal(spec, pod, &azureSvc{}, plugin.host.GetMounter(plugin.GetPluginName()))
  91. }
  92. func (plugin *azureFilePlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, util azureUtil, mounter mount.Interface) (volume.Mounter, error) {
  93. share, readOnly, err := getVolumeSource(spec)
  94. if err != nil {
  95. return nil, err
  96. }
  97. secretName, secretNamespace, err := getSecretNameAndNamespace(spec, pod.Namespace)
  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. notMnt = true
  224. }
  225. var accountKey, accountName string
  226. if accountName, accountKey, err = b.util.GetAzureCredentials(b.plugin.host, b.secretNamespace, b.secretName); err != nil {
  227. return err
  228. }
  229. mountOptions := []string{}
  230. source := ""
  231. osSeparator := string(os.PathSeparator)
  232. source = fmt.Sprintf("%s%s%s.file.%s%s%s", osSeparator, osSeparator, accountName, getStorageEndpointSuffix(b.plugin.host.GetCloudProvider()), osSeparator, b.shareName)
  233. if runtime.GOOS == "windows" {
  234. mountOptions = []string{fmt.Sprintf("AZURE\\%s", accountName), accountKey}
  235. } else {
  236. if err := os.MkdirAll(dir, 0700); err != nil {
  237. return err
  238. }
  239. // parameters suggested by https://azure.microsoft.com/en-us/documentation/articles/storage-how-to-use-files-linux/
  240. options := []string{fmt.Sprintf("username=%s,password=%s", accountName, accountKey)}
  241. if b.readOnly {
  242. options = append(options, "ro")
  243. }
  244. mountOptions = volutil.JoinMountOptions(b.mountOptions, options)
  245. mountOptions = appendDefaultMountOptions(mountOptions, mounterArgs.FsGroup)
  246. }
  247. err = b.mounter.Mount(source, dir, "cifs", mountOptions)
  248. if err != nil {
  249. notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
  250. if mntErr != nil {
  251. klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
  252. return err
  253. }
  254. if !notMnt {
  255. if mntErr = b.mounter.Unmount(dir); mntErr != nil {
  256. klog.Errorf("Failed to unmount: %v", mntErr)
  257. return err
  258. }
  259. notMnt, mntErr := b.mounter.IsLikelyNotMountPoint(dir)
  260. if mntErr != nil {
  261. klog.Errorf("IsLikelyNotMountPoint check failed: %v", mntErr)
  262. return err
  263. }
  264. if !notMnt {
  265. // This is very odd, we don't expect it. We'll try again next sync loop.
  266. klog.Errorf("%s is still mounted, despite call to unmount(). Will try again next sync loop.", dir)
  267. return err
  268. }
  269. }
  270. os.Remove(dir)
  271. return err
  272. }
  273. return nil
  274. }
  275. var _ volume.Unmounter = &azureFileUnmounter{}
  276. type azureFileUnmounter struct {
  277. *azureFile
  278. }
  279. func (c *azureFileUnmounter) TearDown() error {
  280. return c.TearDownAt(c.GetPath())
  281. }
  282. func (c *azureFileUnmounter) TearDownAt(dir string) error {
  283. return mount.CleanupMountPoint(dir, c.mounter, false)
  284. }
  285. func getVolumeSource(spec *volume.Spec) (string, bool, error) {
  286. if spec.Volume != nil && spec.Volume.AzureFile != nil {
  287. share := spec.Volume.AzureFile.ShareName
  288. readOnly := spec.Volume.AzureFile.ReadOnly
  289. return share, readOnly, nil
  290. } else if spec.PersistentVolume != nil &&
  291. spec.PersistentVolume.Spec.AzureFile != nil {
  292. share := spec.PersistentVolume.Spec.AzureFile.ShareName
  293. readOnly := spec.ReadOnly
  294. return share, readOnly, nil
  295. }
  296. return "", false, fmt.Errorf("Spec does not reference an AzureFile volume type")
  297. }
  298. func getSecretNameAndNamespace(spec *volume.Spec, defaultNamespace string) (string, string, error) {
  299. secretName := ""
  300. secretNamespace := ""
  301. if spec.Volume != nil && spec.Volume.AzureFile != nil {
  302. secretName = spec.Volume.AzureFile.SecretName
  303. secretNamespace = defaultNamespace
  304. } else if spec.PersistentVolume != nil &&
  305. spec.PersistentVolume.Spec.AzureFile != nil {
  306. secretNamespace = defaultNamespace
  307. if spec.PersistentVolume.Spec.AzureFile.SecretNamespace != nil {
  308. secretNamespace = *spec.PersistentVolume.Spec.AzureFile.SecretNamespace
  309. }
  310. secretName = spec.PersistentVolume.Spec.AzureFile.SecretName
  311. } else {
  312. return "", "", fmt.Errorf("Spec does not reference an AzureFile volume type")
  313. }
  314. if len(secretNamespace) == 0 {
  315. return "", "", fmt.Errorf("invalid Azure volume: nil namespace")
  316. }
  317. return secretName, secretNamespace, nil
  318. }
  319. func getAzureCloud(cloudProvider cloudprovider.Interface) (*azure.Cloud, error) {
  320. azure, ok := cloudProvider.(*azure.Cloud)
  321. if !ok || azure == nil {
  322. return nil, fmt.Errorf("Failed to get Azure Cloud Provider. GetCloudProvider returned %v instead", cloudProvider)
  323. }
  324. return azure, nil
  325. }
  326. func getStorageEndpointSuffix(cloudprovider cloudprovider.Interface) string {
  327. const publicCloudStorageEndpointSuffix = "core.windows.net"
  328. azure, err := getAzureCloud(cloudprovider)
  329. if err != nil {
  330. klog.Warningf("No Azure cloud provider found. Using the Azure public cloud endpoint: %s", publicCloudStorageEndpointSuffix)
  331. return publicCloudStorageEndpointSuffix
  332. }
  333. return azure.Environment.StorageEndpointSuffix
  334. }