Skip to content

Commit c62d6a9

Browse files
Merge pull request #227 from omersch381/add_predict_ips_mdns
Implement predictable IP generation for miniDNS
2 parents 76a2376 + 8cd5bad commit c62d6a9

File tree

13 files changed

+392
-9
lines changed

13 files changed

+392
-9
lines changed

api/bases/designate.openstack.org_designates.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,11 @@ spec:
940940
required:
941941
- containerImage
942942
type: object
943+
designateNetworkAttachment:
944+
default: designate
945+
description: DesignateNetworkAttachment is a NetworkAttachment resource
946+
name for the Designate Control Network
947+
type: string
943948
designateProducer:
944949
description: DesignateProducer - Spec definition for the Producer
945950
service of this Designate deployment
@@ -1473,6 +1478,7 @@ spec:
14731478
- designateBackendbind9
14741479
- designateCentral
14751480
- designateMdns
1481+
- designateNetworkAttachment
14761482
- designateProducer
14771483
- designateWorker
14781484
- rabbitMqClusterName

api/v1beta1/designate_types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@ type DesignateSpecBase struct {
176176
// Resources - Compute Resources required by this service (Limits/Requests).
177177
// https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
178178
Resources corev1.ResourceRequirements `json:"resources,omitempty"`
179+
180+
// +kubebuilder:validation:Required
181+
// +kubebuilder:default=designate
182+
// DesignateNetworkAttachment is a NetworkAttachment resource name for the Designate Control Network
183+
DesignateNetworkAttachment string `json:"designateNetworkAttachment"`
179184
}
180185

181186
// DesignateStatus defines the observed state of Designate

config/crd/bases/designate.openstack.org_designates.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,6 +940,11 @@ spec:
940940
required:
941941
- containerImage
942942
type: object
943+
designateNetworkAttachment:
944+
default: designate
945+
description: DesignateNetworkAttachment is a NetworkAttachment resource
946+
name for the Designate Control Network
947+
type: string
943948
designateProducer:
944949
description: DesignateProducer - Spec definition for the Producer
945950
service of this Designate deployment
@@ -1473,6 +1478,7 @@ spec:
14731478
- designateBackendbind9
14741479
- designateCentral
14751480
- designateMdns
1481+
- designateNetworkAttachment
14761482
- designateProducer
14771483
- designateWorker
14781484
- rabbitMqClusterName

config/rbac/role.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ metadata:
55
creationTimestamp: null
66
name: manager-role
77
rules:
8+
- apiGroups:
9+
- ""
10+
resources:
11+
- nodes
12+
verbs:
13+
- get
14+
- list
815
- apiGroups:
916
- ""
1017
resources:

