@@ -21,11 +21,13 @@ import (
2121 "fmt"
2222 "time"
2323
24+ "github.com/openstack-k8s-operators/lib-common/modules/common"
2425 condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition"
2526 configmap "github.com/openstack-k8s-operators/lib-common/modules/common/configmap"
2627 common_rbac "github.com/openstack-k8s-operators/lib-common/modules/common/rbac"
2728 commonservice "github.com/openstack-k8s-operators/lib-common/modules/common/service"
2829 commonstatefulset "github.com/openstack-k8s-operators/lib-common/modules/common/statefulset"
30+ "github.com/openstack-k8s-operators/lib-common/modules/common/tls"
2931
3032 env "github.com/openstack-k8s-operators/lib-common/modules/common/env"
3133 helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper"
@@ -34,22 +36,43 @@ import (
3436 corev1 "k8s.io/api/core/v1"
3537 rbacv1 "k8s.io/api/rbac/v1"
3638 k8s_errors "k8s.io/apimachinery/pkg/api/errors"
39+ "k8s.io/apimachinery/pkg/fields"
40+ "k8s.io/apimachinery/pkg/types"
3741 "k8s.io/client-go/kubernetes"
42+ "k8s.io/client-go/rest"
43+ "sigs.k8s.io/controller-runtime/pkg/builder"
44+ "sigs.k8s.io/controller-runtime/pkg/handler"
45+ "sigs.k8s.io/controller-runtime/pkg/predicate"
3846
3947 "github.com/go-logr/logr"
4048 "k8s.io/apimachinery/pkg/runtime"
4149 ctrl "sigs.k8s.io/controller-runtime"
4250 "sigs.k8s.io/controller-runtime/pkg/client"
4351 "sigs.k8s.io/controller-runtime/pkg/log"
52+ "sigs.k8s.io/controller-runtime/pkg/reconcile"
4453
4554 memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1"
4655 memcached "github.com/openstack-k8s-operators/infra-operator/pkg/memcached"
4756)
4857
58+ // fields to index to reconcile on CR change
59+ const (
60+ serviceSecretNameField = ".spec.tls.genericService.SecretName"
61+ caSecretNameField = ".spec.tls.ca.caBundleSecretName"
62+ )
63+
64+ var (
65+ allWatchFields = []string {
66+ serviceSecretNameField ,
67+ caSecretNameField ,
68+ }
69+ )
70+
4971// Reconciler reconciles a Memcached object
5072type Reconciler struct {
5173 client.Client
5274 Kclient kubernetes.Interface
75+ config * rest.Config
5376 Scheme * runtime.Scheme
5477}
5578
@@ -135,6 +158,8 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ct
135158 instance .Status .Conditions = condition.Conditions {}
136159 // initialize conditions used later as Status=Unknown
137160 cl := condition .CreateList (
161+ // TLS cert secrets
162+ condition .UnknownCondition (condition .TLSInputReadyCondition , condition .InitReason , condition .InputReadyInitMessage ),
138163 // endpoint for adoption redirect
139164 condition .UnknownCondition (condition .ExposeServiceReadyCondition , condition .InitReason , condition .ExposeServiceReadyInitMessage ),
140165 // configmap generation
@@ -185,9 +210,41 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ct
185210 return rbacResult , nil
186211 }
187212
213+ // Hash of all resources that may cause a service restart
214+ inputHashEnv := make (map [string ]env.Setter )
215+
216+ // Check and hash inputs
217+ var certHash , caHash string
218+ specTLS := & instance .Spec .TLS
219+ if specTLS .Enabled () {
220+ certHash , _ , err = specTLS .GenericService .ValidateCertSecret (ctx , helper , instance .Namespace )
221+ }
222+ if err == nil && specTLS .Enabled () && specTLS .Ca .CaBundleSecretName != "" {
223+ caName := types.NamespacedName {
224+ Name : specTLS .Ca .CaBundleSecretName ,
225+ Namespace : instance .Namespace ,
226+ }
227+ caHash , _ , err = tls .ValidateCACertSecret (ctx , helper .GetClient (), caName )
228+ }
229+ if err != nil {
230+ instance .Status .Conditions .Set (condition .FalseCondition (
231+ condition .TLSInputReadyCondition ,
232+ condition .ErrorReason ,
233+ condition .SeverityWarning ,
234+ condition .TLSInputErrorMessage ,
235+ err .Error ()))
236+ return ctrl.Result {}, fmt .Errorf ("error calculating input hash: %w" , err )
237+ }
238+ if certHash != "" {
239+ inputHashEnv ["Cert" ] = env .SetValue (certHash )
240+ }
241+ if caHash != "" {
242+ inputHashEnv ["CA" ] = env .SetValue (caHash )
243+ }
244+ instance .Status .Conditions .MarkTrue (condition .TLSInputReadyCondition , condition .InputReadyMessage )
245+
188246 // Memcached config maps
189- configMapVars := make (map [string ]env.Setter )
190- err = r .generateConfigMaps (ctx , helper , instance , & configMapVars )
247+ err = r .generateConfigMaps (ctx , helper , instance , & inputHashEnv )
191248 if err != nil {
192249 instance .Status .Conditions .Set (condition .FalseCondition (
193250 condition .ServiceConfigReadyCondition ,
@@ -199,6 +256,27 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ct
199256 }
200257 instance .Status .Conditions .MarkTrue (condition .ServiceConfigReadyCondition , condition .ServiceConfigReadyMessage )
201258
259+ //
260+ // create hash over all the different input resources to identify if any those changed
261+ // and a restart/recreate is required.
262+ //
263+ hashOfHashes , err := util .HashOfInputHashes (inputHashEnv )
264+ if err != nil {
265+ return ctrl.Result {}, err
266+ }
267+ if hashMap , changed := util .SetHash (instance .Status .Hash , common .InputHashName , hashOfHashes ); changed {
268+ // Hash changed and instance status should be updated (which will be done by main defer func),
269+ // so update all the input hashes and return to reconcile again
270+ instance .Status .Hash = hashMap
271+ for k , s := range inputHashEnv {
272+ var envVar corev1.EnvVar
273+ s (& envVar )
274+ instance .Status .Hash [k ] = envVar .Value
275+ }
276+ Log .Info (fmt .Sprintf ("Input hash changed %s" , hashOfHashes ), "instance" , instance )
277+ return ctrl.Result {}, nil
278+ }
279+
202280 // Service to expose Memcached pods
203281 commonsvc , err := commonservice .NewService (memcached .HeadlessService (instance ), time .Duration (5 )* time .Second , nil )
204282 if err != nil {
@@ -247,17 +325,28 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ct
247325 return ctrl.Result {}, nil
248326}
249327
250- // generateConfigMaps returns the config map resource for a galera instance
328+ // generateConfigMaps returns the config map resource for a memcached instance
251329func (r * Reconciler ) generateConfigMaps (
252330 ctx context.Context ,
253331 h * helper.Helper ,
254332 instance * memcachedv1.Memcached ,
255333 envVars * map [string ]env.Setter ,
256334) error {
257- Log := r .GetLogger (ctx )
335+ Log := h .GetLogger ()
258336
259- templateParameters := make (map [string ]interface {})
260337 customData := make (map [string ]string )
338+ var memcachedTLSConfig string
339+ if instance .Spec .TLS .Enabled () {
340+ memcachedTLSConfig = "-Z " +
341+ "-o ssl_chain_cert=/etc/pki/tls/certs/memcached.crt " +
342+ "-o ssl_key=/etc/pki/tls/private/memcached.key " +
343+ "-o ssl_ca_cert=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"
344+ } else {
345+ memcachedTLSConfig = ""
346+ }
347+ templateParameters := map [string ]interface {}{
348+ "memcachedTLSConfig" : memcachedTLSConfig ,
349+ }
261350
262351 cms := []util.Template {
263352 // ConfigMap
@@ -283,16 +372,80 @@ func (r *Reconciler) generateConfigMaps(
283372
284373// SetupWithManager sets up the controller with the Manager.
285374func (r * Reconciler ) SetupWithManager (mgr ctrl.Manager ) error {
375+ r .config = mgr .GetConfig ()
376+
377+ // Various CR fields need to be indexed to filter watch events
378+ // for the secret changes we want to be notified of
379+ // index caBundleSecretName
380+ if err := mgr .GetFieldIndexer ().IndexField (context .Background (), & memcachedv1.Memcached {}, caSecretNameField , func (rawObj client.Object ) []string {
381+ // Extract the secret name from the spec, if one is provided
382+ cr := rawObj .(* memcachedv1.Memcached )
383+ tls := & cr .Spec .TLS
384+ if tls .Ca .CaBundleSecretName != "" {
385+ return []string {tls .Ca .CaBundleSecretName }
386+ }
387+ return nil
388+ }); err != nil {
389+ return err
390+ }
391+ // index secretName
392+ if err := mgr .GetFieldIndexer ().IndexField (context .Background (), & memcachedv1.Memcached {}, serviceSecretNameField , func (rawObj client.Object ) []string {
393+ // Extract the secret name from the spec, if one is provided
394+ cr := rawObj .(* memcachedv1.Memcached )
395+ tls := & cr .Spec .TLS
396+ if tls .Enabled () {
397+ return []string {* tls .GenericService .SecretName }
398+ }
399+ return nil
400+ }); err != nil {
401+ return err
402+ }
403+
286404 return ctrl .NewControllerManagedBy (mgr ).
287405 For (& memcachedv1.Memcached {}).
288406 Owns (& appsv1.StatefulSet {}).
289407 Owns (& corev1.Service {}).
290408 Owns (& corev1.ServiceAccount {}).
291409 Owns (& rbacv1.Role {}).
292410 Owns (& rbacv1.RoleBinding {}).
411+ Watches (
412+ & corev1.Secret {},
413+ handler .EnqueueRequestsFromMapFunc (r .findObjectsForSrc ),
414+ builder .WithPredicates (predicate.ResourceVersionChangedPredicate {}),
415+ ).
293416 Complete (r )
294417}
295418
419+ // findObjectsForSrc - returns a reconcile request if the object is referenced by a Memcached CR
420+ func (r * Reconciler ) findObjectsForSrc (_ context.Context , src client.Object ) []reconcile.Request {
421+ requests := []reconcile.Request {}
422+
423+ for _ , field := range allWatchFields {
424+ crList := & memcachedv1.MemcachedList {}
425+ listOps := & client.ListOptions {
426+ FieldSelector : fields .OneTermEqualSelector (field , src .GetName ()),
427+ Namespace : src .GetNamespace (),
428+ }
429+ err := r .List (context .TODO (), crList , listOps )
430+ if err != nil {
431+ return []reconcile.Request {}
432+ }
433+
434+ for _ , item := range crList .Items {
435+ requests = append (requests ,
436+ reconcile.Request {
437+ NamespacedName : types.NamespacedName {
438+ Name : item .GetName (),
439+ Namespace : item .GetNamespace (),
440+ },
441+ },
442+ )
443+ }
444+ }
445+
446+ return requests
447+ }
448+
296449// GetServerLists returns list of memcached server without/with inet prefix
297450func (r * Reconciler ) GetServerLists (
298451 instance * memcachedv1.Memcached ,
0 commit comments