PageRenderTime 25ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/pkg/cloudprovider/providers/openstack/openstack.go

https://gitlab.com/github-cloud-corporation/kubernetes
Go | 443 lines | 341 code | 67 blank | 35 comment | 83 complexity | 3f1efb2854a7560d77773cb475a2754b 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 openstack
  14. import (
  15. "encoding/json"
  16. "errors"
  17. "fmt"
  18. "io"
  19. "io/ioutil"
  20. "net/http"
  21. "regexp"
  22. "strings"
  23. "time"
  24. "gopkg.in/gcfg.v1"
  25. "github.com/rackspace/gophercloud"
  26. "github.com/rackspace/gophercloud/openstack"
  27. "github.com/rackspace/gophercloud/openstack/compute/v2/servers"
  28. "github.com/rackspace/gophercloud/pagination"
  29. "github.com/golang/glog"
  30. "k8s.io/kubernetes/pkg/api"
  31. "k8s.io/kubernetes/pkg/cloudprovider"
  32. )
  33. const ProviderName = "openstack"
  34. // metadataUrl is URL to OpenStack metadata server. It's hardcoded IPv4
  35. // link-local address as documented in "OpenStack Cloud Administrator Guide",
  36. // chapter Compute - Networking with nova-network.
  37. // http://docs.openstack.org/admin-guide-cloud/compute-networking-nova.html#metadata-service
  38. const metadataUrl = "http://169.254.169.254/openstack/2012-08-10/meta_data.json"
  39. var ErrNotFound = errors.New("Failed to find object")
  40. var ErrMultipleResults = errors.New("Multiple results where only one expected")
  41. var ErrNoAddressFound = errors.New("No address found for host")
  42. var ErrAttrNotFound = errors.New("Expected attribute not found")
  43. const (
  44. MiB = 1024 * 1024
  45. GB = 1000 * 1000 * 1000
  46. )
  47. // encoding.TextUnmarshaler interface for time.Duration
  48. type MyDuration struct {
  49. time.Duration
  50. }
  51. func (d *MyDuration) UnmarshalText(text []byte) error {
  52. res, err := time.ParseDuration(string(text))
  53. if err != nil {
  54. return err
  55. }
  56. d.Duration = res
  57. return nil
  58. }
  59. type LoadBalancer struct {
  60. network *gophercloud.ServiceClient
  61. compute *gophercloud.ServiceClient
  62. opts LoadBalancerOpts
  63. }
  64. type LoadBalancerOpts struct {
  65. LBVersion string `gcfg:"lb-version"` // overrides autodetection. v1 or v2
  66. SubnetId string `gcfg:"subnet-id"` // required
  67. FloatingNetworkId string `gcfg:"floating-network-id"`
  68. LBMethod string `gcfg:"lb-method"`
  69. CreateMonitor bool `gcfg:"create-monitor"`
  70. MonitorDelay MyDuration `gcfg:"monitor-delay"`
  71. MonitorTimeout MyDuration `gcfg:"monitor-timeout"`
  72. MonitorMaxRetries uint `gcfg:"monitor-max-retries"`
  73. }
  74. // OpenStack is an implementation of cloud provider Interface for OpenStack.
  75. type OpenStack struct {
  76. provider *gophercloud.ProviderClient
  77. region string
  78. lbOpts LoadBalancerOpts
  79. // InstanceID of the server where this OpenStack object is instantiated.
  80. localInstanceID string
  81. }
  82. type Config struct {
  83. Global struct {
  84. AuthUrl string `gcfg:"auth-url"`
  85. Username string
  86. UserId string `gcfg:"user-id"`
  87. Password string
  88. ApiKey string `gcfg:"api-key"`
  89. TenantId string `gcfg:"tenant-id"`
  90. TenantName string `gcfg:"tenant-name"`
  91. DomainId string `gcfg:"domain-id"`
  92. DomainName string `gcfg:"domain-name"`
  93. Region string
  94. }
  95. LoadBalancer LoadBalancerOpts
  96. }
  97. func init() {
  98. cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) {
  99. cfg, err := readConfig(config)
  100. if err != nil {
  101. return nil, err
  102. }
  103. return newOpenStack(cfg)
  104. })
  105. }
  106. func (cfg Config) toAuthOptions() gophercloud.AuthOptions {
  107. return gophercloud.AuthOptions{
  108. IdentityEndpoint: cfg.Global.AuthUrl,
  109. Username: cfg.Global.Username,
  110. UserID: cfg.Global.UserId,
  111. Password: cfg.Global.Password,
  112. APIKey: cfg.Global.ApiKey,
  113. TenantID: cfg.Global.TenantId,
  114. TenantName: cfg.Global.TenantName,
  115. DomainID: cfg.Global.DomainId,
  116. DomainName: cfg.Global.DomainName,
  117. // Persistent service, so we need to be able to renew tokens.
  118. AllowReauth: true,
  119. }
  120. }
  121. func readConfig(config io.Reader) (Config, error) {
  122. if config == nil {
  123. err := fmt.Errorf("no OpenStack cloud provider config file given")
  124. return Config{}, err
  125. }
  126. var cfg Config
  127. err := gcfg.ReadInto(&cfg, config)
  128. return cfg, err
  129. }
  130. // parseMetadataUUID reads JSON from OpenStack metadata server and parses
  131. // instance ID out of it.
  132. func parseMetadataUUID(jsonData []byte) (string, error) {
  133. // We should receive an object with { 'uuid': '<uuid>' } and couple of other
  134. // properties (which we ignore).
  135. obj := struct{ UUID string }{}
  136. err := json.Unmarshal(jsonData, &obj)
  137. if err != nil {
  138. return "", err
  139. }
  140. uuid := obj.UUID
  141. if uuid == "" {
  142. err = fmt.Errorf("cannot parse OpenStack metadata, got empty uuid")
  143. return "", err
  144. }
  145. return uuid, nil
  146. }
  147. func readInstanceID() (string, error) {
  148. // Try to find instance ID on the local filesystem (created by cloud-init)
  149. const instanceIDFile = "/var/lib/cloud/data/instance-id"
  150. idBytes, err := ioutil.ReadFile(instanceIDFile)
  151. if err == nil {
  152. instanceID := string(idBytes)
  153. instanceID = strings.TrimSpace(instanceID)
  154. glog.V(3).Infof("Got instance id from %s: %s", instanceIDFile, instanceID)
  155. if instanceID != "" {
  156. return instanceID, nil
  157. }
  158. // Fall through with empty instanceID and try metadata server.
  159. }
  160. glog.V(5).Infof("Cannot read %s: '%v', trying metadata server", instanceIDFile, err)
  161. // Try to get JSON from metdata server.
  162. resp, err := http.Get(metadataUrl)
  163. if err != nil {
  164. glog.V(3).Infof("Cannot read %s: %v", metadataUrl, err)
  165. return "", err
  166. }
  167. if resp.StatusCode != 200 {
  168. err = fmt.Errorf("got unexpected status code when reading metadata from %s: %s", metadataUrl, resp.Status)
  169. glog.V(3).Infof("%v", err)
  170. return "", err
  171. }
  172. defer resp.Body.Close()
  173. bodyBytes, err := ioutil.ReadAll(resp.Body)
  174. if err != nil {
  175. glog.V(3).Infof("Cannot get HTTP response body from %s: %v", metadataUrl, err)
  176. return "", err
  177. }
  178. instanceID, err := parseMetadataUUID(bodyBytes)
  179. if err != nil {
  180. glog.V(3).Infof("Cannot parse instance ID from metadata from %s: %v", metadataUrl, err)
  181. return "", err
  182. }
  183. glog.V(3).Infof("Got instance id from %s: %s", metadataUrl, instanceID)
  184. return instanceID, nil
  185. }
  186. func newOpenStack(cfg Config) (*OpenStack, error) {
  187. provider, err := openstack.AuthenticatedClient(cfg.toAuthOptions())
  188. if err != nil {
  189. return nil, err
  190. }
  191. id, err := readInstanceID()
  192. if err != nil {
  193. return nil, err
  194. }
  195. os := OpenStack{
  196. provider: provider,
  197. region: cfg.Global.Region,
  198. lbOpts: cfg.LoadBalancer,
  199. localInstanceID: id,
  200. }
  201. return &os, nil
  202. }
  203. func getServerByName(client *gophercloud.ServiceClient, name string) (*servers.Server, error) {
  204. opts := servers.ListOpts{
  205. Name: fmt.Sprintf("^%s$", regexp.QuoteMeta(name)),
  206. Status: "ACTIVE",
  207. }
  208. pager := servers.List(client, opts)
  209. serverList := make([]servers.Server, 0, 1)
  210. err := pager.EachPage(func(page pagination.Page) (bool, error) {
  211. s, err := servers.ExtractServers(page)
  212. if err != nil {
  213. return false, err
  214. }
  215. serverList = append(serverList, s...)
  216. if len(serverList) > 1 {
  217. return false, ErrMultipleResults
  218. }
  219. return true, nil
  220. })
  221. if err != nil {
  222. return nil, err
  223. }
  224. if len(serverList) == 0 {
  225. return nil, ErrNotFound
  226. } else if len(serverList) > 1 {
  227. return nil, ErrMultipleResults
  228. }
  229. return &serverList[0], nil
  230. }
  231. func getAddressesByName(client *gophercloud.ServiceClient, name string) ([]api.NodeAddress, error) {
  232. srv, err := getServerByName(client, name)
  233. if err != nil {
  234. return nil, err
  235. }
  236. addrs := []api.NodeAddress{}
  237. for network, netblob := range srv.Addresses {
  238. list, ok := netblob.([]interface{})
  239. if !ok {
  240. continue
  241. }
  242. for _, item := range list {
  243. var addressType api.NodeAddressType
  244. props, ok := item.(map[string]interface{})
  245. if !ok {
  246. continue
  247. }
  248. extIPType, ok := props["OS-EXT-IPS:type"]
  249. if (ok && extIPType == "floating") || (!ok && network == "public") {
  250. addressType = api.NodeExternalIP
  251. } else {
  252. addressType = api.NodeInternalIP
  253. }
  254. tmp, ok := props["addr"]
  255. if !ok {
  256. continue
  257. }
  258. addr, ok := tmp.(string)
  259. if !ok {
  260. continue
  261. }
  262. api.AddToNodeAddresses(&addrs,
  263. api.NodeAddress{
  264. Type: addressType,
  265. Address: addr,
  266. },
  267. )
  268. }
  269. }
  270. // AccessIPs are usually duplicates of "public" addresses.
  271. if srv.AccessIPv4 != "" {
  272. api.AddToNodeAddresses(&addrs,
  273. api.NodeAddress{
  274. Type: api.NodeExternalIP,
  275. Address: srv.AccessIPv4,
  276. },
  277. )
  278. }
  279. if srv.AccessIPv6 != "" {
  280. api.AddToNodeAddresses(&addrs,
  281. api.NodeAddress{
  282. Type: api.NodeExternalIP,
  283. Address: srv.AccessIPv6,
  284. },
  285. )
  286. }
  287. return addrs, nil
  288. }
  289. func getAddressByName(client *gophercloud.ServiceClient, name string) (string, error) {
  290. addrs, err := getAddressesByName(client, name)
  291. if err != nil {
  292. return "", err
  293. } else if len(addrs) == 0 {
  294. return "", ErrNoAddressFound
  295. }
  296. for _, addr := range addrs {
  297. if addr.Type == api.NodeInternalIP {
  298. return addr.Address, nil
  299. }
  300. }
  301. return addrs[0].Address, nil
  302. }
  303. func (os *OpenStack) Clusters() (cloudprovider.Clusters, bool) {
  304. return nil, false
  305. }
  306. // ProviderName returns the cloud provider ID.
  307. func (os *OpenStack) ProviderName() string {
  308. return ProviderName
  309. }
  310. // ScrubDNS filters DNS settings for pods.
  311. func (os *OpenStack) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) {
  312. return nameservers, searches
  313. }
  314. func (os *OpenStack) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
  315. glog.V(4).Info("openstack.LoadBalancer() called")
  316. // TODO: Search for and support Rackspace loadbalancer API, and others.
  317. network, err := openstack.NewNetworkV2(os.provider, gophercloud.EndpointOpts{
  318. Region: os.region,
  319. })
  320. if err != nil {
  321. glog.Warningf("Failed to find neutron endpoint: %v", err)
  322. return nil, false
  323. }
  324. compute, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{
  325. Region: os.region,
  326. })
  327. if err != nil {
  328. glog.Warningf("Failed to find compute endpoint: %v", err)
  329. return nil, false
  330. }
  331. lbversion := os.lbOpts.LBVersion
  332. if lbversion == "" {
  333. // No version specified, try newest supported by server
  334. netExts, err := networkExtensions(network)
  335. if err != nil {
  336. glog.Warningf("Failed to list neutron extensions: %v", err)
  337. return nil, false
  338. }
  339. if netExts["lbaasv2"] {
  340. lbversion = "v2"
  341. } else if netExts["lbaas"] {
  342. lbversion = "v1"
  343. } else {
  344. glog.Warningf("Failed to find neutron LBaaS extension (v1 or v2)")
  345. return nil, false
  346. }
  347. glog.V(3).Infof("Using LBaaS extension %v", lbversion)
  348. }
  349. glog.V(1).Info("Claiming to support LoadBalancer")
  350. if os.lbOpts.LBVersion == "v2" {
  351. return &LbaasV2{LoadBalancer{network, compute, os.lbOpts}}, true
  352. } else if lbversion == "v1" {
  353. return &LbaasV1{LoadBalancer{network, compute, os.lbOpts}}, true
  354. } else {
  355. glog.Warningf("Config error: unrecognised lb-version \"%v\"", lbversion)
  356. return nil, false
  357. }
  358. }
  359. func isNotFound(err error) bool {
  360. e, ok := err.(*gophercloud.UnexpectedResponseCodeError)
  361. return ok && e.Actual == http.StatusNotFound
  362. }
  363. func (os *OpenStack) Zones() (cloudprovider.Zones, bool) {
  364. glog.V(1).Info("Claiming to support Zones")
  365. return os, true
  366. }
  367. func (os *OpenStack) GetZone() (cloudprovider.Zone, error) {
  368. glog.V(1).Infof("Current zone is %v", os.region)
  369. return cloudprovider.Zone{Region: os.region}, nil
  370. }
  371. func (os *OpenStack) Routes() (cloudprovider.Routes, bool) {
  372. return nil, false
  373. }