controllers/designate_controller.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ type DesignateReconciler struct {
115115
// +kubebuilder:rbac:groups=keystone.openstack.org,resources=keystoneapis,verbs=get;list;watch
116116
// +kubebuilder:rbac:groups=rabbitmq.openstack.org,resources=transporturls,verbs=get;list;watch;create;update;patch;delete
117117
// +kubebuilder:rbac:groups=k8s.cni.cncf.io,resources=network-attachment-definitions,verbs=get;list;watch
118+
// +kubebuilder:rbac:groups="",resources=nodes,verbs=get;list
118119

119120
// service account, role, rolebinding
120121
// +kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch;create;update;patch
@@ -713,6 +714,112 @@ func (r *DesignateReconciler) reconcileNormal(ctx context.Context, instance *des
713714
}
714715
Log.Info("Deployment API task reconciled")
715716

717+
nad, err := nad.GetNADWithName(ctx, helper, instance.Spec.DesignateNetworkAttachment, instance.Namespace)
718+
if err != nil {
719+
return ctrl.Result{}, err
720+
}
721+
722+
networkParameters, err := designate.GetNetworkParametersFromNAD(nad)
723+
if err != nil {
724+
return ctrl.Result{}, err
725+
}
726+
727+
nodeConfigMap := &corev1.ConfigMap{
728+
ObjectMeta: metav1.ObjectMeta{
729+
Name: designate.MdnsPredIPConfigMap,
730+
Namespace: instance.GetNamespace(),
731+
Labels: labels.GetLabels(instance, labels.GetGroupLabel(instance.ObjectMeta.Name), map[string]string{}),
732+
},
733+
Data: make(map[string]string),
734+
}
735+
736+
// Look for existing config map and if exists, read existing data and match
737+
// against nodes.
738+
foundMap := &corev1.ConfigMap{}
739+
err = helper.GetClient().Get(ctx, types.NamespacedName{Name: designate.MdnsPredIPConfigMap, Namespace: instance.GetNamespace()},
740+
foundMap)
741+
if err != nil {
742+
if k8s_errors.IsNotFound(err) {
743+
Log.Info(fmt.Sprintf("Ip map %s doesn't exist, creating.", designate.MdnsPredIPConfigMap))
744+
} else {
745+
return ctrl.Result{}, err
746+
}
747+
} else {
748+
Log.Info("Retrieved existing map, updating..")
749+
nodeConfigMap.Data = foundMap.Data
750+
}
751+
752+
//
753+
// Predictable IPs.
754+
//
755+
// NOTE(oschwart): refactoring this might be nice. This could also be
756+
// optimized but the data sets are small (nodes an IP ranges are less than
757+
// 100) so optimization might be a waste.
758+
//
759+
predictableIPParams, err := designate.GetPredictableIPAM(networkParameters)
760+
if err != nil {
761+
return ctrl.Result{}, err
762+
}
763+
// Get a list of the nodes in the cluster
764+
765+
// TODO(oschwart):
766+
// * confirm whether or not this lists only the nodes we want (i.e. ones
767+
// that will host the daemonset)
768+
// * do we want to provide a mechanism to temporarily disabling this list
769+
// for maintenance windows where nodes might be "coming and going"
770+
771+
nodes, err := helper.GetKClient().CoreV1().Nodes().List(ctx, metav1.ListOptions{})
772+
if err != nil {
773+
return ctrl.Result{}, err
774+
}
775+
updatedMap := make(map[string]string)
776+
allocatedIPs := make(map[string]bool)
777+
var predictableIPsRequired []string
778+
779+
// First scan existing allocations so we can keep existing allocations.
780+
// Keeping track of what's required and what already exists. If a node is
781+
// removed from the cluster, it's IPs will not be added to the allocated
782+
// list and are effectively recycled.
783+
for _, node := range nodes.Items {
784+
nodeName := fmt.Sprintf("mdns_%s", node.Name)
785+
if ipValue, ok := nodeConfigMap.Data[nodeName]; ok {
786+
updatedMap[nodeName] = ipValue
787+
allocatedIPs[ipValue] = true
788+
Log.Info(fmt.Sprintf("%s has IP mapping %s: %s", node.Name, nodeName, ipValue))
789+
} else {
790+
predictableIPsRequired = append(predictableIPsRequired, nodeName)
791+
}
792+
}
793+
// Get new IPs using the range from predictableIPParmas minus the
794+
// allocatedIPs captured above.
795+
Log.Info(fmt.Sprintf("Allocating %d predictable IPs", len(predictableIPsRequired)))
796+
for _, nodeName := range predictableIPsRequired {
797+
nodeIP, err := designate.GetNextIP(predictableIPParams, allocatedIPs)
798+
if err != nil {
799+
// An error here is really unexpected- it means either we have
800+
// messed up the allocatedIPs list or the range we are assuming is
801+
// too small for the number of mdns pod.
802+
return ctrl.Result{}, err
803+
}
804+
updatedMap[nodeName] = nodeIP
805+
}
806+
807+
mapLabels := labels.GetLabels(instance, labels.GetGroupLabel(instance.ObjectMeta.Name), map[string]string{})
808+
_, err = controllerutil.CreateOrPatch(ctx, helper.GetClient(), nodeConfigMap, func() error {
809+
nodeConfigMap.Labels = util.MergeStringMaps(nodeConfigMap.Labels, mapLabels)
810+
nodeConfigMap.Data = updatedMap
811+
err := controllerutil.SetControllerReference(instance, nodeConfigMap, helper.GetScheme())
812+
if err != nil {
813+
return err
814+
}
815+
return nil
816+
})
817+
818+
if err != nil {
819+
Log.Info("Unable to create config map for mdns ips...")
820+
return ctrl.Result{}, err
821+
}
822+
716823
// deploy designate-central
717824
designateCentral, op, err := r.centralDeploymentCreateOrUpdate(ctx, instance)
718825
if err != nil {

controllers/designatemdns_controller.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343
designatemdns "github.com/openstack-k8s-operators/designate-operator/pkg/designatemdns"
4444
"github.com/openstack-k8s-operators/lib-common/modules/common"
4545
"github.com/openstack-k8s-operators/lib-common/modules/common/condition"
46+
"github.com/openstack-k8s-operators/lib-common/modules/common/configmap"
4647
"github.com/openstack-k8s-operators/lib-common/modules/common/daemonset"
4748
"github.com/openstack-k8s-operators/lib-common/modules/common/env"
4849
"github.com/openstack-k8s-operators/lib-common/modules/common/helper"
@@ -489,7 +490,7 @@ func (r *DesignateMdnsReconciler) reconcileNormal(ctx context.Context, instance
489490
// create hash over all the different input resources to identify if any those changed
490491
// and a restart/recreate is required.
491492
//
492-
inputHash, hashChanged, err := r.createHashOfInputHashes(ctx, instance, configMapVars)
493+
inputHash, hashChanged, err := r.createHashOfInputHashes(ctx, helper, instance, configMapVars)
493494
if err != nil {
494495
instance.Status.Conditions.Set(condition.FalseCondition(
495496
condition.ServiceConfigReadyCondition,
@@ -803,13 +804,30 @@ func (r *DesignateMdnsReconciler) generateServiceConfigMaps(
803804
// returns the hash, whether the hash changed (as a bool) and any error
804805
func (r *DesignateMdnsReconciler) createHashOfInputHashes(
805806
ctx context.Context,
807+
h *helper.Helper,
806808
instance *designatev1beta1.DesignateMdns,
807809
envVars map[string]env.Setter,
808810
) (string, bool, error) {
809811
Log := r.GetLogger(ctx)
810812

811813
var hashMap map[string]string
812814
changed := false
815+
816+
// If MdnsPredIPConfigMap exists, add its hash to status hash
817+
mdnsPredIPCM := &corev1.ConfigMap{}
818+
err := h.GetClient().Get(ctx, types.NamespacedName{
819+
Name: designate.MdnsPredIPConfigMap,
820+
Namespace: instance.Namespace,
821+
}, mdnsPredIPCM)
822+
if err != nil {
823+
Log.Error(err, "Unable to retrieve Mdns predictable IPs ConfigMap")
824+
return "", false, err
825+
}
826+
mdnsPredIPCMHash, err := configmap.Hash(mdnsPredIPCM)
827+
if err != nil {
828+
return mdnsPredIPCMHash, changed, err
829+
}
830+
813831
mergedMapVars := env.MergeEnvs([]corev1.EnvVar{}, envVars)
814832
hash, err := util.ObjectHash(mergedMapVars)
815833
if err != nil {

controllers/designateworker_controller.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ func (r *DesignateWorkerReconciler) reconcileNormal(ctx context.Context, instanc
487487
// create hash over all the different input resources to identify if any those changed
488488
// and a restart/recreate is required.
489489
//
490-
inputHash, hashChanged, err := r.createHashOfInputHashes(ctx, instance, configMapVars)
490+
inputHash, hashChanged, err := r.createHashOfInputHashes(ctx, helper, instance, configMapVars)
491491
if err != nil {
492492
instance.Status.Conditions.Set(condition.FalseCondition(
493493
condition.ServiceConfigReadyCondition,
@@ -798,12 +798,30 @@ func (r *DesignateWorkerReconciler) generateServiceConfigMaps(
798798
// returns the hash, whether the hash changed (as a bool) and any error
799799
func (r *DesignateWorkerReconciler) createHashOfInputHashes(
800800
ctx context.Context,
801+
h *helper.Helper,
801802
instance *designatev1beta1.DesignateWorker,
802803
envVars map[string]env.Setter,
803804
) (string, bool, error) {
804805
Log := r.GetLogger(ctx)
805806
var hashMap map[string]string
806807
changed := false
808+
809+
// If DesignateBindKeySecret exists, add its hash to status hash
810+
rndcSecret := &corev1.Secret{}
811+
err := h.GetClient().Get(ctx, types.NamespacedName{
812+
Name: designate.DesignateBindKeySecret,
813+
Namespace: instance.Namespace,
814+
}, rndcSecret)
815+
if err != nil {
816+
Log.Error(err, "Unable to retrieve rndc key secret")
817+
return "", false, err
818+
}
819+
secretHash, err := secret.Hash(rndcSecret)
820+
if err != nil {
821+
return secretHash, changed, err
822+
}
823+
824+
envVars[designate.DesignateBindKeySecret] = env.SetValue(secretHash)
807825
mergedMapVars := env.MergeEnvs([]corev1.EnvVar{}, envVars)
808826
hash, err := util.ObjectHash(mergedMapVars)
809827
if err != nil {

pkg/designate/bind_ctrl_network.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
Licensed under the Apache License, Version 2.0 (the "License");
3+
@you may not use this file except in compliance with the License.
4+
You may obtain a copy of the License at
5+
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
8+
Unless required by applicable law or agreed to in writing, software
9+
distributed under the License is distributed on an "AS IS" BASIS,
10+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
See the License for the specific language governing permissions and
12+
limitations under the License.
13+
*/
14+
15+
package designate
16+
17+
import (
18+
"fmt"
19+
)
20+
21+
// GetPredictableIPAM returns a struct describing the available IP range. If the
22+
// IP pool size does not fit in given networkParameters CIDR it will return an
23+
// error instead.
24+
func GetPredictableIPAM(networkParameters *NetworkParameters) (*NADIpam, error) {
25+
predParams := &NADIpam{}
26+
predParams.CIDR = networkParameters.CIDR
27+
predParams.RangeStart = networkParameters.ProviderAllocationEnd.Next()
28+
endRange := predParams.RangeStart
29+
for i := 0; i < BindProvPredictablePoolSize; i++ {
30+
if !predParams.CIDR.Contains(endRange) {
31+
return nil, fmt.Errorf("predictable IPs: cannot allocate %d IP addresses in %s", BindProvPredictablePoolSize, predParams.CIDR)
32+
}
33+
endRange = endRange.Next()
34+
}
35+
predParams.RangeEnd = endRange
36+
return predParams, nil
37+
}
38+
39+
// GetNextIP picks the next available IP from the range defined by a NADIpam,
40+
// skipping ones that are already used appear as keys in the currentValues map.
41+
func GetNextIP(predParams *NADIpam, currentValues map[string]bool) (string, error) {
42+
candidateAddress := predParams.RangeStart
43+
for alloced := true; alloced; {
44+
45+
if _, ok := currentValues[candidateAddress.String()]; ok {
46+
if candidateAddress == predParams.RangeEnd {
47+
return "", fmt.Errorf("predictable IPs: out of available addresses")
48+
}
49+
candidateAddress = candidateAddress.Next()
50+
} else {
51+
alloced = false
52+
}
53+
}
54+
currentValues[candidateAddress.String()] = true
55+
return candidateAddress.String(), nil
56+
}

pkg/designate/const.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,6 @@ const (
3838
DesignateBindKeySecret = "designate-bind-secret"
3939

4040
DesignateRndcKey = "rndc-key"
41+
42+
MdnsPredIPConfigMap = "designate-mdns-ip-map"
4143
)

pkg/designate/network_consts.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
*/
15+
16+
package designate
17+
18+
// NOTE: Strictly speaking, these don't have to be package scope constants, but having them externally
19+
// accessible might aide constructing functional tests later on.
20+
21+
const (
22+
// Common consts for Control network
23+
24+
// BindProvPredictablePoolSize -
25+
BindProvPredictablePoolSize = 25
26+
)

0 commit comments

Comments
 (0)