sio_client.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. /*
  2. Copyright 2017 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 scaleio
  14. import (
  15. "errors"
  16. "fmt"
  17. "io/ioutil"
  18. "os"
  19. "path/filepath"
  20. "regexp"
  21. "strconv"
  22. "strings"
  23. "sync"
  24. "time"
  25. "k8s.io/kubernetes/pkg/util/mount"
  26. sio "github.com/codedellemc/goscaleio"
  27. siotypes "github.com/codedellemc/goscaleio/types/v1"
  28. "k8s.io/klog"
  29. )
  30. var (
  31. sioDiskIDPath = "/dev/disk/by-id"
  32. )
  33. type sioVolumeID string
  34. type sioInterface interface {
  35. FindVolume(name string) (*siotypes.Volume, error)
  36. Volume(sioVolumeID) (*siotypes.Volume, error)
  37. CreateVolume(name string, sizeGB int64) (*siotypes.Volume, error)
  38. AttachVolume(sioVolumeID, bool) error
  39. DetachVolume(sioVolumeID) error
  40. DeleteVolume(sioVolumeID) error
  41. IID() (string, error)
  42. Devs() (map[string]string, error)
  43. WaitForAttachedDevice(token string) (string, error)
  44. WaitForDetachedDevice(token string) error
  45. GetVolumeRefs(sioVolumeID) (int, error)
  46. }
  47. type sioClient struct {
  48. client *sio.Client
  49. gateway string
  50. username string
  51. password string
  52. insecure bool
  53. certsEnabled bool
  54. system *siotypes.System
  55. sysName string
  56. sysClient *sio.System
  57. protectionDomain *siotypes.ProtectionDomain
  58. pdName string
  59. pdClient *sio.ProtectionDomain
  60. storagePool *siotypes.StoragePool
  61. spName string
  62. spClient *sio.StoragePool
  63. provisionMode string
  64. sdcPath string
  65. sdcGUID string
  66. instanceID string
  67. inited bool
  68. diskRegex *regexp.Regexp
  69. mtx sync.Mutex
  70. exec mount.Exec
  71. }
  72. func newSioClient(gateway, username, password string, sslEnabled bool, exec mount.Exec) (*sioClient, error) {
  73. client := new(sioClient)
  74. client.gateway = gateway
  75. client.username = username
  76. client.password = password
  77. client.exec = exec
  78. if sslEnabled {
  79. client.insecure = false
  80. client.certsEnabled = true
  81. } else {
  82. client.insecure = true
  83. client.certsEnabled = false
  84. }
  85. r, err := regexp.Compile(`^emc-vol-\w*-\w*$`)
  86. if err != nil {
  87. klog.Error(log("failed to compile regex: %v", err))
  88. return nil, err
  89. }
  90. client.diskRegex = r
  91. // delay client setup/login until init()
  92. return client, nil
  93. }
  94. // init setups client and authenticate
  95. func (c *sioClient) init() error {
  96. c.mtx.Lock()
  97. defer c.mtx.Unlock()
  98. if c.inited {
  99. return nil
  100. }
  101. klog.V(4).Infoln(log("initializing scaleio client"))
  102. client, err := sio.NewClientWithArgs(c.gateway, "", c.insecure, c.certsEnabled)
  103. if err != nil {
  104. klog.Error(log("failed to create client: %v", err))
  105. return err
  106. }
  107. c.client = client
  108. if _, err = c.client.Authenticate(
  109. &sio.ConfigConnect{
  110. Endpoint: c.gateway,
  111. Version: "",
  112. Username: c.username,
  113. Password: c.password},
  114. ); err != nil {
  115. klog.Error(log("client authentication failed: %v", err))
  116. return err
  117. }
  118. // retrieve system
  119. if c.system, err = c.findSystem(c.sysName); err != nil {
  120. klog.Error(log("unable to find system %s: %v", c.sysName, err))
  121. return err
  122. }
  123. // retrieve protection domain
  124. if c.protectionDomain, err = c.findProtectionDomain(c.pdName); err != nil {
  125. klog.Error(log("unable to find protection domain %s: %v", c.protectionDomain, err))
  126. return err
  127. }
  128. // retrieve storage pool
  129. if c.storagePool, err = c.findStoragePool(c.spName); err != nil {
  130. klog.Error(log("unable to find storage pool %s: %v", c.storagePool, err))
  131. return err
  132. }
  133. c.inited = true
  134. return nil
  135. }
  136. func (c *sioClient) Volumes() ([]*siotypes.Volume, error) {
  137. if err := c.init(); err != nil {
  138. return nil, err
  139. }
  140. vols, err := c.getVolumes()
  141. if err != nil {
  142. klog.Error(log("failed to retrieve volumes: %v", err))
  143. return nil, err
  144. }
  145. return vols, nil
  146. }
  147. func (c *sioClient) Volume(id sioVolumeID) (*siotypes.Volume, error) {
  148. if err := c.init(); err != nil {
  149. return nil, err
  150. }
  151. vols, err := c.getVolumesByID(id)
  152. if err != nil {
  153. klog.Error(log("failed to retrieve volume by id: %v", err))
  154. return nil, err
  155. }
  156. vol := vols[0]
  157. if vol == nil {
  158. klog.V(4).Info(log("volume not found, id %s", id))
  159. return nil, errors.New("volume not found")
  160. }
  161. return vol, nil
  162. }
  163. func (c *sioClient) FindVolume(name string) (*siotypes.Volume, error) {
  164. if err := c.init(); err != nil {
  165. return nil, err
  166. }
  167. klog.V(4).Info(log("searching for volume %s", name))
  168. volumes, err := c.getVolumesByName(name)
  169. if err != nil {
  170. klog.Error(log("failed to find volume by name %v", err))
  171. return nil, err
  172. }
  173. for _, volume := range volumes {
  174. if volume.Name == name {
  175. klog.V(4).Info(log("found volume %s", name))
  176. return volume, nil
  177. }
  178. }
  179. klog.V(4).Info(log("volume not found, name %s", name))
  180. return nil, errors.New("volume not found")
  181. }
  182. func (c *sioClient) CreateVolume(name string, sizeGB int64) (*siotypes.Volume, error) {
  183. if err := c.init(); err != nil {
  184. return nil, err
  185. }
  186. params := &siotypes.VolumeParam{
  187. Name: name,
  188. VolumeSizeInKb: strconv.Itoa(int(sizeGB) * 1024 * 1024),
  189. VolumeType: c.provisionMode,
  190. }
  191. createResponse, err := c.client.CreateVolume(params, c.storagePool.Name)
  192. if err != nil {
  193. klog.Error(log("failed to create volume %s: %v", name, err))
  194. return nil, err
  195. }
  196. return c.Volume(sioVolumeID(createResponse.ID))
  197. }
  198. // AttachVolume maps the scaleio volume to an sdc node. If the multipleMappings flag
  199. // is true, ScaleIO will allow other SDC to map to that volume.
  200. func (c *sioClient) AttachVolume(id sioVolumeID, multipleMappings bool) error {
  201. if err := c.init(); err != nil {
  202. klog.Error(log("failed to init'd client in attach volume: %v", err))
  203. return err
  204. }
  205. iid, err := c.IID()
  206. if err != nil {
  207. klog.Error(log("failed to get instanceIID for attach volume: %v", err))
  208. return err
  209. }
  210. params := &siotypes.MapVolumeSdcParam{
  211. SdcID: iid,
  212. AllowMultipleMappings: strconv.FormatBool(multipleMappings),
  213. AllSdcs: "",
  214. }
  215. volClient := sio.NewVolume(c.client)
  216. volClient.Volume = &siotypes.Volume{ID: string(id)}
  217. if err := volClient.MapVolumeSdc(params); err != nil {
  218. klog.Error(log("failed to attach volume id %s: %v", id, err))
  219. return err
  220. }
  221. klog.V(4).Info(log("volume %s attached successfully", id))
  222. return nil
  223. }
  224. // DetachVolume detaches the volume with specified id.
  225. func (c *sioClient) DetachVolume(id sioVolumeID) error {
  226. if err := c.init(); err != nil {
  227. return err
  228. }
  229. iid, err := c.IID()
  230. if err != nil {
  231. return err
  232. }
  233. params := &siotypes.UnmapVolumeSdcParam{
  234. SdcID: "",
  235. IgnoreScsiInitiators: "true",
  236. AllSdcs: iid,
  237. }
  238. volClient := sio.NewVolume(c.client)
  239. volClient.Volume = &siotypes.Volume{ID: string(id)}
  240. if err := volClient.UnmapVolumeSdc(params); err != nil {
  241. return err
  242. }
  243. return nil
  244. }
  245. // DeleteVolume deletes the volume with the specified id
  246. func (c *sioClient) DeleteVolume(id sioVolumeID) error {
  247. if err := c.init(); err != nil {
  248. return err
  249. }
  250. vol, err := c.Volume(id)
  251. if err != nil {
  252. return err
  253. }
  254. volClient := sio.NewVolume(c.client)
  255. volClient.Volume = vol
  256. if err := volClient.RemoveVolume("ONLY_ME"); err != nil {
  257. return err
  258. }
  259. return nil
  260. }
  261. // IID returns the scaleio instance id for node
  262. func (c *sioClient) IID() (string, error) {
  263. if err := c.init(); err != nil {
  264. return "", err
  265. }
  266. // if instanceID not set, retrieve it
  267. if c.instanceID == "" {
  268. guid, err := c.getGUID()
  269. if err != nil {
  270. return "", err
  271. }
  272. sdc, err := c.sysClient.FindSdc("SdcGUID", guid)
  273. if err != nil {
  274. klog.Error(log("failed to retrieve sdc info %s", err))
  275. return "", err
  276. }
  277. c.instanceID = sdc.Sdc.ID
  278. klog.V(4).Info(log("retrieved instanceID %s", c.instanceID))
  279. }
  280. return c.instanceID, nil
  281. }
  282. // getGUID returns instance GUID, if not set using resource labels
  283. // it attempts to fallback to using drv_cfg binary
  284. func (c *sioClient) getGUID() (string, error) {
  285. if c.sdcGUID == "" {
  286. klog.V(4).Info(log("sdc guid label not set, falling back to using drv_cfg"))
  287. cmd := c.getSdcCmd()
  288. output, err := c.exec.Run(cmd, "--query_guid")
  289. if err != nil {
  290. klog.Error(log("drv_cfg --query_guid failed: %v", err))
  291. return "", err
  292. }
  293. c.sdcGUID = strings.TrimSpace(string(output))
  294. }
  295. return c.sdcGUID, nil
  296. }
  297. // getSioDiskPaths traverse local disk devices to retrieve device path
  298. // The path is extracted from /dev/disk/by-id; each sio device path has format:
  299. // emc-vol-<mdmID-volID> e.g.:
  300. // emc-vol-788d9efb0a8f20cb-a2b8419300000000
  301. func (c *sioClient) getSioDiskPaths() ([]os.FileInfo, error) {
  302. files, err := ioutil.ReadDir(sioDiskIDPath)
  303. if err != nil {
  304. if os.IsNotExist(err) {
  305. // sioDiskIDPath may not exist yet which is fine
  306. return []os.FileInfo{}, nil
  307. }
  308. klog.Error(log("failed to ReadDir %s: %v", sioDiskIDPath, err))
  309. return nil, err
  310. }
  311. result := []os.FileInfo{}
  312. for _, file := range files {
  313. if c.diskRegex.MatchString(file.Name()) {
  314. result = append(result, file)
  315. }
  316. }
  317. return result, nil
  318. }
  319. // GetVolumeRefs counts the number of references an SIO volume has a disk device.
  320. // This is useful in preventing premature detach.
  321. func (c *sioClient) GetVolumeRefs(volID sioVolumeID) (refs int, err error) {
  322. files, err := c.getSioDiskPaths()
  323. if err != nil {
  324. return 0, err
  325. }
  326. for _, file := range files {
  327. if strings.Contains(file.Name(), string(volID)) {
  328. refs++
  329. }
  330. }
  331. return
  332. }
  333. // Devs returns a map of local devices as map[<volume.id>]<deviceName>
  334. func (c *sioClient) Devs() (map[string]string, error) {
  335. volumeMap := make(map[string]string)
  336. files, err := c.getSioDiskPaths()
  337. if err != nil {
  338. return nil, err
  339. }
  340. for _, f := range files {
  341. // split emc-vol-<mdmID>-<volumeID> to pull out volumeID
  342. parts := strings.Split(f.Name(), "-")
  343. if len(parts) != 4 {
  344. return nil, errors.New("unexpected ScaleIO device name format")
  345. }
  346. volumeID := parts[3]
  347. devPath, err := filepath.EvalSymlinks(fmt.Sprintf("%s/%s", sioDiskIDPath, f.Name()))
  348. if err != nil {
  349. klog.Error(log("devicepath-to-volID mapping error: %v", err))
  350. return nil, err
  351. }
  352. // map volumeID to devicePath
  353. volumeMap[volumeID] = devPath
  354. }
  355. return volumeMap, nil
  356. }
  357. // WaitForAttachedDevice sets up a timer to wait for an attached device to appear in the instance's list.
  358. func (c *sioClient) WaitForAttachedDevice(token string) (string, error) {
  359. if token == "" {
  360. return "", fmt.Errorf("invalid attach token")
  361. }
  362. // wait for device to show up in local device list
  363. ticker := time.NewTicker(time.Second)
  364. defer ticker.Stop()
  365. timer := time.NewTimer(30 * time.Second)
  366. defer timer.Stop()
  367. for {
  368. select {
  369. case <-ticker.C:
  370. devMap, err := c.Devs()
  371. if err != nil {
  372. klog.Error(log("failed while waiting for volume to attach: %v", err))
  373. return "", err
  374. }
  375. go func() {
  376. klog.V(4).Info(log("waiting for volume %s to be mapped/attached", token))
  377. }()
  378. if path, ok := devMap[token]; ok {
  379. klog.V(4).Info(log("device %s mapped to vol %s", path, token))
  380. return path, nil
  381. }
  382. case <-timer.C:
  383. klog.Error(log("timed out while waiting for volume to be mapped to a device"))
  384. return "", fmt.Errorf("volume attach timeout")
  385. }
  386. }
  387. }
  388. // waitForDetachedDevice waits for device to be detached
  389. func (c *sioClient) WaitForDetachedDevice(token string) error {
  390. if token == "" {
  391. return fmt.Errorf("invalid detach token")
  392. }
  393. // wait for attach.Token to show up in local device list
  394. ticker := time.NewTicker(time.Second)
  395. defer ticker.Stop()
  396. timer := time.NewTimer(30 * time.Second)
  397. defer timer.Stop()
  398. for {
  399. select {
  400. case <-ticker.C:
  401. devMap, err := c.Devs()
  402. if err != nil {
  403. klog.Error(log("failed while waiting for volume to unmap/detach: %v", err))
  404. return err
  405. }
  406. go func() {
  407. klog.V(4).Info(log("waiting for volume %s to be unmapped/detached", token))
  408. }()
  409. // cant find vol id, then ok.
  410. if _, ok := devMap[token]; !ok {
  411. return nil
  412. }
  413. case <-timer.C:
  414. klog.Error(log("timed out while waiting for volume %s to be unmapped/detached", token))
  415. return fmt.Errorf("volume detach timeout")
  416. }
  417. }
  418. }
  419. // ***********************************************************************
  420. // Little Helpers!
  421. // ***********************************************************************
  422. func (c *sioClient) findSystem(sysname string) (sys *siotypes.System, err error) {
  423. if c.sysClient, err = c.client.FindSystem("", sysname, ""); err != nil {
  424. return nil, err
  425. }
  426. systems, err := c.client.GetInstance("")
  427. if err != nil {
  428. klog.Error(log("failed to retrieve instances: %v", err))
  429. return nil, err
  430. }
  431. for _, sys = range systems {
  432. if sys.Name == sysname {
  433. return sys, nil
  434. }
  435. }
  436. klog.Error(log("system %s not found", sysname))
  437. return nil, errors.New("system not found")
  438. }
  439. func (c *sioClient) findProtectionDomain(pdname string) (*siotypes.ProtectionDomain, error) {
  440. c.pdClient = sio.NewProtectionDomain(c.client)
  441. if c.sysClient != nil {
  442. protectionDomain, err := c.sysClient.FindProtectionDomain("", pdname, "")
  443. if err != nil {
  444. klog.Error(log("failed to retrieve protection domains: %v", err))
  445. return nil, err
  446. }
  447. c.pdClient.ProtectionDomain = protectionDomain
  448. return protectionDomain, nil
  449. }
  450. klog.Error(log("protection domain %s not set", pdname))
  451. return nil, errors.New("protection domain not set")
  452. }
  453. func (c *sioClient) findStoragePool(spname string) (*siotypes.StoragePool, error) {
  454. c.spClient = sio.NewStoragePool(c.client)
  455. if c.pdClient != nil {
  456. sp, err := c.pdClient.FindStoragePool("", spname, "")
  457. if err != nil {
  458. klog.Error(log("failed to retrieve storage pool: %v", err))
  459. return nil, err
  460. }
  461. c.spClient.StoragePool = sp
  462. return sp, nil
  463. }
  464. klog.Error(log("storage pool %s not set", spname))
  465. return nil, errors.New("storage pool not set")
  466. }
  467. func (c *sioClient) getVolumes() ([]*siotypes.Volume, error) {
  468. return c.client.GetVolume("", "", "", "", true)
  469. }
  470. func (c *sioClient) getVolumesByID(id sioVolumeID) ([]*siotypes.Volume, error) {
  471. return c.client.GetVolume("", string(id), "", "", true)
  472. }
  473. func (c *sioClient) getVolumesByName(name string) ([]*siotypes.Volume, error) {
  474. return c.client.GetVolume("", "", "", name, true)
  475. }
  476. func (c *sioClient) getSdcPath() string {
  477. return sdcRootPath
  478. }
  479. func (c *sioClient) getSdcCmd() string {
  480. return filepath.Join(c.getSdcPath(), "drv_cfg")
  481. }