fc.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. /*
  2. Copyright 2015 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 fc
  14. import (
  15. "fmt"
  16. "os"
  17. "path/filepath"
  18. "strconv"
  19. "strings"
  20. "k8s.io/klog"
  21. utilexec "k8s.io/utils/exec"
  22. "k8s.io/utils/mount"
  23. utilstrings "k8s.io/utils/strings"
  24. v1 "k8s.io/api/core/v1"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. "k8s.io/apimachinery/pkg/types"
  27. utilfeature "k8s.io/apiserver/pkg/util/feature"
  28. "k8s.io/kubernetes/pkg/features"
  29. "k8s.io/kubernetes/pkg/volume"
  30. "k8s.io/kubernetes/pkg/volume/util"
  31. "k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
  32. )
  33. // ProbeVolumePlugins is the primary entrypoint for volume plugins.
  34. func ProbeVolumePlugins() []volume.VolumePlugin {
  35. return []volume.VolumePlugin{&fcPlugin{nil}}
  36. }
  37. type fcPlugin struct {
  38. host volume.VolumeHost
  39. }
  40. var _ volume.VolumePlugin = &fcPlugin{}
  41. var _ volume.PersistentVolumePlugin = &fcPlugin{}
  42. var _ volume.BlockVolumePlugin = &fcPlugin{}
  43. const (
  44. fcPluginName = "kubernetes.io/fc"
  45. )
  46. func (plugin *fcPlugin) Init(host volume.VolumeHost) error {
  47. plugin.host = host
  48. return nil
  49. }
  50. func (plugin *fcPlugin) GetPluginName() string {
  51. return fcPluginName
  52. }
  53. func (plugin *fcPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
  54. volumeSource, _, err := getVolumeSource(spec)
  55. if err != nil {
  56. return "", err
  57. }
  58. // API server validates these parameters beforehand but attach/detach
  59. // controller creates volumespec without validation. They may be nil
  60. // or zero length. We should check again to avoid unexpected conditions.
  61. if len(volumeSource.TargetWWNs) != 0 && volumeSource.Lun != nil {
  62. // TargetWWNs are the FibreChannel target worldwide names
  63. return fmt.Sprintf("%v:%v", volumeSource.TargetWWNs, *volumeSource.Lun), nil
  64. } else if len(volumeSource.WWIDs) != 0 {
  65. // WWIDs are the FibreChannel World Wide Identifiers
  66. return fmt.Sprintf("%v", volumeSource.WWIDs), nil
  67. }
  68. return "", err
  69. }
  70. func (plugin *fcPlugin) CanSupport(spec *volume.Spec) bool {
  71. return (spec.Volume != nil && spec.Volume.FC != nil) || (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.FC != nil)
  72. }
  73. func (plugin *fcPlugin) RequiresRemount() bool {
  74. return false
  75. }
  76. func (plugin *fcPlugin) SupportsMountOption() bool {
  77. return true
  78. }
  79. func (plugin *fcPlugin) SupportsBulkVolumeVerification() bool {
  80. return false
  81. }
  82. func (plugin *fcPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
  83. return []v1.PersistentVolumeAccessMode{
  84. v1.ReadWriteOnce,
  85. v1.ReadOnlyMany,
  86. }
  87. }
  88. func (plugin *fcPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
  89. // Inject real implementations here, test through the internal function.
  90. return plugin.newMounterInternal(spec, pod.UID, &fcUtil{}, plugin.host.GetMounter(plugin.GetPluginName()), plugin.host.GetExec(plugin.GetPluginName()))
  91. }
  92. func (plugin *fcPlugin) newMounterInternal(spec *volume.Spec, podUID types.UID, manager diskManager, mounter mount.Interface, exec utilexec.Interface) (volume.Mounter, error) {
  93. // fc volumes used directly in a pod have a ReadOnly flag set by the pod author.
  94. // fc volumes used as a PersistentVolume gets the ReadOnly flag indirectly through the persistent-claim volume used to mount the PV
  95. fc, readOnly, err := getVolumeSource(spec)
  96. if err != nil {
  97. return nil, err
  98. }
  99. wwns, lun, wwids, err := getWwnsLunWwids(fc)
  100. if err != nil {
  101. return nil, fmt.Errorf("fc: no fc disk information found. failed to make a new mounter")
  102. }
  103. fcDisk := &fcDisk{
  104. podUID: podUID,
  105. volName: spec.Name(),
  106. wwns: wwns,
  107. lun: lun,
  108. wwids: wwids,
  109. manager: manager,
  110. io: &osIOHandler{},
  111. plugin: plugin,
  112. }
  113. // TODO: remove feature gate check after no longer needed
  114. if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
  115. volumeMode, err := util.GetVolumeMode(spec)
  116. if err != nil {
  117. return nil, err
  118. }
  119. klog.V(5).Infof("fc: newMounterInternal volumeMode %s", volumeMode)
  120. return &fcDiskMounter{
  121. fcDisk: fcDisk,
  122. fsType: fc.FSType,
  123. volumeMode: volumeMode,
  124. readOnly: readOnly,
  125. mounter: &mount.SafeFormatAndMount{Interface: mounter, Exec: exec},
  126. deviceUtil: util.NewDeviceHandler(util.NewIOHandler()),
  127. mountOptions: util.MountOptionFromSpec(spec),
  128. }, nil
  129. }
  130. return &fcDiskMounter{
  131. fcDisk: fcDisk,
  132. fsType: fc.FSType,
  133. readOnly: readOnly,
  134. mounter: &mount.SafeFormatAndMount{Interface: mounter, Exec: exec},
  135. deviceUtil: util.NewDeviceHandler(util.NewIOHandler()),
  136. mountOptions: util.MountOptionFromSpec(spec),
  137. }, nil
  138. }
  139. func (plugin *fcPlugin) NewBlockVolumeMapper(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.BlockVolumeMapper, error) {
  140. // If this called via GenerateUnmapDeviceFunc(), pod is nil.
  141. // Pass empty string as dummy uid since uid isn't used in the case.
  142. var uid types.UID
  143. if pod != nil {
  144. uid = pod.UID
  145. }
  146. return plugin.newBlockVolumeMapperInternal(spec, uid, &fcUtil{}, plugin.host.GetMounter(plugin.GetPluginName()), plugin.host.GetExec(plugin.GetPluginName()))
  147. }
  148. func (plugin *fcPlugin) newBlockVolumeMapperInternal(spec *volume.Spec, podUID types.UID, manager diskManager, mounter mount.Interface, exec utilexec.Interface) (volume.BlockVolumeMapper, error) {
  149. fc, readOnly, err := getVolumeSource(spec)
  150. if err != nil {
  151. return nil, err
  152. }
  153. wwns, lun, wwids, err := getWwnsLunWwids(fc)
  154. if err != nil {
  155. return nil, fmt.Errorf("fc: no fc disk information found. failed to make a new mapper")
  156. }
  157. return &fcDiskMapper{
  158. fcDisk: &fcDisk{
  159. podUID: podUID,
  160. volName: spec.Name(),
  161. wwns: wwns,
  162. lun: lun,
  163. wwids: wwids,
  164. manager: manager,
  165. io: &osIOHandler{},
  166. plugin: plugin},
  167. readOnly: readOnly,
  168. mounter: &mount.SafeFormatAndMount{Interface: mounter, Exec: exec},
  169. deviceUtil: util.NewDeviceHandler(util.NewIOHandler()),
  170. }, nil
  171. }
  172. func (plugin *fcPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
  173. // Inject real implementations here, test through the internal function.
  174. return plugin.newUnmounterInternal(volName, podUID, &fcUtil{}, plugin.host.GetMounter(plugin.GetPluginName()))
  175. }
  176. func (plugin *fcPlugin) newUnmounterInternal(volName string, podUID types.UID, manager diskManager, mounter mount.Interface) (volume.Unmounter, error) {
  177. return &fcDiskUnmounter{
  178. fcDisk: &fcDisk{
  179. podUID: podUID,
  180. volName: volName,
  181. manager: manager,
  182. plugin: plugin,
  183. io: &osIOHandler{},
  184. },
  185. mounter: mounter,
  186. deviceUtil: util.NewDeviceHandler(util.NewIOHandler()),
  187. }, nil
  188. }
  189. func (plugin *fcPlugin) NewBlockVolumeUnmapper(volName string, podUID types.UID) (volume.BlockVolumeUnmapper, error) {
  190. return plugin.newUnmapperInternal(volName, podUID, &fcUtil{})
  191. }
  192. func (plugin *fcPlugin) newUnmapperInternal(volName string, podUID types.UID, manager diskManager) (volume.BlockVolumeUnmapper, error) {
  193. return &fcDiskUnmapper{
  194. fcDisk: &fcDisk{
  195. podUID: podUID,
  196. volName: volName,
  197. manager: manager,
  198. plugin: plugin,
  199. io: &osIOHandler{},
  200. },
  201. deviceUtil: util.NewDeviceHandler(util.NewIOHandler()),
  202. }, nil
  203. }
  204. func (plugin *fcPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
  205. // Find globalPDPath from pod volume directory(mountPath)
  206. // examples:
  207. // mountPath: pods/{podUid}/volumes/kubernetes.io~fc/{volumeName}
  208. // globalPDPath : plugins/kubernetes.io/fc/50060e801049cfd1-lun-0
  209. var globalPDPath string
  210. mounter := plugin.host.GetMounter(plugin.GetPluginName())
  211. paths, err := mounter.GetMountRefs(mountPath)
  212. if err != nil {
  213. return nil, err
  214. }
  215. for _, path := range paths {
  216. if strings.Contains(path, plugin.host.GetPluginDir(fcPluginName)) {
  217. globalPDPath = path
  218. break
  219. }
  220. }
  221. // Couldn't fetch globalPDPath
  222. if len(globalPDPath) == 0 {
  223. return nil, fmt.Errorf("couldn't fetch globalPDPath. failed to obtain volume spec")
  224. }
  225. wwns, lun, wwids, err := parsePDName(globalPDPath)
  226. if err != nil {
  227. return nil, fmt.Errorf("failed to retrieve volume plugin information from globalPDPath: %s", err)
  228. }
  229. // Create volume from wwn+lun or wwid
  230. fcVolume := &v1.Volume{
  231. Name: volumeName,
  232. VolumeSource: v1.VolumeSource{
  233. FC: &v1.FCVolumeSource{WWIDs: wwids, Lun: &lun, TargetWWNs: wwns},
  234. },
  235. }
  236. klog.V(5).Infof("ConstructVolumeSpec: TargetWWNs: %v, Lun: %v, WWIDs: %v",
  237. fcVolume.VolumeSource.FC.TargetWWNs, *fcVolume.VolumeSource.FC.Lun, fcVolume.VolumeSource.FC.WWIDs)
  238. return volume.NewSpecFromVolume(fcVolume), nil
  239. }
  240. // ConstructBlockVolumeSpec creates a new volume.Spec with following steps.
  241. // - Searches a file whose name is {pod uuid} under volume plugin directory.
  242. // - If a file is found, then retreives volumePluginDependentPath from globalMapPathUUID.
  243. // - Once volumePluginDependentPath is obtained, store volume information to VolumeSource
  244. // examples:
  245. // mapPath: pods/{podUid}/{DefaultKubeletVolumeDevicesDirName}/{escapeQualifiedPluginName}/{volumeName}
  246. // globalMapPathUUID : plugins/kubernetes.io/{PluginName}/{DefaultKubeletVolumeDevicesDirName}/{volumePluginDependentPath}/{pod uuid}
  247. func (plugin *fcPlugin) ConstructBlockVolumeSpec(podUID types.UID, volumeName, mapPath string) (*volume.Spec, error) {
  248. pluginDir := plugin.host.GetVolumeDevicePluginDir(fcPluginName)
  249. blkutil := volumepathhandler.NewBlockVolumePathHandler()
  250. globalMapPathUUID, err := blkutil.FindGlobalMapPathUUIDFromPod(pluginDir, mapPath, podUID)
  251. if err != nil {
  252. return nil, err
  253. }
  254. klog.V(5).Infof("globalMapPathUUID: %v, err: %v", globalMapPathUUID, err)
  255. // Retrieve globalPDPath from globalMapPathUUID
  256. // globalMapPathUUID examples:
  257. // wwn+lun: plugins/kubernetes.io/fc/volumeDevices/50060e801049cfd1-lun-0/{pod uuid}
  258. // wwid: plugins/kubernetes.io/fc/volumeDevices/3600508b400105e210000900000490000/{pod uuid}
  259. globalPDPath := filepath.Dir(globalMapPathUUID)
  260. // Create volume from wwn+lun or wwid
  261. wwns, lun, wwids, err := parsePDName(globalPDPath)
  262. if err != nil {
  263. return nil, fmt.Errorf("failed to retrieve volume plugin information from globalPDPath: %s", err)
  264. }
  265. fcPV := createPersistentVolumeFromFCVolumeSource(volumeName,
  266. v1.FCVolumeSource{TargetWWNs: wwns, Lun: &lun, WWIDs: wwids})
  267. klog.V(5).Infof("ConstructBlockVolumeSpec: TargetWWNs: %v, Lun: %v, WWIDs: %v",
  268. fcPV.Spec.PersistentVolumeSource.FC.TargetWWNs,
  269. *fcPV.Spec.PersistentVolumeSource.FC.Lun,
  270. fcPV.Spec.PersistentVolumeSource.FC.WWIDs)
  271. return volume.NewSpecFromPersistentVolume(fcPV, false), nil
  272. }
  273. type fcDisk struct {
  274. volName string
  275. podUID types.UID
  276. wwns []string
  277. lun string
  278. wwids []string
  279. plugin *fcPlugin
  280. // Utility interface that provides API calls to the provider to attach/detach disks.
  281. manager diskManager
  282. // io handler interface
  283. io ioHandler
  284. volume.MetricsNil
  285. }
  286. func (fc *fcDisk) GetPath() string {
  287. // safe to use PodVolumeDir now: volume teardown occurs before pod is cleaned up
  288. return fc.plugin.host.GetPodVolumeDir(fc.podUID, utilstrings.EscapeQualifiedName(fcPluginName), fc.volName)
  289. }
  290. func (fc *fcDisk) fcGlobalMapPath(spec *volume.Spec) (string, error) {
  291. mounter, err := volumeSpecToMounter(spec, fc.plugin.host)
  292. if err != nil {
  293. klog.Warningf("failed to get fc mounter: %v", err)
  294. return "", err
  295. }
  296. return fc.manager.MakeGlobalVDPDName(*mounter.fcDisk), nil
  297. }
  298. func (fc *fcDisk) fcPodDeviceMapPath() (string, string) {
  299. return fc.plugin.host.GetPodVolumeDeviceDir(fc.podUID, utilstrings.EscapeQualifiedName(fcPluginName)), fc.volName
  300. }
  301. type fcDiskMounter struct {
  302. *fcDisk
  303. readOnly bool
  304. fsType string
  305. volumeMode v1.PersistentVolumeMode
  306. mounter *mount.SafeFormatAndMount
  307. deviceUtil util.DeviceUtil
  308. mountOptions []string
  309. }
  310. var _ volume.Mounter = &fcDiskMounter{}
  311. func (b *fcDiskMounter) GetAttributes() volume.Attributes {
  312. return volume.Attributes{
  313. ReadOnly: b.readOnly,
  314. Managed: !b.readOnly,
  315. SupportsSELinux: true,
  316. }
  317. }
  318. // Checks prior to mount operations to verify that the required components (binaries, etc.)
  319. // to mount the volume are available on the underlying node.
  320. // If not, it returns an error
  321. func (b *fcDiskMounter) CanMount() error {
  322. return nil
  323. }
  324. func (b *fcDiskMounter) SetUp(mounterArgs volume.MounterArgs) error {
  325. return b.SetUpAt(b.GetPath(), mounterArgs)
  326. }
  327. func (b *fcDiskMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
  328. // diskSetUp checks mountpoints and prevent repeated calls
  329. err := diskSetUp(b.manager, *b, dir, b.mounter, mounterArgs.FsGroup)
  330. if err != nil {
  331. klog.Errorf("fc: failed to setup")
  332. }
  333. return err
  334. }
  335. type fcDiskUnmounter struct {
  336. *fcDisk
  337. mounter mount.Interface
  338. deviceUtil util.DeviceUtil
  339. }
  340. var _ volume.Unmounter = &fcDiskUnmounter{}
  341. // Unmounts the bind mount, and detaches the disk only if the disk
  342. // resource was the last reference to that disk on the kubelet.
  343. func (c *fcDiskUnmounter) TearDown() error {
  344. return c.TearDownAt(c.GetPath())
  345. }
  346. func (c *fcDiskUnmounter) TearDownAt(dir string) error {
  347. return mount.CleanupMountPoint(dir, c.mounter, false)
  348. }
  349. // Block Volumes Support
  350. type fcDiskMapper struct {
  351. *fcDisk
  352. readOnly bool
  353. mounter mount.Interface
  354. deviceUtil util.DeviceUtil
  355. }
  356. var _ volume.BlockVolumeMapper = &fcDiskMapper{}
  357. type fcDiskUnmapper struct {
  358. *fcDisk
  359. deviceUtil util.DeviceUtil
  360. }
  361. var _ volume.BlockVolumeUnmapper = &fcDiskUnmapper{}
  362. var _ volume.CustomBlockVolumeUnmapper = &fcDiskUnmapper{}
  363. func (c *fcDiskUnmapper) TearDownDevice(mapPath, devicePath string) error {
  364. err := c.manager.DetachBlockFCDisk(*c, mapPath, devicePath)
  365. if err != nil {
  366. return fmt.Errorf("fc: failed to detach disk: %s\nError: %v", mapPath, err)
  367. }
  368. klog.V(4).Infof("fc: %s is unmounted, deleting the directory", mapPath)
  369. if err = os.RemoveAll(mapPath); err != nil {
  370. return fmt.Errorf("fc: failed to delete the directory: %s\nError: %v", mapPath, err)
  371. }
  372. klog.V(4).Infof("fc: successfully detached disk: %s", mapPath)
  373. return nil
  374. }
  375. func (c *fcDiskUnmapper) UnmapPodDevice() error {
  376. return nil
  377. }
  378. // GetGlobalMapPath returns global map path and error
  379. // path: plugins/kubernetes.io/{PluginName}/volumeDevices/{WWID}/{podUid}
  380. func (fc *fcDisk) GetGlobalMapPath(spec *volume.Spec) (string, error) {
  381. return fc.fcGlobalMapPath(spec)
  382. }
  383. // GetPodDeviceMapPath returns pod device map path and volume name
  384. // path: pods/{podUid}/volumeDevices/kubernetes.io~fc
  385. // volumeName: pv0001
  386. func (fc *fcDisk) GetPodDeviceMapPath() (string, string) {
  387. return fc.fcPodDeviceMapPath()
  388. }
  389. func getVolumeSource(spec *volume.Spec) (*v1.FCVolumeSource, bool, error) {
  390. // fc volumes used directly in a pod have a ReadOnly flag set by the pod author.
  391. // fc volumes used as a PersistentVolume gets the ReadOnly flag indirectly through the persistent-claim volume used to mount the PV
  392. if spec.Volume != nil && spec.Volume.FC != nil {
  393. return spec.Volume.FC, spec.Volume.FC.ReadOnly, nil
  394. } else if spec.PersistentVolume != nil &&
  395. spec.PersistentVolume.Spec.FC != nil {
  396. return spec.PersistentVolume.Spec.FC, spec.ReadOnly, nil
  397. }
  398. return nil, false, fmt.Errorf("Spec does not reference a FibreChannel volume type")
  399. }
  400. func createPersistentVolumeFromFCVolumeSource(volumeName string, fc v1.FCVolumeSource) *v1.PersistentVolume {
  401. block := v1.PersistentVolumeBlock
  402. return &v1.PersistentVolume{
  403. ObjectMeta: metav1.ObjectMeta{
  404. Name: volumeName,
  405. },
  406. Spec: v1.PersistentVolumeSpec{
  407. PersistentVolumeSource: v1.PersistentVolumeSource{
  408. FC: &fc,
  409. },
  410. VolumeMode: &block,
  411. },
  412. }
  413. }
  414. func getWwnsLunWwids(fc *v1.FCVolumeSource) ([]string, string, []string, error) {
  415. var lun string
  416. var wwids []string
  417. if fc.Lun != nil && len(fc.TargetWWNs) != 0 {
  418. lun = strconv.Itoa(int(*fc.Lun))
  419. return fc.TargetWWNs, lun, wwids, nil
  420. }
  421. if len(fc.WWIDs) != 0 {
  422. for _, wwid := range fc.WWIDs {
  423. wwids = append(wwids, strings.Replace(wwid, " ", "_", -1))
  424. }
  425. return fc.TargetWWNs, lun, wwids, nil
  426. }
  427. return nil, "", nil, fmt.Errorf("fc: no fc disk information found")
  428. }