github.com/huaweicloud/golangsdk@v0.0.0-20210831081626-d823fe11ceba/openstack/obs/provider.go (about)

     1  // Copyright 2019 Huawei Technologies Co.,Ltd.
     2  // Licensed under the Apache License, Version 2.0 (the "License"); you may not use
     3  // this file except in compliance with the License.  You may obtain a copy of the
     4  // License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software distributed
     9  // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    10  // CONDITIONS OF ANY KIND, either express or implied.  See the License for the
    11  // specific language governing permissions and limitations under the License.
    12  
    13  package obs
    14  
    15  import (
    16  	"encoding/json"
    17  	"io/ioutil"
    18  	"math/rand"
    19  	"net"
    20  	"net/http"
    21  	"os"
    22  	"strings"
    23  	"sync"
    24  	"sync/atomic"
    25  	"time"
    26  )
    27  
    28  const (
    29  	accessKeyEnv     = "OBS_ACCESS_KEY_ID"
    30  	securityKeyEnv   = "OBS_SECRET_ACCESS_KEY"
    31  	securityTokenEnv = "OBS_SECURITY_TOKEN"
    32  	ecsRequestURL    = "http://169.254.169.254/openstack/latest/securitykey"
    33  )
    34  
    35  type securityHolder struct {
    36  	ak            string
    37  	sk            string
    38  	securityToken string
    39  }
    40  
    41  var emptySecurityHolder = securityHolder{}
    42  
    43  type securityProvider interface {
    44  	getSecurity() securityHolder
    45  }
    46  
    47  type BasicSecurityProvider struct {
    48  	val atomic.Value
    49  }
    50  
    51  func (bsp *BasicSecurityProvider) getSecurity() securityHolder {
    52  	if sh, ok := bsp.val.Load().(securityHolder); ok {
    53  		return sh
    54  	}
    55  	return emptySecurityHolder
    56  }
    57  
    58  func (bsp *BasicSecurityProvider) refresh(ak, sk, securityToken string) {
    59  	bsp.val.Store(securityHolder{ak: strings.TrimSpace(ak), sk: strings.TrimSpace(sk), securityToken: strings.TrimSpace(securityToken)})
    60  }
    61  
    62  func NewBasicSecurityProvider(ak, sk, securityToken string) *BasicSecurityProvider {
    63  	bsp := &BasicSecurityProvider{}
    64  	bsp.refresh(ak, sk, securityToken)
    65  	return bsp
    66  }
    67  
    68  type EnvSecurityProvider struct {
    69  	sh     securityHolder
    70  	suffix string
    71  	once   sync.Once
    72  }
    73  
    74  func (esp *EnvSecurityProvider) getSecurity() securityHolder {
    75  	//ensure run only once
    76  	esp.once.Do(func() {
    77  		esp.sh = securityHolder{
    78  			ak:            strings.TrimSpace(os.Getenv(accessKeyEnv + esp.suffix)),
    79  			sk:            strings.TrimSpace(os.Getenv(securityKeyEnv + esp.suffix)),
    80  			securityToken: strings.TrimSpace(os.Getenv(securityTokenEnv + esp.suffix)),
    81  		}
    82  	})
    83  
    84  	return esp.sh
    85  }
    86  
    87  func NewEnvSecurityProvider(suffix string) *EnvSecurityProvider {
    88  	if suffix != "" {
    89  		suffix = "_" + suffix
    90  	}
    91  	esp := &EnvSecurityProvider{
    92  		suffix: suffix,
    93  	}
    94  	return esp
    95  }
    96  
    97  type TemporarySecurityHolder struct {
    98  	securityHolder
    99  	expireDate time.Time
   100  }
   101  
   102  var emptyTemporarySecurityHolder = TemporarySecurityHolder{}
   103  
   104  type EcsSecurityProvider struct {
   105  	val        atomic.Value
   106  	lock       sync.Mutex
   107  	httpClient *http.Client
   108  	prefetch   int32
   109  	retryCount int
   110  }
   111  
   112  func (ecsSp *EcsSecurityProvider) loadTemporarySecurityHolder() (TemporarySecurityHolder, bool) {
   113  	if sh := ecsSp.val.Load(); sh == nil {
   114  		return emptyTemporarySecurityHolder, false
   115  	} else if _sh, ok := sh.(TemporarySecurityHolder); !ok {
   116  		return emptyTemporarySecurityHolder, false
   117  	} else {
   118  		return _sh, true
   119  	}
   120  }
   121  
   122  func (ecsSp *EcsSecurityProvider) getAndSetSecurityWithOutLock() securityHolder {
   123  	_sh := TemporarySecurityHolder{}
   124  	_sh.expireDate = time.Now().Add(time.Minute * 5)
   125  	retryCount := 0
   126  	for {
   127  		if req, err := http.NewRequest("GET", ecsRequestURL, nil); err == nil {
   128  			start := GetCurrentTimestamp()
   129  			res, err := ecsSp.httpClient.Do(req)
   130  			if err == nil {
   131  				if data, _err := ioutil.ReadAll(res.Body); _err == nil {
   132  					temp := &struct {
   133  						Credential struct {
   134  							AK            string    `json:"access,omitempty"`
   135  							SK            string    `json:"secret,omitempty"`
   136  							SecurityToken string    `json:"securitytoken,omitempty"`
   137  							ExpireDate    time.Time `json:"expires_at,omitempty"`
   138  						} `json:"credential"`
   139  					}{}
   140  
   141  					doLog(LEVEL_DEBUG, "Get the json data from ecs succeed")
   142  
   143  					if jsonErr := json.Unmarshal(data, temp); jsonErr == nil {
   144  						_sh.ak = temp.Credential.AK
   145  						_sh.sk = temp.Credential.SK
   146  						_sh.securityToken = temp.Credential.SecurityToken
   147  						_sh.expireDate = temp.Credential.ExpireDate.Add(time.Minute * -1)
   148  
   149  						doLog(LEVEL_INFO, "Get security from ecs succeed, AK:xxxx, SK:xxxx, SecurityToken:xxxx, ExprireDate %s", _sh.expireDate)
   150  
   151  						doLog(LEVEL_INFO, "Get security from ecs succeed, cost %d ms", (GetCurrentTimestamp() - start))
   152  						break
   153  					} else {
   154  						err = jsonErr
   155  					}
   156  				} else {
   157  					err = _err
   158  				}
   159  			}
   160  
   161  			doLog(LEVEL_WARN, "Try to get security from ecs failed, cost %d ms, err %s", (GetCurrentTimestamp() - start), err.Error())
   162  		}
   163  
   164  		if retryCount >= ecsSp.retryCount {
   165  			doLog(LEVEL_WARN, "Try to get security from ecs failed and exceed the max retry count")
   166  			break
   167  		}
   168  		sleepTime := float64(retryCount+2) * rand.Float64()
   169  		if sleepTime > 10 {
   170  			sleepTime = 10
   171  		}
   172  		time.Sleep(time.Duration(sleepTime * float64(time.Second)))
   173  		retryCount++
   174  	}
   175  
   176  	ecsSp.val.Store(_sh)
   177  	return _sh.securityHolder
   178  }
   179  
   180  func (ecsSp *EcsSecurityProvider) getAndSetSecurity() securityHolder {
   181  	ecsSp.lock.Lock()
   182  	defer ecsSp.lock.Unlock()
   183  	tsh, succeed := ecsSp.loadTemporarySecurityHolder()
   184  	if !succeed || time.Now().After(tsh.expireDate) {
   185  		return ecsSp.getAndSetSecurityWithOutLock()
   186  	}
   187  	return tsh.securityHolder
   188  }
   189  
   190  func (ecsSp *EcsSecurityProvider) getSecurity() securityHolder {
   191  	if tsh, succeed := ecsSp.loadTemporarySecurityHolder(); succeed {
   192  		if time.Now().Before(tsh.expireDate) {
   193  			//not expire
   194  			if time.Now().Add(time.Minute*5).After(tsh.expireDate) && atomic.CompareAndSwapInt32(&ecsSp.prefetch, 0, 1) {
   195  				//do prefetch
   196  				sh := ecsSp.getAndSetSecurityWithOutLock()
   197  				atomic.CompareAndSwapInt32(&ecsSp.prefetch, 1, 0)
   198  				return sh
   199  			}
   200  			return tsh.securityHolder
   201  		}
   202  		return ecsSp.getAndSetSecurity()
   203  	}
   204  
   205  	return ecsSp.getAndSetSecurity()
   206  }
   207  
   208  func getInternalTransport() *http.Transport {
   209  
   210  	timeout := 10
   211  	transport := &http.Transport{
   212  		Dial: func(network, addr string) (net.Conn, error) {
   213  			start := GetCurrentTimestamp()
   214  			conn, err := (&net.Dialer{
   215  				Timeout:  time.Second * time.Duration(timeout),
   216  				Resolver: net.DefaultResolver,
   217  			}).Dial(network, addr)
   218  
   219  			if isInfoLogEnabled() {
   220  				doLog(LEVEL_INFO, "Do http dial cost %d ms", (GetCurrentTimestamp() - start))
   221  			}
   222  			if err != nil {
   223  				return nil, err
   224  			}
   225  			return getConnDelegate(conn, timeout, timeout*10), nil
   226  		},
   227  		MaxIdleConns:          10,
   228  		MaxIdleConnsPerHost:   10,
   229  		ResponseHeaderTimeout: time.Second * time.Duration(timeout),
   230  		IdleConnTimeout:       time.Second * time.Duration(DEFAULT_IDLE_CONN_TIMEOUT),
   231  		DisableCompression:    true,
   232  	}
   233  
   234  	return transport
   235  }
   236  
   237  func NewEcsSecurityProvider(retryCount int) *EcsSecurityProvider {
   238  	ecsSp := &EcsSecurityProvider{
   239  		retryCount: retryCount,
   240  	}
   241  	ecsSp.httpClient = &http.Client{Transport: getInternalTransport(), CheckRedirect: checkRedirectFunc}
   242  	return ecsSp
   243  }