Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 53 additions & 2 deletions chaos.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const (
ActionError = "error"
// ActionRandom means return random IP for DNS request
ActionRandom = "random"
// ActionChaos means return chaos IP for DNS request
ActionStatic = "static"
)

// PodInfo saves some information for pod
Expand All @@ -39,6 +41,12 @@ type PodInfo struct {
LastUpdateTime time.Time
}

// DomainIP Domain and ip mapping
type DomainIP struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type is not used?

Domain string
IP string
}

// IsOverdue ...
func (p *PodInfo) IsOverdue() bool {
// if the pod's IP is not updated greater than 10 seconds, will treate it as overdue
Expand All @@ -55,8 +63,6 @@ func (k Kubernetes) chaosDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M
return dns.RcodeServerFailure, fmt.Errorf("dns chaos error")
}

// return random IP

answers := []dns.RR{}
qname := state.Name()

Expand Down Expand Up @@ -166,6 +172,15 @@ func (k Kubernetes) needChaos(podInfo *PodInfo, records []dns.RR, name string) b
if podInfo.Scope == ScopeAll {
return true
}
if podInfo.Action == ActionStatic {
domainMap := k.domainAndIPMap[podInfo.Namespace][podInfo.Name]
if domainMap != nil {
if _, ok := domainMap[name]; ok {
return true
}
}
return false
}

rules := podInfo.Selector.Match(name, "")
if len(rules) == 0 {
Expand All @@ -188,3 +203,39 @@ func (k Kubernetes) getPodFromCluster(namespace, name string) (*api.Pod, error)
}
return pods.Get(context.Background(), name, meta.GetOptions{})
}

func generateDNSRecords(state request.Request, domainAndIpMap map[string]string, r *dns.Msg, w dns.ResponseWriter) (int, error) {
answers := []dns.RR{}
qname := state.Name()
if domainAndIpMap == nil {
return dns.RcodeServerFailure, nil
}
ipStr, ok := domainAndIpMap[qname]
if !ok {
return dns.RcodeServerFailure, fmt.Errorf("domain %s not found", qname)
}
ip := net.ParseIP(ipStr)
switch state.QType() {
case dns.TypeA:
ipv4 := ip.To4()
if ipv4 == nil {
return dns.RcodeServerFailure, fmt.Errorf("not a valid IPv4 address: %s", ipStr)
}
answers = a(qname, 10, []net.IP{ipv4})
log.Debugf("dns.TypeA %v", ipv4)
case dns.TypeAAAA:
ipv6 := ip.To16()
if ip.To4() != nil {
return dns.RcodeServerFailure, fmt.Errorf("not a valid IPv6 address: %s", ipStr)
}
log.Debugf("dns.TypeAAAA %v", ipv6)
answers = aaaa(qname, 10, []net.IP{ipv6})
}
m := new(dns.Msg)
m.SetReply(r)
m.Authoritative = true
m.Answer = answers

w.WriteMsg(m)
return dns.RcodeSuccess, nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ require (
github.com/miekg/dns v1.1.43
github.com/pingcap/tidb-tools v6.3.0+incompatible
github.com/prometheus/client_golang v1.11.0
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
google.golang.org/grpc v1.41.0
k8s.io/api v0.22.2
k8s.io/apimachinery v0.22.2
Expand Down Expand Up @@ -45,6 +44,7 @@ require (
github.com/prometheus/common v0.31.1 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 // indirect
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect
Expand Down
36 changes: 34 additions & 2 deletions grpc_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ func (k Kubernetes) SetDNSChaos(ctx context.Context, req *pb.SetDNSChaosRequest)

k.Lock()
defer k.Unlock()

k.chaosMap[req.Name] = req

var scope string
Expand All @@ -70,6 +69,15 @@ func (k Kubernetes) SetDNSChaos(ctx context.Context, req *pb.SetDNSChaosRequest)
}
}
}
if req.Action == ActionStatic && req.IpDomainMaps != nil {
for _, domainIPMap := range req.IpDomainMaps {
err := selector.Insert(domainIPMap.Domain, "", true, trieselector.Insert)
if err != nil {
log.Errorf("fail to build selector %v", err)
return nil, err
}
}
}

for _, pod := range req.Pods {
v1Pod, err := k.getPodFromCluster(pod.Namespace, pod.Name)
Expand Down Expand Up @@ -100,8 +108,15 @@ func (k Kubernetes) SetDNSChaos(ctx context.Context, req *pb.SetDNSChaosRequest)

k.podMap[pod.Namespace][pod.Name] = podInfo
k.ipPodMap[v1Pod.Status.PodIP] = podInfo
}
domainIPMap := saveDomainAndIp(req.IpDomainMaps)
if domainIPMap != nil {
if _, ok := k.domainAndIPMap[pod.Namespace]; !ok {
k.domainAndIPMap[pod.Namespace] = make(map[string]map[string]string)
}
k.domainAndIPMap[pod.Namespace][pod.Name] = domainIPMap
}

}
return &pb.DNSChaosResponse{
Result: true,
}, nil
Expand All @@ -125,6 +140,9 @@ func (k Kubernetes) CancelDNSChaos(ctx context.Context, req *pb.CancelDNSChaosRe
delete(k.podMap[pod.Namespace], pod.Name)
delete(k.ipPodMap, podInfo.IP)
}
if _, ok1 := k.domainAndIPMap[pod.Namespace][pod.Name]; ok1 {
delete(k.domainAndIPMap[pod.Namespace], pod.Name)
}
}
}

