123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930 |
- /*
- Copyright 2014 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package openstack
- import (
- "context"
- "crypto/tls"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "net"
- "net/http"
- "os"
- "reflect"
- "regexp"
- "strings"
- "time"
- "github.com/gophercloud/gophercloud"
- "github.com/gophercloud/gophercloud/openstack"
- "github.com/gophercloud/gophercloud/openstack/compute/v2/extensions/attachinterfaces"
- "github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
- "github.com/gophercloud/gophercloud/openstack/identity/v3/extensions/trusts"
- tokens3 "github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
- "github.com/gophercloud/gophercloud/pagination"
- "github.com/mitchellh/mapstructure"
- "gopkg.in/gcfg.v1"
- "k8s.io/api/core/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/types"
- netutil "k8s.io/apimachinery/pkg/util/net"
- "k8s.io/client-go/kubernetes"
- "k8s.io/client-go/tools/clientcmd"
- certutil "k8s.io/client-go/util/cert"
- cloudprovider "k8s.io/cloud-provider"
- nodehelpers "k8s.io/cloud-provider/node/helpers"
- "k8s.io/klog"
- )
- const (
- // ProviderName is the name of the openstack provider
- ProviderName = "openstack"
- // TypeHostName is the name type of openstack instance
- TypeHostName = "hostname"
- availabilityZone = "availability_zone"
- defaultTimeOut = 60 * time.Second
- )
- // ErrNotFound is used to inform that the object is missing
- var ErrNotFound = errors.New("failed to find object")
- // ErrMultipleResults is used when we unexpectedly get back multiple results
- var ErrMultipleResults = errors.New("multiple results where only one expected")
- // ErrNoAddressFound is used when we cannot find an ip address for the host
- var ErrNoAddressFound = errors.New("no address found for host")
- // MyDuration is the encoding.TextUnmarshaler interface for time.Duration
- type MyDuration struct {
- time.Duration
- }
- // UnmarshalText is used to convert from text to Duration
- func (d *MyDuration) UnmarshalText(text []byte) error {
- res, err := time.ParseDuration(string(text))
- if err != nil {
- return err
- }
- d.Duration = res
- return nil
- }
- // LoadBalancer is used for creating and maintaining load balancers
- type LoadBalancer struct {
- network *gophercloud.ServiceClient
- compute *gophercloud.ServiceClient
- lb *gophercloud.ServiceClient
- opts LoadBalancerOpts
- }
- // LoadBalancerOpts have the options to talk to Neutron LBaaSV2 or Octavia
- type LoadBalancerOpts struct {
- LBVersion string `gcfg:"lb-version"` // overrides autodetection. Only support v2.
- UseOctavia bool `gcfg:"use-octavia"` // uses Octavia V2 service catalog endpoint
- SubnetID string `gcfg:"subnet-id"` // overrides autodetection.
- FloatingNetworkID string `gcfg:"floating-network-id"` // If specified, will create floating ip for loadbalancer, or do not create floating ip.
- LBMethod string `gcfg:"lb-method"` // default to ROUND_ROBIN.
- LBProvider string `gcfg:"lb-provider"`
- CreateMonitor bool `gcfg:"create-monitor"`
- MonitorDelay MyDuration `gcfg:"monitor-delay"`
- MonitorTimeout MyDuration `gcfg:"monitor-timeout"`
- MonitorMaxRetries uint `gcfg:"monitor-max-retries"`
- ManageSecurityGroups bool `gcfg:"manage-security-groups"`
- NodeSecurityGroupIDs []string // Do not specify, get it automatically when enable manage-security-groups. TODO(FengyunPan): move it into cache
- }
- // BlockStorageOpts is used to talk to Cinder service
- type BlockStorageOpts struct {
- BSVersion string `gcfg:"bs-version"` // overrides autodetection. v1 or v2. Defaults to auto
- TrustDevicePath bool `gcfg:"trust-device-path"` // See Issue #33128
- IgnoreVolumeAZ bool `gcfg:"ignore-volume-az"`
- NodeVolumeAttachLimit int `gcfg:"node-volume-attach-limit"` // override volume attach limit for Cinder. Default is : 256
- }
- // RouterOpts is used for Neutron routes
- type RouterOpts struct {
- RouterID string `gcfg:"router-id"` // required
- }
- // MetadataOpts is used for configuring how to talk to metadata service or config drive
- type MetadataOpts struct {
- SearchOrder string `gcfg:"search-order"`
- RequestTimeout MyDuration `gcfg:"request-timeout"`
- }
- var _ cloudprovider.Interface = (*OpenStack)(nil)
- var _ cloudprovider.Zones = (*OpenStack)(nil)
- // OpenStack is an implementation of cloud provider Interface for OpenStack.
- type OpenStack struct {
- provider *gophercloud.ProviderClient
- region string
- lbOpts LoadBalancerOpts
- bsOpts BlockStorageOpts
- routeOpts RouterOpts
- metadataOpts MetadataOpts
- // InstanceID of the server where this OpenStack object is instantiated.
- localInstanceID string
- }
- // Config is used to read and store information from the cloud configuration file
- type Config struct {
- Global struct {
- AuthURL string `gcfg:"auth-url"`
- Username string
- UserID string `gcfg:"user-id"`
- Password string
- TenantID string `gcfg:"tenant-id"`
- TenantName string `gcfg:"tenant-name"`
- TrustID string `gcfg:"trust-id"`
- DomainID string `gcfg:"domain-id"`
- DomainName string `gcfg:"domain-name"`
- Region string
- CAFile string `gcfg:"ca-file"`
- SecretName string `gcfg:"secret-name"`
- SecretNamespace string `gcfg:"secret-namespace"`
- KubeconfigPath string `gcfg:"kubeconfig-path"`
- }
- LoadBalancer LoadBalancerOpts
- BlockStorage BlockStorageOpts
- Route RouterOpts
- Metadata MetadataOpts
- }
- func init() {
- registerMetrics()
- cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) {
- cfg, err := readConfig(config)
- if err != nil {
- return nil, err
- }
- return newOpenStack(cfg)
- })
- }
- func (cfg Config) toAuthOptions() gophercloud.AuthOptions {
- return gophercloud.AuthOptions{
- IdentityEndpoint: cfg.Global.AuthURL,
- Username: cfg.Global.Username,
- UserID: cfg.Global.UserID,
- Password: cfg.Global.Password,
- TenantID: cfg.Global.TenantID,
- TenantName: cfg.Global.TenantName,
- DomainID: cfg.Global.DomainID,
- DomainName: cfg.Global.DomainName,
- // Persistent service, so we need to be able to renew tokens.
- AllowReauth: true,
- }
- }
- func (cfg Config) toAuth3Options() tokens3.AuthOptions {
- return tokens3.AuthOptions{
- IdentityEndpoint: cfg.Global.AuthURL,
- Username: cfg.Global.Username,
- UserID: cfg.Global.UserID,
- Password: cfg.Global.Password,
- DomainID: cfg.Global.DomainID,
- DomainName: cfg.Global.DomainName,
- AllowReauth: true,
- }
- }
- // configFromEnv allows setting up credentials etc using the
- // standard OS_* OpenStack client environment variables.
- func configFromEnv() (cfg Config, ok bool) {
- cfg.Global.AuthURL = os.Getenv("OS_AUTH_URL")
- cfg.Global.Username = os.Getenv("OS_USERNAME")
- cfg.Global.Password = os.Getenv("OS_PASSWORD")
- cfg.Global.Region = os.Getenv("OS_REGION_NAME")
- cfg.Global.UserID = os.Getenv("OS_USER_ID")
- cfg.Global.TrustID = os.Getenv("OS_TRUST_ID")
- cfg.Global.TenantID = os.Getenv("OS_TENANT_ID")
- if cfg.Global.TenantID == "" {
- cfg.Global.TenantID = os.Getenv("OS_PROJECT_ID")
- }
- cfg.Global.TenantName = os.Getenv("OS_TENANT_NAME")
- if cfg.Global.TenantName == "" {
- cfg.Global.TenantName = os.Getenv("OS_PROJECT_NAME")
- }
- cfg.Global.DomainID = os.Getenv("OS_DOMAIN_ID")
- if cfg.Global.DomainID == "" {
- cfg.Global.DomainID = os.Getenv("OS_USER_DOMAIN_ID")
- }
- cfg.Global.DomainName = os.Getenv("OS_DOMAIN_NAME")
- if cfg.Global.DomainName == "" {
- cfg.Global.DomainName = os.Getenv("OS_USER_DOMAIN_NAME")
- }
- cfg.Global.SecretName = os.Getenv("SECRET_NAME")
- cfg.Global.SecretNamespace = os.Getenv("SECRET_NAMESPACE")
- cfg.Global.KubeconfigPath = os.Getenv("KUBECONFIG_PATH")
- ok = cfg.Global.AuthURL != "" &&
- cfg.Global.Username != "" &&
- cfg.Global.Password != "" &&
- (cfg.Global.TenantID != "" || cfg.Global.TenantName != "" ||
- cfg.Global.DomainID != "" || cfg.Global.DomainName != "" ||
- cfg.Global.Region != "" || cfg.Global.UserID != "" ||
- cfg.Global.TrustID != "")
- cfg.Metadata.SearchOrder = fmt.Sprintf("%s,%s", configDriveID, metadataID)
- cfg.BlockStorage.BSVersion = "auto"
- return
- }
- func createKubernetesClient(kubeconfigPath string) (*kubernetes.Clientset, error) {
- klog.Info("Creating kubernetes API client.")
- cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath)
- if err != nil {
- return nil, err
- }
- client, err := kubernetes.NewForConfig(cfg)
- if err != nil {
- return nil, err
- }
- v, err := client.Discovery().ServerVersion()
- if err != nil {
- return nil, err
- }
- klog.Infof("Kubernetes API client created, server version %s", fmt.Sprintf("v%v.%v", v.Major, v.Minor))
- return client, nil
- }
- // setConfigFromSecret allows setting up the config from k8s secret
- func setConfigFromSecret(cfg *Config) error {
- secretName := cfg.Global.SecretName
- secretNamespace := cfg.Global.SecretNamespace
- kubeconfigPath := cfg.Global.KubeconfigPath
- k8sClient, err := createKubernetesClient(kubeconfigPath)
- if err != nil {
- return fmt.Errorf("failed to get kubernetes client: %v", err)
- }
- secret, err := k8sClient.CoreV1().Secrets(secretNamespace).Get(secretName, metav1.GetOptions{})
- if err != nil {
- klog.Warningf("Cannot get secret %s in namespace %s. error: %q", secretName, secretNamespace, err)
- return err
- }
- if content, ok := secret.Data["clouds.conf"]; ok {
- err = gcfg.ReadStringInto(cfg, string(content))
- if err != nil {
- klog.Errorf("Cannot parse data from the secret.")
- return fmt.Errorf("cannot parse data from the secret")
- }
- return nil
- }
- klog.Errorf("Cannot find \"clouds.conf\" key in the secret.")
- return fmt.Errorf("cannot find \"clouds.conf\" key in the secret")
- }
- func readConfig(config io.Reader) (Config, error) {
- if config == nil {
- return Config{}, fmt.Errorf("no OpenStack cloud provider config file given")
- }
- cfg, _ := configFromEnv()
- // Set default values for config params
- cfg.BlockStorage.BSVersion = "auto"
- cfg.BlockStorage.TrustDevicePath = false
- cfg.BlockStorage.IgnoreVolumeAZ = false
- cfg.Metadata.SearchOrder = fmt.Sprintf("%s,%s", configDriveID, metadataID)
- err := gcfg.ReadInto(&cfg, config)
- if err != nil {
- return cfg, err
- }
- if cfg.Global.SecretName != "" && cfg.Global.SecretNamespace != "" {
- klog.Infof("Set credentials from secret %s in namespace %s", cfg.Global.SecretName, cfg.Global.SecretNamespace)
- err = setConfigFromSecret(&cfg)
- if err != nil {
- return cfg, err
- }
- }
- return cfg, nil
- }
- // caller is a tiny helper for conditional unwind logic
- type caller bool
- func newCaller() caller { return caller(true) }
- func (c *caller) disarm() { *c = false }
- func (c *caller) call(f func()) {
- if *c {
- f()
- }
- }
- func readInstanceID(searchOrder string) (string, error) {
- // Try to find instance ID on the local filesystem (created by cloud-init)
- const instanceIDFile = "/var/lib/cloud/data/instance-id"
- idBytes, err := ioutil.ReadFile(instanceIDFile)
- if err == nil {
- instanceID := string(idBytes)
- instanceID = strings.TrimSpace(instanceID)
- klog.V(3).Infof("Got instance id from %s: %s", instanceIDFile, instanceID)
- if instanceID != "" {
- return instanceID, nil
- }
- // Fall through to metadata server lookup
- }
- md, err := getMetadata(searchOrder)
- if err != nil {
- return "", err
- }
- return md.UUID, nil
- }
- // check opts for OpenStack
- func checkOpenStackOpts(openstackOpts *OpenStack) error {
- lbOpts := openstackOpts.lbOpts
- // if need to create health monitor for Neutron LB,
- // monitor-delay, monitor-timeout and monitor-max-retries should be set.
- emptyDuration := MyDuration{}
- if lbOpts.CreateMonitor {
- if lbOpts.MonitorDelay == emptyDuration {
- return fmt.Errorf("monitor-delay not set in cloud provider config")
- }
- if lbOpts.MonitorTimeout == emptyDuration {
- return fmt.Errorf("monitor-timeout not set in cloud provider config")
- }
- if lbOpts.MonitorMaxRetries == uint(0) {
- return fmt.Errorf("monitor-max-retries not set in cloud provider config")
- }
- }
- return checkMetadataSearchOrder(openstackOpts.metadataOpts.SearchOrder)
- }
- func newOpenStack(cfg Config) (*OpenStack, error) {
- provider, err := openstack.NewClient(cfg.Global.AuthURL)
- if err != nil {
- return nil, err
- }
- if cfg.Global.CAFile != "" {
- roots, err := certutil.NewPool(cfg.Global.CAFile)
- if err != nil {
- return nil, err
- }
- config := &tls.Config{}
- config.RootCAs = roots
- provider.HTTPClient.Transport = netutil.SetOldTransportDefaults(&http.Transport{TLSClientConfig: config})
- }
- if cfg.Global.TrustID != "" {
- opts := cfg.toAuth3Options()
- authOptsExt := trusts.AuthOptsExt{
- TrustID: cfg.Global.TrustID,
- AuthOptionsBuilder: &opts,
- }
- err = openstack.AuthenticateV3(provider, authOptsExt, gophercloud.EndpointOpts{})
- } else {
- err = openstack.Authenticate(provider, cfg.toAuthOptions())
- }
- if err != nil {
- return nil, err
- }
- emptyDuration := MyDuration{}
- if cfg.Metadata.RequestTimeout == emptyDuration {
- cfg.Metadata.RequestTimeout.Duration = time.Duration(defaultTimeOut)
- }
- provider.HTTPClient.Timeout = cfg.Metadata.RequestTimeout.Duration
- os := OpenStack{
- provider: provider,
- region: cfg.Global.Region,
- lbOpts: cfg.LoadBalancer,
- bsOpts: cfg.BlockStorage,
- routeOpts: cfg.Route,
- metadataOpts: cfg.Metadata,
- }
- err = checkOpenStackOpts(&os)
- if err != nil {
- return nil, err
- }
- return &os, nil
- }
- // NewFakeOpenStackCloud creates and returns an instance of Openstack cloudprovider.
- // Mainly for use in tests that require instantiating Openstack without having
- // to go through cloudprovider interface.
- func NewFakeOpenStackCloud(cfg Config) (*OpenStack, error) {
- provider, err := openstack.NewClient(cfg.Global.AuthURL)
- if err != nil {
- return nil, err
- }
- emptyDuration := MyDuration{}
- if cfg.Metadata.RequestTimeout == emptyDuration {
- cfg.Metadata.RequestTimeout.Duration = time.Duration(defaultTimeOut)
- }
- provider.HTTPClient.Timeout = cfg.Metadata.RequestTimeout.Duration
- os := OpenStack{
- provider: provider,
- region: cfg.Global.Region,
- lbOpts: cfg.LoadBalancer,
- bsOpts: cfg.BlockStorage,
- routeOpts: cfg.Route,
- metadataOpts: cfg.Metadata,
- }
- return &os, nil
- }
- // Initialize passes a Kubernetes clientBuilder interface to the cloud provider
- func (os *OpenStack) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, stop <-chan struct{}) {
- }
- // mapNodeNameToServerName maps a k8s NodeName to an OpenStack Server Name
- // This is a simple string cast.
- func mapNodeNameToServerName(nodeName types.NodeName) string {
- return string(nodeName)
- }
- // GetNodeNameByID maps instanceid to types.NodeName
- func (os *OpenStack) GetNodeNameByID(instanceID string) (types.NodeName, error) {
- client, err := os.NewComputeV2()
- var nodeName types.NodeName
- if err != nil {
- return nodeName, err
- }
- server, err := servers.Get(client, instanceID).Extract()
- if err != nil {
- return nodeName, err
- }
- nodeName = mapServerToNodeName(server)
- return nodeName, nil
- }
- // mapServerToNodeName maps an OpenStack Server to a k8s NodeName
- func mapServerToNodeName(server *servers.Server) types.NodeName {
- // Node names are always lowercase, and (at least)
- // routecontroller does case-sensitive string comparisons
- // assuming this
- return types.NodeName(strings.ToLower(server.Name))
- }
- func foreachServer(client *gophercloud.ServiceClient, opts servers.ListOptsBuilder, handler func(*servers.Server) (bool, error)) error {
- pager := servers.List(client, opts)
- err := pager.EachPage(func(page pagination.Page) (bool, error) {
- s, err := servers.ExtractServers(page)
- if err != nil {
- return false, err
- }
- for _, server := range s {
- ok, err := handler(&server)
- if !ok || err != nil {
- return false, err
- }
- }
- return true, nil
- })
- return err
- }
- func getServerByName(client *gophercloud.ServiceClient, name types.NodeName) (*servers.Server, error) {
- opts := servers.ListOpts{
- Name: fmt.Sprintf("^%s$", regexp.QuoteMeta(mapNodeNameToServerName(name))),
- }
- pager := servers.List(client, opts)
- serverList := make([]servers.Server, 0, 1)
- err := pager.EachPage(func(page pagination.Page) (bool, error) {
- s, err := servers.ExtractServers(page)
- if err != nil {
- return false, err
- }
- serverList = append(serverList, s...)
- if len(serverList) > 1 {
- return false, ErrMultipleResults
- }
- return true, nil
- })
- if err != nil {
- return nil, err
- }
- if len(serverList) == 0 {
- return nil, ErrNotFound
- }
- return &serverList[0], nil
- }
- func nodeAddresses(srv *servers.Server) ([]v1.NodeAddress, error) {
- addrs := []v1.NodeAddress{}
- type Address struct {
- IPType string `mapstructure:"OS-EXT-IPS:type"`
- Addr string
- }
- var addresses map[string][]Address
- err := mapstructure.Decode(srv.Addresses, &addresses)
- if err != nil {
- return nil, err
- }
- for network, addrList := range addresses {
- for _, props := range addrList {
- var addressType v1.NodeAddressType
- if props.IPType == "floating" || network == "public" {
- addressType = v1.NodeExternalIP
- } else {
- addressType = v1.NodeInternalIP
- }
- nodehelpers.AddToNodeAddresses(&addrs,
- v1.NodeAddress{
- Type: addressType,
- Address: props.Addr,
- },
- )
- }
- }
- // AccessIPs are usually duplicates of "public" addresses.
- if srv.AccessIPv4 != "" {
- nodehelpers.AddToNodeAddresses(&addrs,
- v1.NodeAddress{
- Type: v1.NodeExternalIP,
- Address: srv.AccessIPv4,
- },
- )
- }
- if srv.AccessIPv6 != "" {
- nodehelpers.AddToNodeAddresses(&addrs,
- v1.NodeAddress{
- Type: v1.NodeExternalIP,
- Address: srv.AccessIPv6,
- },
- )
- }
- if srv.Metadata[TypeHostName] != "" {
- nodehelpers.AddToNodeAddresses(&addrs,
- v1.NodeAddress{
- Type: v1.NodeHostName,
- Address: srv.Metadata[TypeHostName],
- },
- )
- }
- return addrs, nil
- }
- func getAddressesByName(client *gophercloud.ServiceClient, name types.NodeName) ([]v1.NodeAddress, error) {
- srv, err := getServerByName(client, name)
- if err != nil {
- return nil, err
- }
- return nodeAddresses(srv)
- }
- func getAddressByName(client *gophercloud.ServiceClient, name types.NodeName, needIPv6 bool) (string, error) {
- addrs, err := getAddressesByName(client, name)
- if err != nil {
- return "", err
- } else if len(addrs) == 0 {
- return "", ErrNoAddressFound
- }
- for _, addr := range addrs {
- isIPv6 := net.ParseIP(addr.Address).To4() == nil
- if (addr.Type == v1.NodeInternalIP) && (isIPv6 == needIPv6) {
- return addr.Address, nil
- }
- }
- for _, addr := range addrs {
- isIPv6 := net.ParseIP(addr.Address).To4() == nil
- if (addr.Type == v1.NodeExternalIP) && (isIPv6 == needIPv6) {
- return addr.Address, nil
- }
- }
- // It should never return an address from a different IP Address family than the one needed
- return "", ErrNoAddressFound
- }
- // getAttachedInterfacesByID returns the node interfaces of the specified instance.
- func getAttachedInterfacesByID(client *gophercloud.ServiceClient, serviceID string) ([]attachinterfaces.Interface, error) {
- var interfaces []attachinterfaces.Interface
- pager := attachinterfaces.List(client, serviceID)
- err := pager.EachPage(func(page pagination.Page) (bool, error) {
- s, err := attachinterfaces.ExtractInterfaces(page)
- if err != nil {
- return false, err
- }
- interfaces = append(interfaces, s...)
- return true, nil
- })
- if err != nil {
- return interfaces, err
- }
- return interfaces, nil
- }
- // Clusters is a no-op
- func (os *OpenStack) Clusters() (cloudprovider.Clusters, bool) {
- return nil, false
- }
- // ProviderName returns the cloud provider ID.
- func (os *OpenStack) ProviderName() string {
- return ProviderName
- }
- // HasClusterID returns true if the cluster has a clusterID
- func (os *OpenStack) HasClusterID() bool {
- return true
- }
- // LoadBalancer initializes a LbaasV2 object
- func (os *OpenStack) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
- klog.V(4).Info("openstack.LoadBalancer() called")
- if reflect.DeepEqual(os.lbOpts, LoadBalancerOpts{}) {
- klog.V(4).Info("LoadBalancer section is empty/not defined in cloud-config")
- return nil, false
- }
- network, err := os.NewNetworkV2()
- if err != nil {
- return nil, false
- }
- compute, err := os.NewComputeV2()
- if err != nil {
- return nil, false
- }
- lb, err := os.NewLoadBalancerV2()
- if err != nil {
- return nil, false
- }
- // LBaaS v1 is deprecated in the OpenStack Liberty release.
- // Currently kubernetes OpenStack cloud provider just support LBaaS v2.
- lbVersion := os.lbOpts.LBVersion
- if lbVersion != "" && lbVersion != "v2" {
- klog.Warningf("Config error: currently only support LBaaS v2, unrecognised lb-version \"%v\"", lbVersion)
- return nil, false
- }
- klog.V(1).Info("Claiming to support LoadBalancer")
- return &LbaasV2{LoadBalancer{network, compute, lb, os.lbOpts}}, true
- }
- func isNotFound(err error) bool {
- if _, ok := err.(gophercloud.ErrDefault404); ok {
- return true
- }
- if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
- if errCode.Actual == http.StatusNotFound {
- return true
- }
- }
- return false
- }
- // Zones indicates that we support zones
- func (os *OpenStack) Zones() (cloudprovider.Zones, bool) {
- klog.V(1).Info("Claiming to support Zones")
- return os, true
- }
- // GetZone returns the current zone
- func (os *OpenStack) GetZone(ctx context.Context) (cloudprovider.Zone, error) {
- md, err := getMetadata(os.metadataOpts.SearchOrder)
- if err != nil {
- return cloudprovider.Zone{}, err
- }
- zone := cloudprovider.Zone{
- FailureDomain: md.AvailabilityZone,
- Region: os.region,
- }
- klog.V(4).Infof("Current zone is %v", zone)
- return zone, nil
- }
- // GetZoneByProviderID implements Zones.GetZoneByProviderID
- // This is particularly useful in external cloud providers where the kubelet
- // does not initialize node data.
- func (os *OpenStack) GetZoneByProviderID(ctx context.Context, providerID string) (cloudprovider.Zone, error) {
- instanceID, err := instanceIDFromProviderID(providerID)
- if err != nil {
- return cloudprovider.Zone{}, err
- }
- compute, err := os.NewComputeV2()
- if err != nil {
- return cloudprovider.Zone{}, err
- }
- srv, err := servers.Get(compute, instanceID).Extract()
- if err != nil {
- return cloudprovider.Zone{}, err
- }
- zone := cloudprovider.Zone{
- FailureDomain: srv.Metadata[availabilityZone],
- Region: os.region,
- }
- klog.V(4).Infof("The instance %s in zone %v", srv.Name, zone)
- return zone, nil
- }
- // GetZoneByNodeName implements Zones.GetZoneByNodeName
- // This is particularly useful in external cloud providers where the kubelet
- // does not initialize node data.
- func (os *OpenStack) GetZoneByNodeName(ctx context.Context, nodeName types.NodeName) (cloudprovider.Zone, error) {
- compute, err := os.NewComputeV2()
- if err != nil {
- return cloudprovider.Zone{}, err
- }
- srv, err := getServerByName(compute, nodeName)
- if err != nil {
- if err == ErrNotFound {
- return cloudprovider.Zone{}, cloudprovider.InstanceNotFound
- }
- return cloudprovider.Zone{}, err
- }
- zone := cloudprovider.Zone{
- FailureDomain: srv.Metadata[availabilityZone],
- Region: os.region,
- }
- klog.V(4).Infof("The instance %s in zone %v", srv.Name, zone)
- return zone, nil
- }
- // Routes initializes routes support
- func (os *OpenStack) Routes() (cloudprovider.Routes, bool) {
- klog.V(4).Info("openstack.Routes() called")
- network, err := os.NewNetworkV2()
- if err != nil {
- return nil, false
- }
- netExts, err := networkExtensions(network)
- if err != nil {
- klog.Warningf("Failed to list neutron extensions: %v", err)
- return nil, false
- }
- if !netExts["extraroute"] {
- klog.V(3).Info("Neutron extraroute extension not found, required for Routes support")
- return nil, false
- }
- compute, err := os.NewComputeV2()
- if err != nil {
- return nil, false
- }
- r, err := NewRoutes(compute, network, os.routeOpts)
- if err != nil {
- klog.Warningf("Error initialising Routes support: %v", err)
- return nil, false
- }
- klog.V(1).Info("Claiming to support Routes")
- return r, true
- }
- func (os *OpenStack) volumeService(forceVersion string) (volumeService, error) {
- bsVersion := ""
- if forceVersion == "" {
- bsVersion = os.bsOpts.BSVersion
- } else {
- bsVersion = forceVersion
- }
- switch bsVersion {
- case "v1":
- sClient, err := os.NewBlockStorageV1()
- if err != nil {
- return nil, err
- }
- klog.V(3).Info("Using Blockstorage API V1")
- return &VolumesV1{sClient, os.bsOpts}, nil
- case "v2":
- sClient, err := os.NewBlockStorageV2()
- if err != nil {
- return nil, err
- }
- klog.V(3).Info("Using Blockstorage API V2")
- return &VolumesV2{sClient, os.bsOpts}, nil
- case "v3":
- sClient, err := os.NewBlockStorageV3()
- if err != nil {
- return nil, err
- }
- klog.V(3).Info("Using Blockstorage API V3")
- return &VolumesV3{sClient, os.bsOpts}, nil
- case "auto":
- // Currently kubernetes support Cinder v1 / Cinder v2 / Cinder v3.
- // Choose Cinder v3 firstly, if kubernetes can't initialize cinder v3 client, try to initialize cinder v2 client.
- // If kubernetes can't initialize cinder v2 client, try to initialize cinder v1 client.
- // Return appropriate message when kubernetes can't initialize them.
- if sClient, err := os.NewBlockStorageV3(); err == nil {
- klog.V(3).Info("Using Blockstorage API V3")
- return &VolumesV3{sClient, os.bsOpts}, nil
- }
- if sClient, err := os.NewBlockStorageV2(); err == nil {
- klog.V(3).Info("Using Blockstorage API V2")
- return &VolumesV2{sClient, os.bsOpts}, nil
- }
- if sClient, err := os.NewBlockStorageV1(); err == nil {
- klog.V(3).Info("Using Blockstorage API V1")
- return &VolumesV1{sClient, os.bsOpts}, nil
- }
- errTxt := "BlockStorage API version autodetection failed. " +
- "Please set it explicitly in cloud.conf in section [BlockStorage] with key `bs-version`"
- return nil, errors.New(errTxt)
- default:
- errTxt := fmt.Sprintf("Config error: unrecognised bs-version \"%v\"", os.bsOpts.BSVersion)
- return nil, errors.New(errTxt)
- }
- }
- func checkMetadataSearchOrder(order string) error {
- if order == "" {
- return errors.New("invalid value in section [Metadata] with key `search-order`. Value cannot be empty")
- }
- elements := strings.Split(order, ",")
- if len(elements) > 2 {
- return errors.New("invalid value in section [Metadata] with key `search-order`. Value cannot contain more than 2 elements")
- }
- for _, id := range elements {
- id = strings.TrimSpace(id)
- switch id {
- case configDriveID:
- case metadataID:
- default:
- return fmt.Errorf("invalid element %q found in section [Metadata] with key `search-order`."+
- "Supported elements include %q and %q", id, configDriveID, metadataID)
- }
- }
- return nil
- }
|