Skip to content

Commit 054207d

Browse files
authored
feat: support adc server mode (#225)
Signed-off-by: Ashing Zheng <[email protected]>
1 parent 3d1a42b commit 054207d

File tree

8 files changed

+314
-10
lines changed

8 files changed

+314
-10
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,10 @@ kind-load-dashboard-images:
200200
@kind load docker-image hkccr.ccs.tencentyun.com/api7-dev/api7-ee-dp-manager:$(DASHBOARD_VERSION) --name $(KIND_NAME)
201201
@kind load docker-image hkccr.ccs.tencentyun.com/api7-dev/api7-ee-3-integrated:$(DASHBOARD_VERSION) --name $(KIND_NAME)
202202

203+
.PHONY: kind-load-adc-image
204+
kind-load-adc-image:
205+
@kind load docker-image ghcr.io/api7/adc:dev --name $(KIND_NAME)
206+
203207
.PHONY: kind-load-ingress-image
204208
kind-load-ingress-image:
205209
@kind load docker-image $(IMG) --name $(KIND_NAME)

api/adc/types.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,6 @@ type StatusEvent struct {
672672
type ResponseDetails struct {
673673
Status int `json:"status"`
674674
Headers map[string]string `json:"headers"`
675-
Data ResponseData `json:"data"`
676675
}
677676

678677
type ResponseData struct {

internal/controller/label/label.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
package label
1919

2020
import (
21+
"fmt"
22+
2123
"sigs.k8s.io/controller-runtime/pkg/client"
2224

2325
"github.com/apache/apisix-ingress-controller/internal/controller/config"
@@ -31,15 +33,17 @@ const (
3133
LabelNamespace = "k8s/namespace"
3234
LabelControllerName = "k8s/controller-name"
3335
LabelManagedBy = "manager-by"
36+
LabelResourceKey = "k8s/resource-key"
3437
)
3538

36-
func GenLabel(client client.Object, args ...string) Label {
39+
func GenLabel(obj client.Object, args ...string) Label {
3740
label := make(Label)
38-
label[LabelKind] = client.GetObjectKind().GroupVersionKind().Kind
39-
label[LabelNamespace] = client.GetNamespace()
40-
label[LabelName] = client.GetName()
41+
label[LabelKind] = obj.GetObjectKind().GroupVersionKind().Kind
42+
label[LabelNamespace] = obj.GetNamespace()
43+
label[LabelName] = obj.GetName()
4144
label[LabelControllerName] = config.ControllerConfig.ControllerName
4245
label[LabelManagedBy] = "apisix-ingress-controller"
46+
label[LabelResourceKey] = fmt.Sprintf("%s/%s/%s", label[LabelKind], label[LabelNamespace], label[LabelName])
4347
for i := 0; i < len(args); i += 2 {
4448
label[args[i]] = args[i+1]
4549
}

internal/provider/adc/adc.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,16 @@ func New(updater status.Updater, readier readiness.ReadinessManager, opts ...Opt
115115
o := Options{}
116116
o.ApplyOptions(opts)
117117

118+
executor := NewHTTPADCExecutor("http://127.0.0.1:3000")
119+
log.Infow("using HTTP ADC Executor", zap.String("server_url", "http://127.0.0.1:3000"))
120+
118121
return &adcClient{
119122
Options: o,
120123
translator: &translator.Translator{},
121124
configs: make(map[types.NamespacedNameKind]adcConfig),
122125
parentRefs: make(map[types.NamespacedNameKind][]types.NamespacedNameKind),
123126
store: NewStore(),
124-
executor: &DefaultADCExecutor{},
127+
executor: executor,
125128
updater: updater,
126129
readier: readier,
127130
syncCh: make(chan struct{}, 1),
@@ -410,6 +413,10 @@ func (d *adcClient) Sync(ctx context.Context) error {
410413

411414
func (d *adcClient) sync(ctx context.Context, task Task) error {
412415
log.Debugw("syncing resources", zap.Any("task", task))
416+
if len(task.Labels) > 0 {
417+
// only keep the id label for filtering resources
418+
task.Labels = map[string]string{label.LabelResourceKey: task.Labels[label.LabelResourceKey]}
419+
}
413420

414421
if len(task.configs) == 0 {
415422
log.Warnw("no adc configs provided", zap.Any("task", task))

internal/provider/adc/executor.go

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"encoding/json"
2424
"errors"
2525
"fmt"
26+
"io"
27+
"net/http"
2628
"os"
2729
"os/exec"
2830
"strings"
@@ -31,6 +33,7 @@ import (
3133

3234
"github.com/api7/gopkg/pkg/log"
3335
"go.uber.org/zap"
36+
"k8s.io/utils/ptr"
3437

3538
adctypes "github.com/apache/apisix-ingress-controller/api/adc"
3639
"github.com/apache/apisix-ingress-controller/internal/types"
@@ -194,3 +197,252 @@ func BuildADCExecuteArgs(filePath string, labels map[string]string, types []stri
194197
}
195198
return args
196199
}
200+
201+
// ADCServerRequest represents the request body for ADC Server /sync endpoint
202+
type ADCServerRequest struct {
203+
Task ADCServerTask `json:"task"`
204+
}
205+
206+
// ADCServerTask represents the task configuration in ADC Server request
207+
type ADCServerTask struct {
208+
Opts ADCServerOpts `json:"opts"`
209+
Config adctypes.Resources `json:"config"`
210+
}
211+
212+
// ADCServerOpts represents the options in ADC Server task
213+
type ADCServerOpts struct {
214+
Backend string `json:"backend"`
215+
Server string `json:"server"`
216+
Token string `json:"token"`
217+
LabelSelector map[string]string `json:"labelSelector,omitempty"`
218+
IncludeResourceType []string `json:"includeResourceType,omitempty"`
219+
TlsSkipVerify *bool `json:"tlsSkipVerify,omitempty"`
220+
}
221+
222+
// HTTPADCExecutor implements ADCExecutor interface using HTTP calls to ADC Server
223+
type HTTPADCExecutor struct {
224+
sync.Mutex
225+
httpClient *http.Client
226+
serverURL string
227+
}
228+
229+
// NewHTTPADCExecutor creates a new HTTPADCExecutor with the specified ADC Server URL
230+
func NewHTTPADCExecutor(serverURL string) *HTTPADCExecutor {
231+
return &HTTPADCExecutor{
232+
httpClient: &http.Client{
233+
Timeout: 30 * time.Second,
234+
},
235+
serverURL: serverURL,
236+
}
237+
}
238+
239+
// Execute implements the ADCExecutor interface using HTTP calls
240+
func (e *HTTPADCExecutor) Execute(ctx context.Context, mode string, config adcConfig, args []string) error {
241+
e.Lock()
242+
defer e.Unlock()
243+
244+
return e.runHTTPSync(ctx, mode, config, args)
245+
}
246+
247+
// runHTTPSync performs HTTP sync to ADC Server for each server address
248+
func (e *HTTPADCExecutor) runHTTPSync(ctx context.Context, mode string, config adcConfig, args []string) error {
249+
var execErrs = types.ADCExecutionError{
250+
Name: config.Name,
251+
}
252+
253+
for _, addr := range config.ServerAddrs {
254+
if err := e.runHTTPSyncForSingleServer(ctx, addr, mode, config, args); err != nil {
255+
log.Errorw("failed to run http sync for server", zap.String("server", addr), zap.Error(err))
256+
var execErr types.ADCExecutionServerAddrError
257+
if errors.As(err, &execErr) {
258+
execErrs.FailedErrors = append(execErrs.FailedErrors, execErr)
259+
} else {
260+
execErrs.FailedErrors = append(execErrs.FailedErrors, types.ADCExecutionServerAddrError{
261+
ServerAddr: addr,
262+
Err: err.Error(),
263+
})
264+
}
265+
}
266+
}
267+
if len(execErrs.FailedErrors) > 0 {
268+
return execErrs
269+
}
270+
return nil
271+
}
272+
273+
// runHTTPSyncForSingleServer performs HTTP sync to a single ADC Server
274+
func (e *HTTPADCExecutor) runHTTPSyncForSingleServer(ctx context.Context, serverAddr, mode string, config adcConfig, args []string) error {
275+
ctx, cancel := context.WithTimeout(ctx, 15*time.Second)
276+
defer cancel()
277+
278+
// Parse args to extract labels, types, and file path
279+
labels, types, filePath, err := e.parseArgs(args)
280+
if err != nil {
281+
return fmt.Errorf("failed to parse args: %w", err)
282+
}
283+
284+
// Load resources from file
285+
resources, err := e.loadResourcesFromFile(filePath)
286+
if err != nil {
287+
return fmt.Errorf("failed to load resources from file %s: %w", filePath, err)
288+
}
289+
290+
// Build HTTP request
291+
req, err := e.buildHTTPRequest(ctx, serverAddr, mode, config, labels, types, resources)
292+
if err != nil {
293+
return fmt.Errorf("failed to build HTTP request: %w", err)
294+
}
295+
296+
// Send HTTP request
297+
resp, err := e.httpClient.Do(req)
298+
if err != nil {
299+
return fmt.Errorf("failed to send HTTP request: %w", err)
300+
}
301+
defer func() {
302+
if closeErr := resp.Body.Close(); closeErr != nil {
303+
log.Warnw("failed to close response body", zap.Error(closeErr))
304+
}
305+
}()
306+
307+
// Handle HTTP response
308+
return e.handleHTTPResponse(resp, serverAddr)
309+
}
310+
311+
// parseArgs parses the command line arguments to extract labels, types, and file path
312+
func (e *HTTPADCExecutor) parseArgs(args []string) (map[string]string, []string, string, error) {
313+
labels := make(map[string]string)
314+
var types []string
315+
var filePath string
316+
317+
for i := 0; i < len(args); i++ {
318+
switch args[i] {
319+
case "-f":
320+
if i+1 < len(args) {
321+
filePath = args[i+1]
322+
i++
323+
}
324+
case "--label-selector":
325+
if i+1 < len(args) {
326+
labelPair := args[i+1]
327+
parts := strings.SplitN(labelPair, "=", 2)
328+
if len(parts) == 2 {
329+
labels[parts[0]] = parts[1]
330+
}
331+
i++
332+
}
333+
case "--include-resource-type":
334+
if i+1 < len(args) {
335+
types = append(types, args[i+1])
336+
i++
337+
}
338+
}
339+
}
340+
341+
if filePath == "" {
342+
return nil, nil, "", errors.New("file path not found in args")
343+
}
344+
345+
return labels, types, filePath, nil
346+
}
347+
348+
// loadResourcesFromFile loads ADC resources from the specified file
349+
func (e *HTTPADCExecutor) loadResourcesFromFile(filePath string) (*adctypes.Resources, error) {
350+
data, err := os.ReadFile(filePath)
351+
if err != nil {
352+
return nil, fmt.Errorf("failed to read file: %w", err)
353+
}
354+
355+
var resources adctypes.Resources
356+
if err := json.Unmarshal(data, &resources); err != nil {
357+
return nil, fmt.Errorf("failed to unmarshal resources: %w", err)
358+
}
359+
360+
return &resources, nil
361+
}
362+
363+
// buildHTTPRequest builds the HTTP request for ADC Server
364+
func (e *HTTPADCExecutor) buildHTTPRequest(ctx context.Context, serverAddr, mode string, config adcConfig, labels map[string]string, types []string, resources *adctypes.Resources) (*http.Request, error) {
365+
// Prepare request body
366+
tlsVerify := config.TlsVerify
367+
reqBody := ADCServerRequest{
368+
Task: ADCServerTask{
369+
Opts: ADCServerOpts{
370+
Backend: mode,
371+
Server: serverAddr,
372+
Token: config.Token,
373+
LabelSelector: labels,
374+
IncludeResourceType: types,
375+
TlsSkipVerify: ptr.To(!tlsVerify),
376+
},
377+
Config: *resources,
378+
},
379+
}
380+
381+
jsonData, err := json.Marshal(reqBody)
382+
if err != nil {
383+
return nil, fmt.Errorf("failed to marshal request body: %w", err)
384+
}
385+
386+
log.Debugw("sending HTTP request to ADC Server",
387+
zap.String("url", e.serverURL+"/sync"),
388+
zap.String("server", serverAddr),
389+
zap.String("mode", mode),
390+
zap.Any("labelSelector", labels),
391+
zap.Strings("includeResourceType", types),
392+
zap.Bool("tlsSkipVerify", !tlsVerify),
393+
)
394+
395+
// Create HTTP request
396+
req, err := http.NewRequestWithContext(ctx, "PUT", e.serverURL+"/sync", bytes.NewBuffer(jsonData))
397+
if err != nil {
398+
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
399+
}
400+
401+
req.Header.Set("Content-Type", "application/json")
402+
return req, nil
403+
}
404+
405+
// handleHTTPResponse handles the HTTP response from ADC Server
406+
func (e *HTTPADCExecutor) handleHTTPResponse(resp *http.Response, serverAddr string) error {
407+
body, err := io.ReadAll(resp.Body)
408+
if err != nil {
409+
return fmt.Errorf("failed to read response body: %w", err)
410+
}
411+
412+
log.Debugw("received HTTP response from ADC Server",
413+
zap.String("server", serverAddr),
414+
zap.Int("status", resp.StatusCode),
415+
zap.String("response", string(body)),
416+
)
417+
418+
// not only 200, HTTP 202 is also accepted
419+
if resp.StatusCode/100 != 2 {
420+
return types.ADCExecutionServerAddrError{
421+
ServerAddr: serverAddr,
422+
Err: fmt.Sprintf("HTTP %d: %s", resp.StatusCode, string(body)),
423+
}
424+
}
425+
426+
// Parse response body
427+
var result adctypes.SyncResult
428+
if err := json.Unmarshal(body, &result); err != nil {
429+
log.Errorw("failed to unmarshal ADC Server response",
430+
zap.Error(err),
431+
zap.String("response", string(body)),
432+
)
433+
return fmt.Errorf("failed to parse ADC Server response: %w", err)
434+
}
435+
436+
// Check for sync failures
437+
if result.FailedCount > 0 && len(result.Failed) > 0 {
438+
log.Errorw("ADC Server sync failed", zap.Any("result", result))
439+
return types.ADCExecutionServerAddrError{
440+
ServerAddr: serverAddr,
441+
Err: result.Failed[0].Reason,
442+
FailedStatuses: result.Failed,
443+
}
444+
}
445+
446+
log.Debugw("ADC Server sync success", zap.Any("result", result))
447+
return nil
448+
}

test/e2e/crds/v2/status.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ spec:
157157
})
158158

159159
It("dataplane unavailable", func() {
160-
if os.Getenv("PROVIDER_TYPE") == adc.BackendModeAPI7EE {
161-
Skip("skip for api7ee mode because it use dashboard admin api")
160+
if os.Getenv("PROVIDER_TYPE") != adc.BackendModeAPISIXStandalone {
161+
Skip("only for apisix standalone mode")
162162
}
163163
By("apply ApisixRoute")
164164
applier.MustApplyAPIv2(types.NamespacedName{Namespace: s.Namespace(), Name: "default"}, &apiv2.ApisixRoute{}, ar)

0 commit comments

Comments
 (0)