Skip to content

Commit 34cedf8

Browse files
committed
feature: support metadata v2
Signed-off-by: bingshen.wbs <[email protected]>
1 parent c52b2cc commit 34cedf8

File tree

4 files changed

+193
-55
lines changed

4 files changed

+193
-55
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ require (
108108
go.uber.org/zap v1.26.0 // indirect
109109
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
110110
golang.org/x/oauth2 v0.21.0 // indirect
111-
golang.org/x/sync v0.7.0 // indirect
111+
golang.org/x/sync v0.7.0
112112
golang.org/x/sys v0.21.0 // indirect
113113
golang.org/x/term v0.21.0 // indirect
114114
golang.org/x/text v0.16.0 // indirect

internal/config/config.go

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,9 @@ package config
33
import (
44
"encoding/json"
55
"fmt"
6-
"io"
7-
"net/http"
86
"os"
9-
"strings"
107

8+
"github.com/AliyunContainerService/alibabacloud-erdma-controller/internal/utils"
119
"k8s.io/utils/ptr"
1210

1311
"github.com/AliyunContainerService/alibabacloud-erdma-controller/internal/types"
@@ -29,27 +27,7 @@ var (
2927

3028
func getRegion() (string, error) {
3129
url := regionIDAddr
32-
resp, err := http.Get(url)
33-
if err != nil {
34-
return "", fmt.Errorf("error get url: %s from metaserver. %w", url, err)
35-
}
36-
//nolint:errcheck
37-
defer resp.Body.Close()
38-
39-
if resp.StatusCode == http.StatusNotFound {
40-
return "", fmt.Errorf("error get url: %s from metaserver, code: %v, %v", url, resp.StatusCode, "Not Found")
41-
}
42-
if resp.StatusCode >= http.StatusBadRequest {
43-
return "", fmt.Errorf("error get url: %s from metaserver, code: %v", url, resp.StatusCode)
44-
}
45-
46-
body, err := io.ReadAll(resp.Body)
47-
if err != nil {
48-
return "", err
49-
}
50-
result := strings.Split(string(body), "\n")
51-
trimResult := strings.Trim(result[0], "/")
52-
return trimResult, nil
30+
return utils.GetStrFromMetadata(url)
5331
}
5432

5533
func InitConfig(configPath, credentialPath string) error {

internal/drivers/netdev.go

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@ package drivers
44

55
import (
66
"fmt"
7-
"io"
87
"net"
9-
"net/http"
10-
"strings"
118

129
"github.com/AliyunContainerService/alibabacloud-erdma-controller/internal/types"
10+
"github.com/AliyunContainerService/alibabacloud-erdma-controller/internal/utils"
1311
"github.com/samber/lo"
1412
"github.com/vishvananda/netlink"
1513
)
@@ -237,15 +235,15 @@ func genRoutesForAddr(gateway net.IP, cidr *net.IPNet) ([]*route, error) {
237235
}
238236

239237
func getNetConfFromMetadata(mac string) (*netConf, error) {
240-
addr, err := getStrFromMetadata(fmt.Sprintf(ipUrl, mac))
238+
addr, err := utils.GetStrFromMetadata(fmt.Sprintf(ipUrl, mac))
241239
if err != nil {
242240
return nil, err
243241
}
244242
ip := net.ParseIP(addr)
245243
if ip == nil {
246244
return nil, fmt.Errorf("invalid ip address: %s", addr)
247245
}
248-
cidr, err := getStrFromMetadata(fmt.Sprintf(cidrURL, mac))
246+
cidr, err := utils.GetStrFromMetadata(fmt.Sprintf(cidrURL, mac))
249247
if err != nil {
250248
return nil, err
251249
}
@@ -254,7 +252,7 @@ func getNetConfFromMetadata(mac string) (*netConf, error) {
254252
return nil, fmt.Errorf("invalid cidr: %s", cidr)
255253
}
256254

257-
gw, err := getStrFromMetadata(fmt.Sprintf(gatewayURL, mac))
255+
gw, err := utils.GetStrFromMetadata(fmt.Sprintf(gatewayURL, mac))
258256
if err != nil {
259257
return nil, err
260258
}
@@ -276,27 +274,3 @@ func getNetConfFromMetadata(mac string) (*netConf, error) {
276274
conf.routes = routes
277275
return conf, nil
278276
}
279-
280-
func getStrFromMetadata(url string) (string, error) {
281-
resp, err := http.Get(url)
282-
if err != nil {
283-
return "", fmt.Errorf("error get url: %s from metaserver. %w", url, err)
284-
}
285-
//nolint:errcheck
286-
defer resp.Body.Close()
287-
288-
if resp.StatusCode == http.StatusNotFound {
289-
return "", fmt.Errorf("error get url: %s from metaserver, code: %v, %v", url, resp.StatusCode, "Not Found")
290-
}
291-
if resp.StatusCode >= http.StatusBadRequest {
292-
return "", fmt.Errorf("error get url: %s from metaserver, code: %v", url, resp.StatusCode)
293-
}
294-
295-
body, err := io.ReadAll(resp.Body)
296-
if err != nil {
297-
return "", err
298-
}
299-
result := strings.Split(string(body), "\n")
300-
trimResult := strings.Trim(result[0], "/")
301-
return trimResult, nil
302-
}

internal/utils/metadata.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package utils
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"strconv"
9+
"strings"
10+
"time"
11+
12+
"golang.org/x/sync/singleflight"
13+
"k8s.io/apimachinery/pkg/util/cache"
14+
"k8s.io/apimachinery/pkg/util/wait"
15+
)
16+
17+
const (
18+
tokenURL = "http://100.100.100.200/latest/api/token"
19+
tokenTimeout = 21600
20+
)
21+
22+
func GetStrFromMetadata(url string) (string, error) {
23+
body, err := getWithToken(url)
24+
if err != nil {
25+
return "", err
26+
}
27+
result := strings.Split(string(body), "\n")
28+
trimResult := strings.Trim(result[0], "/")
29+
return trimResult, nil
30+
}
31+
32+
type Error struct {
33+
URL string
34+
Code string
35+
R error
36+
}
37+
38+
func (e *Error) Error() string {
39+
return fmt.Sprintf("get from metaserver failed code: %s, url: %s, err: %s", e.Code, e.URL, e.R)
40+
}
41+
42+
var (
43+
tokenCache *cache.Expiring
44+
single singleflight.Group
45+
defaultClient *http.Client
46+
)
47+
48+
func getWithToken(url string) ([]byte, error) {
49+
50+
skipRetry := false
51+
retry:
52+
var token string
53+
v, ok := tokenCache.Get(tokenURL)
54+
if !ok {
55+
vv, err, _ := single.Do(tokenURL, func() (interface{}, error) {
56+
out, err := withRetry(tokenURL, [][]string{
57+
{
58+
"X-aliyun-ecs-metadata-token-ttl-seconds", strconv.Itoa(tokenTimeout),
59+
},
60+
})
61+
if err != nil {
62+
return nil, err
63+
}
64+
return string(out), nil
65+
})
66+
if err != nil {
67+
return nil, err
68+
}
69+
70+
token = vv.(string)
71+
72+
tokenCache.Set(tokenURL, token, tokenTimeout*time.Second/2)
73+
} else {
74+
token = v.(string)
75+
}
76+
77+
out, err := withRetry(url, [][]string{
78+
{
79+
"X-aliyun-ecs-metadata-token", token,
80+
},
81+
})
82+
if err != nil {
83+
var typedErr *Error
84+
ok := errors.As(err, &typedErr)
85+
86+
if ok && !skipRetry {
87+
if typedErr.Code == strconv.Itoa(http.StatusUnauthorized) {
88+
skipRetry = true
89+
90+
tokenCache.Delete(tokenURL)
91+
goto retry
92+
}
93+
}
94+
95+
return nil, err
96+
}
97+
98+
return out, err
99+
}
100+
101+
func withRetry(url string, headers [][]string) ([]byte, error) {
102+
var innerErr error
103+
var body []byte
104+
err := wait.ExponentialBackoff(wait.Backoff{
105+
Duration: 500 * time.Millisecond,
106+
Factor: 1.2,
107+
Jitter: 0.1,
108+
Steps: 4,
109+
}, func() (bool, error) {
110+
var err error
111+
112+
method := "GET"
113+
if url == tokenURL {
114+
method = "PUT"
115+
}
116+
req, err := http.NewRequest(method, url, nil)
117+
if err != nil {
118+
innerErr = &Error{
119+
URL: url,
120+
R: err,
121+
}
122+
return false, nil
123+
}
124+
125+
for _, h := range headers {
126+
if len(h) != 2 {
127+
return false, fmt.Errorf("invalid header")
128+
}
129+
req.Header.Set(h[0], h[1])
130+
}
131+
132+
resp, err := defaultClient.Do(req)
133+
if err != nil {
134+
// retryable err
135+
innerErr = &Error{
136+
URL: url,
137+
R: err,
138+
}
139+
return false, nil
140+
}
141+
defer resp.Body.Close() // nolint:errcheck
142+
143+
// retryable err
144+
if resp.StatusCode == http.StatusTooManyRequests ||
145+
resp.StatusCode >= http.StatusInternalServerError {
146+
innerErr = &Error{
147+
URL: url,
148+
Code: strconv.Itoa(resp.StatusCode),
149+
R: nil,
150+
}
151+
return false, nil
152+
}
153+
154+
if resp.StatusCode >= http.StatusBadRequest {
155+
innerErr = &Error{
156+
URL: url,
157+
Code: strconv.Itoa(resp.StatusCode),
158+
R: nil,
159+
}
160+
return false, innerErr
161+
}
162+
163+
body, err = io.ReadAll(resp.Body)
164+
if err != nil {
165+
return false, err
166+
}
167+
return true, nil
168+
})
169+
if err != nil {
170+
if innerErr != nil {
171+
return nil, innerErr
172+
}
173+
return nil, err
174+
}
175+
return body, nil
176+
}
177+
178+
func init() {
179+
tokenCache = cache.NewExpiring()
180+
defaultClient = &http.Client{
181+
Transport: nil,
182+
CheckRedirect: nil,
183+
Jar: nil,
184+
Timeout: 30 * time.Second,
185+
}
186+
}

0 commit comments

Comments
 (0)