|
| 1 | +diff -Naur istio/pilot/pkg/config/kube/gateway/conversion.go istio-new/pilot/pkg/config/kube/gateway/conversion.go |
| 2 | +--- istio/pilot/pkg/config/kube/gateway/conversion.go 2023-11-03 17:18:56.000000000 +0800 |
| 3 | ++++ istio-new/pilot/pkg/config/kube/gateway/conversion.go 2023-11-03 17:14:50.000000000 +0800 |
| 4 | +@@ -151,15 +151,113 @@ |
| 5 | + } |
| 6 | + } |
| 7 | + |
| 8 | ++ // for gateway routes, build one VS per gateway+host |
| 9 | ++ gatewayRoutes := make(map[string]map[string]*config.Config) |
| 10 | ++ |
| 11 | + for _, obj := range r.HTTPRoute { |
| 12 | +- if vsConfig := buildHTTPVirtualServices(obj, gatewayMap, r.Domain); vsConfig != nil { |
| 13 | ++ buildHTTPVirtualServices(r, obj, gatewayMap, gatewayRoutes, r.Domain) |
| 14 | ++ } |
| 15 | ++ for _, vsByHost := range gatewayRoutes { |
| 16 | ++ for _, vsConfig := range vsByHost { |
| 17 | + result = append(result, *vsConfig) |
| 18 | + } |
| 19 | + } |
| 20 | + return result |
| 21 | + } |
| 22 | + |
| 23 | +-func buildHTTPVirtualServices(obj config.Config, gateways map[parentKey]map[gatewayapiV1beta1.SectionName]*parentInfo, domain string) *config.Config { |
| 24 | ++// getURIRank ranks a URI match type. Exact > Prefix > Regex |
| 25 | ++func getURIRank(match *istio.HTTPMatchRequest) int { |
| 26 | ++ if match.Uri == nil { |
| 27 | ++ return -1 |
| 28 | ++ } |
| 29 | ++ switch match.Uri.MatchType.(type) { |
| 30 | ++ case *istio.StringMatch_Exact: |
| 31 | ++ return 3 |
| 32 | ++ case *istio.StringMatch_Prefix: |
| 33 | ++ return 2 |
| 34 | ++ case *istio.StringMatch_Regex: |
| 35 | ++ // TODO optimize in new verison envoy |
| 36 | ++ if strings.HasSuffix(match.Uri.GetRegex(), prefixMatchRegex) && |
| 37 | ++ !strings.ContainsAny(strings.TrimSuffix(match.Uri.GetRegex(), prefixMatchRegex), `\.+*?()|[]{}^$`) { |
| 38 | ++ return 2 |
| 39 | ++ } |
| 40 | ++ return 1 |
| 41 | ++ } |
| 42 | ++ // should not happen |
| 43 | ++ return -1 |
| 44 | ++} |
| 45 | ++ |
| 46 | ++func getURILength(match *istio.HTTPMatchRequest) int { |
| 47 | ++ if match.Uri == nil { |
| 48 | ++ return 0 |
| 49 | ++ } |
| 50 | ++ switch match.Uri.MatchType.(type) { |
| 51 | ++ case *istio.StringMatch_Prefix: |
| 52 | ++ return len(match.Uri.GetPrefix()) |
| 53 | ++ case *istio.StringMatch_Exact: |
| 54 | ++ return len(match.Uri.GetExact()) |
| 55 | ++ case *istio.StringMatch_Regex: |
| 56 | ++ return len(match.Uri.GetRegex()) |
| 57 | ++ } |
| 58 | ++ // should not happen |
| 59 | ++ return -1 |
| 60 | ++} |
| 61 | ++ |
| 62 | ++// sortHTTPRoutes sorts generated vs routes to meet gateway-api requirements |
| 63 | ++// see https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.HTTPRouteRule |
| 64 | ++func sortHTTPRoutes(routes []*istio.HTTPRoute) { |
| 65 | ++ sort.SliceStable(routes, func(i, j int) bool { |
| 66 | ++ if len(routes[i].Match) == 0 { |
| 67 | ++ return false |
| 68 | ++ } else if len(routes[j].Match) == 0 { |
| 69 | ++ return true |
| 70 | ++ } |
| 71 | ++ // Only look at match[0], we always generate only one match |
| 72 | ++ m1, m2 := routes[i].Match[0], routes[j].Match[0] |
| 73 | ++ r1, r2 := getURIRank(m1), getURIRank(m2) |
| 74 | ++ len1, len2 := getURILength(m1), getURILength(m2) |
| 75 | ++ switch { |
| 76 | ++ // 1: Exact/Prefix/Regex |
| 77 | ++ case r1 != r2: |
| 78 | ++ return r1 > r2 |
| 79 | ++ case len1 != len2: |
| 80 | ++ return len1 > len2 |
| 81 | ++ // 2: method math |
| 82 | ++ case (m1.Method == nil) != (m2.Method == nil): |
| 83 | ++ return m1.Method != nil |
| 84 | ++ // 3: number of header matches |
| 85 | ++ case len(m1.Headers) != len(m2.Headers): |
| 86 | ++ return len(m1.Headers) > len(m2.Headers) |
| 87 | ++ // 4: number of query matches |
| 88 | ++ default: |
| 89 | ++ return len(m1.QueryParams) > len(m2.QueryParams) |
| 90 | ++ } |
| 91 | ++ }) |
| 92 | ++} |
| 93 | ++ |
| 94 | ++func routeMeta(obj config.Config) map[string]string { |
| 95 | ++ m := parentMeta(obj, nil) |
| 96 | ++ m[constants.InternalRouteSemantics] = constants.RouteSemanticsGateway |
| 97 | ++ return m |
| 98 | ++} |
| 99 | ++ |
| 100 | ++func filteredReferences(parents []routeParentReference) []routeParentReference { |
| 101 | ++ ret := make([]routeParentReference, 0, len(parents)) |
| 102 | ++ for _, p := range parents { |
| 103 | ++ if p.DeniedReason != nil { |
| 104 | ++ // We should filter this out |
| 105 | ++ continue |
| 106 | ++ } |
| 107 | ++ ret = append(ret, p) |
| 108 | ++ } |
| 109 | ++ // To ensure deterministic order, sort them |
| 110 | ++ sort.Slice(ret, func(i, j int) bool { |
| 111 | ++ return ret[i].InternalName < ret[j].InternalName |
| 112 | ++ }) |
| 113 | ++ return ret |
| 114 | ++} |
| 115 | ++ |
| 116 | ++func buildHTTPVirtualServices(ctx *KubernetesResources, obj config.Config, gateways map[parentKey]map[gatewayapiV1beta1.SectionName]*parentInfo, gatewayRoutes map[string]map[string]*config.Config, domain string) { |
| 117 | + route := obj.Spec.(*gatewayapiV1beta1.HTTPRouteSpec) |
| 118 | + |
| 119 | + parentRefs := extractParentReferenceInfo(gateways, route.ParentRefs, route.Hostnames, gvk.HTTPRoute, obj.Namespace) |
| 120 | +@@ -172,10 +270,7 @@ |
| 121 | + }) |
| 122 | + } |
| 123 | + |
| 124 | +- name := fmt.Sprintf("%s-%s", obj.Name, constants.KubernetesGatewayName) |
| 125 | +- |
| 126 | + httproutes := []*istio.HTTPRoute{} |
| 127 | +- hosts := hostnameToStringList(route.Hostnames) |
| 128 | + for _, r := range route.Rules { |
| 129 | + // TODO: implement rewrite, timeout, mirror, corspolicy, retries |
| 130 | + vs := &istio.HTTPRoute{ |
| 131 | +@@ -185,22 +280,22 @@ |
| 132 | + uri, err := createURIMatch(match) |
| 133 | + if err != nil { |
| 134 | + reportError(err) |
| 135 | +- return nil |
| 136 | ++ return |
| 137 | + } |
| 138 | + headers, err := createHeadersMatch(match) |
| 139 | + if err != nil { |
| 140 | + reportError(err) |
| 141 | +- return nil |
| 142 | ++ return |
| 143 | + } |
| 144 | + qp, err := createQueryParamsMatch(match) |
| 145 | + if err != nil { |
| 146 | + reportError(err) |
| 147 | +- return nil |
| 148 | ++ return |
| 149 | + } |
| 150 | + method, err := createMethodMatch(match) |
| 151 | + if err != nil { |
| 152 | + reportError(err) |
| 153 | +- return nil |
| 154 | ++ return |
| 155 | + } |
| 156 | + vs.Match = append(vs.Match, &istio.HTTPMatchRequest{ |
| 157 | + Uri: uri, |
| 158 | +@@ -219,7 +314,7 @@ |
| 159 | + mirror, err := createMirrorFilter(filter.RequestMirror, obj.Namespace, domain) |
| 160 | + if err != nil { |
| 161 | + reportError(err) |
| 162 | +- return nil |
| 163 | ++ return |
| 164 | + } |
| 165 | + vs.Mirror = mirror |
| 166 | + default: |
| 167 | +@@ -227,7 +322,7 @@ |
| 168 | + Reason: InvalidFilter, |
| 169 | + Message: fmt.Sprintf("unsupported filter type %q", filter.Type), |
| 170 | + }) |
| 171 | +- return nil |
| 172 | ++ return |
| 173 | + } |
| 174 | + } |
| 175 | + |
| 176 | +@@ -255,33 +350,65 @@ |
| 177 | + route, err := buildHTTPDestination(r.BackendRefs, obj.Namespace, domain, zero, fallbackCluster) |
| 178 | + if err != nil { |
| 179 | + reportError(err) |
| 180 | +- return nil |
| 181 | ++ return |
| 182 | + } |
| 183 | + vs.Route = route |
| 184 | + |
| 185 | + httproutes = append(httproutes, vs) |
| 186 | + } |
| 187 | + reportError(nil) |
| 188 | +- gatewayNames := referencesToInternalNames(parentRefs) |
| 189 | +- if len(gatewayNames) == 0 { |
| 190 | +- return nil |
| 191 | ++ |
| 192 | ++ count := 0 |
| 193 | ++ for _, parent := range filteredReferences(parentRefs) { |
| 194 | ++ // for gateway routes, build one VS per gateway+host |
| 195 | ++ routeMap := gatewayRoutes |
| 196 | ++ routeKey := parent.InternalName |
| 197 | ++ vsHosts := hostnameToStringList(route.Hostnames) |
| 198 | ++ routes := httproutes |
| 199 | ++ if len(routes) == 0 { |
| 200 | ++ continue |
| 201 | ++ } |
| 202 | ++ if _, f := routeMap[routeKey]; !f { |
| 203 | ++ routeMap[routeKey] = make(map[string]*config.Config) |
| 204 | ++ } |
| 205 | ++ |
| 206 | ++ // Create one VS per hostname with a single hostname. |
| 207 | ++ // This ensures we can treat each hostname independently, as the spec requires |
| 208 | ++ for _, h := range vsHosts { |
| 209 | ++ if cfg := routeMap[routeKey][h]; cfg != nil { |
| 210 | ++ // merge http routes |
| 211 | ++ vs := cfg.Spec.(*istio.VirtualService) |
| 212 | ++ vs.Http = append(vs.Http, routes...) |
| 213 | ++ // append parents |
| 214 | ++ cfg.Annotations[constants.InternalParentNames] = fmt.Sprintf("%s,%s/%s.%s", |
| 215 | ++ cfg.Annotations[constants.InternalParentNames], obj.GroupVersionKind.Kind, obj.Name, obj.Namespace) |
| 216 | ++ } else { |
| 217 | ++ name := fmt.Sprintf("%s-%d-%s", obj.Name, count, constants.KubernetesGatewayName) |
| 218 | ++ routeMap[routeKey][h] = &config.Config{ |
| 219 | ++ Meta: config.Meta{ |
| 220 | ++ CreationTimestamp: obj.CreationTimestamp, |
| 221 | ++ GroupVersionKind: gvk.VirtualService, |
| 222 | ++ Name: name, |
| 223 | ++ Annotations: routeMeta(obj), |
| 224 | ++ Namespace: obj.Namespace, |
| 225 | ++ Domain: ctx.Domain, |
| 226 | ++ }, |
| 227 | ++ Spec: &istio.VirtualService{ |
| 228 | ++ Hosts: []string{h}, |
| 229 | ++ Gateways: []string{parent.InternalName}, |
| 230 | ++ Http: routes, |
| 231 | ++ }, |
| 232 | ++ } |
| 233 | ++ count++ |
| 234 | ++ } |
| 235 | ++ } |
| 236 | + } |
| 237 | +- vsConfig := config.Config{ |
| 238 | +- Meta: config.Meta{ |
| 239 | +- CreationTimestamp: obj.CreationTimestamp, |
| 240 | +- GroupVersionKind: gvk.VirtualService, |
| 241 | +- Name: name, |
| 242 | +- Annotations: parentMeta(obj, nil), |
| 243 | +- Namespace: obj.Namespace, |
| 244 | +- Domain: domain, |
| 245 | +- }, |
| 246 | +- Spec: &istio.VirtualService{ |
| 247 | +- Hosts: hosts, |
| 248 | +- Gateways: gatewayNames, |
| 249 | +- Http: httproutes, |
| 250 | +- }, |
| 251 | ++ for _, vsByHost := range gatewayRoutes { |
| 252 | ++ for _, cfg := range vsByHost { |
| 253 | ++ vs := cfg.Spec.(*istio.VirtualService) |
| 254 | ++ sortHTTPRoutes(vs.Http) |
| 255 | ++ } |
| 256 | + } |
| 257 | +- return &vsConfig |
| 258 | + } |
| 259 | + |
| 260 | + func parentMeta(obj config.Config, sectionName *gatewayapiV1beta1.SectionName) map[string]string { |
| 261 | +@@ -1155,9 +1282,11 @@ |
| 262 | + } |
| 263 | + gs.Addresses = make([]gatewayapiV1beta1.GatewayAddress, 0, len(addressesToReport)) |
| 264 | + for _, addr := range addressesToReport { |
| 265 | ++ addrPairs := strings.Split(addr, ":") |
| 266 | + gs.Addresses = append(gs.Addresses, gatewayapiV1beta1.GatewayAddress{ |
| 267 | +- Type: &addrType, |
| 268 | +- Value: addr, |
| 269 | ++ Type: &addrType, |
| 270 | ++ // strip the port |
| 271 | ++ Value: addrPairs[0], |
| 272 | + }) |
| 273 | + } |
| 274 | + return gs |
| 275 | +diff -Naur istio/pilot/pkg/model/push_context.go istio-new/pilot/pkg/model/push_context.go |
| 276 | +--- istio/pilot/pkg/model/push_context.go 2023-11-03 17:18:56.000000000 +0800 |
| 277 | ++++ istio-new/pilot/pkg/model/push_context.go 2023-11-03 17:05:47.000000000 +0800 |
| 278 | +@@ -841,7 +841,19 @@ |
| 279 | + func (ps *PushContext) VirtualServicesForGateway(proxy *Proxy, gateway string) []config.Config { |
| 280 | + res := ps.virtualServiceIndex.privateByNamespaceAndGateway[proxy.ConfigNamespace][gateway] |
| 281 | + res = append(res, ps.virtualServiceIndex.exportedToNamespaceByGateway[proxy.ConfigNamespace][gateway]...) |
| 282 | +- res = append(res, ps.virtualServiceIndex.publicByGateway[gateway]...) |
| 283 | ++ |
| 284 | ++ // Favor same-namespace Gateway routes, to give the "consumer override" preference. |
| 285 | ++ // We do 2 iterations here to avoid extra allocations. |
| 286 | ++ for _, vs := range ps.virtualServiceIndex.publicByGateway[gateway] { |
| 287 | ++ if UseGatewaySemantics(vs) && vs.Namespace == proxy.ConfigNamespace { |
| 288 | ++ res = append(res, vs) |
| 289 | ++ } |
| 290 | ++ } |
| 291 | ++ for _, vs := range ps.virtualServiceIndex.publicByGateway[gateway] { |
| 292 | ++ if !(UseGatewaySemantics(vs) && vs.Namespace == proxy.ConfigNamespace) { |
| 293 | ++ res = append(res, vs) |
| 294 | ++ } |
| 295 | ++ } |
| 296 | + return res |
| 297 | + } |
| 298 | + |
| 299 | +diff -Naur istio/pilot/pkg/model/virtualservice.go istio-new/pilot/pkg/model/virtualservice.go |
| 300 | +--- istio/pilot/pkg/model/virtualservice.go 2023-11-03 17:18:55.000000000 +0800 |
| 301 | ++++ istio-new/pilot/pkg/model/virtualservice.go 2023-11-03 15:19:08.000000000 +0800 |
| 302 | +@@ -76,6 +76,11 @@ |
| 303 | + } |
| 304 | + |
| 305 | + func resolveVirtualServiceShortnames(rule *networking.VirtualService, meta config.Meta) { |
| 306 | ++ // Kubernetes Gateway API semantics support shortnames |
| 307 | ++ // if UseGatewaySemantics(config.Config{Meta: meta}) { |
| 308 | ++ // return |
| 309 | ++ // } |
| 310 | ++ |
| 311 | + // resolve top level hosts |
| 312 | + for i, h := range rule.Hosts { |
| 313 | + rule.Hosts[i] = string(ResolveShortnameToFQDN(h, meta)) |
| 314 | +@@ -524,3 +529,10 @@ |
| 315 | + } |
| 316 | + return false |
| 317 | + } |
| 318 | ++ |
| 319 | ++// UseGatewaySemantics determines which logic we should use for VirtualService |
| 320 | ++// This allows gateway-api and VS to both be represented by VirtualService, but have different |
| 321 | ++// semantics. |
| 322 | ++func UseGatewaySemantics(cfg config.Config) bool { |
| 323 | ++ return cfg.Annotations[constants.InternalRouteSemantics] == constants.RouteSemanticsGateway |
| 324 | ++} |
| 325 | +diff -Naur istio/pilot/pkg/networking/core/v1alpha3/route/route.go istio-new/pilot/pkg/networking/core/v1alpha3/route/route.go |
| 326 | +--- istio/pilot/pkg/networking/core/v1alpha3/route/route.go 2023-11-03 17:18:56.000000000 +0800 |
| 327 | ++++ istio-new/pilot/pkg/networking/core/v1alpha3/route/route.go 2023-11-03 17:05:55.000000000 +0800 |
| 328 | +@@ -408,7 +408,6 @@ |
| 329 | + break |
| 330 | + } |
| 331 | + } |
| 332 | +- |
| 333 | + if len(out) == 0 { |
| 334 | + return nil, fmt.Errorf("no routes matched") |
| 335 | + } |
| 336 | +@@ -493,6 +492,14 @@ |
| 337 | + }, |
| 338 | + } |
| 339 | + |
| 340 | ++ if model.UseGatewaySemantics(virtualService) { |
| 341 | ++ if uri, isPrefixReplace := cutPrefix(redirect.Uri, "%PREFIX()%"); isPrefixReplace { |
| 342 | ++ action.Redirect.PathRewriteSpecifier = &route.RedirectAction_PrefixRewrite{ |
| 343 | ++ PrefixRewrite: uri, |
| 344 | ++ } |
| 345 | ++ } |
| 346 | ++ } |
| 347 | ++ |
| 348 | + if redirect.Scheme != "" { |
| 349 | + action.Redirect.SchemeRewriteSpecifier = &route.RedirectAction_SchemeRedirect{SchemeRedirect: redirect.Scheme} |
| 350 | + } |
| 351 | +@@ -1616,3 +1623,10 @@ |
| 352 | + isSupport = curVersion.GreaterThan(notSupportFallback) |
| 353 | + return |
| 354 | + } |
| 355 | ++ |
| 356 | ++func cutPrefix(s, prefix string) (after string, found bool) { |
| 357 | ++ if !strings.HasPrefix(s, prefix) { |
| 358 | ++ return s, false |
| 359 | ++ } |
| 360 | ++ return s[len(prefix):], true |
| 361 | ++} |
| 362 | +diff -Naur istio/pkg/config/constants/constants.go istio-new/pkg/config/constants/constants.go |
| 363 | +--- istio/pkg/config/constants/constants.go 2023-11-03 17:18:54.000000000 +0800 |
| 364 | ++++ istio-new/pkg/config/constants/constants.go 2023-11-03 14:29:27.000000000 +0800 |
| 365 | +@@ -15,6 +15,12 @@ |
| 366 | + package constants |
| 367 | + |
| 368 | + const ( |
| 369 | ++ InternalParentNames = "internal.istio.io/parents" |
| 370 | ++ |
| 371 | ++ InternalRouteSemantics = "internal.istio.io/route-semantics" |
| 372 | ++ |
| 373 | ++ RouteSemanticsGateway = "gateway" |
| 374 | ++ |
| 375 | + // UnspecifiedIP constant for empty IP address |
| 376 | + UnspecifiedIP = "0.0.0.0" |
| 377 | + |
0 commit comments