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 }