// UpdateUrlMap translates the given hostname: endpoint->port mapping into a gce url map. // // HostRule: Conceptually contains all PathRules for a given host. // PathMatcher: Associates a path rule with a host rule. Mostly an optimization. // PathRule: Maps a single path regex to a backend. // // The GCE url map allows multiple hosts to share url->backend mappings without duplication, eg: // Host: foo(PathMatcher1), bar(PathMatcher1,2) // PathMatcher1: // /a -> b1 // /b -> b2 // PathMatcher2: // /c -> b1 // This leads to a lot of complexity in the common case, where all we want is a mapping of // host->{/path: backend}. // // Consider some alternatives: // 1. Using a single backend per PathMatcher: // Host: foo(PathMatcher1,3) bar(PathMatcher1,2,3) // PathMatcher1: // /a -> b1 // PathMatcher2: // /c -> b1 // PathMatcher3: // /b -> b2 // 2. Using a single host per PathMatcher: // Host: foo(PathMatcher1) // PathMatcher1: // /a -> b1 // /b -> b2 // Host: bar(PathMatcher2) // PathMatcher2: // /a -> b1 // /b -> b2 // /c -> b1 // In the context of kubernetes services, 2 makes more sense, because we // rarely want to lookup backends (service:nodeport). When a service is // deleted, we need to find all host PathMatchers that have the backend // and remove the mapping. When a new path is added to a host (happens // more frequently than service deletion) we just need to lookup the 1 // pathmatcher of the host. func (l *L7) UpdateUrlMap(ingressRules utils.GCEURLMap) error { if l.um == nil { return fmt.Errorf("Cannot add url without an urlmap.") } glog.V(3).Infof("Updating urlmap for l7 %v", l.Name) // All UrlMaps must have a default backend. If the Ingress has a default // backend, it applies to all host rules as well as to the urlmap itself. // If it doesn't the urlmap might have a stale default, so replace it with // glbc's default backend. defaultBackend := ingressRules.GetDefaultBackend() if defaultBackend != nil { l.um.DefaultService = defaultBackend.SelfLink } else { l.um.DefaultService = l.glbcDefaultBackend.SelfLink } glog.V(3).Infof("Updating url map %+v", ingressRules) // Every update replaces the entire urlmap. // TODO: when we have multiple loadbalancers point to a single gce url map // this needs modification. For now, there is a 1:1 mapping of urlmaps to // Ingresses, so if the given Ingress doesn't have a host rule we should // delete the path to that backend. l.um.HostRules = []*compute.HostRule{} l.um.PathMatchers = []*compute.PathMatcher{} for hostname, urlToBackend := range ingressRules { // Create a host rule // Create a path matcher // Add all given endpoint:backends to pathRules in path matcher pmName := getNameForPathMatcher(hostname) l.um.HostRules = append(l.um.HostRules, &compute.HostRule{ Hosts: []string{hostname}, PathMatcher: pmName, }) pathMatcher := &compute.PathMatcher{ Name: pmName, DefaultService: l.um.DefaultService, PathRules: []*compute.PathRule{}, } // Longest prefix wins. For equal rules, first hit wins, i.e the second // /foo rule when the first is deleted. for expr, be := range urlToBackend { pathMatcher.PathRules = append( pathMatcher.PathRules, &compute.PathRule{Paths: []string{expr}, Service: be.SelfLink}) } l.um.PathMatchers = append(l.um.PathMatchers, pathMatcher) } um, err := l.cloud.UpdateUrlMap(l.um) if err != nil { return err } l.um = um return nil }
// UpdateUrlMap translates the given hostname: endpoint->port mapping into a gce url map. // // HostRule: Conceptually contains all PathRules for a given host. // PathMatcher: Associates a path rule with a host rule. Mostly an optimization. // PathRule: Maps a single path regex to a backend. // // The GCE url map allows multiple hosts to share url->backend mappings without duplication, eg: // Host: foo(PathMatcher1), bar(PathMatcher1,2) // PathMatcher1: // /a -> b1 // /b -> b2 // PathMatcher2: // /c -> b1 // This leads to a lot of complexity in the common case, where all we want is a mapping of // host->{/path: backend}. // // Consider some alternatives: // 1. Using a single backend per PathMatcher: // Host: foo(PathMatcher1,3) bar(PathMatcher1,2,3) // PathMatcher1: // /a -> b1 // PathMatcher2: // /c -> b1 // PathMatcher3: // /b -> b2 // 2. Using a single host per PathMatcher: // Host: foo(PathMatcher1) // PathMatcher1: // /a -> b1 // /b -> b2 // Host: bar(PathMatcher2) // PathMatcher2: // /a -> b1 // /b -> b2 // /c -> b1 // In the context of kubernetes services, 2 makes more sense, because we // rarely want to lookup backends (service:nodeport). When a service is // deleted, we need to find all host PathMatchers that have the backend // and remove the mapping. When a new path is added to a host (happens // more frequently than service deletion) we just need to lookup the 1 // pathmatcher of the host. func (l *L7) UpdateUrlMap(ingressRules utils.GCEURLMap) error { if l.um == nil { return fmt.Errorf("Cannot add url without an urlmap.") } glog.V(3).Infof("Updating urlmap for l7 %v", l.Name) // All UrlMaps must have a default backend. If the Ingress has a default // backend, it applies to all host rules as well as to the urlmap itself. // If it doesn't the urlmap might have a stale default, so replace it with // glbc's default backend. defaultBackend := ingressRules.GetDefaultBackend() if defaultBackend != nil { l.um.DefaultService = defaultBackend.SelfLink } else { l.um.DefaultService = l.glbcDefaultBackend.SelfLink } glog.V(3).Infof("Updating url map %+v", ingressRules) for hostname, urlToBackend := range ingressRules { // Find the hostrule // Find the path matcher // Add all given endpoint:backends to pathRules in path matcher var hostRule *compute.HostRule pmName := getNameForPathMatcher(hostname) for _, hr := range l.um.HostRules { // TODO: Hostnames must be exact match? if hr.Hosts[0] == hostname { hostRule = hr break } } if hostRule == nil { // This is a new host hostRule = &compute.HostRule{ Hosts: []string{hostname}, PathMatcher: pmName, } // Why not just clobber existing host rules? // Because we can have multiple loadbalancers point to a single // gce url map when we have IngressClaims. l.um.HostRules = append(l.um.HostRules, hostRule) } var pathMatcher *compute.PathMatcher for _, pm := range l.um.PathMatchers { if pm.Name == hostRule.PathMatcher { pathMatcher = pm break } } if pathMatcher == nil { // This is a dangling or new host pathMatcher = &compute.PathMatcher{Name: pmName} l.um.PathMatchers = append(l.um.PathMatchers, pathMatcher) } pathMatcher.DefaultService = l.um.DefaultService // TODO: Every update replaces the entire path map. This will need to // change when we allow joining. Right now we call a single method // to verify current == desired and add new url mappings. pathMatcher.PathRules = []*compute.PathRule{} // Longest prefix wins. For equal rules, first hit wins, i.e the second // /foo rule when the first is deleted. for expr, be := range urlToBackend { pathMatcher.PathRules = append( pathMatcher.PathRules, &compute.PathRule{Paths: []string{expr}, Service: be.SelfLink}) } } um, err := l.cloud.UpdateUrlMap(l.um) if err != nil { return err } l.um = um return nil }