func (kd *KubeDNS) recordsForFederation(records []skymsg.Service, path []string, exact bool, federationSegments []string) (retval []skymsg.Service, err error) { // For federation query, verify that the local service has endpoints. validRecord := false for _, val := range records { // We know that a headless service has endpoints for sure if a // record was returned for it. The record contains endpoint // IPs. So nothing to check for headless services. // // TODO: this access to the cluster IP map does not seem to be // threadsafe. if !kd.isHeadlessServiceRecord(&val) { ok, err := kd.serviceWithClusterIPHasEndpoints(&val) if err != nil { glog.V(2).Infof( "Federation: error finding if service has endpoint: %v", err) continue } if !ok { glog.V(2).Infof("Federation: skipping record since service has no endpoint: %v", val) continue } } validRecord = true break } if validRecord { // There is a local service with valid endpoints, return its CNAME. name := strings.Join(util.ReverseArray(path), ".") // Ensure that this name that we are returning as a CNAME response // is a fully qualified domain name so that the client's resolver // library doesn't have to go through its search list all over // again. if !strings.HasSuffix(name, ".") { name = name + "." } glog.V(3).Infof( "Federation: Returning CNAME for local service: %v", name) return []skymsg.Service{{Host: name}}, nil } // If the name query is not an exact query and does not match any // records in the local store, attempt to send a federation redirect // (CNAME) response. if !exact { glog.V(3).Infof( "Federation: Did not find a local service. Trying federation redirect (CNAME)") return kd.federationRecords(util.ReverseArray(federationSegments)) } return nil, etcd.Error{Code: etcd.ErrorCodeKeyNotFound} }
func assertReverseRecord(t *testing.T, kd *KubeDNS, s *v1.Service) { segments := util.ReverseArray(strings.Split(s.Spec.ClusterIP, ".")) reverseLookup := fmt.Sprintf("%s%s", strings.Join(segments, "."), util.ArpaSuffix) reverseRecord, err := kd.ReverseRecord(reverseLookup) require.NoError(t, err) assert.Equal(t, getServiceFQDN(kd.domain, s), reverseRecord.Host) }
func assertNoReverseRecord(t *testing.T, kd *KubeDNS, s *v1.Service) { segments := util.ReverseArray(strings.Split(s.Spec.ClusterIP, ".")) reverseLookup := fmt.Sprintf("%s%s", strings.Join(segments, "."), util.ArpaSuffix) reverseRecord, err := kd.ReverseRecord(reverseLookup) require.Error(t, err) require.Nil(t, reverseRecord) }
// Records responds with DNS records that match the given name, in a format // understood by the skydns server. If "exact" is true, a single record // matching the given name is returned, otherwise all records stored under // the subtree matching the name are returned. func (kd *KubeDNS) Records(name string, exact bool) (retval []skymsg.Service, err error) { glog.V(2).Infof("Received DNS Request:%s, exact:%v", name, exact) trimmed := strings.TrimRight(name, ".") segments := strings.Split(trimmed, ".") isFederationQuery := false federationSegments := []string{} if !exact && kd.isFederationQuery(segments) { glog.V(2).Infof( "federation service query: Received federation query. Going to try to find local service first") // Try quering the non-federation (local) service first. // Will try the federation one later, if this fails. isFederationQuery = true federationSegments = append(federationSegments, segments...) // To try local service, remove federation name from segments. // Federation name is 3rd in the segment (after service name and namespace). segments = append(segments[:2], segments[3:]...) } path := util.ReverseArray(segments) records, err := kd.getRecordsForPath(path, exact) if err != nil { return nil, err } if isFederationQuery { return kd.recordsForFederation(records, path, exact, federationSegments) } else if len(records) > 0 { return records, nil } return nil, etcd.Error{Code: etcd.ErrorCodeKeyNotFound} }
func newKubeDNS() *KubeDNS { kd := &KubeDNS{ domain: testDomain, endpointsStore: cache.NewStore(cache.MetaNamespaceKeyFunc), servicesStore: cache.NewStore(cache.MetaNamespaceKeyFunc), cache: treecache.NewTreeCache(), reverseRecordMap: make(map[string]*skymsg.Service), clusterIPServiceMap: make(map[string]*kapi.Service), cacheLock: sync.RWMutex{}, domainPath: util.ReverseArray(strings.Split(strings.TrimRight(testDomain, "."), ".")), nodesStore: cache.NewStore(cache.MetaNamespaceKeyFunc), } return kd }
// federationRecords checks if the given `queryPath` is for a federated service and if it is, // it returns a CNAME response containing the cluster zone name and federation domain name // suffix. func (kd *KubeDNS) federationRecords(queryPath []string) ([]skymsg.Service, error) { // `queryPath` is a reversed-array of the queried name, reverse it back to make it easy // to follow through this code and reduce confusion. There is no reason for it to be // reversed here. path := util.ReverseArray(queryPath) // Check if the name query matches the federation query pattern. if !kd.isFederationQuery(path) { return nil, etcd.Error{Code: etcd.ErrorCodeKeyNotFound} } // Now that we have already established that the query is a federation query, remove the local // domain path components, i.e. kd.domainPath, from the query. path = path[:len(path)-len(kd.domainPath)] // Append the zone name (zone in the cloud provider terminology, not a DNS // zone) and the region name. zone, region, err := kd.getClusterZoneAndRegion() if err != nil { return nil, fmt.Errorf("failed to obtain the cluster zone and region: %v", err) } path = append(path, zone, region) // We have already established that the map entry exists for the given federation, // we just need to retrieve the domain name, validate it and append it to the path. kd.configLock.RLock() domain := kd.config.Federations[path[2]] kd.configLock.RUnlock() // We accept valid subdomains as well, so just let all the valid subdomains. if len(validation.IsDNS1123Subdomain(domain)) != 0 { return nil, fmt.Errorf("%s is not a valid domain name for federation %s", domain, path[2]) } name := strings.Join(append(path, domain), ".") // Ensure that this name that we are returning as a CNAME response is a fully qualified // domain name so that the client's resolver library doesn't have to go through its // search list all over again. if !strings.HasSuffix(name, ".") { name = name + "." } return []skymsg.Service{{Host: name}}, nil }
func newKubeDNS() *KubeDNS { return &KubeDNS{ domain: testDomain, domainPath: util.ReverseArray(strings.Split(strings.TrimRight(testDomain, "."), ".")), endpointsStore: cache.NewStore(cache.MetaNamespaceKeyFunc), servicesStore: cache.NewStore(cache.MetaNamespaceKeyFunc), nodesStore: cache.NewStore(cache.MetaNamespaceKeyFunc), cache: treecache.NewTreeCache(), reverseRecordMap: make(map[string]*skymsg.Service), clusterIPServiceMap: make(map[string]*v1.Service), cacheLock: sync.RWMutex{}, config: config.NewDefaultConfig(), configLock: sync.RWMutex{}, configSync: config.NewNopSync(config.NewDefaultConfig()), } }
func NewKubeDNS(client clientset.Interface, clusterDomain string, configSync config.Sync) *KubeDNS { kd := &KubeDNS{ kubeClient: client, domain: clusterDomain, cache: treecache.NewTreeCache(), cacheLock: sync.RWMutex{}, nodesStore: kcache.NewStore(kcache.MetaNamespaceKeyFunc), reverseRecordMap: make(map[string]*skymsg.Service), clusterIPServiceMap: make(map[string]*v1.Service), domainPath: util.ReverseArray(strings.Split(strings.TrimRight(clusterDomain, "."), ".")), configLock: sync.RWMutex{}, configSync: configSync, } kd.setEndpointsStore() kd.setServicesStore() return kd }
func NewKubeDNS(client clientset.Interface, domain string, federations map[string]string) (*KubeDNS, error) { // Verify that federation names should not contain dots ('.') // We can not allow dots since we use that as separator for path segments (svcname.nsname.fedname.svc.domain) for key := range federations { if strings.ContainsAny(key, ".") { return nil, fmt.Errorf("invalid federation name: %s, cannot have '.'", key) } } kd := &KubeDNS{ kubeClient: client, domain: domain, cache: NewTreeCache(), cacheLock: sync.RWMutex{}, nodesStore: kcache.NewStore(kcache.MetaNamespaceKeyFunc), reverseRecordMap: make(map[string]*skymsg.Service), clusterIPServiceMap: make(map[string]*kapi.Service), domainPath: util.ReverseArray(strings.Split(strings.TrimRight(domain, "."), ".")), federations: federations, } kd.setEndpointsStore() kd.setServicesStore() return kd, nil }
// fqdn constructs the fqdn for the given service. subpaths is a list of path // elements rooted at the given service, ending at a service record. func (kd *KubeDNS) fqdn(service *v1.Service, subpaths ...string) string { domainLabels := append(append(kd.domainPath, serviceSubdomain, service.Namespace, service.Name), subpaths...) return dns.Fqdn(strings.Join(util.ReverseArray(domainLabels), ".")) }