ccm: fix nil pointer in pollUsage for connector-mode credentials

Connector-mode credentials (URL + reverse: true) never assigned
httpClient, causing a nil dereference when pollUsage accessed
httpClient.Transport.

Also extract poll request logic into doPollUsageRequest to try
reverse transport first (single attempt), then fall back to
forward transport with retries if the reverse session disconnects.
This commit is contained in:
世界
2026-03-14 18:36:00 +08:00
parent d1e5426bc8
commit 4d8baf7175
2 changed files with 86 additions and 46 deletions
+43 -23
View File
@@ -192,6 +192,7 @@ func newExternalCredential(ctx context.Context, tag string, options option.CCMEx
Time: ntp.TimeFuncFromContext(ctx),
}
}
cred.httpClient = &http.Client{Transport: transport}
} else {
// Normal mode: standard HTTP client for proxying
cred.httpClient = &http.Client{Transport: transport}
@@ -498,6 +499,47 @@ func (c *externalCredential) interruptConnections() {
}
}
func (c *externalCredential) doPollUsageRequest(ctx context.Context) (*http.Response, error) {
buildRequest := func(baseURL string) func() (*http.Request, error) {
return func() (*http.Request, error) {
request, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"/ccm/v1/status", nil)
if err != nil {
return nil, err
}
request.Header.Set("Authorization", "Bearer "+c.token)
return request, nil
}
}
// Try reverse transport first (single attempt, no retry)
if c.reverseHttpClient != nil {
session := c.getReverseSession()
if session != nil && !session.IsClosed() {
request, err := buildRequest(reverseProxyBaseURL)()
if err != nil {
return nil, err
}
reverseClient := &http.Client{
Transport: c.reverseHttpClient.Transport,
Timeout: 5 * time.Second,
}
response, err := reverseClient.Do(request)
if err == nil {
return response, nil
}
// Reverse failed, fall through to forward if available
}
}
// Forward transport with retries
if c.httpClient != nil {
forwardClient := &http.Client{
Transport: c.httpClient.Transport,
Timeout: 5 * time.Second,
}
return doHTTPWithRetry(ctx, forwardClient, buildRequest(c.baseURL))
}
return nil, E.New("no transport available")
}
func (c *externalCredential) pollUsage(ctx context.Context) {
if !c.pollAccess.TryLock() {
return
@@ -505,29 +547,7 @@ func (c *externalCredential) pollUsage(ctx context.Context) {
defer c.pollAccess.Unlock()
defer c.markUsagePollAttempted()
activeBaseURL := c.baseURL
activeTransport := c.httpClient.Transport
if c.reverseHttpClient != nil {
session := c.getReverseSession()
if session != nil && !session.IsClosed() {
activeBaseURL = reverseProxyBaseURL
activeTransport = c.reverseHttpClient.Transport
}
}
statusURL := activeBaseURL + "/ccm/v1/status"
httpClient := &http.Client{
Transport: activeTransport,
Timeout: 5 * time.Second,
}
response, err := doHTTPWithRetry(ctx, httpClient, func() (*http.Request, error) {
request, err := http.NewRequestWithContext(ctx, http.MethodGet, statusURL, nil)
if err != nil {
return nil, err
}
request.Header.Set("Authorization", "Bearer "+c.token)
return request, nil
})
response, err := c.doPollUsageRequest(ctx)
if err != nil {
if !c.isPollBackoffAtCap() {
c.logger.Error("poll usage for ", c.tag, ": ", err)
+43 -23
View File
@@ -211,6 +211,7 @@ func newExternalCredential(ctx context.Context, tag string, options option.OCMEx
Time: ntp.TimeFuncFromContext(ctx),
}
}
cred.httpClient = &http.Client{Transport: transport}
} else {
// Normal mode: standard HTTP client for proxying
cred.credDialer = credentialDialer
@@ -537,6 +538,47 @@ func (c *externalCredential) interruptConnections() {
}
}
func (c *externalCredential) doPollUsageRequest(ctx context.Context) (*http.Response, error) {
buildRequest := func(baseURL string) func() (*http.Request, error) {
return func() (*http.Request, error) {
request, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"/ocm/v1/status", nil)
if err != nil {
return nil, err
}
request.Header.Set("Authorization", "Bearer "+c.token)
return request, nil
}
}
// Try reverse transport first (single attempt, no retry)
if c.reverseHttpClient != nil {
session := c.getReverseSession()
if session != nil && !session.IsClosed() {
request, err := buildRequest(reverseProxyBaseURL)()
if err != nil {
return nil, err
}
reverseClient := &http.Client{
Transport: c.reverseHttpClient.Transport,
Timeout: 5 * time.Second,
}
response, err := reverseClient.Do(request)
if err == nil {
return response, nil
}
// Reverse failed, fall through to forward if available
}
}
// Forward transport with retries
if c.httpClient != nil {
forwardClient := &http.Client{
Transport: c.httpClient.Transport,
Timeout: 5 * time.Second,
}
return doHTTPWithRetry(ctx, forwardClient, buildRequest(c.baseURL))
}
return nil, E.New("no transport available")
}
func (c *externalCredential) pollUsage(ctx context.Context) {
if !c.pollAccess.TryLock() {
return
@@ -544,29 +586,7 @@ func (c *externalCredential) pollUsage(ctx context.Context) {
defer c.pollAccess.Unlock()
defer c.markUsagePollAttempted()
activeBaseURL := c.baseURL
activeTransport := c.httpClient.Transport
if c.reverseHttpClient != nil {
session := c.getReverseSession()
if session != nil && !session.IsClosed() {
activeBaseURL = reverseProxyBaseURL
activeTransport = c.reverseHttpClient.Transport
}
}
statusURL := activeBaseURL + "/ocm/v1/status"
httpClient := &http.Client{
Transport: activeTransport,
Timeout: 5 * time.Second,
}
response, err := doHTTPWithRetry(ctx, httpClient, func() (*http.Request, error) {
request, err := http.NewRequestWithContext(ctx, http.MethodGet, statusURL, nil)
if err != nil {
return nil, err
}
request.Header.Set("Authorization", "Bearer "+c.token)
return request, nil
})
response, err := c.doPollUsageRequest(ctx)
if err != nil {
if !c.isPollBackoffAtCap() {
c.logger.Error("poll usage for ", c.tag, ": ", err)