Expand All @@ -136,6 +154,7 @@ func (k Kubernetes) CancelDNSChaos(ctx context.Context, req *pb.CancelDNSChaosRe
}
for _, namespace := range shouldDeleteNs {
delete(k.podMap, namespace)
delete(k.domainAndIPMap, namespace)
}

delete(k.chaosMap, req.Name)
Expand All @@ -144,3 +163,16 @@ func (k Kubernetes) CancelDNSChaos(ctx context.Context, req *pb.CancelDNSChaosRe
Result: true,
}, nil
}

// save domain and ip
func saveDomainAndIp(domainMapList []*pb.IpDomainMap) map[string]string {
if len(domainMapList) == 0 {
return nil
}
domainIPMap := make(map[string]string)
for _, domainMap := range domainMapList {
key := fmt.Sprintf("%s.", domainMap.Domain)
domainIPMap[key] = domainMap.Ip
}
return domainIPMap
}
18 changes: 15 additions & 3 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package kubernetes

import (
"context"

"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/request"

Expand All @@ -22,10 +21,23 @@ func (k Kubernetes) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.M

records, extra, zone, err := k.getRecords(ctx, state)
log.Debugf("records: %v, err: %v", records, err)

if k.needChaos(chaosPod, records, state.QName()) {
if k.needChaos(chaosPod, records, state.QName()) && chaosPod.Action != ActionStatic {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any considerations not to move the logic of chaosPod.Action != ActionStatic into the k.needChaos?

Theoretically, we need to handle this part of the logic in the function to keep the code clean.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason for not moving the chaosPod. Action != ActionStatic logic into k.needChaos() is to avoid changing the original logic, which might introduce unexpected issues or affect existing behavior. Since this part of the code has been stable, I chose to keep the separation to ensure functional consistency and reduce the risk of regression.

Once the overall behavior is verified to be stable, we can consider refactoring this logic into k.needChaos() in a follow-up PR to improve code clarity.

return k.chaosDNS(ctx, w, r, state, chaosPod)
}
// Check if chaos testing is needed and the action type is static IP.
if k.needChaos(chaosPod, records, state.QName()) && chaosPod.Action == ActionStatic {
log.Debugf("need chaos, but action is static")
// Get the domain-IP mapping for the specific namespace and pod name.
domainMap := k.domainAndIPMap[chaosPod.Namespace][chaosPod.Name]
// Check if the domain-IP mapping exists.
if domainMap != nil {
// Check if the requested domain exists in the mapping.
if _, ok := domainMap[state.Name()]; ok {
// Generate DNS records using the domain-IP mapping and return the result.
return generateDNSRecords(state, domainMap, r, w)
}
}
}

if k.IsNameError(err) {
if len(zone) == 0 {
Expand Down
5 changes: 4 additions & 1 deletion kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import (
"context"
"errors"
"fmt"
"github.com/chaos-mesh/k8s_dns_chaos/pb"
"math/rand"
"net"
"strings"
"sync"
"time"

"github.com/chaos-mesh/k8s_dns_chaos/pb"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/etcd/msg"
"github.com/coredns/coredns/plugin/kubernetes/object"
Expand Down Expand Up @@ -68,6 +68,8 @@ type Kubernetes struct {
podMap map[string]map[string]*PodInfo

ipPodMap map[string]*PodInfo

domainAndIPMap map[string]map[string]map[string]string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I think it would be better to change the name to domainIPMapByNamespacedName. Just looking at the data structure, I can't immediately know what it includes, but changing the name would help with understanding.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion — you're absolutely right. The current name domainAndIPMap is too vague, and domainIPMapByNamespacedName makes the structure much clearer. I've updated the name accordingly.

}

// New returns a initialized Kubernetes. It default interfaceAddrFunc to return 127.0.0.1. All other
Expand All @@ -81,6 +83,7 @@ func New(zones []string) *Kubernetes {
k.chaosMap = make(map[string]*pb.SetDNSChaosRequest)
k.podMap = make(map[string]map[string]*PodInfo)
k.ipPodMap = make(map[string]*PodInfo)
k.domainAndIPMap = make(map[string]map[string]map[string]string)
rand.Seed(time.Now().UnixNano())

return k
Expand Down
Loading