diff --git a/api/v1/ytsaurus_types.go b/api/v1/ytsaurus_types.go index 702e4777..4d7b5609 100644 --- a/api/v1/ytsaurus_types.go +++ b/api/v1/ytsaurus_types.go @@ -517,10 +517,17 @@ type QueryTrackerSpec struct { } type StrawberryControllerSpec struct { - Resources corev1.ResourceRequirements `json:"resources,omitempty"` - Image *string `json:"image,omitempty"` - Tolerations []corev1.Toleration `json:"tolerations,omitempty"` - NodeSelector map[string]string `json:"nodeSelector,omitempty"` + Resources corev1.ResourceRequirements `json:"resources,omitempty"` + Image *string `json:"image,omitempty"` + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + ExternalProxy *string `json:"externalProxy,omitempty"` + // Supported controller families, for example: "chyt", "jupyt", "livy". + ControllerFamilies []string `json:"controllerFamilies,omitempty"` + // The family that will receive requests for domains that are not explicitly specified in http_controller_mappings. + // For example, "chyt" (with `ControllerFamilies` set to {"chyt", "jupyt"} would mean + // that requests to "foo." will be processed by chyt controller. + DefaultRouteFamily *string `json:"defaultRouteFamily,omitempty"` } type YQLAgentSpec struct { diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index efcd74ee..423762c6 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -1489,6 +1489,21 @@ func (in *StrawberryControllerSpec) DeepCopyInto(out *StrawberryControllerSpec) (*out)[key] = val } } + if in.ExternalProxy != nil { + in, out := &in.ExternalProxy, &out.ExternalProxy + *out = new(string) + **out = **in + } + if in.ControllerFamilies != nil { + in, out := &in.ControllerFamilies, &out.ControllerFamilies + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.DefaultRouteFamily != nil { + in, out := &in.DefaultRouteFamily, &out.DefaultRouteFamily + *out = new(string) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StrawberryControllerSpec. diff --git a/config/crd/bases/cluster.ytsaurus.tech_ytsaurus.yaml b/config/crd/bases/cluster.ytsaurus.tech_ytsaurus.yaml index c8d18a16..da3bda15 100644 --- a/config/crd/bases/cluster.ytsaurus.tech_ytsaurus.yaml +++ b/config/crd/bases/cluster.ytsaurus.tech_ytsaurus.yaml @@ -29432,6 +29432,18 @@ spec: type: object strawberry: properties: + controllerFamilies: + description: 'Supported controller families, for example: "chyt", + "jupyt", "livy".' + items: + type: string + type: array + defaultRouteFamily: + description: The family that will receive requests for domains + that are not explicitly specif + type: string + externalProxy: + type: string image: type: string nodeSelector: diff --git a/docs/api.md b/docs/api.md index 4e7a9fc0..c63736b3 100644 --- a/docs/api.md +++ b/docs/api.md @@ -1471,6 +1471,9 @@ _Appears in:_ | `image` _string_ | | | | | `tolerations` _[Toleration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#toleration-v1-core) array_ | | | | | `nodeSelector` _object (keys:string, values:string)_ | | | | +| `externalProxy` _string_ | | | | +| `controllerFamilies` _string array_ | Supported controller families, for example: "chyt", "jupyt", "livy". | | | +| `defaultRouteFamily` _string_ | The family that will receive requests for domains that are not explicitly specified in http_controller_mappings.
For example, "chyt" (with `ControllerFamilies` set to {"chyt", "jupyt"} would mean
that requests to "foo." will be processed by chyt controller. | | | #### StructuredLoggerSpec diff --git a/pkg/canonize/canonize.go b/pkg/canonize/canonize.go index c2e8c11c..1100fed2 100644 --- a/pkg/canonize/canonize.go +++ b/pkg/canonize/canonize.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "testing" "github.com/pmezard/go-difflib/difflib" @@ -35,9 +36,10 @@ func Assert(t *testing.T, data []byte) { t.FailNow() return } + canonDataTrimmed := strings.TrimSpace(string(canonData)) diff := difflib.UnifiedDiff{ - A: difflib.SplitLines(string(canonData)), + A: difflib.SplitLines(canonDataTrimmed), B: difflib.SplitLines(string(data)), FromFile: "old", ToFile: "new", diff --git a/pkg/components/strawberry_controller.go b/pkg/components/strawberry_controller.go index c4b30199..a10d71cc 100644 --- a/pkg/components/strawberry_controller.go +++ b/pkg/components/strawberry_controller.go @@ -97,7 +97,7 @@ func NewStrawberryController( "cluster", ChytInitClusterJobConfigFileName, image, - cfgen.GetChytInitClusterConfig, + cfgen.GetStrawberryInitClusterConfig, getTolerationsWithDefault(resource.Spec.StrawberryController.Tolerations, resource.Spec.Tolerations), getNodeSelectorWithDefault(resource.Spec.StrawberryController.NodeSelector, resource.Spec.NodeSelector), ), diff --git a/pkg/consts/defaults.go b/pkg/consts/defaults.go index e7e96117..db28ebc7 100644 --- a/pkg/consts/defaults.go +++ b/pkg/consts/defaults.go @@ -23,3 +23,9 @@ const DefaultName = "default" const DefaultMedium = "default" const MaxSlotLocationReserve = 10 << 30 // 10GiB + +const DefaultStrawberryControllerFamily = "chyt" + +func GetDefaultStrawberryControllerFamilies() []string { + return []string{"chyt", "jupyt"} +} diff --git a/pkg/ytconfig/canondata/TestGetStrawberryControllerConfig/test.canondata b/pkg/ytconfig/canondata/TestGetStrawberryControllerConfig/test.canondata index b3a01eb1..39c39c09 100644 --- a/pkg/ytconfig/canondata/TestGetStrawberryControllerConfig/test.canondata +++ b/pkg/ytconfig/canondata/TestGetStrawberryControllerConfig/test.canondata @@ -15,6 +15,8 @@ retries=1000; }; }; + jupyt={ + }; }; "http_api_endpoint"=":80"; "http_location_aliases"={ @@ -22,4 +24,7 @@ test; ]; }; -} \ No newline at end of file + "http_controller_mappings"={ + "*"=chyt; + }; +} diff --git a/pkg/ytconfig/canondata/TestGetStrawberryControllerConfigWithCustomFamilies/test.canondata b/pkg/ytconfig/canondata/TestGetStrawberryControllerConfigWithCustomFamilies/test.canondata new file mode 100644 index 00000000..69a7b955 --- /dev/null +++ b/pkg/ytconfig/canondata/TestGetStrawberryControllerConfigWithCustomFamilies/test.canondata @@ -0,0 +1,29 @@ +{ + "location_proxies"=[ + "http-proxies-lb-test.fake.svc.fake.zone"; + ]; + strawberry={ + root="//sys/strawberry"; + stage=production; + "robot_username"="robot-strawberry-controller"; + }; + controllers={ + superservice1={ + }; + superservice2={ + }; + superservice3={ + }; + }; + "http_api_endpoint"=":80"; + "http_location_aliases"={ + "http-proxies-lb-test.fake.svc.fake.zone"=[ + test; + ]; + }; + "http_controller_mappings"={ + "*"=superservice2; + "superservice1.some.domain"=superservice1; + "superservice3.some.domain"=superservice3; + }; +} diff --git a/pkg/ytconfig/canondata/TestGetStrawberryControllerConfigWithExtendedHTTPMapping/test.canondata b/pkg/ytconfig/canondata/TestGetStrawberryControllerConfigWithExtendedHTTPMapping/test.canondata new file mode 100644 index 00000000..9395cf4e --- /dev/null +++ b/pkg/ytconfig/canondata/TestGetStrawberryControllerConfigWithExtendedHTTPMapping/test.canondata @@ -0,0 +1,31 @@ +{ + "location_proxies"=[ + "http-proxies-lb-test.fake.svc.fake.zone"; + ]; + strawberry={ + root="//sys/strawberry"; + stage=production; + "robot_username"="robot-strawberry-controller"; + }; + controllers={ + chyt={ + "address_resolver"={ + "enable_ipv4"=%true; + "enable_ipv6"=%false; + retries=1000; + }; + }; + jupyt={ + }; + }; + "http_api_endpoint"=":80"; + "http_location_aliases"={ + "http-proxies-lb-test.fake.svc.fake.zone"=[ + test; + ]; + }; + "http_controller_mappings"={ + "*"=chyt; + "jupyt.some.domain"=jupyt; + }; +} diff --git a/pkg/ytconfig/canondata/TestGetChytInitClusterConfig/test.canondata b/pkg/ytconfig/canondata/TestGetStrawberryInitClusterConfig/test.canondata similarity index 100% rename from pkg/ytconfig/canondata/TestGetChytInitClusterConfig/test.canondata rename to pkg/ytconfig/canondata/TestGetStrawberryInitClusterConfig/test.canondata diff --git a/pkg/ytconfig/chyt.go b/pkg/ytconfig/chyt.go deleted file mode 100644 index 6b050448..00000000 --- a/pkg/ytconfig/chyt.go +++ /dev/null @@ -1,61 +0,0 @@ -package ytconfig - -import ( - "fmt" - - "github.com/ytsaurus/ytsaurus-k8s-operator/pkg/consts" - "go.ytsaurus.tech/yt/go/yson" -) - -type Strawberry struct { - Root string `yson:"root"` - Stage string `yson:"stage"` - RobotUsername string `yson:"robot_username"` -} - -type StrawberryController struct { - LocationProxies []string `yson:"location_proxies"` - Strawberry Strawberry `yson:"strawberry"` - Controllers map[string]yson.RawValue `yson:"controllers"` - HTTPAPIEndpoint string `yson:"http_api_endpoint"` - HTTPLocationAliases map[string][]string `yson:"http_location_aliases"` -} - -type ChytInitCluster struct { - Proxy string `yson:"proxy"` - StrawberryRoot string `yson:"strawberry_root"` - Families []string `yson:"families"` -} - -type ChytConfig struct { - AddressResolver AddressResolver `yson:"address_resolver"` -} - -func getStrawberryController(resolver AddressResolver) (StrawberryController, error) { - chytConfig := ChytConfig{ - AddressResolver: resolver, - } - chytYsonConfig, err := marshallYsonConfig(chytConfig) - if err != nil { - return StrawberryController{}, err - } - return StrawberryController{ - Strawberry: Strawberry{ - Root: "//sys/strawberry", - Stage: "production", - - RobotUsername: consts.StrawberryControllerUserName, - }, - Controllers: map[string]yson.RawValue{ - "chyt": chytYsonConfig, - }, - HTTPAPIEndpoint: fmt.Sprintf(":%v", consts.StrawberryHTTPAPIPort), - }, nil -} - -func getChytInitCluster() ChytInitCluster { - return ChytInitCluster{ - StrawberryRoot: "//sys/strawberry", - Families: []string{"chyt", "jupyt"}, - } -} diff --git a/pkg/ytconfig/common.go b/pkg/ytconfig/common.go index d1d5bff8..5a60536f 100644 --- a/pkg/ytconfig/common.go +++ b/pkg/ytconfig/common.go @@ -58,6 +58,12 @@ type AddressResolver struct { LocalhostNameOverride *string `yson:"localhost_name_override,omitempty"` } +type StrawberryControllerFamiliesConfig struct { + ControllerFamilies []string `yson:"controller_families,omitempty"` + DefaultRouteFamily string `yson:"default_route_family,omitempty"` + ExternalProxy *string `yson:"external_proxy,omitempty"` +} + type SolomonExporter struct { Host *string `yson:"host,omitempty"` InstanceTags map[string]string `yson:"instance_tags,omitempty"` diff --git a/pkg/ytconfig/generator.go b/pkg/ytconfig/generator.go index 965f9d40..79e2b0a7 100644 --- a/pkg/ytconfig/generator.go +++ b/pkg/ytconfig/generator.go @@ -311,10 +311,28 @@ func (g *Generator) GetClusterConnection() ([]byte, error) { return marshallYsonConfig(c) } +func (g *Generator) fillStrawberryControllerFamiliesConfig(c *StrawberryControllerFamiliesConfig, s *ytv1.StrawberryControllerSpec) { + if s.ControllerFamilies != nil { + c.ControllerFamilies = s.ControllerFamilies + } else { + c.ControllerFamilies = consts.GetDefaultStrawberryControllerFamilies() + } + if s.DefaultRouteFamily != nil { + c.DefaultRouteFamily = *s.DefaultRouteFamily + } else { + c.DefaultRouteFamily = consts.DefaultStrawberryControllerFamily + } + c.ExternalProxy = s.ExternalProxy +} + func (g *Generator) GetStrawberryControllerConfig() ([]byte, error) { var resolver AddressResolver g.fillAddressResolver(&resolver) - c, err := getStrawberryController(resolver) + + var conFamConfig StrawberryControllerFamiliesConfig + g.fillStrawberryControllerFamiliesConfig(&conFamConfig, g.ytsaurus.Spec.StrawberryController) + + c, err := getStrawberryController(conFamConfig, resolver) if err != nil { return nil, err } @@ -326,8 +344,10 @@ func (g *Generator) GetStrawberryControllerConfig() ([]byte, error) { return marshallYsonConfig(c) } -func (g *Generator) GetChytInitClusterConfig() ([]byte, error) { - c := getChytInitCluster() +func (g *Generator) GetStrawberryInitClusterConfig() ([]byte, error) { + var conFamConfig StrawberryControllerFamiliesConfig + g.fillStrawberryControllerFamiliesConfig(&conFamConfig, g.ytsaurus.Spec.StrawberryController) + c := getStrawberryInitCluster(conFamConfig) c.Proxy = g.GetHTTPProxiesAddress(consts.DefaultHTTPProxyRole) return marshallYsonConfig(c) } diff --git a/pkg/ytconfig/generator_test.go b/pkg/ytconfig/generator_test.go index 399329de..a9e31d7f 100644 --- a/pkg/ytconfig/generator_test.go +++ b/pkg/ytconfig/generator_test.go @@ -116,9 +116,9 @@ var ( } ) -func TestGetChytInitClusterConfig(t *testing.T) { +func TestGetStrawberryInitClusterConfig(t *testing.T) { g := NewGenerator(getYtsaurusWithEverything(), testClusterDomain) - cfg, err := g.GetChytInitClusterConfig() + cfg, err := g.GetStrawberryInitClusterConfig() require.NoError(t, err) canonize.Assert(t, cfg) } @@ -339,6 +339,30 @@ func TestGetStrawberryControllerConfig(t *testing.T) { canonize.Assert(t, cfg) } +func TestGetStrawberryControllerConfigWithExtendedHTTPMapping(t *testing.T) { + g := NewGenerator(getYtsaurusWithEverything(), testClusterDomain) + externalProxy := "some.domain" + g.ytsaurus.Spec.StrawberryController.ExternalProxy = &externalProxy + cfg, err := g.GetStrawberryControllerConfig() + require.NoError(t, err) + canonize.Assert(t, cfg) +} + +func TestGetStrawberryControllerConfigWithCustomFamilies(t *testing.T) { + g := NewGenerator(getYtsaurusWithEverything(), testClusterDomain) + externalProxy := "some.domain" + g.ytsaurus.Spec.StrawberryController.ExternalProxy = &externalProxy + g.ytsaurus.Spec.StrawberryController.ControllerFamilies = append( + g.ytsaurus.Spec.StrawberryController.ControllerFamilies, + "superservice1", "superservice2", "superservice3", + ) + defaultRouteFamily := "superservice2" + g.ytsaurus.Spec.StrawberryController.DefaultRouteFamily = &defaultRouteFamily + cfg, err := g.GetStrawberryControllerConfig() + require.NoError(t, err) + canonize.Assert(t, cfg) +} + func TestGetTabletNodeConfig(t *testing.T) { g := NewLocalNodeGenerator(getYtsaurusWithEverything(), testClusterDomain) cfg, err := g.GetTabletNodeConfig(getTabletNodeSpec()) diff --git a/pkg/ytconfig/strawberry.go b/pkg/ytconfig/strawberry.go new file mode 100644 index 00000000..b806e38a --- /dev/null +++ b/pkg/ytconfig/strawberry.go @@ -0,0 +1,88 @@ +package ytconfig + +import ( + "fmt" + + "github.com/ytsaurus/ytsaurus-k8s-operator/pkg/consts" + "go.ytsaurus.tech/yt/go/yson" +) + +type Strawberry struct { + Root string `yson:"root"` + Stage string `yson:"stage"` + RobotUsername string `yson:"robot_username"` +} + +type StrawberryController struct { + LocationProxies []string `yson:"location_proxies"` + Strawberry Strawberry `yson:"strawberry"` + Controllers map[string]yson.RawValue `yson:"controllers"` + HTTPAPIEndpoint string `yson:"http_api_endpoint"` + HTTPLocationAliases map[string][]string `yson:"http_location_aliases"` + HTTPControllerMappings map[string]string `yson:"http_controller_mappings"` +} + +type StrawberryInitCluster struct { + Proxy string `yson:"proxy"` + StrawberryRoot string `yson:"strawberry_root"` + Families []string `yson:"families"` +} + +type ChytConfig struct { + AddressResolver AddressResolver `yson:"address_resolver"` +} + +func getStrawberryController(conFamConfig StrawberryControllerFamiliesConfig, resolver AddressResolver) (StrawberryController, error) { + controllers := make(map[string]yson.RawValue, len(conFamConfig.ControllerFamilies)) + for _, cFamily := range conFamConfig.ControllerFamilies { + var config any + + switch cFamily { + case "chyt": + config = ChytConfig{AddressResolver: resolver} + default: + config = struct{}{} + } + + ysonConfig, err := marshallYsonConfig(config) + if err != nil { + return StrawberryController{}, err + } + controllers[cFamily] = ysonConfig + } + + var httpControllerMappings map[string]string + + if conFamConfig.ExternalProxy != nil { + httpControllerMappings = make(map[string]string, len(conFamConfig.ControllerFamilies)) + for _, cFamily := range conFamConfig.ControllerFamilies { + if cFamily == conFamConfig.DefaultRouteFamily { + httpControllerMappings["*"] = cFamily + } else { + host := fmt.Sprintf("%s.%s", cFamily, *conFamConfig.ExternalProxy) + httpControllerMappings[host] = cFamily + } + } + } else { + httpControllerMappings = map[string]string{"*": conFamConfig.DefaultRouteFamily} + } + + return StrawberryController{ + Strawberry: Strawberry{ + Root: "//sys/strawberry", + Stage: "production", + + RobotUsername: consts.StrawberryControllerUserName, + }, + Controllers: controllers, + HTTPAPIEndpoint: fmt.Sprintf(":%v", consts.StrawberryHTTPAPIPort), + HTTPControllerMappings: httpControllerMappings, + }, nil +} + +func getStrawberryInitCluster(conFamConfig StrawberryControllerFamiliesConfig) StrawberryInitCluster { + return StrawberryInitCluster{ + StrawberryRoot: "//sys/strawberry", + Families: conFamConfig.ControllerFamilies, + } +} diff --git a/ytop-chart/templates/crds/ytsaurus.cluster.ytsaurus.tech.yaml b/ytop-chart/templates/crds/ytsaurus.cluster.ytsaurus.tech.yaml index a2c37e16..ab8b667b 100644 --- a/ytop-chart/templates/crds/ytsaurus.cluster.ytsaurus.tech.yaml +++ b/ytop-chart/templates/crds/ytsaurus.cluster.ytsaurus.tech.yaml @@ -29443,6 +29443,18 @@ spec: type: object strawberry: properties: + controllerFamilies: + description: 'Supported controller families, for example: "chyt", + "jupyt", "livy".' + items: + type: string + type: array + defaultRouteFamily: + description: The family that will receive requests for domains + that are not explicitly specif + type: string + externalProxy: + type: string image: type: string nodeSelector: