New zone database

This commit is contained in:
Alvaro Saurin committed 2015-05-26 10:53:12 +02:00
1 parent 9c48c83701
commit 8a509168ee
8 files changed
+791 -148

No files matched your search

+1 -1
View File
@@ -25,7 +25,7 @@ type ZoneLookup interface {
LookupInaddr(inaddr string) ([]ZoneRecord, error)
}
type ZoneObserver interface {
type ZoneObservable interface {
// Observe anything that affects a particular name in the zone
ObserveName(name string, observer ZoneRecordObserver) error
// Observe anything that affects a particular IP in the zone
+5 -1
View File
@@ -27,7 +27,11 @@ func TestHttp(t *testing.T) {
dockerIP = "9.8.7.6"
)
zone := NewZoneDb(DefaultLocalDomain)
zone, err := NewZoneDb(ZoneConfig{})
wt.AssertNoErr(t, err)
err = zone.Start()
wt.AssertNoErr(t, err)
defer zone.Stop()
port := rand.Intn(10000) + 32768
fmt.Println("Http test on port", port)
-1
View File
@@ -10,7 +10,6 @@ import (
)
const (
DefaultLocalDomain = "weave.local." // The default name used for the local domain
DefaultServerPort = 53 // The default server port
DefaultCLICfgFile = "/etc/resolv.conf" // default "resolv.conf" file to try to load
DefaultUDPBuflen = 4096 // bigger than the default 512
+17 -4
View File
@@ -44,8 +44,15 @@ func TestUDPDNSServer(t *testing.T) {
)
testCIDR1 := testAddr1 + "/24"
InitDefaultLogging(true)
var zone = NewZoneDb(DefaultLocalDomain)
InitDefaultLogging(testing.Verbose())
Info.Println("TestUDPDNSServer starting")
zone, err := NewZoneDb(ZoneConfig{})
wt.AssertNoErr(t, err)
err = zone.Start()
wt.AssertNoErr(t, err)
defer zone.Stop()
ip, _, _ := net.ParseCIDR(testCIDR1)
zone.AddRecord(containerID, successTestName, ip)
@@ -125,9 +132,15 @@ func TestTCPDNSServer(t *testing.T) {
)
dnsAddr := fmt.Sprintf("localhost:%d", testPort)
InitDefaultLogging(true)
var zone = NewZoneDb(DefaultLocalDomain)
InitDefaultLogging(testing.Verbose())
Info.Println("TestTCPDNSServer starting")
zone, err := NewZoneDb(ZoneConfig{})
wt.AssertNoErr(t, err)
err = zone.Start()
wt.AssertNoErr(t, err)
defer zone.Stop()
// generate a list of `numAnswers` IP addresses
var addrs []ZoneRecord
bs := make([]byte, 4)
+593 -101
View File
@@ -3,41 +3,27 @@ package nameserver
import (
"bytes"
"fmt"
"github.com/benbjohnson/clock"
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
"net"
"sync"
"time"
)
const (
RDNSDomain = "in-addr.arpa."
)
const (
DefaultLocalDomain = "weave.local." // The default name used for the local domain
defaultRemoteIdent = "weave:remote" // Ident used for info obtained from mDNS
)
// +1 to also exclude a dot
var rdnsDomainLen = len(RDNSDomain) + 1
type Zone interface {
AddRecord(ident string, name string, ip net.IP) error
DeleteRecord(ident string, ip net.IP) error
DeleteRecordsFor(ident string) error
Domain() string
ZoneLookup
}
type dbRecord struct {
Ident string
Name string
IP net.IP
}
// Very simple data structure for now, with linear searching.
// TODO: make more sophisticated to improve performance.
type ZoneDb struct {
mx sync.RWMutex
recs []dbRecord
domain string
}
type LookupError string
func (ops LookupError) Error() string {
@@ -51,103 +37,609 @@ func (dup DuplicateError) Error() string {
return "Tried to add a duplicate entry"
}
func NewZoneDb(domain string) *ZoneDb {
return &ZoneDb{
domain: domain,
type Zone interface {
ZoneObservable
ZoneLookup
// The domain where we operate (eg, "weave.local.")
Domain() string
// Add a record in the local database
AddRecord(ident string, name string, ip net.IP) error
// Delete a record in the local database
DeleteRecord(ident string, ip net.IP) error
// Delete all records for an ident in the local database
DeleteRecordsFor(ident string) error
// Return a status string
Status() string
}
///////////////////////////////////////////////////////////////////
//
// Zone database:
//
// idents names entry
// *-------* *------* *------*
// zone -> | ident | --*--> | name | --*-> | IPv4 |
// *-------* | *------* | *------*
// | ident | *--> | name | *-> | IPv4 |
// *-------* *------* *------*
// ... ...
//
// The zone database keeps an ident per container, but also a special
// ident for names/IPs obtained from remote peers.
//
// Each name can have multiple IPs (so far, we only consider IPv4s), and
// each IP is stored in a `recordEntry` in the database. Entries store
// some additional information like priority and weight of the IP (for
// example, for future use with SRV records)
//
// An entry in the zone database
type recordEntry struct {
Record
observers []ZoneRecordObserver // the observers for this record
insTime time.Time // time when this record was inserted
}
func newRecordEntry(zr ZoneRecord, now time.Time) *recordEntry {
return &recordEntry{
Record: Record{
name: zr.Name(),
ip: zr.IP(),
priority: zr.Priority(),
weight: zr.Weight(),
ttl: zr.TTL(),
},
insTime: now,
observers: make([]ZoneRecordObserver, 0),
}
}
func (zone *ZoneDb) Domain() string {
return zone.domain
func (i *recordEntry) addIPObserver(zro ZoneRecordObserver) {
i.observers = append(i.observers, zro)
}
func (zone *ZoneDb) indexOf(match func(dbRecord) bool) int {
for i, r := range zone.recs {
if match(r) {
return i
// Notify all the IP observers and then remove them all
// IP address observers are notified when
// - the IP is removed
// - the name is removed
func (re *recordEntry) notifyIPObservers() {
numObservers := len(re.observers)
if numObservers > 0 {
Debug.Printf("[zonedb] Notifying %d observers of '%s'", numObservers, re.ip)
for _, observer := range re.observers {
observer()
}
re.observers = make([]ZoneRecordObserver, 0)
}
return -1
}
func (zone *ZoneDb) String() string {
zone.mx.RLock()
defer zone.mx.RUnlock()
func (re recordEntry) TTL() int {
// if we never set the TTL (eg, when using AddRecord()), return the standard value...
if re.ttl == 0 {
return int(localTTL)
}
return re.ttl
}
// Check if the info in this record is still valid according to the TTL
func (re recordEntry) hasExpired(now time.Time) bool {
return re.ttl > 0 && now.Sub(re.insTime).Seconds() > float64(re.ttl)
}
func (re recordEntry) String() string {
var buf bytes.Buffer
for _, r := range zone.recs {
fmt.Fprintf(&buf, "%.12s %s %v\n", r.Ident, r.IP, r.Name)
fmt.Fprintf(&buf, "%s", re.Record)
lobs := len(re.observers)
if lobs > 0 {
fmt.Fprintf(&buf, "/OBS:%d", lobs)
}
return buf.String()
}
func (zone *ZoneDb) LookupName(name string) ([]ZoneRecord, error) {
///////////////////////////////////////////////////////////////////
// a name, with group of records by IPv4
type name struct {
name string
ipv4 map[IPv4]*recordEntry // all the IPv4 records for this name
observers []ZoneRecordObserver // the observers for this name
lastAccessTime time.Time // last time a Lookup* used info from this name
}
func newName(n string) *name {
return &name{
name: n,
ipv4: make(map[IPv4]*recordEntry),
observers: make([]ZoneRecordObserver, 0),
}
}
func (n name) len() int { return len(n.ipv4) }
func (n name) empty() bool { return n.len() == 0 }
func (n name) hasIP(ip net.IP) bool { return n.hasIPv4(ipToIPv4(ip)) }
func (n name) hasIPv4(ip IPv4) bool { _, found := n.ipv4[ip]; return found }
// Get all the entries for this name
func (n *name) getAllEntries() (res []*recordEntry) {
for _, entry := range n.ipv4 {
res = append(res, entry)
}
return
}
// Get the entry for an IP in this name
func (n *name) getEntryForIP(ip IPv4) (res *recordEntry) {
if entry, found := n.ipv4[ip]; found {
res = entry
}
return
}
// Add a new IPv4 to this name
func (n *name) addIP(zr ZoneRecord, now time.Time) (*recordEntry, error) {
ne := newRecordEntry(zr, now)
ipv4 := ipToIPv4(zr.IP())
n.ipv4[ipv4] = ne
n.notifyNameObservers()
return ne, nil
}
// Delete an IP for this name
func (n *name) deleteIP(ip IPv4) bool {
if ipRecord, found := n.ipv4[ip]; found {
ipRecord.notifyIPObservers()
n.notifyNameObservers()
delete(n.ipv4, ip)
return true
}
return false
}
// Update the list of IPs for this name, adding new ones and removing old IPs...
func (n *name) updateIPs(records []ZoneRecord, now time.Time) (added int, removed int) {
ipsMap := make(map[IPv4]bool)
// add the new IPs
for _, record := range records {
ipv4 := ipToIPv4(record.IP())
ipsMap[ipv4] = true
if !n.hasIPv4(ipv4) {
n.addIP(record, now)
added++
}
}
// remove the old IPs
for ipv4, ipRecord := range n.ipv4 {
if _, isNew := ipsMap[ipv4]; !isNew {
ipRecord.notifyIPObservers() // we must notify the observers before removing the IP record...
delete(n.ipv4, ipv4)
removed++
}
}
// notify observers and update times
if added > 0 || removed > 0 {
n.notifyNameObservers()
}
return
}
// Add a observer for this name
func (n *name) addNameObserver(observer ZoneRecordObserver) {
n.observers = append(n.observers, observer)
}
// Notify and flush all the observers
// Name observers are notified when
// - an IP is added
// - an IP is removed
// - the name is removed
func (n *name) notifyNameObservers() {
numObservers := len(n.observers)
if numObservers > 0 {
Debug.Printf("[zonedb] Notifying %d observers of '%s'", numObservers, n.name)
for _, observer := range n.observers {
observer()
}
n.observers = make([]ZoneRecordObserver, 0)
}
}
// Convert a set of IP records to a comma-separated string
func (n name) String() string {
var buf bytes.Buffer
for _, ip := range n.ipv4 {
fmt.Fprintf(&buf, "%s, ", ip)
}
l := buf.Len()
if l > 2 {
buf.Truncate(l - 2)
}
return buf.String()
}
///////////////////////////////////////////////////////////////////
// all the names in a ident
type namesSet struct {
names map[string]*name
}
func newNamesSet() *namesSet { return &namesSet{names: make(map[string]*name)} }
func (ns namesSet) len() int { return len(ns.names) }
func (ns namesSet) empty() bool { return ns.len() == 0 }
func (ns namesSet) hasName(name string) bool {
if n, found := ns.names[name]; !found {
return false
} else {
// the name must have some valid IPs in order to be considered as "existent"
return len(n.ipv4) > 0
}
}
// Get the zone entries for a name
// If the name does not exist and `create` is true, a new name will be created
func (ns *namesSet) getName(name string, create bool) (n *name) {
if n, found := ns.names[name]; !found && create {
newName := newName(name)
ns.names[name] = newName
return newName
} else {
return n
}
}
// Get all the entries for a name
func (ns *namesSet) getEntriesForName(name string) (res []*recordEntry) {
n := ns.getName(name, false)
if n != nil {
res = n.getAllEntries()
}
return
}
// Get all the entries for an IP, for all names
func (ns *namesSet) getEntriesForIP(ip IPv4) (res []*recordEntry) {
for _, name := range ns.names {
entry := name.getEntryForIP(ip)
if entry != nil {
res = append(res, entry)
}
}
return
}
// Add a new IPv4 to a name
func (ns *namesSet) addIPToName(zr ZoneRecord, now time.Time) (*recordEntry, error) {
n := ns.getName(zr.Name(), true)
ipv4 := ipToIPv4(zr.IP())
if n.hasIPv4(ipv4) {
return n.getEntryForIP(ipv4), DuplicateError{}
}
ns.touchName(zr.Name(), now)
return n.addIP(zr, now)
}
// Delete a name in the names set
func (ns *namesSet) deleteName(n string) error {
if name, found := ns.names[n]; found {
for _, ipRecord := range name.getAllEntries() {
ipRecord.notifyIPObservers()
}
name.notifyNameObservers()
delete(ns.names, n)
return nil
} else {
return LookupError(fmt.Sprintf("%+v", name))
}
}
// Delete an IPv4 from all names
func (ns *namesSet) deleteIP(ip IPv4) error {
cnt := 0
for nkey, n := range ns.names {
if n.deleteIP(ip) {
cnt++
}
if n.empty() {
delete(ns.names, nkey)
}
}
// we must return an error if no records have been found and deleted
if cnt == 0 {
return LookupError(fmt.Sprintf("%+v", ip))
}
return nil
}
// Get the name query time
func (ns namesSet) getNameLastAccess(n string) time.Time {
if name, found := ns.names[n]; found {
return name.lastAccessTime
}
return time.Time{}
}
// Touch the last access time for a name
// The access time is saved only for locally-introduced records (otherwise it could
// be lost when names are irrelevant...)
func (ns *namesSet) touchName(name string, now time.Time) {
Debug.Printf("[zonedb] Touching name %s", name)
n := ns.getName(name, false)
if n != nil {
n.lastAccessTime = now
}
}
func (ns namesSet) String() string {
var buf bytes.Buffer
for _, name := range ns.names {
fmt.Fprintf(&buf, "%s, ", name)
}
l := buf.Len()
if l > 2 {
buf.Truncate(l - 2)
}
return buf.String()
}
///////////////////////////////////////////////////////////////////
// Names sets, by ident name
type identRecordsSet map[string]*namesSet
func (irs identRecordsSet) String() string {
var buf bytes.Buffer
for ident, nameset := range irs {
fmt.Fprintf(&buf, "%.12s: %s\n", ident, nameset)
}
return buf.String()
}
///////////////////////////////////////////////////////////////////
type ZoneConfig struct {
// The local domain
Domain string
// The interface where mDNS operates
Iface *net.Interface
// Force a specific mDNS client
MDNSClient ZoneMDNSClient
// Force a specific mDNS server
MDNSServer ZoneMDNSServer
// For a specific clock provider
Clock clock.Clock
}
// Zone database
// The zone database contains the locally known information about the zone.
// The zone database uses some other mechanisms (eg, mDNS) for finding unknown
// information or for keeping current information up to date.
type zoneDb struct {
mx sync.RWMutex
idents identRecordsSet
mdnsCli ZoneMDNSClient
mdnsSrv ZoneMDNSServer
iface *net.Interface
domain string // the local domain
clock clock.Clock
}
// Create a new zone database
func NewZoneDb(config ZoneConfig) (zone *zoneDb, err error) {
zone = &zoneDb{
domain: config.Domain,
idents: make(identRecordsSet),
mdnsCli: config.MDNSClient,
mdnsSrv: config.MDNSServer,
iface: config.Iface,
clock: config.Clock,
}
// fix the default configuration parameters
if zone.clock == nil {
zone.clock = clock.New()
}
if len(zone.domain) == 0 {
zone.domain = DefaultLocalDomain
}
// Create the mDNS client and server
if zone.mdnsCli == nil {
if zone.mdnsCli, err = NewMDNSClient(); err != nil {
return
}
}
if zone.mdnsSrv == nil {
if zone.mdnsSrv, err = NewMDNSServer(zone); err != nil {
return
}
}
return
}
// Start the zone database
func (zone *zoneDb) Start() (err error) {
if zone.iface != nil {
Info.Printf("[zonedb] Using mDNS on %s", zone.iface)
} else {
Info.Printf("[zonedb] Using mDNS on all interfaces")
}
if err = zone.mdnsCli.Start(zone.iface); err != nil {
return
}
if err = zone.mdnsSrv.Start(zone.iface); err != nil {
return
}
return
}
// Perform a graceful shutdown of the zone database
func (zone *zoneDb) Stop() error {
Debug.Printf("[zonedb] Exiting mDNS client and server...")
zone.mdnsCli.Stop()
zone.mdnsSrv.Stop()
return nil
}
// Obtain the domain where this database keeps information for
func (zone *zoneDb) Domain() string {
return zone.domain
}
// Return the status string
func (zone *zoneDb) Status() string {
var buf bytes.Buffer
fmt.Fprintln(&buf, "Local domain", zone.domain)
fmt.Fprintln(&buf, "Interface", zone.iface)
fmt.Fprintf(&buf, "Zone database:\n%s", zone)
return buf.String()
}
// Add a record with `name` pointing to `ip`
func (zone *zoneDb) AddRecord(ident string, name string, ip net.IP) (err error) {
zone.mx.Lock()
defer zone.mx.Unlock()
Debug.Printf("[zonedb] Adding record: '%s'/'%s'[%s]", ident, name, ip)
record := Record{dns.Fqdn(name), ip, 0, 0, 0}
_, err = zone.getNamesSet(ident).addIPToName(record, zone.clock.Now())
return
}
// Delete all records for an ident
func (zone *zoneDb) DeleteRecordsFor(ident string) (err error) {
zone.mx.Lock()
defer zone.mx.Unlock()
Debug.Printf("[zonedb] Deleting records for ident '%s'", ident)
zone.deleteIdent(ident)
return
}
// Delete an IP in a ident
func (zone *zoneDb) DeleteRecord(ident string, ip net.IP) (err error) {
zone.mx.Lock()
defer zone.mx.Unlock()
Debug.Printf("[zonedb] Deleting record '%s'/[%s]", ident, ip)
if _, found := zone.idents[ident]; found {
err = zone.idents[ident].deleteIP(ipToIPv4(ip))
if zone.idents[ident].empty() {
zone.deleteIdent(ident)
}
} else {
err = LookupError(ident)
}
return
}
// Observe a name.
// The name must have at least one valid IP address.
// Name observers are notified when
// - an IP is added
// - an IP is removed
// - the name is removed
// The observer will be invoked on any change/removal that affects this name.
// Each observer will be invoked at least once (but possibly more). After that, they will be removed.
// The observer should not try to lock the ZoneDB (you will get a deadlock)
func (zone *zoneDb) ObserveName(name string, observer ZoneRecordObserver) (err error) {
zone.mx.Lock()
defer zone.mx.Unlock()
cnt := 0
name = dns.Fqdn(name)
for _, nameset := range zone.idents {
if n := nameset.getName(name, false); n != nil {
n.addNameObserver(observer)
cnt += 1
}
}
if cnt == 0 {
err = LookupError(fmt.Sprintf("%s", name))
}
return
}
// Observe a IP address.
// The IP address must be exit in the database.
// IP address observers are notified when
// - the IP is removed
// - the name is removed
// The observer will be invoked on any change/removal that affects this IP.
// Each observer will be invoked at least once (but possibly more). After that, they will be removed.
// The observer should not try to lock the ZoneDB (you will get a deadlock)
func (zone *zoneDb) ObserveInaddr(inaddr string, observer ZoneRecordObserver) (err error) {
zone.mx.Lock()
defer zone.mx.Unlock()
cnt := 0
revIP, err := raddrToIPv4(inaddr)
if err != nil {
return newParseError("lookup address", inaddr)
}
for _, nameset := range zone.idents {
for _, entry := range nameset.getEntriesForIP(revIP) {
entry.addIPObserver(observer)
cnt += 1
}
}
if cnt == 0 {
err = LookupError(fmt.Sprintf("%+v/%+v", inaddr, revIP))
}
return
}
// Get the string representation of a zone
func (zone zoneDb) String() string {
zone.mx.RLock()
defer zone.mx.RUnlock()
for _, r := range zone.recs {
if r.Name == name {
return []ZoneRecord{Record{r.Name, r.IP, 0, 0, 0}}, nil
}
}
return nil, LookupError(name)
return zone.idents.String()
}
func (zone *ZoneDb) LookupInaddr(inaddr string) ([]ZoneRecord, error) {
if revIP := net.ParseIP(inaddr[:len(inaddr)-rdnsDomainLen]); revIP != nil {
revIP4 := revIP.To4()
ip := []byte{revIP4[3], revIP4[2], revIP4[1], revIP4[0]}
Debug.Printf("[zonedb] Looking for address: %+v", ip)
zone.mx.RLock()
defer zone.mx.RUnlock()
for _, r := range zone.recs {
if r.IP.Equal(ip) {
return []ZoneRecord{Record{r.Name, r.IP, 0, 0, 0}}, nil
}
}
return nil, LookupError(inaddr)
}
Warning.Printf("[zonedb] Asked to reverse lookup %s", inaddr)
return nil, LookupError(inaddr)
}
func (zone *ZoneDb) AddRecord(ident string, name string, ip net.IP) error {
zone.mx.Lock()
defer zone.mx.Unlock()
fqdn := dns.Fqdn(name)
pred := func(r dbRecord) bool { return r.Name == fqdn && r.IP.Equal(ip) && r.Ident == ident }
if index := zone.indexOf(pred); index != -1 {
return DuplicateError{}
}
zone.recs = append(zone.recs, dbRecord{ident, fqdn, ip})
return nil
}
func (zone *ZoneDb) DeleteRecord(ident string, ip net.IP) error {
zone.mx.Lock()
defer zone.mx.Unlock()
pred := func(r dbRecord) bool { return r.Ident == ident && r.IP.Equal(ip) }
if index := zone.indexOf(pred); index != -1 {
zone.recs = append(zone.recs[:index], zone.recs[index+1:]...)
return nil
}
return LookupError(ident)
}
func (zone *ZoneDb) DeleteRecordsFor(ident string) error {
zone.mx.Lock()
defer zone.mx.Unlock()
w := 0 // write index
for _, r := range zone.recs {
if r.Ident != ident {
zone.recs[w] = r
w++
}
}
zone.recs = zone.recs[:w]
return nil
}
func (zone *ZoneDb) ContainerDied(ident string) error {
// Notify that a container has died
func (zone *zoneDb) ContainerDied(ident string) error {
Info.Printf("[zonedb] Container %s down. Removing records", ident)
return zone.DeleteRecordsFor(ident)
}
// Return true if the we have obtained some information for a name from remote peers
// We can have both local (eg, introduced through the HTTP API) and remote (eg, mDNS)
// information for a name. This method only checks if there is remote information...
func (zone *zoneDb) HasNameRemoteInfo(n string) bool {
zone.mx.Lock()
defer zone.mx.Unlock()
remoteNamesSet := zone.getNamesSet(defaultRemoteIdent)
if remoteNamesSet != nil {
return remoteNamesSet.hasName(n)
}
return false
}
// Get or create a names set
func (zone *zoneDb) getNamesSet(ident string) *namesSet {
ns, found := zone.idents[ident]
if !found {
ns = newNamesSet()
zone.idents[ident] = ns
}
return ns
}
// Delete a ident
func (zone *zoneDb) deleteIdent(ident string) {
Info.Printf("[zonedb] Removing everything for ident '%s'", ident)
if ns, found := zone.idents[ident]; found {
for _, n := range ns.names {
for _, entry := range n.getAllEntries() {
entry.notifyIPObservers()
}
n.notifyNameObservers()
}
delete(zone.idents, ident)
}
}
+55
View File
@@ -0,0 +1,55 @@
package nameserver
import (
"github.com/miekg/dns"
. "github.com/weaveworks/weave/common"
)
// Perform a lookup for a name in the zone
// The name can be resolved locally with the local database
func (zone *zoneDb) LookupName(name string) (res []ZoneRecord, err error) {
zone.mx.RLock()
defer zone.mx.RUnlock()
// note: LookupName() is usually called from the mDNS server, so we do not touch the name
name = dns.Fqdn(name)
Debug.Printf("[zonedb] Looking for name '%s' in local database", name)
for identName, nameset := range zone.idents {
if identName != defaultRemoteIdent {
for _, ze := range nameset.getEntriesForName(name) {
res = append(res, ze)
}
}
}
if len(res) == 0 {
err = LookupError(name)
}
return
}
// Perform a lookup for a IP address in the zone
// The address can be resolved locally with the local database
func (zone *zoneDb) LookupInaddr(inaddr string) (res []ZoneRecord, err error) {
zone.mx.RLock()
defer zone.mx.RUnlock()
// note: LookupInaddr() is usually called from the mDNS server, so we do not touch the name
revIPv4, err := raddrToIPv4(inaddr)
if err != nil {
return nil, newParseError("lookup address", inaddr)
}
Debug.Printf("[zonedb] Looking for address in local database: '%s' (%s)", revIPv4, inaddr)
for identName, nameset := range zone.idents {
if identName != defaultRemoteIdent {
for _, ze := range nameset.getEntriesForIP(revIPv4) {
res = append(res, ZoneRecord(ze))
}
}
}
if len(res) == 0 {
err = LookupError(inaddr)
}
return
}
+99 -31
View File
@@ -1,6 +1,8 @@
package nameserver
import (
"fmt"
. "github.com/weaveworks/weave/common"
wt "github.com/weaveworks/weave/testing"
"net"
"testing"
@@ -8,60 +10,118 @@ import (
func TestZone(t *testing.T) {
var (
containerID = "deadbeef"
otherContainerID = "cowjuice"
successTestName = "test1.weave."
testAddr1 = "10.2.2.1/24"
container1 = "deadbeef"
container2 = "cowjuice"
name1 = "test1.weave."
name1Addr1 = "10.9.2.1/24"
name1Addr2 = "10.9.2.2/24"
revName1Addr1 = "1.2.9.10.in-addr.arpa."
)
var zone = NewZoneDb(DefaultLocalDomain)
InitDefaultLogging(testing.Verbose())
ip, _, _ := net.ParseCIDR(testAddr1)
err := zone.AddRecord(containerID, successTestName, ip)
zone, err := NewZoneDb(ZoneConfig{})
wt.AssertNoErr(t, err)
err = zone.Start()
wt.AssertNoErr(t, err)
defer zone.Stop()
ip1, _, _ := net.ParseCIDR(name1Addr1)
t.Logf("Adding '%s'/%s to '%s'", name1, ip1, container1)
err = zone.AddRecord(container1, name1, ip1)
wt.AssertNoErr(t, err)
// Add a few more records to make the job harder
err = zone.AddRecord("abcdef0123", "adummy.weave.", net.ParseIP("10.2.0.1"))
t.Logf("Adding 'adummy.weave.' to 'abcdef0123'")
err = zone.AddRecord("abcdef0123", "adummy.weave.", net.ParseIP("10.9.0.1"))
wt.AssertNoErr(t, err)
err = zone.AddRecord("0123abcdef", "zdummy.weave.", net.ParseIP("10.2.0.2"))
t.Logf("Adding 'zdummy.weave.' to '0123abcdef'")
err = zone.AddRecord("0123abcdef", "zdummy.weave.", net.ParseIP("10.9.0.2"))
wt.AssertNoErr(t, err)
t.Logf("Zone database:\n%s", zone)
t.Logf("Checking if we can find the name '%s'", name1)
foundIPs, err := zone.LookupName(name1)
wt.AssertNoErr(t, err)
// Check that the address is now there.
foundIP, err := zone.LookupName(successTestName)
wt.AssertNoErr(t, err)
if !foundIP[0].IP().Equal(ip) {
t.Fatal("Unexpected result for", successTestName, foundIP)
if !foundIPs[0].IP().Equal(ip1) {
t.Fatal("Unexpected result for", name1, foundIPs)
}
// See if we can find the address by IP.
foundName, err := zone.LookupInaddr("1.2.2.10.in-addr.arpa.")
t.Logf("Checking if we cannot find some silly name like 'something.wrong'")
foundIPs, err = zone.LookupName("something.wrong")
wt.AssertErrorType(t, err, (*LookupError)(nil), fmt.Sprintf("unknown name: %+v", foundIPs))
ip2, _, _ := net.ParseCIDR(name1Addr2)
t.Logf("Adding a second IP for '%s'/%s to '%s'", name1, ip2, container1)
err = zone.AddRecord(container1, name1, ip2)
wt.AssertNoErr(t, err)
if foundName[0].Name() != successTestName {
t.Fatal("Unexpected result for", ip, foundName)
t.Logf("Checking if we can find both the old IP and the new IP for '%s'", name1)
foundIPs, err = zone.LookupName(name1)
wt.AssertNoErr(t, err)
if !(foundIPs[0].IP().Equal(ip1) || foundIPs[1].IP().Equal(ip1)) {
t.Fatal("Unexpected result for", name1, foundIPs)
}
if !(foundIPs[0].IP().Equal(ip2) || foundIPs[1].IP().Equal(ip2)) {
t.Fatal("Unexpected result for", name1, foundIPs)
}
err = zone.AddRecord(containerID, successTestName, ip)
t.Logf("Checking if we can find the address by IP '1.2.9.10.in-addr.arpa.'")
foundNames, err := zone.LookupInaddr("1.2.9.10.in-addr.arpa.")
wt.AssertNoErr(t, err)
if foundNames[0].Name() != name1 {
t.Fatal("Unexpected result for", ip1, foundNames)
}
t.Logf("Checking we can not find an unknown address '30.20.10.1.in-addr.arpa.'")
foundNames, err = zone.LookupInaddr("30.20.10.1.in-addr.arpa.")
wt.AssertErrorType(t, err, (*LookupError)(nil), fmt.Sprintf("unknown IP: %+v", foundNames))
t.Logf("Checking if adding again '%s'/%s in %s results in an error", name1, ip1, container1)
err = zone.AddRecord(container1, name1, ip1)
wt.AssertErrorType(t, err, (*DuplicateError)(nil), "duplicate add")
err = zone.AddRecord(otherContainerID, successTestName, ip)
// Delete the record for the original container
err = zone.DeleteRecord(containerID, ip)
t.Logf("Adding '%s'/%s in %s too", name1, ip1, container2)
err = zone.AddRecord(container2, name1, ip1)
wt.AssertNoErr(t, err)
_, err = zone.LookupName(successTestName)
name1Removed := 0
err = zone.ObserveName(name1, func() { t.Logf("Observer #1 for '%s' notified.", name1); name1Removed++ })
wt.AssertNoErr(t, err)
err = zone.ObserveName(name1, func() { t.Logf("Observer #2 for '%s' notified.", name1); name1Removed++ })
wt.AssertNoErr(t, err)
err = zone.ObserveInaddr(revName1Addr1, func() { t.Logf("Observer #1 for '%s' notified.", revName1Addr1); name1Removed++ })
wt.AssertNoErr(t, err)
err = zone.DeleteRecord(otherContainerID, ip)
t.Logf("Zone database:\n%s", zone)
t.Logf("Deleting the %s in %s", ip1, container1)
err = zone.DeleteRecord(container1, ip1)
wt.AssertNoErr(t, err)
t.Logf("Zone database:\n%s", zone)
t.Logf("Checking %s's observers have been notified on removal", name1)
if name1Removed < 3 {
t.Logf("Zone database:\n%s", zone)
t.Fatalf("Unexpected number (%d) of calls to observers", name1Removed)
}
t.Logf("Checking %s can be found", name1)
_, err = zone.LookupName(name1)
wt.AssertNoErr(t, err)
// Check that the address is not there now.
_, err = zone.LookupName(successTestName)
t.Logf("Checking %s is not found after removing %s it in %s and %s in %s",
name1, ip1, container2, ip2, container1)
err = zone.DeleteRecord(container1, ip2)
err = zone.DeleteRecord(container2, ip1)
wt.AssertNoErr(t, err)
t.Logf("Zone database:\n%s", zone)
_, err = zone.LookupName(name1)
wt.AssertErrorType(t, err, (*LookupError)(nil), "after deleting record")
// Delete a record that isn't there
err = zone.DeleteRecord(containerID, net.ParseIP("0.0.0.0"))
t.Logf("Checking if removing an unknown record results in an error")
err = zone.DeleteRecord(container1, net.ParseIP("0.0.0.0"))
wt.AssertErrorType(t, err, (*LookupError)(nil), "when deleting record that doesn't exist")
}
@@ -72,14 +132,22 @@ func TestDeleteFor(t *testing.T) {
addr1 = "10.2.2.3/24"
addr2 = "10.2.7.8/24"
)
zone := NewZoneDb(DefaultLocalDomain)
InitDefaultLogging(testing.Verbose())
zone, err := NewZoneDb(ZoneConfig{})
wt.AssertNoErr(t, err)
err = zone.Start()
wt.AssertNoErr(t, err)
defer zone.Stop()
for _, addr := range []string{addr1, addr2} {
ip, _, _ := net.ParseCIDR(addr)
err := zone.AddRecord(id, name, ip)
wt.AssertNoErr(t, err)
}
_, err := zone.LookupName(name)
_, err = zone.LookupName(name)
wt.AssertNoErr(t, err)
err = zone.DeleteRecordsFor(id)