You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
go-library/vendor/github.com/huaweicloud/huaweicloud-sdk-go-obs/obs/provider.go

244 lines
6.7 KiB

// Copyright 2019 Huawei Technologies Co.,Ltd.
// 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 obs
import (
"encoding/json"
"io/ioutil"
"math/rand"
"net"
"net/http"
"os"
"strings"
"sync"
"sync/atomic"
"time"
)
const (
accessKeyEnv = "OBS_ACCESS_KEY_ID"
securityKeyEnv = "OBS_SECRET_ACCESS_KEY"
securityTokenEnv = "OBS_SECURITY_TOKEN"
ecsRequestURL = "http://169.254.169.254/openstack/latest/securitykey"
)
type securityHolder struct {
ak string
sk string
securityToken string
}
var emptySecurityHolder = securityHolder{}
type securityProvider interface {
getSecurity() securityHolder
}
type BasicSecurityProvider struct {
val atomic.Value
}
func (bsp *BasicSecurityProvider) getSecurity() securityHolder {
if sh, ok := bsp.val.Load().(securityHolder); ok {
return sh
}
return emptySecurityHolder
}
func (bsp *BasicSecurityProvider) refresh(ak, sk, securityToken string) {
bsp.val.Store(securityHolder{ak: strings.TrimSpace(ak), sk: strings.TrimSpace(sk), securityToken: strings.TrimSpace(securityToken)})
}
func NewBasicSecurityProvider(ak, sk, securityToken string) *BasicSecurityProvider {
bsp := &BasicSecurityProvider{}
bsp.refresh(ak, sk, securityToken)
return bsp
}
type EnvSecurityProvider struct {
sh securityHolder
suffix string
once sync.Once
}
func (esp *EnvSecurityProvider) getSecurity() securityHolder {
//ensure run only once
esp.once.Do(func() {
esp.sh = securityHolder{
ak: strings.TrimSpace(os.Getenv(accessKeyEnv + esp.suffix)),
sk: strings.TrimSpace(os.Getenv(securityKeyEnv + esp.suffix)),
securityToken: strings.TrimSpace(os.Getenv(securityTokenEnv + esp.suffix)),
}
})
return esp.sh
}
func NewEnvSecurityProvider(suffix string) *EnvSecurityProvider {
if suffix != "" {
suffix = "_" + suffix
}
esp := &EnvSecurityProvider{
suffix: suffix,
}
return esp
}
type TemporarySecurityHolder struct {
securityHolder
expireDate time.Time
}
var emptyTemporarySecurityHolder = TemporarySecurityHolder{}
type EcsSecurityProvider struct {
val atomic.Value
lock sync.Mutex
httpClient *http.Client
prefetch int32
retryCount int
}
func (ecsSp *EcsSecurityProvider) loadTemporarySecurityHolder() (TemporarySecurityHolder, bool) {
if sh := ecsSp.val.Load(); sh == nil {
return emptyTemporarySecurityHolder, false
} else if _sh, ok := sh.(TemporarySecurityHolder); !ok {
return emptyTemporarySecurityHolder, false
} else {
return _sh, true
}
}
func (ecsSp *EcsSecurityProvider) getAndSetSecurityWithOutLock() securityHolder {
_sh := TemporarySecurityHolder{}
_sh.expireDate = time.Now().Add(time.Minute * 5)
retryCount := 0
for {
if req, err := http.NewRequest("GET", ecsRequestURL, nil); err == nil {
start := GetCurrentTimestamp()
res, err := ecsSp.httpClient.Do(req)
if err == nil {
if data, _err := ioutil.ReadAll(res.Body); _err == nil {
temp := &struct {
Credential struct {
AK string `json:"access,omitempty"`
SK string `json:"secret,omitempty"`
SecurityToken string `json:"securitytoken,omitempty"`
ExpireDate time.Time `json:"expires_at,omitempty"`
} `json:"credential"`
}{}
doLog(LEVEL_DEBUG, "Get the json data from ecs succeed")
if jsonErr := json.Unmarshal(data, temp); jsonErr == nil {
_sh.ak = temp.Credential.AK
_sh.sk = temp.Credential.SK
_sh.securityToken = temp.Credential.SecurityToken
_sh.expireDate = temp.Credential.ExpireDate.Add(time.Minute * -1)
doLog(LEVEL_INFO, "Get security from ecs succeed, AK:xxxx, SK:xxxx, SecurityToken:xxxx, ExprireDate %s", _sh.expireDate)
doLog(LEVEL_INFO, "Get security from ecs succeed, cost %d ms", (GetCurrentTimestamp() - start))
break
} else {
err = jsonErr
}
} else {
err = _err
}
}
doLog(LEVEL_WARN, "Try to get security from ecs failed, cost %d ms, err %s", (GetCurrentTimestamp() - start), err.Error())
}
if retryCount >= ecsSp.retryCount {
doLog(LEVEL_WARN, "Try to get security from ecs failed and exceed the max retry count")
break
}
sleepTime := float64(retryCount+2) * rand.Float64()
if sleepTime > 10 {
sleepTime = 10
}
time.Sleep(time.Duration(sleepTime * float64(time.Second)))
retryCount++
}
ecsSp.val.Store(_sh)
return _sh.securityHolder
}
func (ecsSp *EcsSecurityProvider) getAndSetSecurity() securityHolder {
ecsSp.lock.Lock()
defer ecsSp.lock.Unlock()
tsh, succeed := ecsSp.loadTemporarySecurityHolder()
if !succeed || time.Now().After(tsh.expireDate) {
return ecsSp.getAndSetSecurityWithOutLock()
}
return tsh.securityHolder
}
func (ecsSp *EcsSecurityProvider) getSecurity() securityHolder {
if tsh, succeed := ecsSp.loadTemporarySecurityHolder(); succeed {
if time.Now().Before(tsh.expireDate) {
//not expire
if time.Now().Add(time.Minute*5).After(tsh.expireDate) && atomic.CompareAndSwapInt32(&ecsSp.prefetch, 0, 1) {
//do prefetch
sh := ecsSp.getAndSetSecurityWithOutLock()
atomic.CompareAndSwapInt32(&ecsSp.prefetch, 1, 0)
return sh
}
return tsh.securityHolder
}
return ecsSp.getAndSetSecurity()
}
return ecsSp.getAndSetSecurity()
}
func getInternalTransport() *http.Transport {
timeout := 10
transport := &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
start := GetCurrentTimestamp()
conn, err := (&net.Dialer{
Timeout: time.Second * time.Duration(timeout),
Resolver: net.DefaultResolver,
}).Dial(network, addr)
if isInfoLogEnabled() {
doLog(LEVEL_INFO, "Do http dial cost %d ms", (GetCurrentTimestamp() - start))
}
if err != nil {
return nil, err
}
return getConnDelegate(conn, timeout, timeout*10), nil
},
MaxIdleConns: 10,
MaxIdleConnsPerHost: 10,
ResponseHeaderTimeout: time.Second * time.Duration(timeout),
IdleConnTimeout: time.Second * time.Duration(DEFAULT_IDLE_CONN_TIMEOUT),
DisableCompression: true,
}
return transport
}
func NewEcsSecurityProvider(retryCount int) *EcsSecurityProvider {
ecsSp := &EcsSecurityProvider{
retryCount: retryCount,
}
ecsSp.httpClient = &http.Client{Transport: getInternalTransport(), CheckRedirect: checkRedirectFunc}
return ecsSp
}