PageRenderTime 185ms CodeModel.GetById 55ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/k8s.io/kubernetes/pkg/cloudprovider/providers/openstack/openstack.go

https://gitlab.com/github-cloud-corporation/bootkube
Go | 752 lines | 595 code | 102 blank | 55 comment | 163 complexity | 72f577e6b5629c76383980e154126861 MD5 | raw file
  1. /*
  2. Copyright 2014 The Kubernetes Authors All rights reserved.
  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 openstack
  14. import (
  15. "encoding/json"
  16. "errors"
  17. "fmt"
  18. "io"
  19. "io/ioutil"
  20. "net/http"
  21. "path"
  22. "regexp"
  23. "strings"
  24. "time"
  25. "gopkg.in/gcfg.v1"
  26. "github.com/rackspace/gophercloud"
  27. "github.com/rackspace/gophercloud/openstack"
  28. "github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes"
  29. "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
  30. "github.com/rackspace/gophercloud/openstack/compute/v2/flavors"
  31. "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
  32. "github.com/rackspace/gophercloud/pagination"
  33. "github.com/golang/glog"
  34. "k8s.io/kubernetes/pkg/api"
  35. "k8s.io/kubernetes/pkg/api/resource"
  36. "k8s.io/kubernetes/pkg/cloudprovider"
  37. )
  38. const ProviderName = "openstack"
  39. // metadataUrl is URL to OpenStack metadata server. It's hadrcoded IPv4
  40. // link-local address as documented in "OpenStack Cloud Administrator Guide",
  41. // chapter Compute - Networking with nova-network.
  42. // http://docs.openstack.org/admin-guide-cloud/compute-networking-nova.html#metadata-service
  43. const metadataUrl = "http://169.254.169.254/openstack/2012-08-10/meta_data.json"
  44. var ErrNotFound = errors.New("Failed to find object")
  45. var ErrMultipleResults = errors.New("Multiple results where only one expected")
  46. var ErrNoAddressFound = errors.New("No address found for host")
  47. var ErrAttrNotFound = errors.New("Expected attribute not found")
  48. const (
  49. MiB = 1024 * 1024
  50. GB = 1000 * 1000 * 1000
  51. )
  52. // encoding.TextUnmarshaler interface for time.Duration
  53. type MyDuration struct {
  54. time.Duration
  55. }
  56. func (d *MyDuration) UnmarshalText(text []byte) error {
  57. res, err := time.ParseDuration(string(text))
  58. if err != nil {
  59. return err
  60. }
  61. d.Duration = res
  62. return nil
  63. }
  64. type LoadBalancer struct {
  65. network *gophercloud.ServiceClient
  66. compute *gophercloud.ServiceClient
  67. opts LoadBalancerOpts
  68. }
  69. type LoadBalancerOpts struct {
  70. LBVersion string `gcfg:"lb-version"` // v1 or v2
  71. SubnetId string `gcfg:"subnet-id"` // required
  72. FloatingNetworkId string `gcfg:"floating-network-id"`
  73. LBMethod string `gcfg:"lb-method"`
  74. CreateMonitor bool `gcfg:"create-monitor"`
  75. MonitorDelay MyDuration `gcfg:"monitor-delay"`
  76. MonitorTimeout MyDuration `gcfg:"monitor-timeout"`
  77. MonitorMaxRetries uint `gcfg:"monitor-max-retries"`
  78. }
  79. // OpenStack is an implementation of cloud provider Interface for OpenStack.
  80. type OpenStack struct {
  81. provider *gophercloud.ProviderClient
  82. region string
  83. lbOpts LoadBalancerOpts
  84. // InstanceID of the server where this OpenStack object is instantiated.
  85. localInstanceID string
  86. }
  87. type Config struct {
  88. Global struct {
  89. AuthUrl string `gcfg:"auth-url"`
  90. Username string
  91. UserId string `gcfg:"user-id"`
  92. Password string
  93. ApiKey string `gcfg:"api-key"`
  94. TenantId string `gcfg:"tenant-id"`
  95. TenantName string `gcfg:"tenant-name"`
  96. DomainId string `gcfg:"domain-id"`
  97. DomainName string `gcfg:"domain-name"`
  98. Region string
  99. }
  100. LoadBalancer LoadBalancerOpts
  101. }
  102. func init() {
  103. cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) {
  104. cfg, err := readConfig(config)
  105. if err != nil {
  106. return nil, err
  107. }
  108. return newOpenStack(cfg)
  109. })
  110. }
  111. func (cfg Config) toAuthOptions() gophercloud.AuthOptions {
  112. return gophercloud.AuthOptions{
  113. IdentityEndpoint: cfg.Global.AuthUrl,
  114. Username: cfg.Global.Username,
  115. UserID: cfg.Global.UserId,
  116. Password: cfg.Global.Password,
  117. APIKey: cfg.Global.ApiKey,
  118. TenantID: cfg.Global.TenantId,
  119. TenantName: cfg.Global.TenantName,
  120. DomainID: cfg.Global.DomainId,
  121. DomainName: cfg.Global.DomainName,
  122. // Persistent service, so we need to be able to renew tokens.
  123. AllowReauth: true,
  124. }
  125. }
  126. func readConfig(config io.Reader) (Config, error) {
  127. if config == nil {
  128. err := fmt.Errorf("no OpenStack cloud provider config file given")
  129. return Config{}, err
  130. }
  131. var cfg Config
  132. err := gcfg.ReadInto(&cfg, config)
  133. return cfg, err
  134. }
  135. // parseMetadataUUID reads JSON from OpenStack metadata server and parses
  136. // instance ID out of it.
  137. func parseMetadataUUID(jsonData []byte) (string, error) {
  138. // We should receive an object with { 'uuid': '<uuid>' } and couple of other
  139. // properties (which we ignore).
  140. obj := struct{ UUID string }{}
  141. err := json.Unmarshal(jsonData, &obj)
  142. if err != nil {
  143. return "", err
  144. }
  145. uuid := obj.UUID
  146. if uuid == "" {
  147. err = fmt.Errorf("cannot parse OpenStack metadata, got empty uuid")
  148. return "", err
  149. }
  150. return uuid, nil
  151. }
  152. func readInstanceID() (string, error) {
  153. // Try to find instance ID on the local filesystem (created by cloud-init)
  154. const instanceIDFile = "/var/lib/cloud/data/instance-id"
  155. idBytes, err := ioutil.ReadFile(instanceIDFile)
  156. if err == nil {
  157. instanceID := string(idBytes)
  158. instanceID = strings.TrimSpace(instanceID)
  159. glog.V(3).Infof("Got instance id from %s: %s", instanceIDFile, instanceID)
  160. if instanceID != "" {
  161. return instanceID, nil
  162. }
  163. // Fall through with empty instanceID and try metadata server.
  164. }
  165. glog.V(5).Infof("Cannot read %s: '%v', trying metadata server", instanceIDFile, err)
  166. // Try to get JSON from metdata server.
  167. resp, err := http.Get(metadataUrl)
  168. if err != nil {
  169. glog.V(3).Infof("Cannot read %s: %v", metadataUrl, err)
  170. return "", err
  171. }
  172. if resp.StatusCode != 200 {
  173. err = fmt.Errorf("got unexpected status code when reading metadata from %s: %s", metadataUrl, resp.Status)
  174. glog.V(3).Infof("%v", err)
  175. return "", err
  176. }
  177. defer resp.Body.Close()
  178. bodyBytes, err := ioutil.ReadAll(resp.Body)
  179. if err != nil {
  180. glog.V(3).Infof("Cannot get HTTP response body from %s: %v", metadataUrl, err)
  181. return "", err
  182. }
  183. instanceID, err := parseMetadataUUID(bodyBytes)
  184. if err != nil {
  185. glog.V(3).Infof("Cannot parse instance ID from metadata from %s: %v", metadataUrl, err)
  186. return "", err
  187. }
  188. glog.V(3).Infof("Got instance id from %s: %s", metadataUrl, instanceID)
  189. return instanceID, nil
  190. }
  191. func newOpenStack(cfg Config) (*OpenStack, error) {
  192. provider, err := openstack.AuthenticatedClient(cfg.toAuthOptions())
  193. if err != nil {
  194. return nil, err
  195. }
  196. id, err := readInstanceID()
  197. if err != nil {
  198. return nil, err
  199. }
  200. os := OpenStack{
  201. provider: provider,
  202. region: cfg.Global.Region,
  203. lbOpts: cfg.LoadBalancer,
  204. localInstanceID: id,
  205. }
  206. return &os, nil
  207. }
  208. type Instances struct {
  209. compute *gophercloud.ServiceClient
  210. flavor_to_resource map[string]*api.NodeResources // keyed by flavor id
  211. }
  212. // Instances returns an implementation of Instances for OpenStack.
  213. func (os *OpenStack) Instances() (cloudprovider.Instances, bool) {
  214. glog.V(4).Info("openstack.Instances() called")
  215. compute, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{
  216. Region: os.region,
  217. })
  218. if err != nil {
  219. glog.Warningf("Failed to find compute endpoint: %v", err)
  220. return nil, false
  221. }
  222. pager := flavors.ListDetail(compute, nil)
  223. flavor_to_resource := make(map[string]*api.NodeResources)
  224. err = pager.EachPage(func(page pagination.Page) (bool, error) {
  225. flavorList, err := flavors.ExtractFlavors(page)
  226. if err != nil {
  227. return false, err
  228. }
  229. for _, flavor := range flavorList {
  230. rsrc := api.NodeResources{
  231. Capacity: api.ResourceList{
  232. api.ResourceCPU: *resource.NewQuantity(int64(flavor.VCPUs), resource.DecimalSI),
  233. api.ResourceMemory: *resource.NewQuantity(int64(flavor.RAM)*MiB, resource.BinarySI),
  234. "openstack.org/disk": *resource.NewQuantity(int64(flavor.Disk)*GB, resource.DecimalSI),
  235. "openstack.org/rxTxFactor": *resource.NewMilliQuantity(int64(flavor.RxTxFactor)*1000, resource.DecimalSI),
  236. "openstack.org/swap": *resource.NewQuantity(int64(flavor.Swap)*MiB, resource.BinarySI),
  237. },
  238. }
  239. flavor_to_resource[flavor.ID] = &rsrc
  240. }
  241. return true, nil
  242. })
  243. if err != nil {
  244. glog.Warningf("Failed to find compute flavors: %v", err)
  245. return nil, false
  246. }
  247. glog.V(3).Infof("Found %v compute flavors", len(flavor_to_resource))
  248. glog.V(1).Info("Claiming to support Instances")
  249. return &Instances{compute, flavor_to_resource}, true
  250. }
  251. func (i *Instances) List(name_filter string) ([]string, error) {
  252. glog.V(4).Infof("openstack List(%v) called", name_filter)
  253. opts := servers.ListOpts{
  254. Name: name_filter,
  255. Status: "ACTIVE",
  256. }
  257. pager := servers.List(i.compute, opts)
  258. ret := make([]string, 0)
  259. err := pager.EachPage(func(page pagination.Page) (bool, error) {
  260. sList, err := servers.ExtractServers(page)
  261. if err != nil {
  262. return false, err
  263. }
  264. for _, server := range sList {
  265. ret = append(ret, server.Name)
  266. }
  267. return true, nil
  268. })
  269. if err != nil {
  270. return nil, err
  271. }
  272. glog.V(3).Infof("Found %v instances matching %v: %v",
  273. len(ret), name_filter, ret)
  274. return ret, nil
  275. }
  276. func getServerByName(client *gophercloud.ServiceClient, name string) (*servers.Server, error) {
  277. opts := servers.ListOpts{
  278. Name: fmt.Sprintf("^%s$", regexp.QuoteMeta(name)),
  279. Status: "ACTIVE",
  280. }
  281. pager := servers.List(client, opts)
  282. serverList := make([]servers.Server, 0, 1)
  283. err := pager.EachPage(func(page pagination.Page) (bool, error) {
  284. s, err := servers.ExtractServers(page)
  285. if err != nil {
  286. return false, err
  287. }
  288. serverList = append(serverList, s...)
  289. if len(serverList) > 1 {
  290. return false, ErrMultipleResults
  291. }
  292. return true, nil
  293. })
  294. if err != nil {
  295. return nil, err
  296. }
  297. if len(serverList) == 0 {
  298. return nil, ErrNotFound
  299. } else if len(serverList) > 1 {
  300. return nil, ErrMultipleResults
  301. }
  302. return &serverList[0], nil
  303. }
  304. func getAddressesByName(client *gophercloud.ServiceClient, name string) ([]api.NodeAddress, error) {
  305. srv, err := getServerByName(client, name)
  306. if err != nil {
  307. return nil, err
  308. }
  309. addrs := []api.NodeAddress{}
  310. for network, netblob := range srv.Addresses {
  311. list, ok := netblob.([]interface{})
  312. if !ok {
  313. continue
  314. }
  315. for _, item := range list {
  316. var addressType api.NodeAddressType
  317. props, ok := item.(map[string]interface{})
  318. if !ok {
  319. continue
  320. }
  321. extIPType, ok := props["OS-EXT-IPS:type"]
  322. if (ok && extIPType == "floating") || (!ok && network == "public") {
  323. addressType = api.NodeExternalIP
  324. } else {
  325. addressType = api.NodeInternalIP
  326. }
  327. tmp, ok := props["addr"]
  328. if !ok {
  329. continue
  330. }
  331. addr, ok := tmp.(string)
  332. if !ok {
  333. continue
  334. }
  335. api.AddToNodeAddresses(&addrs,
  336. api.NodeAddress{
  337. Type: addressType,
  338. Address: addr,
  339. },
  340. )
  341. }
  342. }
  343. // AccessIPs are usually duplicates of "public" addresses.
  344. if srv.AccessIPv4 != "" {
  345. api.AddToNodeAddresses(&addrs,
  346. api.NodeAddress{
  347. Type: api.NodeExternalIP,
  348. Address: srv.AccessIPv4,
  349. },
  350. )
  351. }
  352. if srv.AccessIPv6 != "" {
  353. api.AddToNodeAddresses(&addrs,
  354. api.NodeAddress{
  355. Type: api.NodeExternalIP,
  356. Address: srv.AccessIPv6,
  357. },
  358. )
  359. }
  360. return addrs, nil
  361. }
  362. func getAddressByName(client *gophercloud.ServiceClient, name string) (string, error) {
  363. addrs, err := getAddressesByName(client, name)
  364. if err != nil {
  365. return "", err
  366. } else if len(addrs) == 0 {
  367. return "", ErrNoAddressFound
  368. }
  369. for _, addr := range addrs {
  370. if addr.Type == api.NodeInternalIP {
  371. return addr.Address, nil
  372. }
  373. }
  374. return addrs[0].Address, nil
  375. }
  376. // Implementation of Instances.CurrentNodeName
  377. func (i *Instances) CurrentNodeName(hostname string) (string, error) {
  378. return hostname, nil
  379. }
  380. func (i *Instances) AddSSHKeyToAllInstances(user string, keyData []byte) error {
  381. return errors.New("unimplemented")
  382. }
  383. func (i *Instances) NodeAddresses(name string) ([]api.NodeAddress, error) {
  384. glog.V(4).Infof("NodeAddresses(%v) called", name)
  385. addrs, err := getAddressesByName(i.compute, name)
  386. if err != nil {
  387. return nil, err
  388. }
  389. glog.V(4).Infof("NodeAddresses(%v) => %v", name, addrs)
  390. return addrs, nil
  391. }
  392. // ExternalID returns the cloud provider ID of the specified instance (deprecated).
  393. func (i *Instances) ExternalID(name string) (string, error) {
  394. srv, err := getServerByName(i.compute, name)
  395. if err != nil {
  396. return "", err
  397. }
  398. return srv.ID, nil
  399. }
  400. // InstanceID returns the kubelet's cloud provider ID.
  401. func (os *OpenStack) InstanceID() (string, error) {
  402. return os.localInstanceID, nil
  403. }
  404. // InstanceID returns the cloud provider ID of the specified instance.
  405. func (i *Instances) InstanceID(name string) (string, error) {
  406. srv, err := getServerByName(i.compute, name)
  407. if err != nil {
  408. return "", err
  409. }
  410. // In the future it is possible to also return an endpoint as:
  411. // <endpoint>/<instanceid>
  412. return "/" + srv.ID, nil
  413. }
  414. // InstanceType returns the type of the specified instance.
  415. func (i *Instances) InstanceType(name string) (string, error) {
  416. return "", nil
  417. }
  418. func (os *OpenStack) Clusters() (cloudprovider.Clusters, bool) {
  419. return nil, false
  420. }
  421. // ProviderName returns the cloud provider ID.
  422. func (os *OpenStack) ProviderName() string {
  423. return ProviderName
  424. }
  425. // ScrubDNS filters DNS settings for pods.
  426. func (os *OpenStack) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) {
  427. return nameservers, searches
  428. }
  429. func (os *OpenStack) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
  430. glog.V(4).Info("openstack.LoadBalancer() called")
  431. // TODO: Search for and support Rackspace loadbalancer API, and others.
  432. network, err := openstack.NewNetworkV2(os.provider, gophercloud.EndpointOpts{
  433. Region: os.region,
  434. })
  435. if err != nil {
  436. glog.Warningf("Failed to find neutron endpoint: %v", err)
  437. return nil, false
  438. }
  439. compute, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{
  440. Region: os.region,
  441. })
  442. if err != nil {
  443. glog.Warningf("Failed to find compute endpoint: %v", err)
  444. return nil, false
  445. }
  446. glog.V(1).Info("Claiming to support LoadBalancer")
  447. if os.lbOpts.LBVersion == "v2" {
  448. return &LbaasV2{LoadBalancer{network, compute, os.lbOpts}}, true
  449. } else {
  450. return &LbaasV1{LoadBalancer{network, compute, os.lbOpts}}, true
  451. }
  452. }
  453. func isNotFound(err error) bool {
  454. e, ok := err.(*gophercloud.UnexpectedResponseCodeError)
  455. return ok && e.Actual == http.StatusNotFound
  456. }
  457. func (os *OpenStack) Zones() (cloudprovider.Zones, bool) {
  458. glog.V(1).Info("Claiming to support Zones")
  459. return os, true
  460. }
  461. func (os *OpenStack) GetZone() (cloudprovider.Zone, error) {
  462. glog.V(1).Infof("Current zone is %v", os.region)
  463. return cloudprovider.Zone{Region: os.region}, nil
  464. }
  465. func (os *OpenStack) Routes() (cloudprovider.Routes, bool) {
  466. return nil, false
  467. }
  468. // Attaches given cinder volume to the compute running kubelet
  469. func (os *OpenStack) AttachDisk(instanceID string, diskName string) (string, error) {
  470. disk, err := os.getVolume(diskName)
  471. if err != nil {
  472. return "", err
  473. }
  474. cClient, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{
  475. Region: os.region,
  476. })
  477. if err != nil || cClient == nil {
  478. glog.Errorf("Unable to initialize nova client for region: %s", os.region)
  479. return "", err
  480. }
  481. if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil {
  482. if instanceID == disk.Attachments[0]["server_id"] {
  483. glog.V(4).Infof("Disk: %q is already attached to compute: %q", diskName, instanceID)
  484. return disk.ID, nil
  485. } else {
  486. errMsg := fmt.Sprintf("Disk %q is attached to a different compute: %q, should be detached before proceeding", diskName, disk.Attachments[0]["server_id"])
  487. glog.Errorf(errMsg)
  488. return "", errors.New(errMsg)
  489. }
  490. }
  491. // add read only flag here if possible spothanis
  492. _, err = volumeattach.Create(cClient, instanceID, &volumeattach.CreateOpts{
  493. VolumeID: disk.ID,
  494. }).Extract()
  495. if err != nil {
  496. glog.Errorf("Failed to attach %s volume to %s compute", diskName, instanceID)
  497. return "", err
  498. }
  499. glog.V(2).Infof("Successfully attached %s volume to %s compute", diskName, instanceID)
  500. return disk.ID, nil
  501. }
  502. // Detaches given cinder volume from the compute running kubelet
  503. func (os *OpenStack) DetachDisk(instanceID string, partialDiskId string) error {
  504. disk, err := os.getVolume(partialDiskId)
  505. if err != nil {
  506. return err
  507. }
  508. cClient, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{
  509. Region: os.region,
  510. })
  511. if err != nil || cClient == nil {
  512. glog.Errorf("Unable to initialize nova client for region: %s", os.region)
  513. return err
  514. }
  515. if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil && instanceID == disk.Attachments[0]["server_id"] {
  516. // This is a blocking call and effects kubelet's performance directly.
  517. // We should consider kicking it out into a separate routine, if it is bad.
  518. err = volumeattach.Delete(cClient, instanceID, disk.ID).ExtractErr()
  519. if err != nil {
  520. glog.Errorf("Failed to delete volume %s from compute %s attached %v", disk.ID, instanceID, err)
  521. return err
  522. }
  523. glog.V(2).Infof("Successfully detached volume: %s from compute: %s", disk.ID, instanceID)
  524. } else {
  525. errMsg := fmt.Sprintf("Disk: %s has no attachments or is not attached to compute: %s", disk.Name, instanceID)
  526. glog.Errorf(errMsg)
  527. return errors.New(errMsg)
  528. }
  529. return nil
  530. }
  531. // Takes a partial/full disk id or diskname
  532. func (os *OpenStack) getVolume(diskName string) (volumes.Volume, error) {
  533. sClient, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{
  534. Region: os.region,
  535. })
  536. var volume volumes.Volume
  537. if err != nil || sClient == nil {
  538. glog.Errorf("Unable to initialize cinder client for region: %s", os.region)
  539. return volume, err
  540. }
  541. err = volumes.List(sClient, nil).EachPage(func(page pagination.Page) (bool, error) {
  542. vols, err := volumes.ExtractVolumes(page)
  543. if err != nil {
  544. glog.Errorf("Failed to extract volumes: %v", err)
  545. return false, err
  546. } else {
  547. for _, v := range vols {
  548. glog.V(4).Infof("%s %s %v", v.ID, v.Name, v.Attachments)
  549. if v.Name == diskName || strings.Contains(v.ID, diskName) {
  550. volume = v
  551. return true, nil
  552. }
  553. }
  554. }
  555. // if it reached here then no disk with the given name was found.
  556. errmsg := fmt.Sprintf("Unable to find disk: %s in region %s", diskName, os.region)
  557. return false, errors.New(errmsg)
  558. })
  559. if err != nil {
  560. glog.Errorf("Error occured getting volume: %s", diskName)
  561. return volume, err
  562. }
  563. return volume, err
  564. }
  565. // Create a volume of given size (in GiB)
  566. func (os *OpenStack) CreateVolume(name string, size int, tags *map[string]string) (volumeName string, err error) {
  567. sClient, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{
  568. Region: os.region,
  569. })
  570. if err != nil || sClient == nil {
  571. glog.Errorf("Unable to initialize cinder client for region: %s", os.region)
  572. return "", err
  573. }
  574. opts := volumes.CreateOpts{
  575. Name: name,
  576. Size: size,
  577. }
  578. if tags != nil {
  579. opts.Metadata = *tags
  580. }
  581. vol, err := volumes.Create(sClient, opts).Extract()
  582. if err != nil {
  583. glog.Errorf("Failed to create a %d GB volume: %v", size, err)
  584. return "", err
  585. }
  586. glog.Infof("Created volume %v", vol.ID)
  587. return vol.ID, err
  588. }
  589. // GetDevicePath returns the path of an attached block storage volume, specified by its id.
  590. func (os *OpenStack) GetDevicePath(diskId string) string {
  591. files, _ := ioutil.ReadDir("/dev/disk/by-id/")
  592. for _, f := range files {
  593. if strings.Contains(f.Name(), "virtio-") {
  594. devid_prefix := f.Name()[len("virtio-"):len(f.Name())]
  595. if strings.Contains(diskId, devid_prefix) {
  596. glog.V(4).Infof("Found disk attached as %q; full devicepath: %s\n", f.Name(), path.Join("/dev/disk/by-id/", f.Name()))
  597. return path.Join("/dev/disk/by-id/", f.Name())
  598. }
  599. }
  600. }
  601. glog.Warningf("Failed to find device for the diskid: %q\n", diskId)
  602. return ""
  603. }
  604. func (os *OpenStack) DeleteVolume(volumeName string) error {
  605. sClient, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{
  606. Region: os.region,
  607. })
  608. if err != nil || sClient == nil {
  609. glog.Errorf("Unable to initialize cinder client for region: %s", os.region)
  610. return err
  611. }
  612. err = volumes.Delete(sClient, volumeName).ExtractErr()
  613. if err != nil {
  614. glog.Errorf("Cannot delete volume %s: %v", volumeName, err)
  615. }
  616. return err
  617. }
  618. // Get device path of attached volume to the compute running kubelet
  619. func (os *OpenStack) GetAttachmentDiskPath(instanceID string, diskName string) (string, error) {
  620. disk, err := os.getVolume(diskName)
  621. if err != nil {
  622. return "", err
  623. }
  624. if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil {
  625. if instanceID == disk.Attachments[0]["server_id"] {
  626. // Attachment[0]["device"] points to the device path
  627. // see http://developer.openstack.org/api-ref-blockstorage-v1.html
  628. return disk.Attachments[0]["device"].(string), nil
  629. } else {
  630. errMsg := fmt.Sprintf("Disk %q is attached to a different compute: %q, should be detached before proceeding", diskName, disk.Attachments[0]["server_id"])
  631. glog.Errorf(errMsg)
  632. return "", errors.New(errMsg)
  633. }
  634. }
  635. return "", fmt.Errorf("volume %s is not attached to %s", diskName, instanceID)
  636. }
  637. // query if a volume is attached to a compute instance
  638. func (os *OpenStack) DiskIsAttached(diskName, instanceID string) (bool, error) {
  639. disk, err := os.getVolume(diskName)
  640. if err != nil {
  641. return false, err
  642. }
  643. if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil && instanceID == disk.Attachments[0]["server_id"] {
  644. return true, nil
  645. }
  646. return false, nil
  647. }