1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
|
// Package kubernetes provides the kubernetes backend.
package kubernetes
import (
"errors"
"fmt"
"time"
"github.com/miekg/coredns/middleware"
k8sc "github.com/miekg/coredns/middleware/kubernetes/k8sclient"
"github.com/miekg/coredns/middleware/kubernetes/msg"
"github.com/miekg/coredns/middleware/kubernetes/nametemplate"
"github.com/miekg/coredns/middleware/kubernetes/util"
"github.com/miekg/coredns/middleware/proxy"
// "github.com/miekg/coredns/middleware/singleflight"
"github.com/miekg/dns"
"golang.org/x/net/context"
)
type Kubernetes struct {
Next middleware.Handler
Zones []string
Proxy proxy.Proxy // Proxy for looking up names during the resolution process
Ctx context.Context
// Inflight *singleflight.Group
APIConn *k8sc.K8sConnector
NameTemplate *nametemplate.NameTemplate
Namespaces *[]string
}
// getZoneForName returns the zone string that matches the name and a
// list of the DNS labels from name that are within the zone.
// For example, if "coredns.local" is a zone configured for the
// Kubernetes middleware, then getZoneForName("a.b.coredns.local")
// will return ("coredns.local", ["a", "b"]).
func (g Kubernetes) getZoneForName(name string) (string, []string) {
var zone string
var serviceSegments []string
for _, z := range g.Zones {
if dns.IsSubDomain(z, name) {
zone = z
serviceSegments = dns.SplitDomainName(name)
serviceSegments = serviceSegments[:len(serviceSegments)-dns.CountLabel(zone)]
break
}
}
return zone, serviceSegments
}
// Records looks up services in kubernetes.
// If exact is true, it will lookup just
// this name. This is used when find matches when completing SRV lookups
// for instance.
func (g Kubernetes) Records(name string, exact bool) ([]msg.Service, error) {
var (
serviceName string
namespace string
typeName string
)
fmt.Println("enter Records('", name, "', ", exact, ")")
zone, serviceSegments := g.getZoneForName(name)
/*
// For initial implementation, assume namespace is first serviceSegment
// and service name is remaining segments.
serviceSegLen := len(serviceSegments)
if serviceSegLen >= 2 {
namespace = serviceSegments[serviceSegLen-1]
serviceName = strings.Join(serviceSegments[:serviceSegLen-1], ".")
}
// else we are looking up the zone. So handle the NS, SOA records etc.
*/
// TODO: Implementation above globbed together segments for the serviceName if
// multiple segments remained. Determine how to do similar globbing using
// the template-based implementation.
namespace = g.NameTemplate.GetNamespaceFromSegmentArray(serviceSegments)
serviceName = g.NameTemplate.GetServiceFromSegmentArray(serviceSegments)
typeName = g.NameTemplate.GetTypeFromSegmentArray(serviceSegments)
fmt.Println("[debug] exact: ", exact)
fmt.Println("[debug] zone: ", zone)
fmt.Println("[debug] servicename: ", serviceName)
fmt.Println("[debug] namespace: ", namespace)
fmt.Println("[debug] typeName: ", typeName)
fmt.Println("[debug] APIconn: ", g.APIConn)
// TODO: Implement wildcard support to allow blank namespace value
if namespace == "" {
err := errors.New("Parsing query string did not produce a namespace value")
fmt.Printf("[ERROR] %v\n", err)
return nil, err
}
// Abort if the namespace is not published per CoreFile
if g.Namespaces != nil && !util.StringInSlice(namespace, *g.Namespaces) {
return nil, nil
}
k8sItems, err := g.APIConn.GetServiceItemsInNamespace(namespace, serviceName)
fmt.Println("[debug] k8s items:", k8sItems)
if err != nil {
fmt.Printf("[ERROR] Got error while looking up ServiceItems. Error is: %v\n", err)
return nil, err
}
if k8sItems == nil {
// Did not find item in k8s
return nil, nil
}
// test := g.NameTemplate.GetRecordNameFromNameValues(nametemplate.NameValues{ServiceName: serviceName, TypeName: typeName, Namespace: namespace, Zone: zone})
// fmt.Printf("[debug] got recordname %v\n", test)
records := g.getRecordsForServiceItems(k8sItems, name)
return records, nil
}
// TODO: assemble name from parts found in k8s data based on name template rather than reusing query string
func (g Kubernetes) getRecordsForServiceItems(serviceItems []*k8sc.ServiceItem, name string) []msg.Service {
var records []msg.Service
for _, item := range serviceItems {
fmt.Println("[debug] clusterIP:", item.Spec.ClusterIP)
for _, p := range item.Spec.Ports {
fmt.Println("[debug] port:", p.Port)
}
clusterIP := item.Spec.ClusterIP
s := msg.Service{Host: name}
records = append(records, s)
for _, p := range item.Spec.Ports {
s := msg.Service{Host: clusterIP, Port: p.Port}
records = append(records, s)
}
}
fmt.Printf("[debug] records from getRecordsForServiceItems(): %v\n", records)
return records
}
/*
// Get performs the call to the Kubernetes http API.
func (g Kubernetes) Get(path string, recursive bool) (bool, error) {
fmt.Println("[debug] in Get path: ", path)
fmt.Println("[debug] in Get recursive: ", recursive)
return false, nil
}
*/
func (g Kubernetes) splitDNSName(name string) []string {
l := dns.SplitDomainName(name)
for i, j := 0, len(l)-1; i < j; i, j = i+1, j-1 {
l[i], l[j] = l[j], l[i]
}
return l
}
// skydns/local/skydns/east/staging/web
// skydns/local/skydns/west/production/web
//
// skydns/local/skydns/*/*/web
// skydns/local/skydns/*/web
// loopNodes recursively loops through the nodes and returns all the values. The nodes' keyname
// will be match against any wildcards when star is true.
/*
func (g Kubernetes) loopNodes(ns []*etcdc.Node, nameParts []string, star bool, bx map[msg.Service]bool) (sx []msg.Service, err error) {
if bx == nil {
bx = make(map[msg.Service]bool)
}
Nodes:
for _, n := range ns {
if n.Dir {
nodes, err := g.loopNodes(n.Nodes, nameParts, star, bx)
if err != nil {
return nil, err
}
sx = append(sx, nodes...)
continue
}
if star {
keyParts := strings.Split(n.Key, "/")
for i, n := range nameParts {
if i > len(keyParts)-1 {
// name is longer than key
continue Nodes
}
if n == "*" || n == "any" {
continue
}
if keyParts[i] != n {
continue Nodes
}
}
}
serv := new(msg.Service)
if err := json.Unmarshal([]byte(n.Value), serv); err != nil {
return nil, err
}
b := msg.Service{Host: serv.Host, Port: serv.Port, Priority: serv.Priority, Weight: serv.Weight, Text: serv.Text, Key: n.Key}
if _, ok := bx[b]; ok {
continue
}
bx[b] = true
serv.Key = n.Key
serv.Ttl = g.Ttl(n, serv)
if serv.Priority == 0 {
serv.Priority = priority
}
sx = append(sx, *serv)
}
return sx, nil
}
// Ttl returns the smaller of the kubernetes TTL and the service's
// TTL. If neither of these are set (have a zero value), a default is used.
func (g Kubernetes) Ttl(node *etcdc.Node, serv *msg.Service) uint32 {
kubernetesTtl := uint32(node.TTL)
if kubernetesTtl == 0 && serv.Ttl == 0 {
return ttl
}
if kubernetesTtl == 0 {
return serv.Ttl
}
if serv.Ttl == 0 {
return kubernetesTtl
}
if kubernetesTtl < serv.Ttl {
return kubernetesTtl
}
return serv.Ttl
}
*/
// kubernetesNameError checks if the error is ErrorCodeKeyNotFound from kubernetes.
func isKubernetesNameError(err error) bool {
return false
}
const (
priority = 10 // default priority when nothing is set
ttl = 300 // default ttl when nothing is set
minTtl = 60
hostmaster = "hostmaster"
k8sTimeout = 5 * time.Second
)
|