Skip to content

Commit 0c6e35c

Browse files
Multi-cluster RGs Implementation (#207)
* Multi-cluster RGs Implementation * PR feedback
1 parent f88141a commit 0c6e35c

File tree

6 files changed

+135
-39
lines changed

6 files changed

+135
-39
lines changed

internal/command/local/proxy.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package local
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"io"
78
"log/slog"
@@ -27,15 +28,15 @@ func newProxy(localConfig *config.Local) *cobra.Command {
2728
Use: "proxy [--sandbox SANDBOX|--routegroup ROUTEGROUP|--cluster CLUSTER] --map <target-protocol>://<target-addr>@<bind-addr> [--map <target-protocol>://<target-addr>@<bind-addr>]",
2829
Short: "Proxy connections based on the specified mappings",
2930
RunE: func(cmd *cobra.Command, args []string) error {
30-
return runProxy(cmd, cmd.OutOrStdout(), cfg, args)
31+
return runProxy(cmd.OutOrStdout(), cfg)
3132
},
3233
}
3334
cfg.AddFlags(cmd)
3435

3536
return cmd
3637
}
3738

38-
func runProxy(cmd *cobra.Command, out io.Writer, cfg *config.LocalProxy, args []string) error {
39+
func runProxy(out io.Writer, cfg *config.LocalProxy) error {
3940
ctx := context.Background()
4041

4142
if err := cfg.InitLocalProxyConfig(); err != nil {
@@ -48,7 +49,8 @@ func runProxy(cmd *cobra.Command, out io.Writer, cfg *config.LocalProxy, args []
4849
// define the cluster and routing key to use
4950
var cluster, routingKey string
5051

51-
if cfg.Sandbox != "" {
52+
switch {
53+
case cfg.Sandbox != "":
5254
// resolve the sandbox
5355
params := sandboxes.NewGetSandboxParams().
5456
WithOrgName(cfg.Org).WithSandboxName(cfg.Sandbox)
@@ -59,7 +61,8 @@ func runProxy(cmd *cobra.Command, out io.Writer, cfg *config.LocalProxy, args []
5961

6062
cluster = *resp.Payload.Spec.Cluster
6163
routingKey = resp.Payload.RoutingKey
62-
} else if cfg.RouteGroup != "" {
64+
65+
case cfg.RouteGroup != "":
6366
// resolve the routegroup
6467
params := routegroups.NewGetRoutegroupParams().
6568
WithOrgName(cfg.Org).WithRoutegroupName(cfg.RouteGroup)
@@ -70,7 +73,21 @@ func runProxy(cmd *cobra.Command, out io.Writer, cfg *config.LocalProxy, args []
7073

7174
cluster = resp.Payload.Spec.Cluster
7275
routingKey = resp.Payload.RoutingKey
73-
} else {
76+
if cluster == "" {
77+
// this is a multi-cluster RG, the cluster must be explicitly defined
78+
if cfg.Cluster == "" {
79+
return errors.New("--cluster must be specified in multi-cluster route groups")
80+
}
81+
// validate the cluster
82+
params := clusters.NewGetClusterParams().
83+
WithOrgName(cfg.Org).WithClusterName(cfg.Cluster)
84+
if _, err := cfg.Client.Cluster.GetCluster(params, nil); err != nil {
85+
return err
86+
}
87+
cluster = cfg.Cluster
88+
}
89+
90+
default:
7491
// validate the cluster
7592
params := clusters.NewGetClusterParams().
7693
WithOrgName(cfg.Org).WithClusterName(cfg.Cluster)

internal/command/routegroup/apply.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,13 @@ func apply(cfg *config.RouteGroupApply, out, log io.Writer, args []string) error
5151
}
5252
resp := result.Payload
5353

54-
fmt.Fprintf(log, "Created routegroup %q (routing key: %s) in cluster %q.\n\n",
55-
req.Name, resp.RoutingKey, req.Spec.Cluster)
54+
if req.Spec.Cluster != "" {
55+
fmt.Fprintf(log, "Created routegroup %q (routing key: %s) in cluster %q.\n\n",
56+
req.Name, resp.RoutingKey, req.Spec.Cluster)
57+
} else {
58+
fmt.Fprintf(log, "Created multi-cluster routegroup %q (routing key: %s).\n\n",
59+
req.Name, resp.RoutingKey)
60+
}
5661

5762
if cfg.Wait {
5863
// Wait for the routegroup to be ready.
@@ -79,7 +84,7 @@ func writeOutput(cfg *config.RouteGroupApply, out io.Writer, resp *models.RouteG
7984
fmt.Fprintf(out, "\nDashboard page: %v\n\n", sbURL)
8085

8186
if len(resp.Endpoints) > 0 {
82-
if err := printEndpointTable(out, resp.Endpoints); err != nil {
87+
if err := printEndpointTable(out, resp); err != nil {
8388
return err
8489
}
8590
}

internal/command/routegroup/printers.go

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func printRouteGroupTable(cfg *config.RouteGroupList, out io.Writer, rgs []*mode
4141
t.AddRow(routegroupRow{
4242
Name: rg.Name,
4343
RoutingKey: rg.RoutingKey,
44-
Cluster: rg.Spec.Cluster,
44+
Cluster: getCluster(rg.Spec.Cluster),
4545
Created: timeago.NoMax(timeago.English).Format(createdAt),
4646
Status: readiness(rg.Status),
4747
Ready: sbxStatus,
@@ -55,7 +55,7 @@ func printRouteGroupDetails(cfg *config.RouteGroup, out io.Writer, rg *models.Ro
5555

5656
fmt.Fprintf(tw, "Name:\t%s\n", rg.Name)
5757
fmt.Fprintf(tw, "Routing Key:\t%s\n", rg.RoutingKey)
58-
fmt.Fprintf(tw, "Cluster:\t%s\n", rg.Spec.Cluster)
58+
fmt.Fprintf(tw, "Cluster:\t%s\n", getCluster(rg.Spec.Cluster))
5959
fmt.Fprintf(tw, "Created:\t%s\n", utils.FormatTimestamp(rg.CreatedAt))
6060
fmt.Fprintf(tw, "TTL:\t%s\n", formatTTL(rg.Spec, rg.Status.ScheduledDeleteTime))
6161
fmt.Fprintf(tw, "Dashboard page:\t%s\n", cfg.DashboardURL)
@@ -67,14 +67,21 @@ func printRouteGroupDetails(cfg *config.RouteGroup, out io.Writer, rg *models.Ro
6767

6868
if len(rg.Endpoints) > 0 {
6969
fmt.Fprintln(out)
70-
if err := printEndpointTable(out, rg.Endpoints); err != nil {
70+
if err := printEndpointTable(out, rg); err != nil {
7171
return err
7272
}
7373
}
7474

7575
return nil
7676
}
7777

78+
func getCluster(cluster string) string {
79+
if cluster != "" {
80+
return cluster
81+
}
82+
return "(multi-cluster)"
83+
}
84+
7885
func readiness(status *models.RouteGroupStatus) string {
7986
if status.Ready {
8087
return "Ready"
@@ -119,21 +126,49 @@ func formatTTL(spec *models.RouteGroupSpec, deletionTime string) string {
119126
return fmt.Sprintf("%s (%s)", local, timeago.NoMax(timeago.English).Format(t))
120127
}
121128

122-
type endpointRow struct {
123-
Name string `sdtab:"ROUTEGROUP ENDPOINT"`
124-
Target string `sdtab:"TARGET"`
125-
URL string `sdtab:"URL"`
129+
func printEndpointTable(out io.Writer, rg *models.RouteGroup) error {
130+
if rg.Spec.Cluster == "" {
131+
return printMultiClusterRGEndpointTable(out, rg.Endpoints)
132+
}
133+
return printRegularRGEndpointTable(out, rg.Endpoints)
126134
}
127135

128-
func printEndpointTable(out io.Writer, endpoints []*models.RoutegroupsEndpointURL) error {
129-
t := sdtab.New[endpointRow](out)
136+
func printRegularRGEndpointTable(out io.Writer, endpoints []*models.RoutegroupsEndpointURL) error {
137+
type row struct {
138+
Name string `sdtab:"ROUTEGROUP ENDPOINT"`
139+
Target string `sdtab:"TARGET"`
140+
URL string `sdtab:"URL"`
141+
}
142+
143+
t := sdtab.New[row](out)
130144
t.AddHeader()
131145
for _, ep := range endpoints {
132-
t.AddRow(endpointRow{
146+
t.AddRow(row{
133147
Name: ep.Name,
134148
Target: ep.Target,
135149
URL: ep.URL,
136150
})
137151
}
138152
return t.Flush()
139153
}
154+
155+
func printMultiClusterRGEndpointTable(out io.Writer, endpoints []*models.RoutegroupsEndpointURL) error {
156+
type row struct {
157+
Name string `sdtab:"ROUTEGROUP ENDPOINT"`
158+
Cluster string `sdtab:"CLUSTER"`
159+
Target string `sdtab:"TARGET"`
160+
URL string `sdtab:"URL"`
161+
}
162+
163+
t := sdtab.New[row](out)
164+
t.AddHeader()
165+
for _, ep := range endpoints {
166+
t.AddRow(row{
167+
Name: ep.Name,
168+
Cluster: ep.Cluster,
169+
Target: ep.Target,
170+
URL: ep.URL,
171+
})
172+
}
173+
return t.Flush()
174+
}

internal/command/smarttest/run.go

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package smarttest
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"io"
78
"os"
@@ -99,26 +100,14 @@ func validateRun(cfg *config.SmartTestRun) error {
99100
if err := validateList(cfg.SmartTestList); err != nil {
100101
return err
101102
}
102-
count := 0
103-
if cfg.Cluster != "" {
104-
count++
105-
}
106-
if cfg.Sandbox != "" {
107-
count++
108-
}
109-
if cfg.RouteGroup != "" {
110-
count++
111-
}
112-
113-
if count == 0 {
114-
return fmt.Errorf("you must specify one of '--cluster', '--sandbox' or '--route-group'")
115-
}
116-
if count > 1 {
117-
return fmt.Errorf("only one of '--cluster', '--sandbox' or '--route-group' should be specified")
103+
if err := cfg.Validate(); err != nil {
104+
return err
118105
}
119106

120107
// load the corresponding entity from the API
121-
if cfg.Sandbox != "" {
108+
switch {
109+
case cfg.Sandbox != "":
110+
// validate that the sandbox exists and resolve the cluster based on it
122111
params := sandboxes.NewGetSandboxParams().
123112
WithOrgName(cfg.Org).WithSandboxName(cfg.Sandbox)
124113
resp, err := cfg.Client.Sandboxes.GetSandbox(params, nil)
@@ -127,16 +116,33 @@ func validateRun(cfg *config.SmartTestRun) error {
127116
}
128117
// store the cluster for later use
129118
cfg.Cluster = *resp.Payload.Spec.Cluster
130-
} else if cfg.RouteGroup != "" {
119+
120+
case cfg.RouteGroup != "":
121+
// validate that the route group exists and try resolving the cluster
122+
// based on it
131123
params := routegroups.NewGetRoutegroupParams().
132124
WithOrgName(cfg.Org).WithRoutegroupName(cfg.RouteGroup)
133125
resp, err := cfg.Client.RouteGroups.GetRoutegroup(params, nil)
134126
if err != nil {
135127
return fmt.Errorf("failed to load routegroup %q: %v", cfg.RouteGroup, err)
136128
}
137-
// store the cluster for later use
138-
cfg.Cluster = resp.Payload.Spec.Cluster
139-
} else {
129+
if resp.Payload.Spec.Cluster != "" {
130+
// store the cluster for later use
131+
cfg.Cluster = resp.Payload.Spec.Cluster
132+
} else {
133+
// this is a multi-cluster RG, the cluster must be explicitly defined
134+
if cfg.Cluster == "" {
135+
return errors.New("--cluster must be specified in multi-cluster route groups")
136+
}
137+
// validate the cluster exists
138+
params := clusters.NewGetClusterParams().
139+
WithOrgName(cfg.Org).WithClusterName(cfg.Cluster)
140+
if _, err := cfg.Client.Cluster.GetCluster(params, nil); err != nil {
141+
return fmt.Errorf("failed to load cluster %q: %v", cfg.Cluster, err)
142+
}
143+
}
144+
145+
default:
140146
// validate the cluster exists
141147
params := clusters.NewGetClusterParams().
142148
WithOrgName(cfg.Org).WithClusterName(cfg.Cluster)

internal/config/local.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,10 +233,17 @@ func (lp *LocalProxy) Validate() error {
233233
if c == 0 {
234234
return errors.New("you should specify one of '--sandbox', '--routegroup' or '--cluster'")
235235
}
236+
// Allow routeGroup + cluster combination
237+
if c == 2 && lp.RouteGroup != "" && lp.Cluster != "" {
238+
return lp.validateProxyMappings()
239+
}
236240
if c > 1 {
237241
return errors.New("only one of '--sandbox', '--routegroup' or '--cluster' should be specified")
238242
}
243+
return lp.validateProxyMappings()
244+
}
239245

246+
func (lp *LocalProxy) validateProxyMappings() error {
240247
for i := range lp.ProxyMappings {
241248
pm := &lp.ProxyMappings[i]
242249
if err := pm.Validate(); err != nil {

internal/config/smarttest.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package config
22

33
import (
44
"bytes"
5+
"errors"
56
"fmt"
67
"sort"
78
"strings"
@@ -45,6 +46,31 @@ type SmartTestRun struct {
4546
NoWait bool
4647
}
4748

49+
func (smr *SmartTestRun) Validate() error {
50+
c := 0
51+
if smr.Cluster != "" {
52+
c++
53+
}
54+
if smr.Sandbox != "" {
55+
c++
56+
}
57+
if smr.RouteGroup != "" {
58+
c++
59+
}
60+
61+
if c == 0 {
62+
return errors.New("you must specify one of '--cluster', '--sandbox' or '--route-group'")
63+
}
64+
// Allow routeGroup + cluster combination
65+
if c == 2 && smr.RouteGroup != "" && smr.Cluster != "" {
66+
return nil
67+
}
68+
if c > 1 {
69+
return errors.New("only one of '--cluster', '--sandbox' or '--route-group' should be specified")
70+
}
71+
return nil
72+
}
73+
4874
type TestExecLabels map[string]string
4975

5076
func (rl TestExecLabels) String() string {

0 commit comments

Comments
 (0)