Skip to content

Commit 04b3a2e

Browse files
beaglesCursor AI
andcommitted
Calculate allowed addr clause in unbound
This patch improves the UX for configuring unbound by automatically adding allow clauses for configured network attachments. It also attempts to discover if the cluster's internal join address has been changed to a non-default value and sets that instead of the default. Note: a unit test and some commented code removal snuck in as well. Co-authored-by: Cursor AI <[email protected]> Co-Authored-By: Claude [email protected]
1 parent 933871a commit 04b3a2e

19 files changed

+993
-281
lines changed

api/v1beta1/designateunbound_types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ type UnboundOverrideSpec struct {
7070
}
7171

7272
type StubZone struct {
73-
Name string `json:"name"`
73+
Name string `json:"name"`
7474
// +kubebuilder:validation:Optional
7575
Options map[string]string `json:"options,omitempty"`
7676
}

config/rbac/role.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,14 @@ rules:
152152
verbs:
153153
- patch
154154
- update
155+
- apiGroups:
156+
- operator.openshift.io
157+
resources:
158+
- networks
159+
verbs:
160+
- get
161+
- list
162+
- watch
155163
- apiGroups:
156164
- rabbitmq.openstack.org
157165
resources:

controllers/designate_controller.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ func (r *DesignateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
253253
condition.UnknownCondition(designatev1beta1.DesignateCentralReadyCondition, condition.InitReason, designatev1beta1.DesignateCentralReadyInitMessage),
254254
condition.UnknownCondition(designatev1beta1.DesignateWorkerReadyCondition, condition.InitReason, designatev1beta1.DesignateWorkerReadyInitMessage),
255255
condition.UnknownCondition(designatev1beta1.DesignateMdnsReadyCondition, condition.InitReason, designatev1beta1.DesignateMdnsReadyInitMessage),
256+
condition.UnknownCondition(designatev1beta1.DesignateUnboundReadyCondition, condition.InitReason, designatev1beta1.DesignateUnboundReadyInitMessage),
256257
condition.UnknownCondition(designatev1beta1.DesignateProducerReadyCondition, condition.InitReason, designatev1beta1.DesignateProducerReadyInitMessage),
257258
condition.UnknownCondition(designatev1beta1.DesignateBackendbind9ReadyCondition, condition.InitReason, designatev1beta1.DesignateBackendbind9ReadyInitMessage),
258259
// service account, role, rolebinding conditions
@@ -339,6 +340,8 @@ func (r *DesignateReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Man
339340
return nil
340341
}
341342

343+
// TODO(beagles):
344+
// - Watch for changes to the redis PODs and resync the headless hostnames for the PODs if necessary.
342345
return ctrl.NewControllerManagedBy(mgr).
343346
For(&designatev1beta1.Designate{}).
344347
Owns(&mariadbv1.MariaDBDatabase{}).

controllers/designateunbound_controller.go

Lines changed: 122 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
"github.com/go-logr/logr"
2626
networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
27+
operatorv1 "github.com/openshift/api/operator/v1"
2728
designatev1 "github.com/openstack-k8s-operators/designate-operator/api/v1beta1"
2829
"github.com/openstack-k8s-operators/designate-operator/pkg/designate"
2930
"github.com/openstack-k8s-operators/designate-operator/pkg/designateunbound"
@@ -74,11 +75,24 @@ type StubZoneTmplRec struct {
7475
Servers []string
7576
}
7677

78+
func getCIDRsFromNADs(nadList []networkv1.NetworkAttachmentDefinition) ([]string, error) {
79+
cidrs := []string{}
80+
for _, nad := range nadList {
81+
nadConfig, err := designate.GetNADConfig(&nad)
82+
if err != nil {
83+
return nil, err
84+
}
85+
cidrs = append(cidrs, nadConfig.IPAM.CIDR.String())
86+
}
87+
return cidrs, nil
88+
}
89+
7790
//+kubebuilder:rbac:groups=designate.openstack.org,resources=designateunbounds,verbs=get;list;watch;create;update;patch;delete
7891
//+kubebuilder:rbac:groups=designate.openstack.org,resources=designateunbounds/status,verbs=get;update;patch
7992
//+kubebuilder:rbac:groups=designate.openstack.org,resources=designateunbounds/finalizers,verbs=update;patch
8093
//+kubebuilder:rbac:groups=k8s.cni.cncf.io,resources=network-attachment-definitions,verbs=get;list;watch
8194
//+kubebuilder:rbac:groups=topology.openstack.org,resources=topologies,verbs=get;list;watch;update
95+
//+kubebuilder:rbac:groups=operator.openshift.io,resources=networks,verbs=get;list;watch
8296

8397
// Reconcile implementation for designate's Unbound resolver
8498
func (r *UnboundReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, _err error) {
@@ -315,33 +329,8 @@ func (r *UnboundReconciler) reconcileNormal(ctx context.Context, instance *desig
315329
}
316330
instance.Status.Conditions.MarkTrue(condition.CreateServiceReadyCondition, condition.CreateServiceReadyMessage)
317331

318-
configMapVars := make(map[string]env.Setter)
319-
err := r.generateServiceConfigMaps(ctx, instance, helper, &configMapVars)
320-
if err != nil {
321-
instance.Status.Conditions.Set(condition.FalseCondition(
322-
condition.ServiceConfigReadyCondition,
323-
condition.ErrorReason,
324-
condition.SeverityWarning,
325-
condition.ServiceConfigReadyErrorMessage,
326-
err.Error()))
327-
return ctrl.Result{}, err
328-
}
329-
330-
//
331-
// create hash over all the different input resources to identify if any those changed
332-
// and a restart/recreate is required.
333-
//
334-
inputHash, hashChanged, err := r.createHashOfInputHashes(ctx, instance, configMapVars)
335-
if err != nil {
336-
return ctrl.Result{}, err
337-
} else if hashChanged {
338-
// Hash changed and instance status should be updated (which will be done by main defer func),
339-
// so we need to return and reconcile again
340-
return ctrl.Result{}, nil
341-
}
342-
343-
instance.Status.Conditions.MarkTrue(condition.ServiceConfigReadyCondition, condition.ServiceConfigReadyMessage)
344-
332+
// We do initial processing of the network attachments before the configs because it influences some of the
333+
// automatically configurations.
345334
nadList := []networkv1.NetworkAttachmentDefinition{}
346335
for _, networkAttachment := range instance.Spec.NetworkAttachments {
347336
nad, err := nad.GetNADWithName(ctx, helper, networkAttachment, instance.Namespace)
@@ -372,6 +361,33 @@ func (r *UnboundReconciler) reconcileNormal(ctx context.Context, instance *desig
372361
}
373362
}
374363

364+
configMapVars := make(map[string]env.Setter)
365+
err := r.generateServiceConfigMaps(ctx, instance, helper, &configMapVars, nadList)
366+
if err != nil {
367+
instance.Status.Conditions.Set(condition.FalseCondition(
368+
condition.ServiceConfigReadyCondition,
369+
condition.ErrorReason,
370+
condition.SeverityWarning,
371+
condition.ServiceConfigReadyErrorMessage,
372+
err.Error()))
373+
return ctrl.Result{}, err
374+
}
375+
376+
//
377+
// create hash over all the different input resources to identify if any those changed
378+
// and a restart/recreate is required.
379+
//
380+
inputHash, hashChanged, err := r.createHashOfInputHashes(ctx, instance, configMapVars)
381+
if err != nil {
382+
return ctrl.Result{}, err
383+
} else if hashChanged {
384+
// Hash changed and instance status should be updated (which will be done by main defer func),
385+
// so we need to return and reconcile again
386+
return ctrl.Result{}, nil
387+
}
388+
389+
instance.Status.Conditions.MarkTrue(condition.ServiceConfigReadyCondition, condition.ServiceConfigReadyMessage)
390+
375391
serviceAnnotations, err := nad.EnsureNetworksAnnotation(nadList)
376392
if err != nil {
377393
return ctrl.Result{}, fmt.Errorf("failed create network annotation from %s: %w",
@@ -518,6 +534,7 @@ func (r *UnboundReconciler) onIPChange() (ctrl.Result, error) {
518534
return ctrl.Result{}, nil
519535
}
520536

537+
// Set stub zone configuration defaults if they are net set by CR data.
521538
func stubZoneDefaults(values map[string]string) map[string]string {
522539

523540
if values == nil {
@@ -532,20 +549,84 @@ func stubZoneDefaults(values map[string]string) map[string]string {
532549
return values
533550
}
534551

552+
// getClusterJoinSubnets will return configured join subnets from the cluster network if available. If noit will
553+
// return the expected defaults defined in the build.
554+
// TODO(beagles): it might be a good idea to have this settable through an environment variable to bridge
555+
// unexpected compatibility issues.
556+
func (r *UnboundReconciler) getClusterJoinSubnets(ctx context.Context) []string {
557+
Log := r.GetLogger(ctx)
558+
559+
// Get the cluster network operator configuration
560+
network := &operatorv1.Network{}
561+
err := r.Get(ctx, types.NamespacedName{Name: "cluster"}, network)
562+
if err != nil {
563+
// If we can't get the network config, fall back to defaults.
564+
// TODO(beagles): while probably not necessary, we should revisit to see if this should be an error.
565+
Log.Info("Unable to get cluster network configuration, using defaults", "error", err)
566+
return []string{designateunbound.DefaultJoinSubnetV4, designateunbound.DefaultJoinSubnetV6}
567+
}
568+
569+
var joinSubnets []string
570+
571+
// NOTE(beagles): we are only handling OVN-Kubernetes configuration at this time. There is a
572+
// question of whether we should only set defaults for IP versions that are 'enabled'.
573+
// Unfortunately, I'm not sure we can know that for sure from the network operator configuration.
574+
// That is, absence of configuration might mean that it is enabled, but with all defaults.
575+
576+
// Extract join subnets from OVN-Kubernetes configuration
577+
if network.Spec.DefaultNetwork.OVNKubernetesConfig != nil {
578+
ovnConfig := network.Spec.DefaultNetwork.OVNKubernetesConfig
579+
580+
// IPv4 join subnet
581+
if ovnConfig.IPv4 != nil && ovnConfig.IPv4.InternalJoinSubnet != "" {
582+
joinSubnets = append(joinSubnets, ovnConfig.IPv4.InternalJoinSubnet)
583+
Log.Info("Found IPv4 join subnet from cluster config", "subnet", ovnConfig.IPv4.InternalJoinSubnet)
584+
} else {
585+
joinSubnets = append(joinSubnets, designateunbound.DefaultJoinSubnetV4)
586+
Log.Info("Using default IPv4 join subnet", "subnet", designateunbound.DefaultJoinSubnetV4)
587+
}
588+
589+
// IPv6 join subnet
590+
if ovnConfig.IPv6 != nil && ovnConfig.IPv6.InternalJoinSubnet != "" {
591+
joinSubnets = append(joinSubnets, ovnConfig.IPv6.InternalJoinSubnet)
592+
Log.Info("Found IPv6 join subnet from cluster config", "subnet", ovnConfig.IPv6.InternalJoinSubnet)
593+
} else {
594+
joinSubnets = append(joinSubnets, designateunbound.DefaultJoinSubnetV6)
595+
Log.Info("Using default IPv6 join subnet", "subnet", designateunbound.DefaultJoinSubnetV6)
596+
}
597+
} else {
598+
// No OVN-Kubernetes config found, use defaults
599+
Log.Info("No OVN-Kubernetes configuration found, using defaults")
600+
joinSubnets = []string{designateunbound.DefaultJoinSubnetV4, designateunbound.DefaultJoinSubnetV6}
601+
}
602+
603+
return joinSubnets
604+
}
605+
535606
func (r *UnboundReconciler) generateServiceConfigMaps(
536607
ctx context.Context,
537608
instance *designatev1.DesignateUnbound,
538609
h *helper.Helper,
539610
envVars *map[string]env.Setter,
611+
nadList []networkv1.NetworkAttachmentDefinition,
540612
) error {
541613
Log := r.GetLogger(ctx)
542614
Log.Info("Generating service config map")
543615
cmLabels := labels.GetLabels(instance, labels.GetGroupLabel(designateunbound.Component), map[string]string{})
544616
customData := map[string]string{common.CustomServiceConfigFileName: instance.Spec.CustomServiceConfig}
617+
// NOTE(beagles): DefaultConfigOverwrite is bugged in unbound because it is
618+
// applied to conf.d when it really is meant to for overwriting stuff in
619+
// /etc/unbound. A reasonable fix would be to have special handling for the
620+
// "unbound.conf" and if any of the other files exist in /etc/unbound,
621+
// overwrite them. The ones that aren't can go into /etc/unbound/conf.d for
622+
// backwards compatibility.
545623
maps.Copy(customData, instance.Spec.DefaultConfigOverwrite)
546624

547625
templateParameters := make(map[string]any)
548626

627+
//
628+
// Create stub zone configuration data from predictable IP map.
629+
//
549630
stubZoneData := make([]StubZoneTmplRec, len(instance.Spec.StubZones))
550631
if len(instance.Spec.StubZones) > 0 {
551632
bindIPMap := &corev1.ConfigMap{}
@@ -572,6 +653,19 @@ func (r *UnboundReconciler) generateServiceConfigMaps(
572653
}
573654
templateParameters["StubZones"] = stubZoneData
574655

656+
// TODO(beagles): There are situations where the allowCidrs should be overriddable, do we want to support that at the API level or
657+
// customServiceConfig
658+
// Dynamically discover join subnets from the cluster network operator configuration
659+
allowCidrs := r.getClusterJoinSubnets(ctx)
660+
661+
// TODO(beagles): create entries for each network attachment.
662+
nadCIDRs, nadErr := getCIDRsFromNADs(nadList)
663+
if nadErr != nil {
664+
Log.Error(nadErr, "unable to get CIDRs from network attachment definitions")
665+
}
666+
allowCidrs = append(allowCidrs, nadCIDRs...)
667+
templateParameters["AllowCidrs"] = allowCidrs
668+
575669
cms := []util.Template{
576670
// ConfigMap
577671
{
@@ -587,7 +681,7 @@ func (r *UnboundReconciler) generateServiceConfigMaps(
587681
err := secret.EnsureSecrets(ctx, h, instance, cms, envVars)
588682

589683
if err != nil {
590-
Log.Error(err, "uanble to process config map")
684+
Log.Error(err, "unable to process config map")
591685
return err
592686
}
593687
Log.Info("Service config map generated")

0 commit comments

Comments
 (0)