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

/pkg/cloudprovider/providers/rackspace/rackspace.go

https://gitlab.com/CORP-RESELLER/kubernetes
Go | 647 lines | 504 code | 96 blank | 47 comment | 135 complexity | c3972e1375a1a2fd6c2a149273b46233 MD5 | raw file
  1. /*
  2. Copyright 2014 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 rackspace
  14. import (
  15. "encoding/json"
  16. "errors"
  17. "fmt"
  18. "io"
  19. "io/ioutil"
  20. "net"
  21. "os"
  22. "regexp"
  23. "strings"
  24. "time"
  25. "gopkg.in/gcfg.v1"
  26. "github.com/golang/glog"
  27. "github.com/rackspace/gophercloud"
  28. osvolumeattach "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
  29. osservers "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
  30. "github.com/rackspace/gophercloud/pagination"
  31. "github.com/rackspace/gophercloud/rackspace"
  32. "github.com/rackspace/gophercloud/rackspace/blockstorage/v1/volumes"
  33. "github.com/rackspace/gophercloud/rackspace/compute/v2/servers"
  34. "github.com/rackspace/gophercloud/rackspace/compute/v2/volumeattach"
  35. "k8s.io/kubernetes/pkg/api"
  36. "k8s.io/kubernetes/pkg/cloudprovider"
  37. )
  38. const ProviderName = "rackspace"
  39. const metaDataPath = "/media/configdrive/openstack/latest/meta_data.json"
  40. var ErrNotFound = errors.New("Failed to find object")
  41. var ErrMultipleResults = errors.New("Multiple results where only one expected")
  42. var ErrNoAddressFound = errors.New("No address found for host")
  43. var ErrAttrNotFound = errors.New("Expected attribute not found")
  44. // encoding.TextUnmarshaler interface for time.Duration
  45. type MyDuration struct {
  46. time.Duration
  47. }
  48. func (d *MyDuration) UnmarshalText(text []byte) error {
  49. res, err := time.ParseDuration(string(text))
  50. if err != nil {
  51. return err
  52. }
  53. d.Duration = res
  54. return nil
  55. }
  56. type MetaData struct {
  57. UUID string `json:"uuid"`
  58. Name string `json:"name"`
  59. }
  60. type LoadBalancerOpts struct {
  61. SubnetId string `gcfg:"subnet-id"` // required
  62. CreateMonitor bool `gcfg:"create-monitor"`
  63. MonitorDelay MyDuration `gcfg:"monitor-delay"`
  64. MonitorTimeout MyDuration `gcfg:"monitor-timeout"`
  65. MonitorMaxRetries uint `gcfg:"monitor-max-retries"`
  66. }
  67. // Rackspace is an implementation of cloud provider Interface for Rackspace.
  68. type Rackspace struct {
  69. provider *gophercloud.ProviderClient
  70. region string
  71. lbOpts LoadBalancerOpts
  72. }
  73. type Config struct {
  74. Global struct {
  75. AuthUrl string `gcfg:"auth-url"`
  76. Username string
  77. UserId string `gcfg:"user-id"`
  78. Password string
  79. ApiKey string `gcfg:"api-key"`
  80. TenantId string `gcfg:"tenant-id"`
  81. TenantName string `gcfg:"tenant-name"`
  82. DomainId string `gcfg:"domain-id"`
  83. DomainName string `gcfg:"domain-name"`
  84. Region string
  85. }
  86. LoadBalancer LoadBalancerOpts
  87. }
  88. func probeNodeAddress(compute *gophercloud.ServiceClient, name string) (string, error) {
  89. id, err := readInstanceID()
  90. if err == nil {
  91. srv, err := servers.Get(compute, id).Extract()
  92. if err != nil {
  93. return "", err
  94. }
  95. return getAddressByServer(srv)
  96. }
  97. ip, err := getAddressByName(compute, name)
  98. if err != nil {
  99. return "", err
  100. }
  101. return ip, nil
  102. }
  103. func probeInstanceID(client *gophercloud.ServiceClient, name string) (string, error) {
  104. // Attempt to read id from config drive.
  105. id, err := readInstanceID()
  106. if err == nil {
  107. return id, nil
  108. }
  109. // Attempt to get the server by the name from the API
  110. server, err := getServerByName(client, name)
  111. if err != nil {
  112. return "", err
  113. }
  114. return server.ID, nil
  115. }
  116. func parseMetaData(file io.Reader) (string, error) {
  117. metaDataBytes, err := ioutil.ReadAll(file)
  118. if err != nil {
  119. return "", fmt.Errorf("Cannot read %s: %v", file, err)
  120. }
  121. metaData := MetaData{}
  122. err = json.Unmarshal(metaDataBytes, &metaData)
  123. if err != nil {
  124. return "", fmt.Errorf("Cannot parse %s: %v", metaDataPath, err)
  125. }
  126. return metaData.UUID, nil
  127. }
  128. func readInstanceID() (string, error) {
  129. file, err := os.Open(metaDataPath)
  130. if err != nil {
  131. return "", fmt.Errorf("Cannot open %s: %v", metaDataPath, err)
  132. }
  133. return parseMetaData(file)
  134. }
  135. func init() {
  136. cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) {
  137. cfg, err := readConfig(config)
  138. if err != nil {
  139. return nil, err
  140. }
  141. return newRackspace(cfg)
  142. })
  143. }
  144. func (cfg Config) toAuthOptions() gophercloud.AuthOptions {
  145. return gophercloud.AuthOptions{
  146. IdentityEndpoint: cfg.Global.AuthUrl,
  147. Username: cfg.Global.Username,
  148. UserID: cfg.Global.UserId,
  149. Password: cfg.Global.Password,
  150. APIKey: cfg.Global.ApiKey,
  151. TenantID: cfg.Global.TenantId,
  152. TenantName: cfg.Global.TenantName,
  153. // Persistent service, so we need to be able to renew tokens
  154. AllowReauth: true,
  155. }
  156. }
  157. func readConfig(config io.Reader) (Config, error) {
  158. if config == nil {
  159. err := fmt.Errorf("no Rackspace cloud provider config file given")
  160. return Config{}, err
  161. }
  162. var cfg Config
  163. err := gcfg.ReadInto(&cfg, config)
  164. return cfg, err
  165. }
  166. func newRackspace(cfg Config) (*Rackspace, error) {
  167. provider, err := rackspace.AuthenticatedClient(cfg.toAuthOptions())
  168. if err != nil {
  169. return nil, err
  170. }
  171. os := Rackspace{
  172. provider: provider,
  173. region: cfg.Global.Region,
  174. lbOpts: cfg.LoadBalancer,
  175. }
  176. return &os, nil
  177. }
  178. type Instances struct {
  179. compute *gophercloud.ServiceClient
  180. }
  181. // Instances returns an implementation of Instances for Rackspace.
  182. func (os *Rackspace) Instances() (cloudprovider.Instances, bool) {
  183. glog.V(2).Info("rackspace.Instances() called")
  184. compute, err := os.getComputeClient()
  185. if err != nil {
  186. glog.Warningf("Failed to find compute endpoint: %v", err)
  187. return nil, false
  188. }
  189. glog.V(1).Info("Claiming to support Instances")
  190. return &Instances{compute}, true
  191. }
  192. func (i *Instances) List(name_filter string) ([]string, error) {
  193. glog.V(2).Infof("rackspace List(%v) called", name_filter)
  194. opts := osservers.ListOpts{
  195. Name: name_filter,
  196. Status: "ACTIVE",
  197. }
  198. pager := servers.List(i.compute, opts)
  199. ret := make([]string, 0)
  200. err := pager.EachPage(func(page pagination.Page) (bool, error) {
  201. sList, err := servers.ExtractServers(page)
  202. if err != nil {
  203. return false, err
  204. }
  205. for _, server := range sList {
  206. ret = append(ret, server.Name)
  207. }
  208. return true, nil
  209. })
  210. if err != nil {
  211. return nil, err
  212. }
  213. glog.V(2).Infof("Found %v entries: %v", len(ret), ret)
  214. return ret, nil
  215. }
  216. func serverHasAddress(srv osservers.Server, ip string) bool {
  217. if ip == firstAddr(srv.Addresses["private"]) {
  218. return true
  219. }
  220. if ip == firstAddr(srv.Addresses["public"]) {
  221. return true
  222. }
  223. if ip == srv.AccessIPv4 {
  224. return true
  225. }
  226. if ip == srv.AccessIPv6 {
  227. return true
  228. }
  229. return false
  230. }
  231. func getServerByAddress(client *gophercloud.ServiceClient, name string) (*osservers.Server, error) {
  232. pager := servers.List(client, nil)
  233. serverList := make([]osservers.Server, 0, 1)
  234. err := pager.EachPage(func(page pagination.Page) (bool, error) {
  235. s, err := servers.ExtractServers(page)
  236. if err != nil {
  237. return false, err
  238. }
  239. for _, v := range s {
  240. if serverHasAddress(v, name) {
  241. serverList = append(serverList, v)
  242. }
  243. }
  244. if len(serverList) > 1 {
  245. return false, ErrMultipleResults
  246. }
  247. return true, nil
  248. })
  249. if err != nil {
  250. return nil, err
  251. }
  252. if len(serverList) == 0 {
  253. return nil, ErrNotFound
  254. } else if len(serverList) > 1 {
  255. return nil, ErrMultipleResults
  256. }
  257. return &serverList[0], nil
  258. }
  259. func getServerByName(client *gophercloud.ServiceClient, name string) (*osservers.Server, error) {
  260. if net.ParseIP(name) != nil {
  261. // we're an IP, so we'll have to walk the full list of servers to
  262. // figure out which one we are.
  263. return getServerByAddress(client, name)
  264. }
  265. opts := osservers.ListOpts{
  266. Name: fmt.Sprintf("^%s$", regexp.QuoteMeta(name)),
  267. Status: "ACTIVE",
  268. }
  269. pager := servers.List(client, opts)
  270. serverList := make([]osservers.Server, 0, 1)
  271. err := pager.EachPage(func(page pagination.Page) (bool, error) {
  272. s, err := servers.ExtractServers(page)
  273. if err != nil {
  274. return false, err
  275. }
  276. serverList = append(serverList, s...)
  277. if len(serverList) > 1 {
  278. return false, ErrMultipleResults
  279. }
  280. return true, nil
  281. })
  282. if err != nil {
  283. return nil, err
  284. }
  285. if len(serverList) == 0 {
  286. return nil, ErrNotFound
  287. } else if len(serverList) > 1 {
  288. return nil, ErrMultipleResults
  289. }
  290. return &serverList[0], nil
  291. }
  292. func firstAddr(netblob interface{}) string {
  293. // Run-time types for the win :(
  294. list, ok := netblob.([]interface{})
  295. if !ok || len(list) < 1 {
  296. return ""
  297. }
  298. props, ok := list[0].(map[string]interface{})
  299. if !ok {
  300. return ""
  301. }
  302. tmp, ok := props["addr"]
  303. if !ok {
  304. return ""
  305. }
  306. addr, ok := tmp.(string)
  307. if !ok {
  308. return ""
  309. }
  310. return addr
  311. }
  312. func getAddressByServer(srv *osservers.Server) (string, error) {
  313. var s string
  314. if s == "" {
  315. s = firstAddr(srv.Addresses["private"])
  316. }
  317. if s == "" {
  318. s = firstAddr(srv.Addresses["public"])
  319. }
  320. if s == "" {
  321. s = srv.AccessIPv4
  322. }
  323. if s == "" {
  324. s = srv.AccessIPv6
  325. }
  326. if s == "" {
  327. return "", ErrNoAddressFound
  328. }
  329. return s, nil
  330. }
  331. func getAddressByName(api *gophercloud.ServiceClient, name string) (string, error) {
  332. srv, err := getServerByName(api, name)
  333. if err != nil {
  334. return "", err
  335. }
  336. return getAddressByServer(srv)
  337. }
  338. func (i *Instances) NodeAddresses(name string) ([]api.NodeAddress, error) {
  339. glog.V(2).Infof("NodeAddresses(%v) called", name)
  340. ip, err := probeNodeAddress(i.compute, name)
  341. if err != nil {
  342. return nil, err
  343. }
  344. glog.V(2).Infof("NodeAddresses(%v) => %v", name, ip)
  345. // net.ParseIP().String() is to maintain compatibility with the old code
  346. return []api.NodeAddress{{Type: api.NodeLegacyHostIP, Address: net.ParseIP(ip).String()}}, nil
  347. }
  348. // ExternalID returns the cloud provider ID of the specified instance (deprecated).
  349. func (i *Instances) ExternalID(name string) (string, error) {
  350. return probeInstanceID(i.compute, name)
  351. }
  352. // InstanceID returns the cloud provider ID of the kubelet's instance.
  353. func (rs *Rackspace) InstanceID() (string, error) {
  354. return readInstanceID()
  355. }
  356. // InstanceID returns the cloud provider ID of the specified instance.
  357. func (i *Instances) InstanceID(name string) (string, error) {
  358. return probeInstanceID(i.compute, name)
  359. }
  360. // InstanceType returns the type of the specified instance.
  361. func (i *Instances) InstanceType(name string) (string, error) {
  362. return "", nil
  363. }
  364. func (i *Instances) AddSSHKeyToAllInstances(user string, keyData []byte) error {
  365. return errors.New("unimplemented")
  366. }
  367. // Implementation of Instances.CurrentNodeName
  368. func (i *Instances) CurrentNodeName(hostname string) (string, error) {
  369. // Beware when changing this, nodename == hostname assumption is crucial to
  370. // apiserver => kubelet communication.
  371. return hostname, nil
  372. }
  373. func (os *Rackspace) Clusters() (cloudprovider.Clusters, bool) {
  374. return nil, false
  375. }
  376. // ProviderName returns the cloud provider ID.
  377. func (os *Rackspace) ProviderName() string {
  378. return ProviderName
  379. }
  380. // ScrubDNS filters DNS settings for pods.
  381. func (os *Rackspace) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) {
  382. return nameservers, searches
  383. }
  384. func (os *Rackspace) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
  385. return nil, false
  386. }
  387. func (os *Rackspace) Zones() (cloudprovider.Zones, bool) {
  388. glog.V(1).Info("Claiming to support Zones")
  389. return os, true
  390. }
  391. func (os *Rackspace) Routes() (cloudprovider.Routes, bool) {
  392. return nil, false
  393. }
  394. func (os *Rackspace) GetZone() (cloudprovider.Zone, error) {
  395. glog.V(1).Infof("Current zone is %v", os.region)
  396. return cloudprovider.Zone{Region: os.region}, nil
  397. }
  398. // Create a volume of given size (in GiB)
  399. func (rs *Rackspace) CreateVolume(name string, size int, tags *map[string]string) (volumeName string, err error) {
  400. return "", errors.New("unimplemented")
  401. }
  402. func (rs *Rackspace) DeleteVolume(volumeName string) error {
  403. return errors.New("unimplemented")
  404. }
  405. // Attaches given cinder volume to the compute running kubelet
  406. func (rs *Rackspace) AttachDisk(instanceID string, diskName string) (string, error) {
  407. disk, err := rs.getVolume(diskName)
  408. if err != nil {
  409. return "", err
  410. }
  411. compute, err := rs.getComputeClient()
  412. if err != nil {
  413. return "", err
  414. }
  415. if len(disk.Attachments) > 0 {
  416. if instanceID == disk.Attachments[0]["server_id"] {
  417. glog.V(4).Infof("Disk: %q is already attached to compute: %q", diskName, instanceID)
  418. return disk.ID, nil
  419. }
  420. errMsg := fmt.Sprintf("Disk %q is attached to a different compute: %q, should be detached before proceeding", diskName, disk.Attachments[0]["server_id"])
  421. glog.Errorf(errMsg)
  422. return "", errors.New(errMsg)
  423. }
  424. _, err = volumeattach.Create(compute, instanceID, &osvolumeattach.CreateOpts{
  425. VolumeID: disk.ID,
  426. }).Extract()
  427. if err != nil {
  428. glog.Errorf("Failed to attach %s volume to %s compute", diskName, instanceID)
  429. return "", err
  430. }
  431. glog.V(2).Infof("Successfully attached %s volume to %s compute", diskName, instanceID)
  432. return disk.ID, nil
  433. }
  434. // GetDevicePath returns the path of an attached block storage volume, specified by its id.
  435. func (rs *Rackspace) GetDevicePath(diskId string) string {
  436. volume, err := rs.getVolume(diskId)
  437. if err != nil {
  438. return ""
  439. }
  440. attachments := volume.Attachments
  441. if len(attachments) != 1 {
  442. glog.Warningf("Unexpected number of volume attachments on %s: %d", diskId, len(attachments))
  443. return ""
  444. }
  445. return attachments[0]["device"].(string)
  446. }
  447. // Takes a partial/full disk id or diskname
  448. func (rs *Rackspace) getVolume(diskName string) (volumes.Volume, error) {
  449. sClient, err := rackspace.NewBlockStorageV1(rs.provider, gophercloud.EndpointOpts{
  450. Region: rs.region,
  451. })
  452. var volume volumes.Volume
  453. if err != nil || sClient == nil {
  454. glog.Errorf("Unable to initialize cinder client for region: %s", rs.region)
  455. return volume, err
  456. }
  457. err = volumes.List(sClient).EachPage(func(page pagination.Page) (bool, error) {
  458. vols, err := volumes.ExtractVolumes(page)
  459. if err != nil {
  460. glog.Errorf("Failed to extract volumes: %v", err)
  461. return false, err
  462. }
  463. for _, v := range vols {
  464. glog.V(4).Infof("%s %s %v", v.ID, v.Name, v.Attachments)
  465. if v.Name == diskName || strings.Contains(v.ID, diskName) {
  466. volume = v
  467. return true, nil
  468. }
  469. }
  470. // if it reached here then no disk with the given name was found.
  471. errmsg := fmt.Sprintf("Unable to find disk: %s in region %s", diskName, rs.region)
  472. return false, errors.New(errmsg)
  473. })
  474. if err != nil {
  475. glog.Errorf("Error occured getting volume: %s", diskName)
  476. }
  477. return volume, err
  478. }
  479. func (rs *Rackspace) getComputeClient() (*gophercloud.ServiceClient, error) {
  480. client, err := rackspace.NewComputeV2(rs.provider, gophercloud.EndpointOpts{
  481. Region: rs.region,
  482. })
  483. if err != nil || client == nil {
  484. glog.Errorf("Unable to initialize nova client for region: %s", rs.region)
  485. }
  486. return client, nil
  487. }
  488. // Detaches given cinder volume from the compute running kubelet
  489. func (rs *Rackspace) DetachDisk(instanceID string, partialDiskId string) error {
  490. disk, err := rs.getVolume(partialDiskId)
  491. if err != nil {
  492. return err
  493. }
  494. compute, err := rs.getComputeClient()
  495. if err != nil {
  496. return err
  497. }
  498. if len(disk.Attachments) > 1 {
  499. // Rackspace does not support "multiattach", this is a sanity check.
  500. errmsg := fmt.Sprintf("Volume %s is attached to multiple instances, which is not supported by this provider.", disk.ID)
  501. return errors.New(errmsg)
  502. }
  503. if len(disk.Attachments) > 0 && instanceID == disk.Attachments[0]["server_id"] {
  504. // This is a blocking call and effects kubelet's performance directly.
  505. // We should consider kicking it out into a separate routine, if it is bad.
  506. err = volumeattach.Delete(compute, instanceID, disk.ID).ExtractErr()
  507. if err != nil {
  508. glog.Errorf("Failed to delete volume %s from compute %s attached %v", disk.ID, instanceID, err)
  509. return err
  510. }
  511. glog.V(2).Infof("Successfully detached volume: %s from compute: %s", disk.ID, instanceID)
  512. } else {
  513. errMsg := fmt.Sprintf("Disk: %s has no attachments or is not attached to compute: %s", disk.Name, instanceID)
  514. glog.Errorf(errMsg)
  515. return errors.New(errMsg)
  516. }
  517. return nil
  518. }
  519. // Get device path of attached volume to the compute running kubelet
  520. func (rs *Rackspace) GetAttachmentDiskPath(instanceID string, diskName string) (string, error) {
  521. disk, err := rs.getVolume(diskName)
  522. if err != nil {
  523. return "", err
  524. }
  525. if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil {
  526. if instanceID == disk.Attachments[0]["server_id"] {
  527. // Attachment[0]["device"] points to the device path
  528. // see http://developer.openstack.org/api-ref-blockstorage-v1.html
  529. return disk.Attachments[0]["device"].(string), nil
  530. } else {
  531. errMsg := fmt.Sprintf("Disk %q is attached to a different compute: %q, should be detached before proceeding", diskName, disk.Attachments[0]["server_id"])
  532. glog.Errorf(errMsg)
  533. return "", errors.New(errMsg)
  534. }
  535. }
  536. return "", fmt.Errorf("volume %s is not attached to %s", diskName, instanceID)
  537. }
  538. // query if a volume is attached to a compute instance
  539. func (rs *Rackspace) DiskIsAttached(diskName, instanceID string) (bool, error) {
  540. disk, err := rs.getVolume(diskName)
  541. if err != nil {
  542. return false, err
  543. }
  544. if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil && instanceID == disk.Attachments[0]["server_id"] {
  545. return true, nil
  546. }
  547. return false, nil
  548. }