@@ -17,6 +17,7 @@ import (
1717 . "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers"
1818 mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1"
1919 watcherv1beta1 "github.com/openstack-k8s-operators/watcher-operator/api/v1beta1"
20+ "github.com/openstack-k8s-operators/watcher-operator/internal/watcher"
2021 corev1 "k8s.io/api/core/v1"
2122 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2223 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -1858,6 +1859,206 @@ var _ = Describe("Watcher controller", func() {
18581859 })
18591860 })
18601861
1862+ When ("ApplicationCredential consumer finalizer is managed" , func () {
1863+ var acSecretName string
1864+
1865+ BeforeEach (func () {
1866+ acSecretName = "ac-watcher-consumer-fnz-secret" //nolint:gosec
1867+
1868+ acSecret := & corev1.Secret {
1869+ ObjectMeta : metav1.ObjectMeta {
1870+ Name : acSecretName ,
1871+ Namespace : watcherTest .Instance .Namespace ,
1872+ },
1873+ Data : map [string ][]byte {
1874+ keystonev1beta1 .ACIDSecretKey : []byte ("consumer-test-ac-id" ),
1875+ keystonev1beta1 .ACSecretSecretKey : []byte ("consumer-test-ac-secret" ), //nolint:gosec
1876+ },
1877+ }
1878+ Expect (k8sClient .Create (ctx , acSecret )).To (Succeed ())
1879+ DeferCleanup (k8sClient .Delete , ctx , acSecret )
1880+
1881+ DeferCleanup (k8sClient .Delete , ctx , CreateWatcherMessageBusSecret (watcherTest .Instance .Namespace , "rabbitmq-secret" ))
1882+
1883+ memcachedSpec := memcachedv1.MemcachedSpec {
1884+ MemcachedSpecCore : memcachedv1.MemcachedSpecCore {
1885+ Replicas : ptr .To (int32 (1 )),
1886+ },
1887+ }
1888+ DeferCleanup (infra .DeleteMemcached , infra .CreateMemcached (watcherTest .Watcher .Namespace , MemcachedInstance , memcachedSpec ))
1889+ infra .SimulateMemcachedReady (watcherTest .MemcachedNamespace )
1890+
1891+ DeferCleanup (keystone .DeleteKeystoneAPI , keystone .CreateKeystoneAPI (watcherTest .WatcherAPI .Namespace ))
1892+
1893+ DeferCleanup (
1894+ k8sClient .Delete , ctx , th .CreateSecret (
1895+ types.NamespacedName {Namespace : watcherTest .Instance .Namespace , Name : "metric-storage-prometheus-endpoint" },
1896+ map [string ][]byte {
1897+ "host" : []byte ("prometheus.example.com" ),
1898+ "port" : []byte ("9090" ),
1899+ },
1900+ ))
1901+
1902+ DeferCleanup (
1903+ k8sClient .Delete , ctx , th .CreateSecret (
1904+ types.NamespacedName {Namespace : watcherTest .Instance .Namespace , Name : SecretName },
1905+ map [string ][]byte {
1906+ "WatcherPassword" : []byte ("password" ),
1907+ },
1908+ ))
1909+
1910+ // Create Watcher CR after all secrets and dependencies are in place
1911+ // so sub-CR controllers don't enter long exponential backoff.
1912+ spec := GetDefaultWatcherSpec ()
1913+ spec ["auth" ] = map [string ]any {"applicationCredentialSecret" : acSecretName }
1914+ DeferCleanup (th .DeleteInstance , CreateWatcher (watcherTest .Instance , spec ))
1915+
1916+ DeferCleanup (
1917+ mariadb .DeleteDBService ,
1918+ mariadb .CreateDBService (
1919+ watcherTest .Instance .Namespace ,
1920+ * GetWatcher (watcherTest .Instance ).Spec .DatabaseInstance ,
1921+ corev1.ServiceSpec {
1922+ Ports : []corev1.ServicePort {{Port : 3306 }},
1923+ },
1924+ ),
1925+ )
1926+
1927+ mariadb .SimulateMariaDBAccountCompleted (watcherTest .WatcherDatabaseAccount )
1928+ mariadb .SimulateMariaDBDatabaseCompleted (watcherTest .WatcherDatabaseName )
1929+ infra .SimulateTransportURLReady (watcherTest .WatcherTransportURL )
1930+
1931+ keystone .SimulateKeystoneServiceReady (watcherTest .KeystoneServiceName )
1932+ th .SimulateJobSuccess (watcherTest .WatcherDBSync )
1933+ })
1934+
1935+ It ("should add the consumer finalizer to the AC secret" , func () {
1936+ Eventually (func (g Gomega ) {
1937+ secret := th .GetSecret (types.NamespacedName {
1938+ Namespace : watcherTest .Instance .Namespace ,
1939+ Name : acSecretName ,
1940+ })
1941+ g .Expect (secret .Finalizers ).To (
1942+ ContainElement (watcher .ACConsumerFinalizer ))
1943+ }, timeout , interval ).Should (Succeed ())
1944+ })
1945+
1946+ It ("should track the consumed AC secret in status" , func () {
1947+ Eventually (func (g Gomega ) {
1948+ w := GetWatcher (watcherTest .Instance )
1949+ g .Expect (w .Status .ApplicationCredentialSecret ).To (Equal (acSecretName ))
1950+ }, timeout , interval ).Should (Succeed ())
1951+ })
1952+
1953+ It ("should move the finalizer from the old to the new secret on rotation" , func () {
1954+ Eventually (func (g Gomega ) {
1955+ secret := th .GetSecret (types.NamespacedName {
1956+ Namespace : watcherTest .Instance .Namespace ,
1957+ Name : acSecretName ,
1958+ })
1959+ g .Expect (secret .Finalizers ).To (
1960+ ContainElement (watcher .ACConsumerFinalizer ))
1961+ }, timeout , interval ).Should (Succeed ())
1962+
1963+ // Simulate all watcher services deploying successfully
1964+ th .SimulateStatefulSetReplicaReady (watcherTest .WatcherAPIStatefulSet )
1965+ keystone .SimulateKeystoneEndpointReady (watcherTest .WatcherKeystoneEndpointName )
1966+ th .SimulateStatefulSetReplicaReady (watcherTest .WatcherApplierStatefulSet )
1967+ th .SimulateStatefulSetReplicaReady (watcherTest .WatcherDecisionEngineStatefulSet )
1968+
1969+ Eventually (func (g Gomega ) {
1970+ w := GetWatcher (watcherTest .Instance )
1971+ g .Expect (w .Status .ApplicationCredentialSecret ).To (Equal (acSecretName ))
1972+ }, timeout , interval ).Should (Succeed ())
1973+
1974+ th .ExpectCondition (
1975+ watcherTest .Instance ,
1976+ ConditionGetterFunc (WatcherConditionGetter ),
1977+ condition .ReadyCondition ,
1978+ corev1 .ConditionTrue ,
1979+ )
1980+
1981+ newACSecretName := "ac-watcher-consumer-rotated-secret" //nolint:gosec
1982+ newSecret := & corev1.Secret {
1983+ ObjectMeta : metav1.ObjectMeta {
1984+ Namespace : watcherTest .Instance .Namespace ,
1985+ Name : newACSecretName ,
1986+ },
1987+ Data : map [string ][]byte {
1988+ keystonev1beta1 .ACIDSecretKey : []byte ("rotated-ac-id" ),
1989+ keystonev1beta1 .ACSecretSecretKey : []byte ("rotated-ac-secret-value" ), //nolint:gosec
1990+ },
1991+ }
1992+ DeferCleanup (k8sClient .Delete , ctx , newSecret )
1993+ Expect (k8sClient .Create (ctx , newSecret )).To (Succeed ())
1994+
1995+ Eventually (func (g Gomega ) {
1996+ w := GetWatcher (watcherTest .Instance )
1997+ w .Spec .Auth .ApplicationCredentialSecret = newACSecretName
1998+ g .Expect (k8sClient .Update (ctx , w )).Should (Succeed ())
1999+ }, timeout , interval ).Should (Succeed ())
2000+
2001+ // New secret gets the consumer finalizer immediately (early in reconcile)
2002+ Eventually (func (g Gomega ) {
2003+ secret := th .GetSecret (types.NamespacedName {
2004+ Namespace : watcherTest .Instance .Namespace ,
2005+ Name : newACSecretName ,
2006+ })
2007+ g .Expect (secret .Finalizers ).To (
2008+ ContainElement (watcher .ACConsumerFinalizer ))
2009+ }, timeout , interval ).Should (Succeed ())
2010+
2011+ // Old secret keeps the finalizer until all services deploy (split pattern)
2012+ secret := th .GetSecret (types.NamespacedName {
2013+ Namespace : watcherTest .Instance .Namespace ,
2014+ Name : acSecretName ,
2015+ })
2016+ Expect (secret .Finalizers ).To (
2017+ ContainElement (watcher .ACConsumerFinalizer ))
2018+
2019+ // Simulate all watcher services deploying successfully
2020+ th .SimulateStatefulSetReplicaReady (watcherTest .WatcherAPIStatefulSet )
2021+ keystone .SimulateKeystoneEndpointReady (watcherTest .WatcherKeystoneEndpointName )
2022+ th .SimulateStatefulSetReplicaReady (watcherTest .WatcherApplierStatefulSet )
2023+ th .SimulateStatefulSetReplicaReady (watcherTest .WatcherDecisionEngineStatefulSet )
2024+
2025+ // Now the old secret's finalizer is removed and status updated
2026+ Eventually (func (g Gomega ) {
2027+ secret := th .GetSecret (types.NamespacedName {
2028+ Namespace : watcherTest .Instance .Namespace ,
2029+ Name : acSecretName ,
2030+ })
2031+ g .Expect (secret .Finalizers ).NotTo (
2032+ ContainElement (watcher .ACConsumerFinalizer ))
2033+ }, timeout , interval ).Should (Succeed ())
2034+
2035+ Eventually (func (g Gomega ) {
2036+ w := GetWatcher (watcherTest .Instance )
2037+ g .Expect (w .Status .ApplicationCredentialSecret ).To (Equal (newACSecretName ))
2038+ }, timeout , interval ).Should (Succeed ())
2039+ })
2040+
2041+ It ("should remove the consumer finalizer from AC secret on CR deletion" , func () {
2042+ Eventually (func (g Gomega ) {
2043+ secret := th .GetSecret (types.NamespacedName {
2044+ Namespace : watcherTest .Instance .Namespace ,
2045+ Name : acSecretName ,
2046+ })
2047+ g .Expect (secret .Finalizers ).To (
2048+ ContainElement (watcher .ACConsumerFinalizer ))
2049+ }, timeout , interval ).Should (Succeed ())
2050+
2051+ th .DeleteInstance (GetWatcher (watcherTest .Instance ))
2052+
2053+ secret := th .GetSecret (types.NamespacedName {
2054+ Namespace : watcherTest .Instance .Namespace ,
2055+ Name : acSecretName ,
2056+ })
2057+ Expect (secret .Finalizers ).NotTo (
2058+ ContainElement (watcher .ACConsumerFinalizer ))
2059+ })
2060+ })
2061+
18612062 When ("ApplicationCredential is adopted on existing deployment" , func () {
18622063 var appCredSecretName string
18632064 var appCredID string
0 commit comments