openstack.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930
  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. "context"
  16. "crypto/tls"
  17. "errors"
  18. "fmt"
  19. "io"
  20. "io/ioutil"
  21. "net"
  22. "net/http"
  23. "os"
  24. "reflect"
  25. "regexp"
  26. "strings"
  27. "time"
  28. "github.com/gophercloud/gophercloud"
  29. "github.com/gophercloud/gophercloud/openstack"
  30. "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces"
  31. "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
  32. "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts"
  33. tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
  34. "github.com/gophercloud/gophercloud/pagination"
  35. "github.com/mitchellh/mapstructure"
  36. "gopkg.in/gcfg.v1"
  37. "k8s.io/api/core/v1"
  38. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  39. "k8s.io/apimachinery/pkg/types"
  40. netutil "k8s.io/apimachinery/pkg/util/net"
  41. "k8s.io/client-go/kubernetes"
  42. "k8s.io/client-go/tools/clientcmd"
  43. certutil "k8s.io/client-go/util/cert"
  44. cloudprovider "k8s.io/cloud-provider"
  45. nodehelpers "k8s.io/cloud-provider/node/helpers"
  46. "k8s.io/klog"
  47. )
  48. const (
  49. // ProviderName is the name of the openstack provider
  50. ProviderName = "openstack"
  51. // TypeHostName is the name type of openstack instance
  52. TypeHostName = "hostname"
  53. availabilityZone = "availability_zone"
  54. defaultTimeOut = 60 * time.Second
  55. )
  56. // ErrNotFound is used to inform that the object is missing
  57. var ErrNotFound = errors.New("failed to find object")
  58. // ErrMultipleResults is used when we unexpectedly get back multiple results
  59. var ErrMultipleResults = errors.New("multiple results where only one expected")
  60. // ErrNoAddressFound is used when we cannot find an ip address for the host
  61. var ErrNoAddressFound = errors.New("no address found for host")
  62. // MyDuration is the encoding.TextUnmarshaler interface for time.Duration
  63. type MyDuration struct {
  64. time.Duration
  65. }
  66. // UnmarshalText is used to convert from text to Duration
  67. func (d *MyDuration) UnmarshalText(text []byte) error {
  68. res, err := time.ParseDuration(string(text))
  69. if err != nil {
  70. return err
  71. }
  72. d.Duration = res
  73. return nil
  74. }
  75. // LoadBalancer is used for creating and maintaining load balancers
  76. type LoadBalancer struct {
  77. network *gophercloud.ServiceClient
  78. compute *gophercloud.ServiceClient
  79. lb *gophercloud.ServiceClient
  80. opts LoadBalancerOpts
  81. }
  82. // LoadBalancerOpts have the options to talk to Neutron LBaaSV2 or Octavia
  83. type LoadBalancerOpts struct {
  84. LBVersion string `gcfg:"lb-version"` // overrides autodetection. Only support v2.
  85. UseOctavia bool `gcfg:"use-octavia"` // uses Octavia V2 service catalog endpoint
  86. SubnetID string `gcfg:"subnet-id"` // overrides autodetection.
  87. FloatingNetworkID string `gcfg:"floating-network-id"` // If specified, will create floating ip for loadbalancer, or do not create floating ip.
  88. LBMethod string `gcfg:"lb-method"` // default to ROUND_ROBIN.
  89. LBProvider string `gcfg:"lb-provider"`
  90. CreateMonitor bool `gcfg:"create-monitor"`
  91. MonitorDelay MyDuration `gcfg:"monitor-delay"`
  92. MonitorTimeout MyDuration `gcfg:"monitor-timeout"`
  93. MonitorMaxRetries uint `gcfg:"monitor-max-retries"`
  94. ManageSecurityGroups bool `gcfg:"manage-security-groups"`
  95. NodeSecurityGroupIDs []string // Do not specify, get it automatically when enable manage-security-groups. TODO(FengyunPan): move it into cache
  96. }
  97. // BlockStorageOpts is used to talk to Cinder service
  98. type BlockStorageOpts struct {
  99. BSVersion string `gcfg:"bs-version"` // overrides autodetection. v1 or v2. Defaults to auto
  100. TrustDevicePath bool `gcfg:"trust-device-path"` // See Issue #33128
  101. IgnoreVolumeAZ bool `gcfg:"ignore-volume-az"`
  102. NodeVolumeAttachLimit int `gcfg:"node-volume-attach-limit"` // override volume attach limit for Cinder. Default is : 256
  103. }
  104. // RouterOpts is used for Neutron routes
  105. type RouterOpts struct {
  106. RouterID string `gcfg:"router-id"` // required
  107. }
  108. // MetadataOpts is used for configuring how to talk to metadata service or config drive
  109. type MetadataOpts struct {
  110. SearchOrder string `gcfg:"search-order"`
  111. RequestTimeout MyDuration `gcfg:"request-timeout"`
  112. }
  113. var _ cloudprovider.Interface = (*OpenStack)(nil)
  114. var _ cloudprovider.Zones = (*OpenStack)(nil)
  115. // OpenStack is an implementation of cloud provider Interface for OpenStack.
  116. type OpenStack struct {
  117. provider *gophercloud.ProviderClient
  118. region string
  119. lbOpts LoadBalancerOpts
  120. bsOpts BlockStorageOpts
  121. routeOpts RouterOpts
  122. metadataOpts MetadataOpts
  123. // InstanceID of the server where this OpenStack object is instantiated.
  124. localInstanceID string
  125. }
  126. // Config is used to read and store information from the cloud configuration file
  127. type Config struct {
  128. Global struct {
  129. AuthURL string `gcfg:"auth-url"`
  130. Username string
  131. UserID string `gcfg:"user-id"`
  132. Password string
  133. TenantID string `gcfg:"tenant-id"`
  134. TenantName string `gcfg:"tenant-name"`
  135. TrustID string `gcfg:"trust-id"`
  136. DomainID string `gcfg:"domain-id"`
  137. DomainName string `gcfg:"domain-name"`
  138. Region string
  139. CAFile string `gcfg:"ca-file"`
  140. SecretName string `gcfg:"secret-name"`
  141. SecretNamespace string `gcfg:"secret-namespace"`
  142. KubeconfigPath string `gcfg:"kubeconfig-path"`
  143. }
  144. LoadBalancer LoadBalancerOpts
  145. BlockStorage BlockStorageOpts
  146. Route RouterOpts
  147. Metadata MetadataOpts
  148. }
  149. func init() {
  150. registerMetrics()
  151. cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) {
  152. cfg, err := readConfig(config)
  153. if err != nil {
  154. return nil, err
  155. }
  156. return newOpenStack(cfg)
  157. })
  158. }
  159. func (cfg Config) toAuthOptions() gophercloud.AuthOptions {
  160. return gophercloud.AuthOptions{
  161. IdentityEndpoint: cfg.Global.AuthURL,
  162. Username: cfg.Global.Username,
  163. UserID: cfg.Global.UserID,
  164. Password: cfg.Global.Password,
  165. TenantID: cfg.Global.TenantID,
  166. TenantName: cfg.Global.TenantName,
  167. DomainID: cfg.Global.DomainID,
  168. DomainName: cfg.Global.DomainName,
  169. // Persistent service, so we need to be able to renew tokens.
  170. AllowReauth: true,
  171. }
  172. }
  173. func (cfg Config) toAuth3Options() tokens3.AuthOptions {
  174. return tokens3.AuthOptions{
  175. IdentityEndpoint: cfg.Global.AuthURL,
  176. Username: cfg.Global.Username,
  177. UserID: cfg.Global.UserID,
  178. Password: cfg.Global.Password,
  179. DomainID: cfg.Global.DomainID,
  180. DomainName: cfg.Global.DomainName,
  181. AllowReauth: true,
  182. }
  183. }
  184. // configFromEnv allows setting up credentials etc using the
  185. // standard OS_* OpenStack client environment variables.
  186. func configFromEnv() (cfg Config, ok bool) {
  187. cfg.Global.AuthURL = os.Getenv("OS_AUTH_URL")
  188. cfg.Global.Username = os.Getenv("OS_USERNAME")
  189. cfg.Global.Password = os.Getenv("OS_PASSWORD")
  190. cfg.Global.Region = os.Getenv("OS_REGION_NAME")
  191. cfg.Global.UserID = os.Getenv("OS_USER_ID")
  192. cfg.Global.TrustID = os.Getenv("OS_TRUST_ID")
  193. cfg.Global.TenantID = os.Getenv("OS_TENANT_ID")
  194. if cfg.Global.TenantID == "" {
  195. cfg.Global.TenantID = os.Getenv("OS_PROJECT_ID")
  196. }
  197. cfg.Global.TenantName = os.Getenv("OS_TENANT_NAME")
  198. if cfg.Global.TenantName == "" {
  199. cfg.Global.TenantName = os.Getenv("OS_PROJECT_NAME")
  200. }
  201. cfg.Global.DomainID = os.Getenv("OS_DOMAIN_ID")
  202. if cfg.Global.DomainID == "" {
  203. cfg.Global.DomainID = os.Getenv("OS_USER_DOMAIN_ID")
  204. }
  205. cfg.Global.DomainName = os.Getenv("OS_DOMAIN_NAME")
  206. if cfg.Global.DomainName == "" {
  207. cfg.Global.DomainName = os.Getenv("OS_USER_DOMAIN_NAME")
  208. }
  209. cfg.Global.SecretName = os.Getenv("SECRET_NAME")
  210. cfg.Global.SecretNamespace = os.Getenv("SECRET_NAMESPACE")
  211. cfg.Global.KubeconfigPath = os.Getenv("KUBECONFIG_PATH")
  212. ok = cfg.Global.AuthURL != "" &&
  213. cfg.Global.Username != "" &&
  214. cfg.Global.Password != "" &&
  215. (cfg.Global.TenantID != "" || cfg.Global.TenantName != "" ||
  216. cfg.Global.DomainID != "" || cfg.Global.DomainName != "" ||
  217. cfg.Global.Region != "" || cfg.Global.UserID != "" ||
  218. cfg.Global.TrustID != "")
  219. cfg.Metadata.SearchOrder = fmt.Sprintf("%s,%s", configDriveID, metadataID)
  220. cfg.BlockStorage.BSVersion = "auto"
  221. return
  222. }
  223. func createKubernetesClient(kubeconfigPath string) (*kubernetes.Clientset, error) {
  224. klog.Info("Creating kubernetes API client.")
  225. cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath)
  226. if err != nil {
  227. return nil, err
  228. }
  229. client, err := kubernetes.NewForConfig(cfg)
  230. if err != nil {
  231. return nil, err
  232. }
  233. v, err := client.Discovery().ServerVersion()
  234. if err != nil {
  235. return nil, err
  236. }
  237. klog.Infof("Kubernetes API client created, server version %s", fmt.Sprintf("v%v.%v", v.Major, v.Minor))
  238. return client, nil
  239. }
  240. // setConfigFromSecret allows setting up the config from k8s secret
  241. func setConfigFromSecret(cfg *Config) error {
  242. secretName := cfg.Global.SecretName
  243. secretNamespace := cfg.Global.SecretNamespace
  244. kubeconfigPath := cfg.Global.KubeconfigPath
  245. k8sClient, err := createKubernetesClient(kubeconfigPath)
  246. if err != nil {
  247. return fmt.Errorf("failed to get kubernetes client: %v", err)
  248. }
  249. secret, err := k8sClient.CoreV1().Secrets(secretNamespace).Get(secretName, metav1.GetOptions{})
  250. if err != nil {
  251. klog.Warningf("Cannot get secret %s in namespace %s. error: %q", secretName, secretNamespace, err)
  252. return err
  253. }
  254. if content, ok := secret.Data["clouds.conf"]; ok {
  255. err = gcfg.ReadStringInto(cfg, string(content))
  256. if err != nil {
  257. klog.Errorf("Cannot parse data from the secret.")
  258. return fmt.Errorf("cannot parse data from the secret")
  259. }
  260. return nil
  261. }
  262. klog.Errorf("Cannot find \"clouds.conf\" key in the secret.")
  263. return fmt.Errorf("cannot find \"clouds.conf\" key in the secret")
  264. }
  265. func readConfig(config io.Reader) (Config, error) {
  266. if config == nil {
  267. return Config{}, fmt.Errorf("no OpenStack cloud provider config file given")
  268. }
  269. cfg, _ := configFromEnv()
  270. // Set default values for config params
  271. cfg.BlockStorage.BSVersion = "auto"
  272. cfg.BlockStorage.TrustDevicePath = false
  273. cfg.BlockStorage.IgnoreVolumeAZ = false
  274. cfg.Metadata.SearchOrder = fmt.Sprintf("%s,%s", configDriveID, metadataID)
  275. err := gcfg.ReadInto(&cfg, config)
  276. if err != nil {
  277. return cfg, err
  278. }
  279. if cfg.Global.SecretName != "" && cfg.Global.SecretNamespace != "" {
  280. klog.Infof("Set credentials from secret %s in namespace %s", cfg.Global.SecretName, cfg.Global.SecretNamespace)
  281. err = setConfigFromSecret(&cfg)
  282. if err != nil {
  283. return cfg, err
  284. }
  285. }
  286. return cfg, nil
  287. }
  288. // caller is a tiny helper for conditional unwind logic
  289. type caller bool
  290. func newCaller() caller { return caller(true) }
  291. func (c *caller) disarm() { *c = false }
  292. func (c *caller) call(f func()) {
  293. if *c {
  294. f()
  295. }
  296. }
  297. func readInstanceID(searchOrder string) (string, error) {
  298. // Try to find instance ID on the local filesystem (created by cloud-init)
  299. const instanceIDFile = "/var/lib/cloud/data/instance-id"
  300. idBytes, err := ioutil.ReadFile(instanceIDFile)
  301. if err == nil {
  302. instanceID := string(idBytes)
  303. instanceID = strings.TrimSpace(instanceID)
  304. klog.V(3).Infof("Got instance id from %s: %s", instanceIDFile, instanceID)
  305. if instanceID != "" {
  306. return instanceID, nil
  307. }
  308. // Fall through to metadata server lookup
  309. }
  310. md, err := getMetadata(searchOrder)
  311. if err != nil {
  312. return "", err
  313. }
  314. return md.UUID, nil
  315. }
  316. // check opts for OpenStack
  317. func checkOpenStackOpts(openstackOpts *OpenStack) error {
  318. lbOpts := openstackOpts.lbOpts
  319. // if need to create health monitor for Neutron LB,
  320. // monitor-delay, monitor-timeout and monitor-max-retries should be set.
  321. emptyDuration := MyDuration{}
  322. if lbOpts.CreateMonitor {
  323. if lbOpts.MonitorDelay == emptyDuration {
  324. return fmt.Errorf("monitor-delay not set in cloud provider config")
  325. }
  326. if lbOpts.MonitorTimeout == emptyDuration {
  327. return fmt.Errorf("monitor-timeout not set in cloud provider config")
  328. }
  329. if lbOpts.MonitorMaxRetries == uint(0) {
  330. return fmt.Errorf("monitor-max-retries not set in cloud provider config")
  331. }
  332. }
  333. return checkMetadataSearchOrder(openstackOpts.metadataOpts.SearchOrder)
  334. }
  335. func newOpenStack(cfg Config) (*OpenStack, error) {
  336. provider, err := openstack.NewClient(cfg.Global.AuthURL)
  337. if err != nil {
  338. return nil, err
  339. }
  340. if cfg.Global.CAFile != "" {
  341. roots, err := certutil.NewPool(cfg.Global.CAFile)
  342. if err != nil {
  343. return nil, err
  344. }
  345. config := &tls.Config{}
  346. config.RootCAs = roots
  347. provider.HTTPClient.Transport = netutil.SetOldTransportDefaults(&http.Transport{TLSClientConfig: config})
  348. }
  349. if cfg.Global.TrustID != "" {
  350. opts := cfg.toAuth3Options()
  351. authOptsExt := trusts.AuthOptsExt{
  352. TrustID: cfg.Global.TrustID,
  353. AuthOptionsBuilder: &opts,
  354. }
  355. err = openstack.AuthenticateV3(provider, authOptsExt, gophercloud.EndpointOpts{})
  356. } else {
  357. err = openstack.Authenticate(provider, cfg.toAuthOptions())
  358. }
  359. if err != nil {
  360. return nil, err
  361. }
  362. emptyDuration := MyDuration{}
  363. if cfg.Metadata.RequestTimeout == emptyDuration {
  364. cfg.Metadata.RequestTimeout.Duration = time.Duration(defaultTimeOut)
  365. }
  366. provider.HTTPClient.Timeout = cfg.Metadata.RequestTimeout.Duration
  367. os := OpenStack{
  368. provider: provider,
  369. region: cfg.Global.Region,
  370. lbOpts: cfg.LoadBalancer,
  371. bsOpts: cfg.BlockStorage,
  372. routeOpts: cfg.Route,
  373. metadataOpts: cfg.Metadata,
  374. }
  375. err = checkOpenStackOpts(&os)
  376. if err != nil {
  377. return nil, err
  378. }
  379. return &os, nil
  380. }
  381. // NewFakeOpenStackCloud creates and returns an instance of Openstack cloudprovider.
  382. // Mainly for use in tests that require instantiating Openstack without having
  383. // to go through cloudprovider interface.
  384. func NewFakeOpenStackCloud(cfg Config) (*OpenStack, error) {
  385. provider, err := openstack.NewClient(cfg.Global.AuthURL)
  386. if err != nil {
  387. return nil, err
  388. }
  389. emptyDuration := MyDuration{}
  390. if cfg.Metadata.RequestTimeout == emptyDuration {
  391. cfg.Metadata.RequestTimeout.Duration = time.Duration(defaultTimeOut)
  392. }
  393. provider.HTTPClient.Timeout = cfg.Metadata.RequestTimeout.Duration
  394. os := OpenStack{
  395. provider: provider,
  396. region: cfg.Global.Region,
  397. lbOpts: cfg.LoadBalancer,
  398. bsOpts: cfg.BlockStorage,
  399. routeOpts: cfg.Route,
  400. metadataOpts: cfg.Metadata,
  401. }
  402. return &os, nil
  403. }
  404. // Initialize passes a Kubernetes clientBuilder interface to the cloud provider
  405. func (os *OpenStack) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, stop <-chan struct{}) {
  406. }
  407. // mapNodeNameToServerName maps a k8s NodeName to an OpenStack Server Name
  408. // This is a simple string cast.
  409. func mapNodeNameToServerName(nodeName types.NodeName) string {
  410. return string(nodeName)
  411. }
  412. // GetNodeNameByID maps instanceid to types.NodeName
  413. func (os *OpenStack) GetNodeNameByID(instanceID string) (types.NodeName, error) {
  414. client, err := os.NewComputeV2()
  415. var nodeName types.NodeName
  416. if err != nil {
  417. return nodeName, err
  418. }
  419. server, err := servers.Get(client, instanceID).Extract()
  420. if err != nil {
  421. return nodeName, err
  422. }
  423. nodeName = mapServerToNodeName(server)
  424. return nodeName, nil
  425. }
  426. // mapServerToNodeName maps an OpenStack Server to a k8s NodeName
  427. func mapServerToNodeName(server *servers.Server) types.NodeName {
  428. // Node names are always lowercase, and (at least)
  429. // routecontroller does case-sensitive string comparisons
  430. // assuming this
  431. return types.NodeName(strings.ToLower(server.Name))
  432. }
  433. func foreachServer(client *gophercloud.ServiceClient, opts servers.ListOptsBuilder, handler func(*servers.Server) (bool, error)) error {
  434. pager := servers.List(client, opts)
  435. err := pager.EachPage(func(page pagination.Page) (bool, error) {
  436. s, err := servers.ExtractServers(page)
  437. if err != nil {
  438. return false, err
  439. }
  440. for _, server := range s {
  441. ok, err := handler(&server)
  442. if !ok || err != nil {
  443. return false, err
  444. }
  445. }
  446. return true, nil
  447. })
  448. return err
  449. }
  450. func getServerByName(client *gophercloud.ServiceClient, name types.NodeName) (*servers.Server, error) {
  451. opts := servers.ListOpts{
  452. Name: fmt.Sprintf("^%s$", regexp.QuoteMeta(mapNodeNameToServerName(name))),
  453. }
  454. pager := servers.List(client, opts)
  455. serverList := make([]servers.Server, 0, 1)
  456. err := pager.EachPage(func(page pagination.Page) (bool, error) {
  457. s, err := servers.ExtractServers(page)
  458. if err != nil {
  459. return false, err
  460. }
  461. serverList = append(serverList, s...)
  462. if len(serverList) > 1 {
  463. return false, ErrMultipleResults
  464. }
  465. return true, nil
  466. })
  467. if err != nil {
  468. return nil, err
  469. }
  470. if len(serverList) == 0 {
  471. return nil, ErrNotFound
  472. }
  473. return &serverList[0], nil
  474. }
  475. func nodeAddresses(srv *servers.Server) ([]v1.NodeAddress, error) {
  476. addrs := []v1.NodeAddress{}
  477. type Address struct {
  478. IPType string `mapstructure:"OS-EXT-IPS:type"`
  479. Addr string
  480. }
  481. var addresses map[string][]Address
  482. err := mapstructure.Decode(srv.Addresses, &addresses)
  483. if err != nil {
  484. return nil, err
  485. }
  486. for network, addrList := range addresses {
  487. for _, props := range addrList {
  488. var addressType v1.NodeAddressType
  489. if props.IPType == "floating" || network == "public" {
  490. addressType = v1.NodeExternalIP
  491. } else {
  492. addressType = v1.NodeInternalIP
  493. }
  494. nodehelpers.AddToNodeAddresses(&addrs,
  495. v1.NodeAddress{
  496. Type: addressType,
  497. Address: props.Addr,
  498. },
  499. )
  500. }
  501. }
  502. // AccessIPs are usually duplicates of "public" addresses.
  503. if srv.AccessIPv4 != "" {
  504. nodehelpers.AddToNodeAddresses(&addrs,
  505. v1.NodeAddress{
  506. Type: v1.NodeExternalIP,
  507. Address: srv.AccessIPv4,
  508. },
  509. )
  510. }
  511. if srv.AccessIPv6 != "" {
  512. nodehelpers.AddToNodeAddresses(&addrs,
  513. v1.NodeAddress{
  514. Type: v1.NodeExternalIP,
  515. Address: srv.AccessIPv6,
  516. },
  517. )
  518. }
  519. if srv.Metadata[TypeHostName] != "" {
  520. nodehelpers.AddToNodeAddresses(&addrs,
  521. v1.NodeAddress{
  522. Type: v1.NodeHostName,
  523. Address: srv.Metadata[TypeHostName],
  524. },
  525. )
  526. }
  527. return addrs, nil
  528. }
  529. func getAddressesByName(client *gophercloud.ServiceClient, name types.NodeName) ([]v1.NodeAddress, error) {
  530. srv, err := getServerByName(client, name)
  531. if err != nil {
  532. return nil, err
  533. }
  534. return nodeAddresses(srv)
  535. }
  536. func getAddressByName(client *gophercloud.ServiceClient, name types.NodeName, needIPv6 bool) (string, error) {
  537. addrs, err := getAddressesByName(client, name)
  538. if err != nil {
  539. return "", err
  540. } else if len(addrs) == 0 {
  541. return "", ErrNoAddressFound
  542. }
  543. for _, addr := range addrs {
  544. isIPv6 := net.ParseIP(addr.Address).To4() == nil
  545. if (addr.Type == v1.NodeInternalIP) && (isIPv6 == needIPv6) {
  546. return addr.Address, nil
  547. }
  548. }
  549. for _, addr := range addrs {
  550. isIPv6 := net.ParseIP(addr.Address).To4() == nil
  551. if (addr.Type == v1.NodeExternalIP) && (isIPv6 == needIPv6) {
  552. return addr.Address, nil
  553. }
  554. }
  555. // It should never return an address from a different IP Address family than the one needed
  556. return "", ErrNoAddressFound
  557. }
  558. // getAttachedInterfacesByID returns the node interfaces of the specified instance.
  559. func getAttachedInterfacesByID(client *gophercloud.ServiceClient, serviceID string) ([]attachinterfaces.Interface, error) {
  560. var interfaces []attachinterfaces.Interface
  561. pager := attachinterfaces.List(client, serviceID)
  562. err := pager.EachPage(func(page pagination.Page) (bool, error) {
  563. s, err := attachinterfaces.ExtractInterfaces(page)
  564. if err != nil {
  565. return false, err
  566. }
  567. interfaces = append(interfaces, s...)
  568. return true, nil
  569. })
  570. if err != nil {
  571. return interfaces, err
  572. }
  573. return interfaces, nil
  574. }
  575. // Clusters is a no-op
  576. func (os *OpenStack) Clusters() (cloudprovider.Clusters, bool) {
  577. return nil, false
  578. }
  579. // ProviderName returns the cloud provider ID.
  580. func (os *OpenStack) ProviderName() string {
  581. return ProviderName
  582. }
  583. // HasClusterID returns true if the cluster has a clusterID
  584. func (os *OpenStack) HasClusterID() bool {
  585. return true
  586. }
  587. // LoadBalancer initializes a LbaasV2 object
  588. func (os *OpenStack) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
  589. klog.V(4).Info("openstack.LoadBalancer() called")
  590. if reflect.DeepEqual(os.lbOpts, LoadBalancerOpts{}) {
  591. klog.V(4).Info("LoadBalancer section is empty/not defined in cloud-config")
  592. return nil, false
  593. }
  594. network, err := os.NewNetworkV2()
  595. if err != nil {
  596. return nil, false
  597. }
  598. compute, err := os.NewComputeV2()
  599. if err != nil {
  600. return nil, false
  601. }
  602. lb, err := os.NewLoadBalancerV2()
  603. if err != nil {
  604. return nil, false
  605. }
  606. // LBaaS v1 is deprecated in the OpenStack Liberty release.
  607. // Currently kubernetes OpenStack cloud provider just support LBaaS v2.
  608. lbVersion := os.lbOpts.LBVersion
  609. if lbVersion != "" && lbVersion != "v2" {
  610. klog.Warningf("Config error: currently only support LBaaS v2, unrecognised lb-version \"%v\"", lbVersion)
  611. return nil, false
  612. }
  613. klog.V(1).Info("Claiming to support LoadBalancer")
  614. return &LbaasV2{LoadBalancer{network, compute, lb, os.lbOpts}}, true
  615. }
  616. func isNotFound(err error) bool {
  617. if _, ok := err.(gophercloud.ErrDefault404); ok {
  618. return true
  619. }
  620. if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
  621. if errCode.Actual == http.StatusNotFound {
  622. return true
  623. }
  624. }
  625. return false
  626. }
  627. // Zones indicates that we support zones
  628. func (os *OpenStack) Zones() (cloudprovider.Zones, bool) {
  629. klog.V(1).Info("Claiming to support Zones")
  630. return os, true
  631. }
  632. // GetZone returns the current zone
  633. func (os *OpenStack) GetZone(ctx context.Context) (cloudprovider.Zone, error) {
  634. md, err := getMetadata(os.metadataOpts.SearchOrder)
  635. if err != nil {
  636. return cloudprovider.Zone{}, err
  637. }
  638. zone := cloudprovider.Zone{
  639. FailureDomain: md.AvailabilityZone,
  640. Region: os.region,
  641. }
  642. klog.V(4).Infof("Current zone is %v", zone)
  643. return zone, nil
  644. }
  645. // GetZoneByProviderID implements Zones.GetZoneByProviderID
  646. // This is particularly useful in external cloud providers where the kubelet
  647. // does not initialize node data.
  648. func (os *OpenStack) GetZoneByProviderID(ctx context.Context, providerID string) (cloudprovider.Zone, error) {
  649. instanceID, err := instanceIDFromProviderID(providerID)
  650. if err != nil {
  651. return cloudprovider.Zone{}, err
  652. }
  653. compute, err := os.NewComputeV2()
  654. if err != nil {
  655. return cloudprovider.Zone{}, err
  656. }
  657. srv, err := servers.Get(compute, instanceID).Extract()
  658. if err != nil {
  659. return cloudprovider.Zone{}, err
  660. }
  661. zone := cloudprovider.Zone{
  662. FailureDomain: srv.Metadata[availabilityZone],
  663. Region: os.region,
  664. }
  665. klog.V(4).Infof("The instance %s in zone %v", srv.Name, zone)
  666. return zone, nil
  667. }
  668. // GetZoneByNodeName implements Zones.GetZoneByNodeName
  669. // This is particularly useful in external cloud providers where the kubelet
  670. // does not initialize node data.
  671. func (os *OpenStack) GetZoneByNodeName(ctx context.Context, nodeName types.NodeName) (cloudprovider.Zone, error) {
  672. compute, err := os.NewComputeV2()
  673. if err != nil {
  674. return cloudprovider.Zone{}, err
  675. }
  676. srv, err := getServerByName(compute, nodeName)
  677. if err != nil {
  678. if err == ErrNotFound {
  679. return cloudprovider.Zone{}, cloudprovider.InstanceNotFound
  680. }
  681. return cloudprovider.Zone{}, err
  682. }
  683. zone := cloudprovider.Zone{
  684. FailureDomain: srv.Metadata[availabilityZone],
  685. Region: os.region,
  686. }
  687. klog.V(4).Infof("The instance %s in zone %v", srv.Name, zone)
  688. return zone, nil
  689. }
  690. // Routes initializes routes support
  691. func (os *OpenStack) Routes() (cloudprovider.Routes, bool) {
  692. klog.V(4).Info("openstack.Routes() called")
  693. network, err := os.NewNetworkV2()
  694. if err != nil {
  695. return nil, false
  696. }
  697. netExts, err := networkExtensions(network)
  698. if err != nil {
  699. klog.Warningf("Failed to list neutron extensions: %v", err)
  700. return nil, false
  701. }
  702. if !netExts["extraroute"] {
  703. klog.V(3).Info("Neutron extraroute extension not found, required for Routes support")
  704. return nil, false
  705. }
  706. compute, err := os.NewComputeV2()
  707. if err != nil {
  708. return nil, false
  709. }
  710. r, err := NewRoutes(compute, network, os.routeOpts)
  711. if err != nil {
  712. klog.Warningf("Error initialising Routes support: %v", err)
  713. return nil, false
  714. }
  715. klog.V(1).Info("Claiming to support Routes")
  716. return r, true
  717. }
  718. func (os *OpenStack) volumeService(forceVersion string) (volumeService, error) {
  719. bsVersion := ""
  720. if forceVersion == "" {
  721. bsVersion = os.bsOpts.BSVersion
  722. } else {
  723. bsVersion = forceVersion
  724. }
  725. switch bsVersion {
  726. case "v1":
  727. sClient, err := os.NewBlockStorageV1()
  728. if err != nil {
  729. return nil, err
  730. }
  731. klog.V(3).Info("Using Blockstorage API V1")
  732. return &VolumesV1{sClient, os.bsOpts}, nil
  733. case "v2":
  734. sClient, err := os.NewBlockStorageV2()
  735. if err != nil {
  736. return nil, err
  737. }
  738. klog.V(3).Info("Using Blockstorage API V2")
  739. return &VolumesV2{sClient, os.bsOpts}, nil
  740. case "v3":
  741. sClient, err := os.NewBlockStorageV3()
  742. if err != nil {
  743. return nil, err
  744. }
  745. klog.V(3).Info("Using Blockstorage API V3")
  746. return &VolumesV3{sClient, os.bsOpts}, nil
  747. case "auto":
  748. // Currently kubernetes support Cinder v1 / Cinder v2 / Cinder v3.
  749. // Choose Cinder v3 firstly, if kubernetes can't initialize cinder v3 client, try to initialize cinder v2 client.
  750. // If kubernetes can't initialize cinder v2 client, try to initialize cinder v1 client.
  751. // Return appropriate message when kubernetes can't initialize them.
  752. if sClient, err := os.NewBlockStorageV3(); err == nil {
  753. klog.V(3).Info("Using Blockstorage API V3")
  754. return &VolumesV3{sClient, os.bsOpts}, nil
  755. }
  756. if sClient, err := os.NewBlockStorageV2(); err == nil {
  757. klog.V(3).Info("Using Blockstorage API V2")
  758. return &VolumesV2{sClient, os.bsOpts}, nil
  759. }
  760. if sClient, err := os.NewBlockStorageV1(); err == nil {
  761. klog.V(3).Info("Using Blockstorage API V1")
  762. return &VolumesV1{sClient, os.bsOpts}, nil
  763. }
  764. errTxt := "BlockStorage API version autodetection failed. " +
  765. "Please set it explicitly in cloud.conf in section [BlockStorage] with key `bs-version`"
  766. return nil, errors.New(errTxt)
  767. default:
  768. errTxt := fmt.Sprintf("Config error: unrecognised bs-version \"%v\"", os.bsOpts.BSVersion)
  769. return nil, errors.New(errTxt)
  770. }
  771. }
  772. func checkMetadataSearchOrder(order string) error {
  773. if order == "" {
  774. return errors.New("invalid value in section [Metadata] with key `search-order`. Value cannot be empty")
  775. }
  776. elements := strings.Split(order, ",")
  777. if len(elements) > 2 {
  778. return errors.New("invalid value in section [Metadata] with key `search-order`. Value cannot contain more than 2 elements")
  779. }
  780. for _, id := range elements {
  781. id = strings.TrimSpace(id)
  782. switch id {
  783. case configDriveID:
  784. case metadataID:
  785. default:
  786. return fmt.Errorf("invalid element %q found in section [Metadata] with key `search-order`."+
  787. "Supported elements include %q and %q", id, configDriveID, metadataID)
  788. }
  789. }
  790. return nil
  791. }