diff --git a/api/v1alpha1/ipaddress_types.go b/api/v1alpha1/ipaddress_types.go index ffb1a211..a028b400 100644 --- a/api/v1alpha1/ipaddress_types.go +++ b/api/v1alpha1/ipaddress_types.go @@ -45,6 +45,9 @@ type IPAddressSpec struct { // Address contains the IP address Address IPAddressStr `json:"address"` + + // DNSServers is the list of dns servers + DNSServers []IPAddressStr `json:"dnsServers,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/api/v1alpha1/ippool_types.go b/api/v1alpha1/ippool_types.go index 847f7e07..99e212b8 100644 --- a/api/v1alpha1/ippool_types.go +++ b/api/v1alpha1/ippool_types.go @@ -48,6 +48,9 @@ type Pool struct { // Gateway is the gateway ip address Gateway *IPAddressStr `json:"gateway,omitempty"` + + // DNSServers is the list of dns servers + DNSServers []IPAddressStr `json:"dnsServers,omitempty"` } // IPPoolSpec defines the desired state of IPPool. @@ -69,6 +72,9 @@ type IPPoolSpec struct { // Gateway is the gateway ip address Gateway *IPAddressStr `json:"gateway,omitempty"` + // DNSServers is the list of dns servers + DNSServers []IPAddressStr `json:"dnsServers,omitempty"` + // +kubebuilder:validation:MinLength=1 // namePrefix is the prefix used to generate the IPAddress object names NamePrefix string `json:"namePrefix"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index cdfae2eb..812b44d7 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -93,6 +93,11 @@ func (in *IPAddressSpec) DeepCopyInto(out *IPAddressSpec) { *out = new(IPAddressStr) **out = **in } + if in.DNSServers != nil { + in, out := &in.DNSServers, &out.DNSServers + *out = make([]IPAddressStr, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPAddressSpec. @@ -291,6 +296,11 @@ func (in *IPPoolSpec) DeepCopyInto(out *IPPoolSpec) { *out = new(IPAddressStr) **out = **in } + if in.DNSServers != nil { + in, out := &in.DNSServers, &out.DNSServers + *out = make([]IPAddressStr, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IPPoolSpec. @@ -352,6 +362,11 @@ func (in *Pool) DeepCopyInto(out *Pool) { *out = new(IPAddressStr) **out = **in } + if in.DNSServers != nil { + in, out := &in.DNSServers, &out.DNSServers + *out = make([]IPAddressStr, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Pool. diff --git a/config/crd/bases/ipam.metal3.io_ipaddresses.yaml b/config/crd/bases/ipam.metal3.io_ipaddresses.yaml index 2e08ef3b..be5de560 100644 --- a/config/crd/bases/ipam.metal3.io_ipaddresses.yaml +++ b/config/crd/bases/ipam.metal3.io_ipaddresses.yaml @@ -82,6 +82,13 @@ spec: description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids' type: string type: object + dnsServers: + description: DNSServers is the list of dns servers + items: + description: IPAddress is used for validation of an IP address + pattern: ((^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))$)|(^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$)) + type: string + type: array gateway: description: Gateway is the gateway ip address pattern: ((^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))$)|(^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$)) diff --git a/config/crd/bases/ipam.metal3.io_ippools.yaml b/config/crd/bases/ipam.metal3.io_ippools.yaml index f8998c1f..a3f5d378 100644 --- a/config/crd/bases/ipam.metal3.io_ippools.yaml +++ b/config/crd/bases/ipam.metal3.io_ippools.yaml @@ -50,6 +50,13 @@ spec: description: ClusterName is the name of the Cluster this object belongs to. type: string + dnsServers: + description: DNSServers is the list of dns servers + items: + description: IPAddress is used for validation of an IP address + pattern: ((^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))$)|(^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$)) + type: string + type: array gateway: description: Gateway is the gateway ip address pattern: ((^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))$)|(^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$)) @@ -65,6 +72,13 @@ spec: description: MetaDataIPAddress contains the info to render th ip address. It is IP-version agnostic properties: + dnsServers: + description: DNSServers is the list of dns servers + items: + description: IPAddress is used for validation of an IP address + pattern: ((^((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))$)|(^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$)) + type: string + type: array end: description: End is the last IP address that can be rendered. It is used as a validation that the rendered IP is in bound. diff --git a/ipam/ippool_manager.go b/ipam/ippool_manager.go index f5971a36..86cb91e6 100644 --- a/ipam/ippool_manager.go +++ b/ipam/ippool_manager.go @@ -230,7 +230,7 @@ func (m *IPPoolManager) updateAddress(ctx context.Context, func (m *IPPoolManager) allocateAddress(addressClaim *ipamv1.IPClaim, addresses map[ipamv1.IPAddressStr]string, -) (ipamv1.IPAddressStr, int, *ipamv1.IPAddressStr, error) { +) (ipamv1.IPAddressStr, int, *ipamv1.IPAddressStr, []ipamv1.IPAddressStr, error) { var err error // Get pre-allocated addresses @@ -238,17 +238,12 @@ func (m *IPPoolManager) allocateAddress(addressClaim *ipamv1.IPClaim, // If the IP is pre-allocated, the default prefix and gateway are used prefix := m.IPPool.Spec.Prefix gateway := m.IPPool.Spec.Gateway + dnsServers := m.IPPool.Spec.DNSServers for _, pool := range m.IPPool.Spec.Pools { if ipAllocated { break } - if pool.Prefix != 0 { - prefix = pool.Prefix - } - if pool.Gateway != nil { - gateway = pool.Gateway - } index := 0 for !ipAllocated { allocatedAddress, err = getIPAddress(pool, index) @@ -258,14 +253,23 @@ func (m *IPPoolManager) allocateAddress(addressClaim *ipamv1.IPClaim, index++ if _, ok := addresses[allocatedAddress]; !ok && allocatedAddress != "" { ipAllocated = true + if pool.Prefix != 0 { + prefix = pool.Prefix + } + if pool.Gateway != nil { + gateway = pool.Gateway + } + if len(pool.DNSServers) != 0 { + dnsServers = pool.DNSServers + } } } } if !ipAllocated { addressClaim.Status.ErrorMessage = pointer.StringPtr("Exhausted IP Pools") - return "", 0, nil, errors.New("Exhausted IP Pools") + return "", 0, nil, []ipamv1.IPAddressStr{}, errors.New("Exhausted IP Pools") } - return allocatedAddress, prefix, gateway, nil + return allocatedAddress, prefix, gateway, dnsServers, nil } func (m *IPPoolManager) createAddress(ctx context.Context, @@ -288,7 +292,7 @@ func (m *IPPoolManager) createAddress(ctx context.Context, // Get a new index for this machine m.Log.Info("Getting address", "Claim", addressClaim.Name) // Get a new IP for this owner - allocatedAddress, prefix, gateway, err := m.allocateAddress(addressClaim, addresses) + allocatedAddress, prefix, gateway, dnsServers, err := m.allocateAddress(addressClaim, addresses) if err != nil { return addresses, err } @@ -337,8 +341,9 @@ func (m *IPPoolManager) createAddress(ctx context.Context, Name: addressClaim.Name, Namespace: m.IPPool.Namespace, }, - Prefix: prefix, - Gateway: gateway, + Prefix: prefix, + Gateway: gateway, + DNSServers: dnsServers, }, } diff --git a/ipam/ippool_manager_test.go b/ipam/ippool_manager_test.go index 42eff670..d775f1da 100644 --- a/ipam/ippool_manager_test.go +++ b/ipam/ippool_manager_test.go @@ -679,13 +679,14 @@ var _ = Describe("IPPool manager", func() { ) type testCaseAllocateAddress struct { - ipPool *ipamv1.IPPool - ipClaim *ipamv1.IPClaim - addresses map[ipamv1.IPAddressStr]string - expectedAddress ipamv1.IPAddressStr - expectedPrefix int - expectedGateway *ipamv1.IPAddressStr - expectError bool + ipPool *ipamv1.IPPool + ipClaim *ipamv1.IPClaim + addresses map[ipamv1.IPAddressStr]string + expectedAddress ipamv1.IPAddressStr + expectedPrefix int + expectedGateway *ipamv1.IPAddressStr + expectedDNSServers []ipamv1.IPAddressStr + expectError bool } DescribeTable("Test AllocateAddress", @@ -694,7 +695,7 @@ var _ = Describe("IPPool manager", func() { klogr.New(), ) Expect(err).NotTo(HaveOccurred()) - allocatedAddress, prefix, gateway, err := ipPoolMgr.allocateAddress( + allocatedAddress, prefix, gateway, dnsServers, err := ipPoolMgr.allocateAddress( tc.ipClaim, tc.addresses, ) if tc.expectError { @@ -706,6 +707,7 @@ var _ = Describe("IPPool manager", func() { Expect(allocatedAddress).To(Equal(tc.expectedAddress)) Expect(prefix).To(Equal(tc.expectedPrefix)) Expect(*gateway).To(Equal(*tc.expectedGateway)) + Expect(dnsServers).To(Equal(tc.expectedDNSServers)) }, Entry("Empty pools", testCaseAllocateAddress{ ipPool: &ipamv1.IPPool{ @@ -727,6 +729,9 @@ var _ = Describe("IPPool manager", func() { End: (*ipamv1.IPAddressStr)(pointer.StringPtr("192.168.0.20")), Prefix: 26, Gateway: (*ipamv1.IPAddressStr)(pointer.StringPtr("192.168.1.1")), + DNSServers: []ipamv1.IPAddressStr{ + ipamv1.IPAddressStr("8.8.8.8"), + }, }, }, PreAllocations: map[string]ipamv1.IPAddressStr{ @@ -734,6 +739,9 @@ var _ = Describe("IPPool manager", func() { }, Prefix: 24, Gateway: (*ipamv1.IPAddressStr)(pointer.StringPtr("192.168.0.1")), + DNSServers: []ipamv1.IPAddressStr{ + ipamv1.IPAddressStr("8.8.4.4"), + }, }, }, ipClaim: &ipamv1.IPClaim{ @@ -743,7 +751,10 @@ var _ = Describe("IPPool manager", func() { }, expectedAddress: ipamv1.IPAddressStr("192.168.0.15"), expectedGateway: (*ipamv1.IPAddressStr)(pointer.StringPtr("192.168.0.1")), - expectedPrefix: 24, + expectedDNSServers: []ipamv1.IPAddressStr{ + ipamv1.IPAddressStr("8.8.4.4"), + }, + expectedPrefix: 24, }), Entry("One pool, with start and existing address", testCaseAllocateAddress{ ipPool: &ipamv1.IPPool{ @@ -780,10 +791,16 @@ var _ = Describe("IPPool manager", func() { End: (*ipamv1.IPAddressStr)(pointer.StringPtr("192.168.0.20")), Prefix: 24, Gateway: (*ipamv1.IPAddressStr)(pointer.StringPtr("192.168.0.1")), + DNSServers: []ipamv1.IPAddressStr{ + ipamv1.IPAddressStr("8.8.8.8"), + }, }, }, Prefix: 26, Gateway: (*ipamv1.IPAddressStr)(pointer.StringPtr("192.168.1.1")), + DNSServers: []ipamv1.IPAddressStr{ + ipamv1.IPAddressStr("8.8.4.4"), + }, }, }, ipClaim: &ipamv1.IPClaim{ @@ -797,7 +814,50 @@ var _ = Describe("IPPool manager", func() { }, expectedAddress: ipamv1.IPAddressStr("192.168.0.13"), expectedGateway: (*ipamv1.IPAddressStr)(pointer.StringPtr("192.168.0.1")), - expectedPrefix: 24, + expectedDNSServers: []ipamv1.IPAddressStr{ + ipamv1.IPAddressStr("8.8.8.8"), + }, + expectedPrefix: 24, + }), + Entry("two pools, with subnet and override prefix in first", testCaseAllocateAddress{ + ipPool: &ipamv1.IPPool{ + Spec: ipamv1.IPPoolSpec{ + Pools: []ipamv1.Pool{ + ipamv1.Pool{ + Start: (*ipamv1.IPAddressStr)(pointer.StringPtr("192.168.0.10")), + End: (*ipamv1.IPAddressStr)(pointer.StringPtr("192.168.0.10")), + Prefix: 24, + Gateway: (*ipamv1.IPAddressStr)(pointer.StringPtr("192.168.1.1")), + DNSServers: []ipamv1.IPAddressStr{ + ipamv1.IPAddressStr("8.8.8.8"), + }, + }, + ipamv1.Pool{ + Subnet: (*ipamv1.IPSubnetStr)(pointer.StringPtr("192.168.1.10/24")), + }, + }, + Prefix: 26, + Gateway: (*ipamv1.IPAddressStr)(pointer.StringPtr("192.168.2.1")), + DNSServers: []ipamv1.IPAddressStr{ + ipamv1.IPAddressStr("8.8.4.4"), + }, + }, + }, + ipClaim: &ipamv1.IPClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: "TestRef", + }, + }, + addresses: map[ipamv1.IPAddressStr]string{ + ipamv1.IPAddressStr("192.168.1.11"): "bcde", + ipamv1.IPAddressStr("192.168.0.10"): "abcd", + }, + expectedAddress: ipamv1.IPAddressStr("192.168.1.12"), + expectedGateway: (*ipamv1.IPAddressStr)(pointer.StringPtr("192.168.2.1")), + expectedDNSServers: []ipamv1.IPAddressStr{ + ipamv1.IPAddressStr("8.8.4.4"), + }, + expectedPrefix: 26, }), Entry("two pools, with subnet and override prefix", testCaseAllocateAddress{ ipPool: &ipamv1.IPPool{ @@ -811,10 +871,16 @@ var _ = Describe("IPPool manager", func() { Subnet: (*ipamv1.IPSubnetStr)(pointer.StringPtr("192.168.1.10/24")), Prefix: 24, Gateway: (*ipamv1.IPAddressStr)(pointer.StringPtr("192.168.1.1")), + DNSServers: []ipamv1.IPAddressStr{ + ipamv1.IPAddressStr("8.8.8.8"), + }, }, }, Prefix: 26, Gateway: (*ipamv1.IPAddressStr)(pointer.StringPtr("192.168.2.1")), + DNSServers: []ipamv1.IPAddressStr{ + ipamv1.IPAddressStr("8.8.4.4"), + }, }, }, ipClaim: &ipamv1.IPClaim{ @@ -828,7 +894,10 @@ var _ = Describe("IPPool manager", func() { }, expectedAddress: ipamv1.IPAddressStr("192.168.1.12"), expectedGateway: (*ipamv1.IPAddressStr)(pointer.StringPtr("192.168.1.1")), - expectedPrefix: 24, + expectedDNSServers: []ipamv1.IPAddressStr{ + ipamv1.IPAddressStr("8.8.8.8"), + }, + expectedPrefix: 24, }), Entry("Exhausted pools start", testCaseAllocateAddress{ ipPool: &ipamv1.IPPool{