fc.go 16 KB

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