From 4f31f81f050def1205cc35a714a7d5bc4a317d92 Mon Sep 17 00:00:00 2001 From: santiago-ventura <160760719+santiago-ventura@users.noreply.github.com> Date: Mon, 12 Aug 2024 08:55:59 +0200 Subject: [PATCH 01/63] Add cache objects to facade.client and cf.client --- internal/cf/client.go | 11 +++--- internal/cf/instance.go | 1 - internal/facade/client.go | 75 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 81 insertions(+), 6 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index 4285f36..2beae12 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -31,11 +31,13 @@ const ( type organizationClient struct { organizationName string client cfclient.Client + resourcesCache *facade.Cache } type spaceClient struct { - spaceGuid string - client cfclient.Client + spaceGuid string + client cfclient.Client + resourcesCache *facade.Cache } type clientIdentifier struct { @@ -53,6 +55,7 @@ type clientCacheEntry struct { var ( cacheMutex = &sync.Mutex{} clientCache = make(map[clientIdentifier]*clientCacheEntry) + cfCache = facade.InitResourcesCache() ) func newOrganizationClient(organizationName string, url string, username string, password string) (*organizationClient, error) { @@ -83,7 +86,7 @@ func newOrganizationClient(organizationName string, url string, username string, if err != nil { return nil, err } - return &organizationClient{organizationName: organizationName, client: *c}, nil + return &organizationClient{organizationName: organizationName, client: *c, resourcesCache: cfCache}, nil } func newSpaceClient(spaceGuid string, url string, username string, password string) (*spaceClient, error) { @@ -114,7 +117,7 @@ func newSpaceClient(spaceGuid string, url string, username string, password stri if err != nil { return nil, err } - return &spaceClient{spaceGuid: spaceGuid, client: *c}, nil + return &spaceClient{spaceGuid: spaceGuid, client: *c, resourcesCache: cfCache}, nil } func NewOrganizationClient(organizationName string, url string, username string, password string) (facade.OrganizationClient, error) { diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 5da95ff..3f93c0b 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -48,7 +48,6 @@ func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOpt // If multiple instances are found, an error is returned. // The function add the parameter values to the orphan cf instance, so that can be adopted. func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]string) (*facade.Instance, error) { - var filterOpts instanceFilter if instanceOpts["name"] != "" { filterOpts = &instanceFilterName{name: instanceOpts["name"]} diff --git a/internal/facade/client.go b/internal/facade/client.go index 6990788..86d855c 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -5,7 +5,10 @@ SPDX-License-Identifier: Apache-2.0 package facade -import "context" +import ( + "context" + "sync" +) type Space struct { Guid string @@ -71,6 +74,9 @@ type OrganizationClient interface { AddAuditor(ctx context.Context, guid string, username string) error AddDeveloper(ctx context.Context, guid string, username string) error AddManager(ctx context.Context, guid string, username string) error + + AddSpaceInCanche(key string, space *Space) + GetSpaceFromCache(key string) (*Space, bool) } type OrganizationClientBuilder func(string, string, string, string) (OrganizationClient, error) @@ -88,6 +94,73 @@ type SpaceClient interface { DeleteBinding(ctx context.Context, guid string) error FindServicePlan(ctx context.Context, serviceOfferingName string, servicePlanName string, spaceGuid string) (string, error) + + AddInstanceInCanche(key string, instance *Instance) + GetInstanceFromCache(key string) (*Instance, bool) + AddBindingInCanche(key string, binding *Binding) + GetBindingFromCache(key string) (*Binding, bool) } type SpaceClientBuilder func(string, string, string, string) (SpaceClient, error) + +// Cache is a simple in-memory cache to store spaces, instances, and bindings +type Cache struct { + spaces map[string]*Space + instances map[string]*Instance + bindings map[string]*Binding + mutex sync.RWMutex +} + +// AddSpaceInCanche stores a space in the cache +func (c *Cache) AddSpaceInCanche(key string, space *Space) { + c.mutex.Lock() + defer c.mutex.Unlock() + c.spaces[key] = space +} + +// GetSpaceFromCache retrieves a space from the cache +func (c *Cache) GetSpaceFromCache(key string) (*Space, bool) { + c.mutex.RLock() + defer c.mutex.RUnlock() + space, found := c.spaces[key] + return space, found +} + +// AddInstanceInCanche stores an instance in the cache +func (c *Cache) AddInstanceInCanche(key string, instance *Instance) { + c.mutex.Lock() + defer c.mutex.Unlock() + c.instances[key] = instance +} + +// GetInstanceFromCache retrieves an instance from the cache +func (c *Cache) GetInstanceFromCache(key string) (*Instance, bool) { + c.mutex.RLock() + defer c.mutex.RUnlock() + instance, found := c.instances[key] + return instance, found +} + +// AddBindingInCanche stores a binding in the cache +func (c *Cache) AddBindingInCanche(key string, binding *Binding) { + c.mutex.Lock() + defer c.mutex.Unlock() + c.bindings[key] = binding +} + +// GetBindingFromCache retrieves a binding from the cache +func (c *Cache) GetBindingFromCache(key string) (*Binding, bool) { + c.mutex.RLock() + defer c.mutex.RUnlock() + binding, found := c.bindings[key] + return binding, found +} + +// InitResourcesCache initializes a new cache +func InitResourcesCache() *Cache { + return &Cache{ + spaces: make(map[string]*Space), + instances: make(map[string]*Instance), + bindings: make(map[string]*Binding), + } +} From fdbce796e2b1c1d9758e39b73f92cafebcf3453a Mon Sep 17 00:00:00 2001 From: santiago-ventura <160760719+santiago-ventura@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:36:54 +0200 Subject: [PATCH 02/63] Add initial logic for caching resources --- go.mod | 2 + go.sum | 30 ++++++++++++ internal/cf/client.go | 57 +++++++++++++++++++++++ internal/cf/instance.go | 96 +++++++++++++++++++++------------------ internal/facade/client.go | 10 ++-- 5 files changed, 148 insertions(+), 47 deletions(-) diff --git a/go.mod b/go.mod index 7506b26..33dde06 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/sap/cf-service-operator go 1.22.5 require ( + github.com/cloudfoundry-community/go-cfclient v0.0.0-20220930021109-9c4e6c59ccf1 github.com/cloudfoundry-community/go-cfclient/v3 v3.0.0-alpha.5 github.com/go-logr/logr v1.4.1 github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1 @@ -18,6 +19,7 @@ require ( ) require ( + github.com/Masterminds/semver v1.4.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect diff --git a/go.sum b/go.sum index 5e90dbd..e92dbb8 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,6 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= +github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= @@ -5,6 +8,8 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cloudfoundry-community/go-cfclient v0.0.0-20220930021109-9c4e6c59ccf1 h1:ef0OsiQjSQggHrLFAMDRiu6DfkVSElA5jfG1/Nkyu6c= +github.com/cloudfoundry-community/go-cfclient v0.0.0-20220930021109-9c4e6c59ccf1/go.mod h1:sgaEj3tRn0hwe7GPdEUwxrdOqjBzyjyvyOCGf1OQyZY= github.com/cloudfoundry-community/go-cfclient/v3 v3.0.0-alpha.5 h1:D6Kc4/ockmFHKNKUSeMNt3G82rddwZV6xWbjIXv8oaE= github.com/cloudfoundry-community/go-cfclient/v3 v3.0.0-alpha.5/go.mod h1:hFja9UPzLkfNxTF8EM0sqs7K+J2BCoLcjNmrMbP24xY= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q= @@ -40,13 +45,16 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -57,6 +65,8 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJY github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -64,6 +74,8 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -94,6 +106,7 @@ github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab5aMCs5usQRVBGThelUKBNnoSOuso= github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -110,6 +123,10 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -139,15 +156,21 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -157,18 +180,23 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -180,10 +208,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/cf/client.go b/internal/cf/client.go index 2beae12..677c084 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -7,6 +7,7 @@ package cf import ( "fmt" + "log" "sync" cfclient "github.com/cloudfoundry-community/go-cfclient/v3/client" @@ -215,3 +216,59 @@ func NewSpaceHealthChecker(spaceGuid string, url string, username string, passwo return client, err } + +func (c *spaceClient) populateResourcesCache(instanceOpts cfclient.ListOptions, bindingOptions cfclient.ServiceCredentialBindingListOptions, spaceOptions cfclient.SpaceListOptions) *Cache { + // // populate instance cache + // for { + // spaces, pager, err := c.Space.List(ctx, spaceOptions) + // if err != nil { + // log.Fatalf("Error listing spaces: %s", err) + // } + + // // Cache the service instance + // for _, space := range spaces { + // // ... some caching logic + // c.resourcesCache.AddSpaceInCanche(*space.Metadata.Labels[labelOwner], &facade.Space{ + // Guid: space.GUID, + // Name: space.Name, + // State: InstanceState(serviceInstance.LastOperation.State), + // }) + // } + + // serviceInstances = append(serviceInstances, srvInstanes...) + // if !pager.HasNextPage() { + // fmt.Printf("No more pages\n") + // break + // } + + // pager.NextPage(listOpts) + // } + + // populate instance cache + for { + srvInstanes, pager, err := c.ServiceInstances.List(ctx, listOpts) + if err != nil { + log.Fatalf("Error listing service instances: %s", err) + } + + // Cache the service instance + for _, serviceInstance := range srvInstanes { + // ... some caching logic + // instance := &facade.Instance{ + // Guid: serviceInstance.GUID, + // Name: serviceInstance.Name, + // State: InstanceState(serviceInstance.LastOperation.State), + // } + instance := facade.InitInstance(serviceInstance) + c.resourcesCache.AddInstanceInCanche(*serviceInstance.Metadata.Labels[labelOwner], instance) + } + + serviceInstances = append(serviceInstances, srvInstanes...) + if !pager.HasNextPage() { + fmt.Printf("No more pages\n") + break + } + + pager.NextPage(listOpts) + } +} diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 3f93c0b..2f31418 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -11,6 +11,7 @@ import ( "fmt" "strconv" + "github.com/cloudfoundry-community/go-cfclient" cfclient "github.com/cloudfoundry-community/go-cfclient/v3/client" cfresource "github.com/cloudfoundry-community/go-cfclient/v3/resource" "github.com/pkg/errors" @@ -48,6 +49,11 @@ func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOpt // If multiple instances are found, an error is returned. // The function add the parameter values to the orphan cf instance, so that can be adopted. func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]string) (*facade.Instance, error) { + // else check empty cache + + // check timeOut + // if expired, refresh cache (InitInstanceCache) + var filterOpts instanceFilter if instanceOpts["name"] != "" { filterOpts = &instanceFilterName{name: instanceOpts["name"]} @@ -76,49 +82,7 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s serviceInstance.Metadata.Annotations[annotationParameterHash] = ¶meterHashValue } - guid := serviceInstance.GUID - name := serviceInstance.Name - servicePlanGuid := serviceInstance.Relationships.ServicePlan.Data.GUID - generation, err := strconv.ParseInt(*serviceInstance.Metadata.Annotations[annotationGeneration], 10, 64) - if err != nil { - return nil, errors.Wrap(err, "error parsing service instance generation") - } - parameterHash := *serviceInstance.Metadata.Annotations[annotationParameterHash] - var state facade.InstanceState - switch serviceInstance.LastOperation.Type + ":" + serviceInstance.LastOperation.State { - case "create:in progress": - state = facade.InstanceStateCreating - case "create:succeeded": - state = facade.InstanceStateReady - case "create:failed": - state = facade.InstanceStateCreatedFailed - case "update:in progress": - state = facade.InstanceStateUpdating - case "update:succeeded": - state = facade.InstanceStateReady - case "update:failed": - state = facade.InstanceStateUpdateFailed - case "delete:in progress": - state = facade.InstanceStateDeleting - case "delete:succeeded": - state = facade.InstanceStateDeleted - case "delete:failed": - state = facade.InstanceStateDeleteFailed - default: - state = facade.InstanceStateUnknown - } - stateDescription := serviceInstance.LastOperation.Description - - return &facade.Instance{ - Guid: guid, - Name: name, - ServicePlanGuid: servicePlanGuid, - Owner: instanceOpts["owner"], - Generation: generation, - ParameterHash: parameterHash, - State: state, - StateDescription: stateDescription, - }, nil + returns InitInstance(serviceInstance), nil } // Required parameters (may not be initial): name, servicePlanGuid, owner, generation @@ -185,3 +149,49 @@ func (c *spaceClient) DeleteInstance(ctx context.Context, guid string) error { _, err := c.client.ServiceInstances.Delete(ctx, guid) return err } + +func InitInstance(serviceInstance cfresource.ServiceInstance) *facade.Instance { + guid := serviceInstance.GUID + name := serviceInstance.Name + servicePlanGuid := serviceInstance.Relationships.ServicePlan.Data.GUID + generation, err := strconv.ParseInt(*serviceInstance.Metadata.Annotations[annotationGeneration], 10, 64) + if err != nil { + return nil, errors.Wrap(err, "error parsing service instance generation") + } + parameterHash := *serviceInstance.Metadata.Annotations[annotationParameterHash] + var state facade.InstanceState + switch serviceInstance.LastOperation.Type + ":" + serviceInstance.LastOperation.State { + case "create:in progress": + state = facade.InstanceStateCreating + case "create:succeeded": + state = facade.InstanceStateReady + case "create:failed": + state = facade.InstanceStateCreatedFailed + case "update:in progress": + state = facade.InstanceStateUpdating + case "update:succeeded": + state = facade.InstanceStateReady + case "update:failed": + state = facade.InstanceStateUpdateFailed + case "delete:in progress": + state = facade.InstanceStateDeleting + case "delete:succeeded": + state = facade.InstanceStateDeleted + case "delete:failed": + state = facade.InstanceStateDeleteFailed + default: + state = facade.InstanceStateUnknown + } + stateDescription := serviceInstance.LastOperation.Description + + return &facade.Instance{ + Guid: guid, + Name: name, + ServicePlanGuid: servicePlanGuid, + Owner: instanceOpts["owner"], + Generation: generation, + ParameterHash: parameterHash, + State: state, + StateDescription: stateDescription, + } +} diff --git a/internal/facade/client.go b/internal/facade/client.go index 86d855c..72083d7 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -105,10 +105,12 @@ type SpaceClientBuilder func(string, string, string, string) (SpaceClient, error // Cache is a simple in-memory cache to store spaces, instances, and bindings type Cache struct { - spaces map[string]*Space - instances map[string]*Instance - bindings map[string]*Binding - mutex sync.RWMutex + spaces map[string]*Space + instances map[string]*Instance + bindings map[string]*Binding + mutex sync.RWMutex + initTime int64 + cacheTimeOut int64 } // AddSpaceInCanche stores a space in the cache From 5d28fbef81f05dc0ccdad6743507eb25ad9d9139 Mon Sep 17 00:00:00 2001 From: santiago-ventura <160760719+santiago-ventura@users.noreply.github.com> Date: Mon, 12 Aug 2024 11:21:05 +0200 Subject: [PATCH 03/63] fix some errors in code --- internal/cf/client.go | 41 +++++------------------------------------ internal/cf/instance.go | 6 +++--- 2 files changed, 8 insertions(+), 39 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index 677c084..b7da6e2 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -6,6 +6,7 @@ SPDX-License-Identifier: Apache-2.0 package cf import ( + "context" "fmt" "log" "sync" @@ -217,36 +218,10 @@ func NewSpaceHealthChecker(spaceGuid string, url string, username string, passwo return client, err } -func (c *spaceClient) populateResourcesCache(instanceOpts cfclient.ListOptions, bindingOptions cfclient.ServiceCredentialBindingListOptions, spaceOptions cfclient.SpaceListOptions) *Cache { - // // populate instance cache - // for { - // spaces, pager, err := c.Space.List(ctx, spaceOptions) - // if err != nil { - // log.Fatalf("Error listing spaces: %s", err) - // } - - // // Cache the service instance - // for _, space := range spaces { - // // ... some caching logic - // c.resourcesCache.AddSpaceInCanche(*space.Metadata.Labels[labelOwner], &facade.Space{ - // Guid: space.GUID, - // Name: space.Name, - // State: InstanceState(serviceInstance.LastOperation.State), - // }) - // } - - // serviceInstances = append(serviceInstances, srvInstanes...) - // if !pager.HasNextPage() { - // fmt.Printf("No more pages\n") - // break - // } - - // pager.NextPage(listOpts) - // } - +func (c *spaceClient) populateResourcesCache(ctx context.Context, instanceOpts cfclient.ServiceInstanceListOptions, bindingOptions cfclient.ServiceCredentialBindingListOptions, spaceOptions cfclient.SpaceListOptions) *Cache { // populate instance cache for { - srvInstanes, pager, err := c.ServiceInstances.List(ctx, listOpts) + srvInstanes, pager, err := c.client.ServiceInstances.List(ctx, &instanceOpts) if err != nil { log.Fatalf("Error listing service instances: %s", err) } @@ -254,21 +229,15 @@ func (c *spaceClient) populateResourcesCache(instanceOpts cfclient.ListOptions, // Cache the service instance for _, serviceInstance := range srvInstanes { // ... some caching logic - // instance := &facade.Instance{ - // Guid: serviceInstance.GUID, - // Name: serviceInstance.Name, - // State: InstanceState(serviceInstance.LastOperation.State), - // } - instance := facade.InitInstance(serviceInstance) + instance := InitInstance(*serviceInstance) c.resourcesCache.AddInstanceInCanche(*serviceInstance.Metadata.Labels[labelOwner], instance) } - serviceInstances = append(serviceInstances, srvInstanes...) if !pager.HasNextPage() { fmt.Printf("No more pages\n") break } - pager.NextPage(listOpts) + pager.NextPage(instanceOpts) } } diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 2f31418..91f0060 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -50,7 +50,7 @@ func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOpt // The function add the parameter values to the orphan cf instance, so that can be adopted. func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]string) (*facade.Instance, error) { // else check empty cache - + // check timeOut // if expired, refresh cache (InitInstanceCache) @@ -82,7 +82,7 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s serviceInstance.Metadata.Annotations[annotationParameterHash] = ¶meterHashValue } - returns InitInstance(serviceInstance), nil + return InitInstance(serviceInstance), nil } // Required parameters (may not be initial): name, servicePlanGuid, owner, generation @@ -153,7 +153,7 @@ func (c *spaceClient) DeleteInstance(ctx context.Context, guid string) error { func InitInstance(serviceInstance cfresource.ServiceInstance) *facade.Instance { guid := serviceInstance.GUID name := serviceInstance.Name - servicePlanGuid := serviceInstance.Relationships.ServicePlan.Data.GUID + servicePlanGuid := serviceInstance.Relationships.ServicePlan.Data.GUID generation, err := strconv.ParseInt(*serviceInstance.Metadata.Annotations[annotationGeneration], 10, 64) if err != nil { return nil, errors.Wrap(err, "error parsing service instance generation") From 860820de30f6be86873b20d9758c8319087bdb68 Mon Sep 17 00:00:00 2001 From: santiago-ventura <160760719+santiago-ventura@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:40:05 +0200 Subject: [PATCH 04/63] Add logic in cf client for populating the cf cache --- internal/cf/client.go | 39 +++++++++++++++++++++++++++++++++------ internal/cf/instance.go | 16 +++++++++++----- internal/facade/client.go | 39 +++++++++++++++------------------------ 3 files changed, 59 insertions(+), 35 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index b7da6e2..92ff7e1 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -57,7 +57,7 @@ type clientCacheEntry struct { var ( cacheMutex = &sync.Mutex{} clientCache = make(map[clientIdentifier]*clientCacheEntry) - cfCache = facade.InitResourcesCache() + cfCache = InitResourcesCache() ) func newOrganizationClient(organizationName string, url string, username string, password string) (*organizationClient, error) { @@ -88,6 +88,7 @@ func newOrganizationClient(organizationName string, url string, username string, if err != nil { return nil, err } + return &organizationClient{organizationName: organizationName, client: *c, resourcesCache: cfCache}, nil } @@ -119,7 +120,11 @@ func newSpaceClient(spaceGuid string, url string, username string, password stri if err != nil { return nil, err } - return &spaceClient{spaceGuid: spaceGuid, client: *c, resourcesCache: cfCache}, nil + + spcClient := &spaceClient{spaceGuid: spaceGuid, client: *c, resourcesCache: cfCache} + spcClient.populateResourcesCache() + return spcClient, nil + } func NewOrganizationClient(organizationName string, url string, username string, password string) (facade.OrganizationClient, error) { @@ -218,10 +223,29 @@ func NewSpaceHealthChecker(spaceGuid string, url string, username string, passwo return client, err } -func (c *spaceClient) populateResourcesCache(ctx context.Context, instanceOpts cfclient.ServiceInstanceListOptions, bindingOptions cfclient.ServiceCredentialBindingListOptions, spaceOptions cfclient.SpaceListOptions) *Cache { +// InitResourcesCache initializes a new cache +func InitResourcesCache() *facade.Cache { + return &facade.Cache{ + Spaces: make(map[string]*facade.Space), + Instances: make(map[string]*facade.Instance), + Bindings: make(map[string]*facade.Binding), + } +} + +func (c *spaceClient) populateResourcesCache() { + instanceOptions := cfclient.NewServiceInstanceListOptions() + instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) + instanceOptions.Page = 1 + instanceOptions.PerPage = 500 + instanceOptions.OrganizationGUIDs.EqualTo("21dc8fd6-ea17-49df-99e9-cacf57b479fc") + + // bindingOptions := cfclient.NewServiceCredentialBindingListOptions() + // spaceOptions := cfclient.NewSpaceListOptions() + + ctx := context.Background() // populate instance cache for { - srvInstanes, pager, err := c.client.ServiceInstances.List(ctx, &instanceOpts) + srvInstanes, pager, err := c.client.ServiceInstances.List(ctx, instanceOptions) if err != nil { log.Fatalf("Error listing service instances: %s", err) } @@ -229,7 +253,10 @@ func (c *spaceClient) populateResourcesCache(ctx context.Context, instanceOpts c // Cache the service instance for _, serviceInstance := range srvInstanes { // ... some caching logic - instance := InitInstance(*serviceInstance) + instance, err := InitInstance(serviceInstance) + if err != nil { + // TODO: add logic here + } c.resourcesCache.AddInstanceInCanche(*serviceInstance.Metadata.Labels[labelOwner], instance) } @@ -238,6 +265,6 @@ func (c *spaceClient) populateResourcesCache(ctx context.Context, instanceOpts c break } - pager.NextPage(instanceOpts) + pager.NextPage(instanceOptions) } } diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 91f0060..5ede7dc 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -11,7 +11,6 @@ import ( "fmt" "strconv" - "github.com/cloudfoundry-community/go-cfclient" cfclient "github.com/cloudfoundry-community/go-cfclient/v3/client" cfresource "github.com/cloudfoundry-community/go-cfclient/v3/resource" "github.com/pkg/errors" @@ -49,6 +48,13 @@ func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOpt // If multiple instances are found, an error is returned. // The function add the parameter values to the orphan cf instance, so that can be adopted. func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]string) (*facade.Instance, error) { + if len(c.resourcesCache.Instances) > 0 { // || timeOutCache == true { + c.populateResourcesCache() + serviceInstance := c.resourcesCache.GetInstanceFromCache(instanceOpts["owner"]) + } else { + serviceInstance := c.resourcesCache.GetInstanceFromCache() + } + // else check empty cache // check timeOut @@ -82,7 +88,7 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s serviceInstance.Metadata.Annotations[annotationParameterHash] = ¶meterHashValue } - return InitInstance(serviceInstance), nil + return InitInstance(serviceInstance) } // Required parameters (may not be initial): name, servicePlanGuid, owner, generation @@ -150,7 +156,7 @@ func (c *spaceClient) DeleteInstance(ctx context.Context, guid string) error { return err } -func InitInstance(serviceInstance cfresource.ServiceInstance) *facade.Instance { +func InitInstance(serviceInstance *cfresource.ServiceInstance) (*facade.Instance, error) { guid := serviceInstance.GUID name := serviceInstance.Name servicePlanGuid := serviceInstance.Relationships.ServicePlan.Data.GUID @@ -188,10 +194,10 @@ func InitInstance(serviceInstance cfresource.ServiceInstance) *facade.Instance { Guid: guid, Name: name, ServicePlanGuid: servicePlanGuid, - Owner: instanceOpts["owner"], + Owner: *serviceInstance.Metadata.Labels[labelOwner], Generation: generation, ParameterHash: parameterHash, State: state, StateDescription: stateDescription, - } + }, nil } diff --git a/internal/facade/client.go b/internal/facade/client.go index 72083d7..e6c1fd5 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -75,8 +75,8 @@ type OrganizationClient interface { AddDeveloper(ctx context.Context, guid string, username string) error AddManager(ctx context.Context, guid string, username string) error - AddSpaceInCanche(key string, space *Space) - GetSpaceFromCache(key string) (*Space, bool) + // AddSpaceInCanche(key string, space *Space) + // GetSpaceFromCache(key string) (*Space, bool) } type OrganizationClientBuilder func(string, string, string, string) (OrganizationClient, error) @@ -95,19 +95,19 @@ type SpaceClient interface { FindServicePlan(ctx context.Context, serviceOfferingName string, servicePlanName string, spaceGuid string) (string, error) - AddInstanceInCanche(key string, instance *Instance) - GetInstanceFromCache(key string) (*Instance, bool) - AddBindingInCanche(key string, binding *Binding) - GetBindingFromCache(key string) (*Binding, bool) + // AddInstanceInCanche(key string, instance *Instance) + // GetInstanceFromCache(key string) (*Instance, bool) + // AddBindingInCanche(key string, binding *Binding) + // GetBindingFromCache(key string) (*Binding, bool) } type SpaceClientBuilder func(string, string, string, string) (SpaceClient, error) // Cache is a simple in-memory cache to store spaces, instances, and bindings type Cache struct { - spaces map[string]*Space - instances map[string]*Instance - bindings map[string]*Binding + Spaces map[string]*Space + Instances map[string]*Instance + Bindings map[string]*Binding mutex sync.RWMutex initTime int64 cacheTimeOut int64 @@ -117,14 +117,14 @@ type Cache struct { func (c *Cache) AddSpaceInCanche(key string, space *Space) { c.mutex.Lock() defer c.mutex.Unlock() - c.spaces[key] = space + c.Spaces[key] = space } // GetSpaceFromCache retrieves a space from the cache func (c *Cache) GetSpaceFromCache(key string) (*Space, bool) { c.mutex.RLock() defer c.mutex.RUnlock() - space, found := c.spaces[key] + space, found := c.Spaces[key] return space, found } @@ -132,14 +132,14 @@ func (c *Cache) GetSpaceFromCache(key string) (*Space, bool) { func (c *Cache) AddInstanceInCanche(key string, instance *Instance) { c.mutex.Lock() defer c.mutex.Unlock() - c.instances[key] = instance + c.Instances[key] = instance } // GetInstanceFromCache retrieves an instance from the cache func (c *Cache) GetInstanceFromCache(key string) (*Instance, bool) { c.mutex.RLock() defer c.mutex.RUnlock() - instance, found := c.instances[key] + instance, found := c.Instances[key] return instance, found } @@ -147,22 +147,13 @@ func (c *Cache) GetInstanceFromCache(key string) (*Instance, bool) { func (c *Cache) AddBindingInCanche(key string, binding *Binding) { c.mutex.Lock() defer c.mutex.Unlock() - c.bindings[key] = binding + c.Bindings[key] = binding } // GetBindingFromCache retrieves a binding from the cache func (c *Cache) GetBindingFromCache(key string) (*Binding, bool) { c.mutex.RLock() defer c.mutex.RUnlock() - binding, found := c.bindings[key] + binding, found := c.Bindings[key] return binding, found } - -// InitResourcesCache initializes a new cache -func InitResourcesCache() *Cache { - return &Cache{ - spaces: make(map[string]*Space), - instances: make(map[string]*Instance), - bindings: make(map[string]*Binding), - } -} From 30a47ef8e68dca2f3e304927a3a1a007f881d3d7 Mon Sep 17 00:00:00 2001 From: santiago-ventura <160760719+santiago-ventura@users.noreply.github.com> Date: Tue, 13 Aug 2024 11:24:12 +0200 Subject: [PATCH 05/63] GetInstance function retrieving instance from Canch --- internal/cf/client.go | 62 ++++++++++++++++++++--------------------- internal/cf/instance.go | 21 ++++++++------ 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index 92ff7e1..8df457a 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -40,6 +40,7 @@ type spaceClient struct { spaceGuid string client cfclient.Client resourcesCache *facade.Cache + populateOnce sync.Once } type clientIdentifier struct { @@ -122,7 +123,7 @@ func newSpaceClient(spaceGuid string, url string, username string, password stri } spcClient := &spaceClient{spaceGuid: spaceGuid, client: *c, resourcesCache: cfCache} - spcClient.populateResourcesCache() + go spcClient.populateResourcesCache() return spcClient, nil } @@ -233,38 +234,37 @@ func InitResourcesCache() *facade.Cache { } func (c *spaceClient) populateResourcesCache() { - instanceOptions := cfclient.NewServiceInstanceListOptions() - instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) - instanceOptions.Page = 1 - instanceOptions.PerPage = 500 - instanceOptions.OrganizationGUIDs.EqualTo("21dc8fd6-ea17-49df-99e9-cacf57b479fc") - - // bindingOptions := cfclient.NewServiceCredentialBindingListOptions() - // spaceOptions := cfclient.NewSpaceListOptions() - - ctx := context.Background() - // populate instance cache - for { - srvInstanes, pager, err := c.client.ServiceInstances.List(ctx, instanceOptions) - if err != nil { - log.Fatalf("Error listing service instances: %s", err) - } - - // Cache the service instance - for _, serviceInstance := range srvInstanes { - // ... some caching logic - instance, err := InitInstance(serviceInstance) + c.populateOnce.Do(func() { + instanceOptions := cfclient.NewServiceInstanceListOptions() + instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) + instanceOptions.Page = 1 + instanceOptions.PerPage = 500 + instanceOptions.OrganizationGUIDs.EqualTo("21dc8fd6-ea17-49df-99e9-cacf57b479fc") + + ctx := context.Background() + // populate instance cache + for { + srvInstanes, pager, err := c.client.ServiceInstances.List(ctx, instanceOptions) if err != nil { - // TODO: add logic here + log.Fatalf("Error listing service instances: %s", err) } - c.resourcesCache.AddInstanceInCanche(*serviceInstance.Metadata.Labels[labelOwner], instance) - } - if !pager.HasNextPage() { - fmt.Printf("No more pages\n") - break - } + // Cache the service instance + for _, serviceInstance := range srvInstanes { + // ... some caching logic + instance, err := InitInstance(serviceInstance) + // instance is added to cache only if error is nil + if err == nil { + c.resourcesCache.AddInstanceInCanche(*serviceInstance.Metadata.Labels[labelOwner], instance) + } + } - pager.NextPage(instanceOptions) - } + if !pager.HasNextPage() { + fmt.Printf("No more pages\n") + break + } + + pager.NextPage(instanceOptions) + } + }) } diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 5ede7dc..85bbbf3 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -48,17 +48,22 @@ func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOpt // If multiple instances are found, an error is returned. // The function add the parameter values to the orphan cf instance, so that can be adopted. func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]string) (*facade.Instance, error) { - if len(c.resourcesCache.Instances) > 0 { // || timeOutCache == true { + // Attempt to retrieve instance from Cache + var inCache bool + var instance *facade.Instance + if len(c.resourcesCache.Instances) == 0 { c.populateResourcesCache() - serviceInstance := c.resourcesCache.GetInstanceFromCache(instanceOpts["owner"]) + instance, inCache = c.resourcesCache.GetInstanceFromCache(instanceOpts["owner"]) } else { - serviceInstance := c.resourcesCache.GetInstanceFromCache() + instance, inCache = c.resourcesCache.GetInstanceFromCache(instanceOpts["owner"]) } - // else check empty cache + if inCache { + return instance, nil + } - // check timeOut - // if expired, refresh cache (InitInstanceCache) + // Attempt to retrieve instance from Cloud Foundry + var serviceInstance *cfresource.ServiceInstance var filterOpts instanceFilter if instanceOpts["name"] != "" { @@ -71,14 +76,12 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s if err != nil { return nil, fmt.Errorf("failed to list service instances: %w", err) } - if len(serviceInstances) == 0 { return nil, nil } else if len(serviceInstances) > 1 { return nil, errors.New(fmt.Sprintf("found multiple service instances with owner: %s", instanceOpts["owner"])) } - - serviceInstance := serviceInstances[0] + serviceInstance = serviceInstances[0] // add parameter values to the orphan cf instance if instanceOpts["name"] != "" { From 402e470f27d4d6dc99e70e2b55380f70a3f0497e Mon Sep 17 00:00:00 2001 From: santiago-ventura <160760719+santiago-ventura@users.noreply.github.com> Date: Tue, 13 Aug 2024 16:37:32 +0200 Subject: [PATCH 06/63] "Add go routine for cache execution. Include use of env variable for the interval" --- go.mod | 2 -- go.sum | 30 ---------------------------- internal/cf/client.go | 44 +++++++++++++++++++++++++++++++++++++---- internal/cf/instance.go | 11 ++++------- 4 files changed, 44 insertions(+), 43 deletions(-) diff --git a/go.mod b/go.mod index 33dde06..7506b26 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/sap/cf-service-operator go 1.22.5 require ( - github.com/cloudfoundry-community/go-cfclient v0.0.0-20220930021109-9c4e6c59ccf1 github.com/cloudfoundry-community/go-cfclient/v3 v3.0.0-alpha.5 github.com/go-logr/logr v1.4.1 github.com/maxbrunsfeld/counterfeiter/v6 v6.8.1 @@ -19,7 +18,6 @@ require ( ) require ( - github.com/Masterminds/semver v1.4.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect diff --git a/go.sum b/go.sum index e92dbb8..5e90dbd 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,3 @@ -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= -github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= @@ -8,8 +5,6 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cloudfoundry-community/go-cfclient v0.0.0-20220930021109-9c4e6c59ccf1 h1:ef0OsiQjSQggHrLFAMDRiu6DfkVSElA5jfG1/Nkyu6c= -github.com/cloudfoundry-community/go-cfclient v0.0.0-20220930021109-9c4e6c59ccf1/go.mod h1:sgaEj3tRn0hwe7GPdEUwxrdOqjBzyjyvyOCGf1OQyZY= github.com/cloudfoundry-community/go-cfclient/v3 v3.0.0-alpha.5 h1:D6Kc4/ockmFHKNKUSeMNt3G82rddwZV6xWbjIXv8oaE= github.com/cloudfoundry-community/go-cfclient/v3 v3.0.0-alpha.5/go.mod h1:hFja9UPzLkfNxTF8EM0sqs7K+J2BCoLcjNmrMbP24xY= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q= @@ -45,16 +40,13 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -65,8 +57,6 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJY github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -74,8 +64,6 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -106,7 +94,6 @@ github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab5aMCs5usQRVBGThelUKBNnoSOuso= github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -123,10 +110,6 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -156,21 +139,15 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/oauth2 v0.0.0-20190130055435-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -180,23 +157,18 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -208,12 +180,10 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/cf/client.go b/internal/cf/client.go index 8df457a..b8a9328 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -9,7 +9,10 @@ import ( "context" "fmt" "log" + "os" + "strconv" "sync" + "time" cfclient "github.com/cloudfoundry-community/go-cfclient/v3/client" cfconfig "github.com/cloudfoundry-community/go-cfclient/v3/config" @@ -56,9 +59,10 @@ type clientCacheEntry struct { } var ( - cacheMutex = &sync.Mutex{} - clientCache = make(map[clientIdentifier]*clientCacheEntry) - cfCache = InitResourcesCache() + cacheMutex = &sync.Mutex{} + clientCache = make(map[clientIdentifier]*clientCacheEntry) + cfCache = InitResourcesCache() + isResourceCacheEnabled = false ) func newOrganizationClient(organizationName string, url string, username string, password string) (*organizationClient, error) { @@ -123,7 +127,12 @@ func newSpaceClient(spaceGuid string, url string, username string, password stri } spcClient := &spaceClient{spaceGuid: spaceGuid, client: *c, resourcesCache: cfCache} - go spcClient.populateResourcesCache() + + isResourceCacheEnabled, _ := strconv.ParseBool(os.Getenv("ENABLE_RESOURCES_CACHE")) + if isResourceCacheEnabled { + spcClient.refreshCache() + } + return spcClient, nil } @@ -235,6 +244,10 @@ func InitResourcesCache() *facade.Cache { func (c *spaceClient) populateResourcesCache() { c.populateOnce.Do(func() { + + // TODO: Create the space options + // TODO: Add for loop for space + instanceOptions := cfclient.NewServiceInstanceListOptions() instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) instanceOptions.Page = 1 @@ -267,4 +280,27 @@ func (c *spaceClient) populateResourcesCache() { pager.NextPage(instanceOptions) } }) + + // TODO: Add for loop for bindings +} + +func (c *spaceClient) refreshCache() { + cacheInterval := os.Getenv("RESOURCES_CACHE_INTERVAL") + var interval time.Duration + if cacheInterval == "" { + cacheInterval = "300" // Default to 5 minutes + log.Println("Empty RESOURCES_CACHE_INTERVAL, using 5 minutes as default cache interval.") + } + interval, err := time.ParseDuration(cacheInterval + "s") + if err != nil { + log.Fatalf("Invalid RESOURCES_CACHE_INTERVAL: %s.", err) + } + + go func() { + for { + c.populateResourcesCache() + log.Println("Last resource cached time", time.Now()) + time.Sleep(interval) + } + }() } diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 85bbbf3..0e5dad0 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -49,16 +49,13 @@ func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOpt // The function add the parameter values to the orphan cf instance, so that can be adopted. func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]string) (*facade.Instance, error) { // Attempt to retrieve instance from Cache - var inCache bool + var instanceInCache bool var instance *facade.Instance - if len(c.resourcesCache.Instances) == 0 { - c.populateResourcesCache() - instance, inCache = c.resourcesCache.GetInstanceFromCache(instanceOpts["owner"]) - } else { - instance, inCache = c.resourcesCache.GetInstanceFromCache(instanceOpts["owner"]) + if len(c.resourcesCache.Instances) != 0 { + instance, instanceInCache = c.resourcesCache.GetInstanceFromCache(instanceOpts["owner"]) } - if inCache { + if instanceInCache { return instance, nil } From 21011cb5e5c26e16a9254eff15182470699279c7 Mon Sep 17 00:00:00 2001 From: santiago-ventura <160760719+santiago-ventura@users.noreply.github.com> Date: Mon, 19 Aug 2024 08:32:29 +0200 Subject: [PATCH 07/63] Check for nil and ensure resourcesCache is initialized --- internal/cf/client.go | 11 ++++++++--- internal/cf/instance.go | 5 +++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index b8a9328..dbbe51d 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -61,7 +61,7 @@ type clientCacheEntry struct { var ( cacheMutex = &sync.Mutex{} clientCache = make(map[clientIdentifier]*clientCacheEntry) - cfCache = InitResourcesCache() + cfCache *facade.Cache isResourceCacheEnabled = false ) @@ -126,7 +126,7 @@ func newSpaceClient(spaceGuid string, url string, username string, password stri return nil, err } - spcClient := &spaceClient{spaceGuid: spaceGuid, client: *c, resourcesCache: cfCache} + spcClient := &spaceClient{spaceGuid: spaceGuid, client: *c} isResourceCacheEnabled, _ := strconv.ParseBool(os.Getenv("ENABLE_RESOURCES_CACHE")) if isResourceCacheEnabled { @@ -285,10 +285,15 @@ func (c *spaceClient) populateResourcesCache() { } func (c *spaceClient) refreshCache() { + resrcCache := InitResourcesCache() + c.resourcesCache = resrcCache + cfCache = c.resourcesCache + cacheInterval := os.Getenv("RESOURCES_CACHE_INTERVAL") var interval time.Duration if cacheInterval == "" { - cacheInterval = "300" // Default to 5 minutes + // TODO. put this code back, cacheInterval = "300" // Default to 5 minutes + cacheInterval = "15" log.Println("Empty RESOURCES_CACHE_INTERVAL, using 5 minutes as default cache interval.") } interval, err := time.ParseDuration(cacheInterval + "s") diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 0e5dad0..edd155c 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -48,6 +48,11 @@ func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOpt // If multiple instances are found, an error is returned. // The function add the parameter values to the orphan cf instance, so that can be adopted. func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]string) (*facade.Instance, error) { + // Ensure resourcesCache is initialized + if c.resourcesCache == nil { + c.resourcesCache = InitResourcesCache() + } + // Attempt to retrieve instance from Cache var instanceInCache bool var instance *facade.Instance From 9df5828c1630624dd05f84a7bbcae2e74a1b64b3 Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Mon, 19 Aug 2024 20:13:25 +0530 Subject: [PATCH 08/63] changes to client.go to refresh go routine --- internal/cf/client.go | 114 +++++++++++++++++++++++------------------- 1 file changed, 62 insertions(+), 52 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index dbbe51d..65fb0a0 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -10,7 +10,6 @@ import ( "fmt" "log" "os" - "strconv" "sync" "time" @@ -36,14 +35,14 @@ const ( type organizationClient struct { organizationName string client cfclient.Client - resourcesCache *facade.Cache + //resourcesCache *facade.Cache } type spaceClient struct { spaceGuid string client cfclient.Client resourcesCache *facade.Cache - populateOnce sync.Once + cancelFunc context.CancelFunc } type clientIdentifier struct { @@ -59,10 +58,10 @@ type clientCacheEntry struct { } var ( - cacheMutex = &sync.Mutex{} - clientCache = make(map[clientIdentifier]*clientCacheEntry) - cfCache *facade.Cache - isResourceCacheEnabled = false + cacheMutex = &sync.Mutex{} + clientCache = make(map[clientIdentifier]*clientCacheEntry) + cfCache *facade.Cache + //isResourceCacheEnabled = false ) func newOrganizationClient(organizationName string, url string, username string, password string) (*organizationClient, error) { @@ -94,7 +93,7 @@ func newOrganizationClient(organizationName string, url string, username string, return nil, err } - return &organizationClient{organizationName: organizationName, client: *c, resourcesCache: cfCache}, nil + return &organizationClient{organizationName: organizationName, client: *c}, nil } func newSpaceClient(spaceGuid string, url string, username string, password string) (*spaceClient, error) { @@ -128,11 +127,13 @@ func newSpaceClient(spaceGuid string, url string, username string, password stri spcClient := &spaceClient{spaceGuid: spaceGuid, client: *c} - isResourceCacheEnabled, _ := strconv.ParseBool(os.Getenv("ENABLE_RESOURCES_CACHE")) - if isResourceCacheEnabled { - spcClient.refreshCache() - } - + // isResourceCacheEnabled, _ := strconv.ParseBool(os.Getenv("ENABLE_RESOURCES_CACHE")) + // if isResourceCacheEnabled { + // spcClient.refreshCache() + // } + ctx, cancel := context.WithCancel(context.Background()) + spcClient.cancelFunc = cancel + spcClient.refreshCache(ctx) return spcClient, nil } @@ -181,7 +182,7 @@ func NewSpaceClient(spaceGuid string, url string, username string, password stri var client *spaceClient = nil if isInCache { // re-use CF client from cache and wrap it as spaceClient - client = &spaceClient{spaceGuid: spaceGuid, client: cacheEntry.client} + client = &spaceClient{spaceGuid: spaceGuid, client: cacheEntry.client, resourcesCache: cfCache} if cacheEntry.password != password { // password was rotated => delete client from cache and create a new one below delete(clientCache, identifier) @@ -243,50 +244,47 @@ func InitResourcesCache() *facade.Cache { } func (c *spaceClient) populateResourcesCache() { - c.populateOnce.Do(func() { - - // TODO: Create the space options - // TODO: Add for loop for space - - instanceOptions := cfclient.NewServiceInstanceListOptions() - instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) - instanceOptions.Page = 1 - instanceOptions.PerPage = 500 - instanceOptions.OrganizationGUIDs.EqualTo("21dc8fd6-ea17-49df-99e9-cacf57b479fc") - ctx := context.Background() - // populate instance cache - for { - srvInstanes, pager, err := c.client.ServiceInstances.List(ctx, instanceOptions) - if err != nil { - log.Fatalf("Error listing service instances: %s", err) - } - - // Cache the service instance - for _, serviceInstance := range srvInstanes { - // ... some caching logic - instance, err := InitInstance(serviceInstance) - // instance is added to cache only if error is nil - if err == nil { - c.resourcesCache.AddInstanceInCanche(*serviceInstance.Metadata.Labels[labelOwner], instance) - } - } + // TODO: Create the space options + // TODO: Add for loop for space + + instanceOptions := cfclient.NewServiceInstanceListOptions() + instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) + instanceOptions.Page = 1 + instanceOptions.PerPage = 500 + //instanceOptions.OrganizationGUIDs.EqualTo("21dc8fd6-ea17-49df-99e9-cacf57b479fc") + + ctx := context.Background() + // populate instance cache + for { + srvInstanes, pager, err := c.client.ServiceInstances.List(ctx, instanceOptions) + if err != nil { + log.Fatalf("Error listing service instances: %s", err) + } - if !pager.HasNextPage() { - fmt.Printf("No more pages\n") - break + // Cache the service instance + for _, serviceInstance := range srvInstanes { + // ... some caching logic + instance, err := InitInstance(serviceInstance) + // instance is added to cache only if error is nil + if err == nil { + c.resourcesCache.AddInstanceInCanche(*serviceInstance.Metadata.Labels[labelOwner], instance) } + } - pager.NextPage(instanceOptions) + if !pager.HasNextPage() { + fmt.Printf("No more pages\n") + break } - }) + + pager.NextPage(instanceOptions) + } // TODO: Add for loop for bindings } -func (c *spaceClient) refreshCache() { - resrcCache := InitResourcesCache() - c.resourcesCache = resrcCache +func (c *spaceClient) refreshCache(ctx context.Context) { + c.resourcesCache = InitResourcesCache() cfCache = c.resourcesCache cacheInterval := os.Getenv("RESOURCES_CACHE_INTERVAL") @@ -303,9 +301,21 @@ func (c *spaceClient) refreshCache() { go func() { for { - c.populateResourcesCache() - log.Println("Last resource cached time", time.Now()) - time.Sleep(interval) + select { + case <-ctx.Done(): + log.Println("Stopping cache refresh goroutine") + return + default: + c.populateResourcesCache() + log.Println("Last resource cached time", time.Now()) + time.Sleep(interval) + } } }() } + +func (c *spaceClient) StopCacheRefresh() { + if c.cancelFunc != nil { + c.cancelFunc() + } +} From e625b82d1c6be9dc6bc444808e16bec0461186a0 Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Mon, 19 Aug 2024 23:05:41 +0530 Subject: [PATCH 09/63] added channel for cache refresh --- internal/cf/client.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/internal/cf/client.go b/internal/cf/client.go index 65fb0a0..0b21033 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -299,6 +299,8 @@ func (c *spaceClient) refreshCache(ctx context.Context) { log.Fatalf("Invalid RESOURCES_CACHE_INTERVAL: %s.", err) } + doneCh := make(chan bool) // Channel to signal cache refresh completion + go func() { for { select { @@ -308,10 +310,19 @@ func (c *spaceClient) refreshCache(ctx context.Context) { default: c.populateResourcesCache() log.Println("Last resource cached time", time.Now()) + doneCh <- true // Signal that cache has been refreshed time.Sleep(interval) } } }() + + // Example of waiting for a single cache refresh (could be in a different part of code) + select { + case <-doneCh: + log.Println("Cache has been refreshed") + case <-ctx.Done(): + log.Println("Context cancelled") + } } func (c *spaceClient) StopCacheRefresh() { From 9a5bef6f1d867c56d2943d9ad7de3782dec5ffcb Mon Sep 17 00:00:00 2001 From: santiago-ventura <160760719+santiago-ventura@users.noreply.github.com> Date: Tue, 20 Aug 2024 08:33:27 +0200 Subject: [PATCH 10/63] remove no needed comment --- internal/cf/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index 0b21033..0b79928 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -316,7 +316,7 @@ func (c *spaceClient) refreshCache(ctx context.Context) { } }() - // Example of waiting for a single cache refresh (could be in a different part of code) + // Waiting for a single cache refresh select { case <-doneCh: log.Println("Cache has been refreshed") From 824a3bfd8a01f803e771052c6b95463d335bc525 Mon Sep 17 00:00:00 2001 From: santiago-ventura <160760719+santiago-ventura@users.noreply.github.com> Date: Tue, 20 Aug 2024 08:43:50 +0200 Subject: [PATCH 11/63] correct typo --- internal/cf/client.go | 2 +- internal/facade/client.go | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index 0b79928..be24867 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -268,7 +268,7 @@ func (c *spaceClient) populateResourcesCache() { instance, err := InitInstance(serviceInstance) // instance is added to cache only if error is nil if err == nil { - c.resourcesCache.AddInstanceInCanche(*serviceInstance.Metadata.Labels[labelOwner], instance) + c.resourcesCache.AddInstanceInCache(*serviceInstance.Metadata.Labels[labelOwner], instance) } } diff --git a/internal/facade/client.go b/internal/facade/client.go index e6c1fd5..23ed7b2 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -75,7 +75,7 @@ type OrganizationClient interface { AddDeveloper(ctx context.Context, guid string, username string) error AddManager(ctx context.Context, guid string, username string) error - // AddSpaceInCanche(key string, space *Space) + // AddSpaceInCache(key string, space *Space) // GetSpaceFromCache(key string) (*Space, bool) } @@ -95,9 +95,9 @@ type SpaceClient interface { FindServicePlan(ctx context.Context, serviceOfferingName string, servicePlanName string, spaceGuid string) (string, error) - // AddInstanceInCanche(key string, instance *Instance) + // AddInstanceInCache(key string, instance *Instance) // GetInstanceFromCache(key string) (*Instance, bool) - // AddBindingInCanche(key string, binding *Binding) + // AddBindingInCache(key string, binding *Binding) // GetBindingFromCache(key string) (*Binding, bool) } @@ -113,8 +113,8 @@ type Cache struct { cacheTimeOut int64 } -// AddSpaceInCanche stores a space in the cache -func (c *Cache) AddSpaceInCanche(key string, space *Space) { +// AddSpaceInCache stores a space in the cache +func (c *Cache) AddSpaceInCache(key string, space *Space) { c.mutex.Lock() defer c.mutex.Unlock() c.Spaces[key] = space @@ -128,8 +128,8 @@ func (c *Cache) GetSpaceFromCache(key string) (*Space, bool) { return space, found } -// AddInstanceInCanche stores an instance in the cache -func (c *Cache) AddInstanceInCanche(key string, instance *Instance) { +// AddInstanceInCache stores an instance in the cache +func (c *Cache) AddInstanceInCache(key string, instance *Instance) { c.mutex.Lock() defer c.mutex.Unlock() c.Instances[key] = instance @@ -143,8 +143,8 @@ func (c *Cache) GetInstanceFromCache(key string) (*Instance, bool) { return instance, found } -// AddBindingInCanche stores a binding in the cache -func (c *Cache) AddBindingInCanche(key string, binding *Binding) { +// AddBindingInCache stores a binding in the cache +func (c *Cache) AddBindingInCache(key string, binding *Binding) { c.mutex.Lock() defer c.mutex.Unlock() c.Bindings[key] = binding From 1d3c1a6060e244650ce3caab6e2ff6b03e229cad Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Wed, 21 Aug 2024 10:07:21 +0530 Subject: [PATCH 12/63] populate cache on demand based on expiry --- internal/cf/client.go | 108 ++++++++++++-------------------------- internal/cf/instance.go | 34 +++++++----- internal/facade/client.go | 32 ++++++++--- 3 files changed, 81 insertions(+), 93 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index be24867..3668550 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -9,7 +9,6 @@ import ( "context" "fmt" "log" - "os" "sync" "time" @@ -51,17 +50,19 @@ type clientIdentifier struct { } type clientCacheEntry struct { - url string - username string - password string - client cfclient.Client + url string + username string + password string + client cfclient.Client + resourcesCache *facade.Cache } var ( - cacheMutex = &sync.Mutex{} - clientCache = make(map[clientIdentifier]*clientCacheEntry) - cfCache *facade.Cache - //isResourceCacheEnabled = false + cacheMutex = &sync.Mutex{} + clientCache = make(map[clientIdentifier]*clientCacheEntry) + cfCache *facade.Cache + isResourceCacheEnabled = true + refreshCacheMutex = &sync.Mutex{} ) func newOrganizationClient(organizationName string, url string, username string, password string) (*organizationClient, error) { @@ -92,6 +93,11 @@ func newOrganizationClient(organizationName string, url string, username string, if err != nil { return nil, err } + // isResourceCacheEnabled, _ := strconv.ParseBool(os.Getenv("ENABLE_RESOURCES_CACHE")) + // if isResourceCacheEnabled && c.resourcesCache == nil { + // c.resourcesCache = InitResourcesCache() + // c.populateResourcesCache() + // } return &organizationClient{organizationName: organizationName, client: *c}, nil } @@ -127,15 +133,7 @@ func newSpaceClient(spaceGuid string, url string, username string, password stri spcClient := &spaceClient{spaceGuid: spaceGuid, client: *c} - // isResourceCacheEnabled, _ := strconv.ParseBool(os.Getenv("ENABLE_RESOURCES_CACHE")) - // if isResourceCacheEnabled { - // spcClient.refreshCache() - // } - ctx, cancel := context.WithCancel(context.Background()) - spcClient.cancelFunc = cancel - spcClient.refreshCache(ctx) return spcClient, nil - } func NewOrganizationClient(organizationName string, url string, username string, password string) (facade.OrganizationClient, error) { @@ -179,10 +177,10 @@ func NewSpaceClient(spaceGuid string, url string, username string, password stri cacheEntry, isInCache := clientCache[identifier] var err error = nil - var client *spaceClient = nil + var spcClient *spaceClient = nil if isInCache { // re-use CF client from cache and wrap it as spaceClient - client = &spaceClient{spaceGuid: spaceGuid, client: cacheEntry.client, resourcesCache: cfCache} + spcClient = &spaceClient{spaceGuid: spaceGuid, client: cacheEntry.client, resourcesCache: cfCache} if cacheEntry.password != password { // password was rotated => delete client from cache and create a new one below delete(clientCache, identifier) @@ -192,14 +190,20 @@ func NewSpaceClient(spaceGuid string, url string, username string, password stri if !isInCache { // create new CF client and wrap it as spaceClient - client, err = newSpaceClient(spaceGuid, url, username, password) + spcClient, err = newSpaceClient(spaceGuid, url, username, password) if err == nil { // add CF client to cache - clientCache[identifier] = &clientCacheEntry{url: url, username: username, password: password, client: client.client} + clientCache[identifier] = &clientCacheEntry{url: url, username: username, password: password, client: spcClient.client} } } - return client, err + //isResourceCacheEnabled, _ := strconv.ParseBool(os.Getenv("ENABLE_RESOURCES_CACHE")) + if isResourceCacheEnabled && spcClient.resourcesCache == nil { + spcClient.resourcesCache = InitResourcesCache() + spcClient.populateResourcesCache() + } + + return spcClient, err } func NewSpaceHealthChecker(spaceGuid string, url string, username string, password string) (facade.SpaceHealthChecker, error) { @@ -237,16 +241,18 @@ func NewSpaceHealthChecker(spaceGuid string, url string, username string, passwo // InitResourcesCache initializes a new cache func InitResourcesCache() *facade.Cache { return &facade.Cache{ - Spaces: make(map[string]*facade.Space), - Instances: make(map[string]*facade.Instance), - Bindings: make(map[string]*facade.Binding), + Spaces: make(map[string]*facade.Space), + Instances: make(map[string]*facade.Instance), + Bindings: make(map[string]*facade.Binding), + CacheTimeOut: 1 * time.Minute, } } func (c *spaceClient) populateResourcesCache() { - // TODO: Create the space options // TODO: Add for loop for space + refreshCacheMutex.Lock() + defer refreshCacheMutex.Unlock() instanceOptions := cfclient.NewServiceInstanceListOptions() instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) @@ -280,53 +286,7 @@ func (c *spaceClient) populateResourcesCache() { pager.NextPage(instanceOptions) } - // TODO: Add for loop for bindings -} - -func (c *spaceClient) refreshCache(ctx context.Context) { - c.resourcesCache = InitResourcesCache() cfCache = c.resourcesCache - - cacheInterval := os.Getenv("RESOURCES_CACHE_INTERVAL") - var interval time.Duration - if cacheInterval == "" { - // TODO. put this code back, cacheInterval = "300" // Default to 5 minutes - cacheInterval = "15" - log.Println("Empty RESOURCES_CACHE_INTERVAL, using 5 minutes as default cache interval.") - } - interval, err := time.ParseDuration(cacheInterval + "s") - if err != nil { - log.Fatalf("Invalid RESOURCES_CACHE_INTERVAL: %s.", err) - } - - doneCh := make(chan bool) // Channel to signal cache refresh completion - - go func() { - for { - select { - case <-ctx.Done(): - log.Println("Stopping cache refresh goroutine") - return - default: - c.populateResourcesCache() - log.Println("Last resource cached time", time.Now()) - doneCh <- true // Signal that cache has been refreshed - time.Sleep(interval) - } - } - }() - - // Waiting for a single cache refresh - select { - case <-doneCh: - log.Println("Cache has been refreshed") - case <-ctx.Done(): - log.Println("Context cancelled") - } -} - -func (c *spaceClient) StopCacheRefresh() { - if c.cancelFunc != nil { - c.cancelFunc() - } + c.resourcesCache.SetLastCacheTime() + // TODO: Add for loop for bindings } diff --git a/internal/cf/instance.go b/internal/cf/instance.go index edd155c..c2a8f47 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -48,22 +48,30 @@ func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOpt // If multiple instances are found, an error is returned. // The function add the parameter values to the orphan cf instance, so that can be adopted. func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]string) (*facade.Instance, error) { - // Ensure resourcesCache is initialized - if c.resourcesCache == nil { - c.resourcesCache = InitResourcesCache() - } - // Attempt to retrieve instance from Cache - var instanceInCache bool - var instance *facade.Instance - if len(c.resourcesCache.Instances) != 0 { - instance, instanceInCache = c.resourcesCache.GetInstanceFromCache(instanceOpts["owner"]) - } + if isResourceCacheEnabled { + // Ensure resourcesCache is initialized + if c.resourcesCache == nil { + c.resourcesCache = InitResourcesCache() + } - if instanceInCache { - return instance, nil - } + // Attempt to retrieve instance from Cache + var instanceInCache bool + var instance *facade.Instance + + if len(c.resourcesCache.Instances) != 0 { + if c.resourcesCache.IsCacheExpired() { + c.populateResourcesCache() + } + + instance, instanceInCache = c.resourcesCache.GetInstanceFromCache(instanceOpts["owner"]) + } + + if instanceInCache { + return instance, nil + } + } // Attempt to retrieve instance from Cloud Foundry var serviceInstance *cfresource.ServiceInstance diff --git a/internal/facade/client.go b/internal/facade/client.go index 23ed7b2..4557875 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -8,6 +8,7 @@ package facade import ( "context" "sync" + "time" ) type Space struct { @@ -105,12 +106,31 @@ type SpaceClientBuilder func(string, string, string, string) (SpaceClient, error // Cache is a simple in-memory cache to store spaces, instances, and bindings type Cache struct { - Spaces map[string]*Space - Instances map[string]*Instance - Bindings map[string]*Binding - mutex sync.RWMutex - initTime int64 - cacheTimeOut int64 + Spaces map[string]*Space + Instances map[string]*Instance + Bindings map[string]*Binding + mutex sync.RWMutex + lastCacheTime time.Time + CacheTimeOut time.Duration +} + +func (c *Cache) GetLastCacheTime() time.Time { + return c.lastCacheTime +} + +func (c *Cache) GetCacheTimeOut() time.Duration { + return c.CacheTimeOut +} + +func (c *Cache) IsCacheExpired() bool { + + expiryTime := time.Until(c.lastCacheTime) + + return expiryTime > c.CacheTimeOut + +} +func (c *Cache) SetLastCacheTime() { + c.lastCacheTime = time.Now() } // AddSpaceInCache stores a space in the cache From 8df71cb73177f2cb7be1be1ce063c643433a6b31 Mon Sep 17 00:00:00 2001 From: santiago-ventura <160760719+santiago-ventura@users.noreply.github.com> Date: Wed, 21 Aug 2024 09:12:30 +0200 Subject: [PATCH 13/63] Refactoring Cache objects and adding more public functions. --- internal/cf/client.go | 17 ++----- internal/cf/instance.go | 4 +- internal/facade/client.go | 96 +++++++++++++++++++++++++++++++++------ 3 files changed, 87 insertions(+), 30 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index 3668550..963761a 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -10,7 +10,6 @@ import ( "fmt" "log" "sync" - "time" cfclient "github.com/cloudfoundry-community/go-cfclient/v3/client" cfconfig "github.com/cloudfoundry-community/go-cfclient/v3/config" @@ -93,7 +92,7 @@ func newOrganizationClient(organizationName string, url string, username string, if err != nil { return nil, err } - // isResourceCacheEnabled, _ := strconv.ParseBool(os.Getenv("ENABLE_RESOURCES_CACHE")) + // isResourceCacheEnabled, _ := strconv.ParseBool(os.Getenv("RESOURCE_CACHE_ENABLED")) // if isResourceCacheEnabled && c.resourcesCache == nil { // c.resourcesCache = InitResourcesCache() // c.populateResourcesCache() @@ -197,9 +196,9 @@ func NewSpaceClient(spaceGuid string, url string, username string, password stri } } - //isResourceCacheEnabled, _ := strconv.ParseBool(os.Getenv("ENABLE_RESOURCES_CACHE")) + //isResourceCacheEnabled, _ := strconv.ParseBool(os.Getenv("RESOURCE_CACHE_ENABLED")) if isResourceCacheEnabled && spcClient.resourcesCache == nil { - spcClient.resourcesCache = InitResourcesCache() + spcClient.resourcesCache = facade.InitResourcesCache() spcClient.populateResourcesCache() } @@ -238,16 +237,6 @@ func NewSpaceHealthChecker(spaceGuid string, url string, username string, passwo return client, err } -// InitResourcesCache initializes a new cache -func InitResourcesCache() *facade.Cache { - return &facade.Cache{ - Spaces: make(map[string]*facade.Space), - Instances: make(map[string]*facade.Instance), - Bindings: make(map[string]*facade.Binding), - CacheTimeOut: 1 * time.Minute, - } -} - func (c *spaceClient) populateResourcesCache() { // TODO: Create the space options // TODO: Add for loop for space diff --git a/internal/cf/instance.go b/internal/cf/instance.go index c2a8f47..75bc5a0 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -52,14 +52,14 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s if isResourceCacheEnabled { // Ensure resourcesCache is initialized if c.resourcesCache == nil { - c.resourcesCache = InitResourcesCache() + c.resourcesCache = facade.InitResourcesCache() } // Attempt to retrieve instance from Cache var instanceInCache bool var instance *facade.Instance - if len(c.resourcesCache.Instances) != 0 { + if len(c.resourcesCache.GetCachedInstances()) != 0 { if c.resourcesCache.IsCacheExpired() { c.populateResourcesCache() diff --git a/internal/facade/client.go b/internal/facade/client.go index 4557875..107e9b5 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -7,6 +7,9 @@ package facade import ( "context" + "fmt" + "os" + "strconv" "sync" "time" ) @@ -106,12 +109,57 @@ type SpaceClientBuilder func(string, string, string, string) (SpaceClient, error // Cache is a simple in-memory cache to store spaces, instances, and bindings type Cache struct { - Spaces map[string]*Space - Instances map[string]*Instance - Bindings map[string]*Binding - mutex sync.RWMutex - lastCacheTime time.Time - CacheTimeOut time.Duration + spaces map[string]*Space + instances map[string]*Instance + bindings map[string]*Binding + mutex sync.RWMutex + lastCacheTime time.Time + cacheTimeOut time.Duration + isResourceCacheEnabled bool +} + +// InitResourcesCache initializes a new cache +func InitResourcesCache() *Cache { + cache := &Cache{ + spaces: make(map[string]*Space), + instances: make(map[string]*Instance), + bindings: make(map[string]*Binding), + cacheTimeOut: 1 * time.Minute, + } + cache.GetResourceCacheEnabledEnv() + cache.GetCacheTimeOut() + return cache +} + +func (c *Cache) GetCachedInstances() map[string]*Instance { + return c.instances +} + +func (c *Cache) GetCachedBindings() map[string]*Binding { + return c.bindings +} + +func (c *Cache) GetCachedSpaces() map[string]*Space { + return c.spaces +} + +// function to set the resource cache enabled flag from environment variable "RESOURCE_CACHE_ENABLED" +func (c *Cache) GetResourceCacheEnabledEnv() { + enabled := false + isResourceCacheEnabled, err := strconv.ParseBool(os.Getenv("RESOURCE_CACHE_ENABLED")) + if err != nil { + isResourceCacheEnabled = enabled + } + c.setResourceCacheEnabled(isResourceCacheEnabled) +} + +func (c *Cache) setResourceCacheEnabled(enabled bool) { + c.isResourceCacheEnabled = enabled +} + +// function to is resource cache enabled +func (c *Cache) IsResourceCacheEnabled() bool { + return c.isResourceCacheEnabled } func (c *Cache) GetLastCacheTime() time.Time { @@ -119,14 +167,34 @@ func (c *Cache) GetLastCacheTime() time.Time { } func (c *Cache) GetCacheTimeOut() time.Duration { - return c.CacheTimeOut + return c.cacheTimeOut +} + +// Function to set the resource cache enabled flag from environment variable "CACHE_TIMEOUT" +func (c *Cache) GetCacheTimeOutEnv() { + timeOut := 1 * time.Minute // Default timeout value + + cacheTimeOutStr := os.Getenv("CACHE_TIMEOUT") + + cacheTimeOutInt, err := strconv.Atoi(cacheTimeOutStr) + if err != nil { + fmt.Printf("Invalid CACHE_TIMEOUT value, using default: %v", timeOut) + cacheTimeOutInt = int(timeOut.Minutes()) // converting to minutes as integer + } + + cacheTimeOut := time.Duration(cacheTimeOutInt) * time.Minute + c.setCacheTimeOut(cacheTimeOut) +} + +func (c *Cache) setCacheTimeOut(timeOut time.Duration) { + c.cacheTimeOut = timeOut } func (c *Cache) IsCacheExpired() bool { expiryTime := time.Until(c.lastCacheTime) - return expiryTime > c.CacheTimeOut + return expiryTime > c.cacheTimeOut } func (c *Cache) SetLastCacheTime() { @@ -137,14 +205,14 @@ func (c *Cache) SetLastCacheTime() { func (c *Cache) AddSpaceInCache(key string, space *Space) { c.mutex.Lock() defer c.mutex.Unlock() - c.Spaces[key] = space + c.spaces[key] = space } // GetSpaceFromCache retrieves a space from the cache func (c *Cache) GetSpaceFromCache(key string) (*Space, bool) { c.mutex.RLock() defer c.mutex.RUnlock() - space, found := c.Spaces[key] + space, found := c.spaces[key] return space, found } @@ -152,14 +220,14 @@ func (c *Cache) GetSpaceFromCache(key string) (*Space, bool) { func (c *Cache) AddInstanceInCache(key string, instance *Instance) { c.mutex.Lock() defer c.mutex.Unlock() - c.Instances[key] = instance + c.instances[key] = instance } // GetInstanceFromCache retrieves an instance from the cache func (c *Cache) GetInstanceFromCache(key string) (*Instance, bool) { c.mutex.RLock() defer c.mutex.RUnlock() - instance, found := c.Instances[key] + instance, found := c.instances[key] return instance, found } @@ -167,13 +235,13 @@ func (c *Cache) GetInstanceFromCache(key string) (*Instance, bool) { func (c *Cache) AddBindingInCache(key string, binding *Binding) { c.mutex.Lock() defer c.mutex.Unlock() - c.Bindings[key] = binding + c.bindings[key] = binding } // GetBindingFromCache retrieves a binding from the cache func (c *Cache) GetBindingFromCache(key string) (*Binding, bool) { c.mutex.RLock() defer c.mutex.RUnlock() - binding, found := c.Bindings[key] + binding, found := c.bindings[key] return binding, found } From f866ead8923b8988b9f98967f53df90cb65c23e6 Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Wed, 21 Aug 2024 23:40:59 +0530 Subject: [PATCH 14/63] Added config package to read env variables --- go.mod | 1 + go.sum | 2 + internal/cf/client.go | 86 +++++++++---------- internal/cf/instance.go | 2 +- internal/config/config.go | 25 ++++++ .../controllers/servicebinding_controller.go | 4 +- .../controllers/serviceinstance_controller.go | 4 +- internal/facade/client.go | 63 +++++--------- main.go | 10 ++- 9 files changed, 109 insertions(+), 88 deletions(-) create mode 100644 internal/config/config.go diff --git a/go.mod b/go.mod index 7506b26..03f6968 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect + github.com/caarlos0/env/v11 v11.2.2 github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index 5e90dbd..137ea13 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/caarlos0/env/v11 v11.2.2 h1:95fApNrUyueipoZN/EhA8mMxiNxrBwDa+oAZrMWl3Kg= +github.com/caarlos0/env/v11 v11.2.2/go.mod h1:JBfcdeQiBoI3Zh1QRAWfe+tpiNTmDtcCj/hHHHMx0vc= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= diff --git a/internal/cf/client.go b/internal/cf/client.go index 963761a..feb73ad 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -15,6 +15,7 @@ import ( cfconfig "github.com/cloudfoundry-community/go-cfclient/v3/config" "sigs.k8s.io/controller-runtime/pkg/metrics" + "github.com/sap/cf-service-operator/internal/config" "github.com/sap/cf-service-operator/internal/facade" cfmetrics "github.com/sap/cf-service-operator/pkg/metrics" ) @@ -40,7 +41,6 @@ type spaceClient struct { spaceGuid string client cfclient.Client resourcesCache *facade.Cache - cancelFunc context.CancelFunc } type clientIdentifier struct { @@ -49,19 +49,17 @@ type clientIdentifier struct { } type clientCacheEntry struct { - url string - username string - password string - client cfclient.Client - resourcesCache *facade.Cache + url string + username string + password string + client cfclient.Client } var ( - cacheMutex = &sync.Mutex{} - clientCache = make(map[clientIdentifier]*clientCacheEntry) - cfCache *facade.Cache - isResourceCacheEnabled = true - refreshCacheMutex = &sync.Mutex{} + cacheMutex = &sync.Mutex{} + clientCache = make(map[clientIdentifier]*clientCacheEntry) + cfCache *facade.Cache + refreshCacheMutex = &sync.Mutex{} ) func newOrganizationClient(organizationName string, url string, username string, password string) (*organizationClient, error) { @@ -167,7 +165,7 @@ func NewOrganizationClient(organizationName string, url string, username string, return client, err } -func NewSpaceClient(spaceGuid string, url string, username string, password string) (facade.SpaceClient, error) { +func NewSpaceClient(spaceGuid string, url string, username string, password string, config config.Config) (facade.SpaceClient, error) { cacheMutex.Lock() defer cacheMutex.Unlock() @@ -196,13 +194,15 @@ func NewSpaceClient(spaceGuid string, url string, username string, password stri } } - //isResourceCacheEnabled, _ := strconv.ParseBool(os.Getenv("RESOURCE_CACHE_ENABLED")) - if isResourceCacheEnabled && spcClient.resourcesCache == nil { + if config.IsResourceCacheEnabled && spcClient.resourcesCache == nil { spcClient.resourcesCache = facade.InitResourcesCache() + spcClient.resourcesCache.SetResourceCacheEnabled(config.IsResourceCacheEnabled) + spcClient.resourcesCache.SetCacheTimeOut(config.CacheTimeOut) spcClient.populateResourcesCache() } return spcClient, err + } func NewSpaceHealthChecker(spaceGuid string, url string, username string, password string) (facade.SpaceHealthChecker, error) { @@ -242,40 +242,40 @@ func (c *spaceClient) populateResourcesCache() { // TODO: Add for loop for space refreshCacheMutex.Lock() defer refreshCacheMutex.Unlock() + if c.resourcesCache.IsCacheExpired() { + instanceOptions := cfclient.NewServiceInstanceListOptions() + instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) + instanceOptions.Page = 1 + instanceOptions.PerPage = 500 + //instanceOptions.OrganizationGUIDs.EqualTo("21dc8fd6-ea17-49df-99e9-cacf57b479fc") + + ctx := context.Background() + // populate instance cache + for { + srvInstanes, pager, err := c.client.ServiceInstances.List(ctx, instanceOptions) + if err != nil { + log.Fatalf("Error listing service instances: %s", err) + } - instanceOptions := cfclient.NewServiceInstanceListOptions() - instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) - instanceOptions.Page = 1 - instanceOptions.PerPage = 500 - //instanceOptions.OrganizationGUIDs.EqualTo("21dc8fd6-ea17-49df-99e9-cacf57b479fc") - - ctx := context.Background() - // populate instance cache - for { - srvInstanes, pager, err := c.client.ServiceInstances.List(ctx, instanceOptions) - if err != nil { - log.Fatalf("Error listing service instances: %s", err) - } + // Cache the service instance + for _, serviceInstance := range srvInstanes { + // ... some caching logic + instance, err := InitInstance(serviceInstance) + // instance is added to cache only if error is nil + if err == nil { + c.resourcesCache.AddInstanceInCache(*serviceInstance.Metadata.Labels[labelOwner], instance) + } + } - // Cache the service instance - for _, serviceInstance := range srvInstanes { - // ... some caching logic - instance, err := InitInstance(serviceInstance) - // instance is added to cache only if error is nil - if err == nil { - c.resourcesCache.AddInstanceInCache(*serviceInstance.Metadata.Labels[labelOwner], instance) + if !pager.HasNextPage() { + fmt.Printf("No more pages\n") + break } - } - if !pager.HasNextPage() { - fmt.Printf("No more pages\n") - break + pager.NextPage(instanceOptions) } - - pager.NextPage(instanceOptions) + c.resourcesCache.SetLastCacheTime() + cfCache = c.resourcesCache } - - cfCache = c.resourcesCache - c.resourcesCache.SetLastCacheTime() // TODO: Add for loop for bindings } diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 75bc5a0..f6c912b 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -49,7 +49,7 @@ func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOpt // The function add the parameter values to the orphan cf instance, so that can be adopted. func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]string) (*facade.Instance, error) { - if isResourceCacheEnabled { + if c.resourcesCache.GetResourceCacheEnabled() { // Ensure resourcesCache is initialized if c.resourcesCache == nil { c.resourcesCache = facade.InitResourcesCache() diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..322e180 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,25 @@ +package config + +import ( + "github.com/caarlos0/env/v11" +) + +// Config defines the configuration keys +type Config struct { + + //Resource cache is enabled or disabled + IsResourceCacheEnabled bool `env:"RESOURCE_CACHE_ENABLED" envDefault:"true"` + + //cache timeout in seconds,minutes or hours for resource cache + CacheTimeOut string `env:"CACHE_TIMEOUT" envDefault:"1m"` +} + +// Load the configuration +func Load() (*Config, error) { + cfg := &Config{} + if err := env.Parse(cfg); err != nil { + return nil, err + } + + return cfg, nil +} diff --git a/internal/controllers/servicebinding_controller.go b/internal/controllers/servicebinding_controller.go index 12ff006..aa69b5b 100644 --- a/internal/controllers/servicebinding_controller.go +++ b/internal/controllers/servicebinding_controller.go @@ -24,6 +24,7 @@ import ( cfv1alpha1 "github.com/sap/cf-service-operator/api/v1alpha1" "github.com/sap/cf-service-operator/internal/binding" + "github.com/sap/cf-service-operator/internal/config" "github.com/sap/cf-service-operator/internal/facade" ) @@ -47,6 +48,7 @@ type ServiceBindingReconciler struct { ClusterResourceNamespace string EnableBindingMetadata bool ClientBuilder facade.SpaceClientBuilder + Config *config.Config } // +kubebuilder:rbac:groups=cf.cs.sap.com,resources=servicebindings,verbs=get;list;watch;update @@ -168,7 +170,7 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque // Build cloud foundry client var client facade.SpaceClient if spaceGuid != "" { - client, err = r.ClientBuilder(spaceGuid, string(spaceSecret.Data["url"]), string(spaceSecret.Data["username"]), string(spaceSecret.Data["password"])) + client, err = r.ClientBuilder(spaceGuid, string(spaceSecret.Data["url"]), string(spaceSecret.Data["username"]), string(spaceSecret.Data["password"]), *r.Config) if err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to build the client from secret %s", spaceSecretName) } diff --git a/internal/controllers/serviceinstance_controller.go b/internal/controllers/serviceinstance_controller.go index a4e51d2..c9d2f86 100644 --- a/internal/controllers/serviceinstance_controller.go +++ b/internal/controllers/serviceinstance_controller.go @@ -25,6 +25,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" cfv1alpha1 "github.com/sap/cf-service-operator/api/v1alpha1" + "github.com/sap/cf-service-operator/internal/config" "github.com/sap/cf-service-operator/internal/facade" ) @@ -54,6 +55,7 @@ type ServiceInstanceReconciler struct { Scheme *runtime.Scheme ClusterResourceNamespace string ClientBuilder facade.SpaceClientBuilder + Config *config.Config } // RetryError is a special error to indicate that the operation should be retried. @@ -182,7 +184,7 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ // Build cloud foundry client var client facade.SpaceClient if spaceGuid != "" { - client, err = r.ClientBuilder(spaceGuid, string(spaceSecret.Data["url"]), string(spaceSecret.Data["username"]), string(spaceSecret.Data["password"])) + client, err = r.ClientBuilder(spaceGuid, string(spaceSecret.Data["url"]), string(spaceSecret.Data["username"]), string(spaceSecret.Data["password"]), *r.Config) if err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to build the client from secret %s", spaceSecretName) } diff --git a/internal/facade/client.go b/internal/facade/client.go index 107e9b5..d1c2ffa 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -8,10 +8,10 @@ package facade import ( "context" "fmt" - "os" - "strconv" "sync" "time" + + "github.com/sap/cf-service-operator/internal/config" ) type Space struct { @@ -105,7 +105,7 @@ type SpaceClient interface { // GetBindingFromCache(key string) (*Binding, bool) } -type SpaceClientBuilder func(string, string, string, string) (SpaceClient, error) +type SpaceClientBuilder func(string, string, string, string, config.Config) (SpaceClient, error) // Cache is a simple in-memory cache to store spaces, instances, and bindings type Cache struct { @@ -124,10 +124,8 @@ func InitResourcesCache() *Cache { spaces: make(map[string]*Space), instances: make(map[string]*Instance), bindings: make(map[string]*Binding), - cacheTimeOut: 1 * time.Minute, + cacheTimeOut: 5 * time.Minute, } - cache.GetResourceCacheEnabledEnv() - cache.GetCacheTimeOut() return cache } @@ -143,62 +141,45 @@ func (c *Cache) GetCachedSpaces() map[string]*Space { return c.spaces } -// function to set the resource cache enabled flag from environment variable "RESOURCE_CACHE_ENABLED" -func (c *Cache) GetResourceCacheEnabledEnv() { - enabled := false - isResourceCacheEnabled, err := strconv.ParseBool(os.Getenv("RESOURCE_CACHE_ENABLED")) - if err != nil { - isResourceCacheEnabled = enabled - } - c.setResourceCacheEnabled(isResourceCacheEnabled) -} - -func (c *Cache) setResourceCacheEnabled(enabled bool) { +// function to set the resource cache enabled flag from config +func (c *Cache) SetResourceCacheEnabled(enabled bool) { c.isResourceCacheEnabled = enabled } - -// function to is resource cache enabled -func (c *Cache) IsResourceCacheEnabled() bool { +func (c *Cache) GetResourceCacheEnabled() bool { return c.isResourceCacheEnabled } -func (c *Cache) GetLastCacheTime() time.Time { - return c.lastCacheTime -} - +// Function to set the resource cache enabled flag from config func (c *Cache) GetCacheTimeOut() time.Duration { return c.cacheTimeOut } -// Function to set the resource cache enabled flag from environment variable "CACHE_TIMEOUT" -func (c *Cache) GetCacheTimeOutEnv() { - timeOut := 1 * time.Minute // Default timeout value - - cacheTimeOutStr := os.Getenv("CACHE_TIMEOUT") - - cacheTimeOutInt, err := strconv.Atoi(cacheTimeOutStr) +func (c *Cache) SetCacheTimeOut(timeOut string) { + cacheTimeOut, err := time.ParseDuration(timeOut) if err != nil { - fmt.Printf("Invalid CACHE_TIMEOUT value, using default: %v", timeOut) - cacheTimeOutInt = int(timeOut.Minutes()) // converting to minutes as integer + fmt.Printf("Error parsing duration: %v\n", err) + return } - - cacheTimeOut := time.Duration(cacheTimeOutInt) * time.Minute - c.setCacheTimeOut(cacheTimeOut) + c.cacheTimeOut = cacheTimeOut } -func (c *Cache) setCacheTimeOut(timeOut time.Duration) { - c.cacheTimeOut = timeOut +// Function to set the Last cache time +func (c *Cache) GetLastCacheTime() time.Time { + return c.lastCacheTime } func (c *Cache) IsCacheExpired() bool { - expiryTime := time.Until(c.lastCacheTime) - - return expiryTime > c.cacheTimeOut + expirationTime := c.lastCacheTime.Add(c.cacheTimeOut) + //expiryTime := time.Until(c.lastCacheTime) + fmt.Printf("Expiry time: %v\n", expirationTime) + fmt.Printf("Cache timeout: %v\n", c.cacheTimeOut) + return time.Now().After(expirationTime) } func (c *Cache) SetLastCacheTime() { c.lastCacheTime = time.Now() + fmt.Printf("Last cache time: %v\n", c.lastCacheTime) } // AddSpaceInCache stores a space in the cache diff --git a/main.go b/main.go index 825f142..9a6a617 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ package main import ( "flag" + "fmt" "net" "os" "strconv" @@ -28,6 +29,7 @@ import ( cfv1alpha1 "github.com/sap/cf-service-operator/api/v1alpha1" "github.com/sap/cf-service-operator/internal/cf" + "github.com/sap/cf-service-operator/internal/config" "github.com/sap/cf-service-operator/internal/controllers" // +kubebuilder:scaffold:imports ) @@ -98,7 +100,11 @@ func main() { setupLog.Error(err, "unable to parse webhook bind address", "controller", "Space") os.Exit(1) } - + cfg, err := config.Load() + if err != nil { + fmt.Printf("failed to load config %v\n", err) + os.Exit(1) + } options := ctrl.Options{ Scheme: scheme, // TODO: disable cache for further resources (e.g. secrets) ? @@ -160,6 +166,7 @@ func main() { Scheme: mgr.GetScheme(), ClusterResourceNamespace: clusterResourceNamespace, ClientBuilder: cf.NewSpaceClient, + Config: cfg, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ServiceInstance") os.Exit(1) @@ -170,6 +177,7 @@ func main() { ClusterResourceNamespace: clusterResourceNamespace, EnableBindingMetadata: enableBindingMetadata, ClientBuilder: cf.NewSpaceClient, + Config: cfg, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ServiceBinding") os.Exit(1) From 4ee8437bd21d9538347480f50286b851651714ec Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Thu, 22 Aug 2024 14:41:06 +0530 Subject: [PATCH 15/63] Adjusted the existing tests --- internal/cf/client_test.go | 24 +++++++++++++++++------- internal/cf/instance.go | 2 +- internal/config/config.go | 2 +- internal/controllers/suite_test.go | 13 ++++++++++++- internal/facade/client.go | 7 ++++++- 5 files changed, 37 insertions(+), 11 deletions(-) diff --git a/internal/cf/client_test.go b/internal/cf/client_test.go index eafc2aa..fd63a20 100644 --- a/internal/cf/client_test.go +++ b/internal/cf/client_test.go @@ -14,6 +14,7 @@ import ( . "github.com/onsi/gomega" "github.com/onsi/gomega/ghttp" "github.com/prometheus/client_golang/prometheus" + "github.com/sap/cf-service-operator/internal/config" "sigs.k8s.io/controller-runtime/pkg/metrics" ) @@ -49,6 +50,15 @@ func TestCFClient(t *testing.T) { RunSpecs(t, "CF Client Test Suite") } +// is resource cache enabled and cache timeout +var resourceCacheEnabled = false +var resourceCacheTimeout = 5 * time.Minute + +var cfg = &config.Config{ + IsResourceCacheEnabled: resourceCacheEnabled, + CacheTimeOut: resourceCacheTimeout.String(), +} + // ----------------------------------------------------------------------------------------------- // Tests // ----------------------------------------------------------------------------------------------- @@ -224,7 +234,7 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should create SpaceClient", func() { - NewSpaceClient(OrgName, url, Username, Password) + NewSpaceClient(OrgName, url, Username, Password, *cfg) // Discover UAA endpoint Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) @@ -234,7 +244,7 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should be able to query some space", func() { - spaceClient, err := NewSpaceClient(OrgName, url, Username, Password) + spaceClient, err := NewSpaceClient(OrgName, url, Username, Password, *cfg) Expect(err).To(BeNil()) spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) @@ -266,11 +276,11 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should be able to query some space twice", func() { - spaceClient, err := NewSpaceClient(OrgName, url, Username, Password) + spaceClient, err := NewSpaceClient(OrgName, url, Username, Password, *cfg) Expect(err).To(BeNil()) spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - spaceClient, err = NewSpaceClient(OrgName, url, Username, Password) + spaceClient, err = NewSpaceClient(OrgName, url, Username, Password, *cfg) Expect(err).To(BeNil()) spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) @@ -293,7 +303,7 @@ var _ = Describe("CF Client tests", Ordered, func() { It("should be able to query two different spaces", func() { // test space 1 - spaceClient1, err1 := NewSpaceClient(SpaceName, url, Username, Password) + spaceClient1, err1 := NewSpaceClient(SpaceName, url, Username, Password, *cfg) Expect(err1).To(BeNil()) spaceClient1.GetInstance(ctx, map[string]string{"owner": Owner}) // Discover UAA endpoint @@ -307,7 +317,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(Owner)) // test space 2 - spaceClient2, err2 := NewSpaceClient(SpaceName2, url, Username, Password) + spaceClient2, err2 := NewSpaceClient(SpaceName2, url, Username, Password, *cfg) Expect(err2).To(BeNil()) spaceClient2.GetInstance(ctx, map[string]string{"owner": Owner2}) // no discovery of UAA endpoint or oAuth token here due to caching @@ -336,7 +346,7 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should register prometheus metrics for SpaceClient", func() { - spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password) + spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *cfg) Expect(err).To(BeNil()) Expect(spaceClient).ToNot(BeNil()) diff --git a/internal/cf/instance.go b/internal/cf/instance.go index f6c912b..ff9d7c6 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -49,7 +49,7 @@ func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOpt // The function add the parameter values to the orphan cf instance, so that can be adopted. func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]string) (*facade.Instance, error) { - if c.resourcesCache.GetResourceCacheEnabled() { + if c.resourcesCache.IsResourceCacheEnabled() { // Ensure resourcesCache is initialized if c.resourcesCache == nil { c.resourcesCache = facade.InitResourcesCache() diff --git a/internal/config/config.go b/internal/config/config.go index 322e180..fb1e77f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,7 +10,7 @@ type Config struct { //Resource cache is enabled or disabled IsResourceCacheEnabled bool `env:"RESOURCE_CACHE_ENABLED" envDefault:"true"` - //cache timeout in seconds,minutes or hours for resource cache + //cache timeout in seconds,minutes or hours CacheTimeOut string `env:"CACHE_TIMEOUT" envDefault:"1m"` } diff --git a/internal/controllers/suite_test.go b/internal/controllers/suite_test.go index f6895ec..ca98edc 100644 --- a/internal/controllers/suite_test.go +++ b/internal/controllers/suite_test.go @@ -16,6 +16,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/sap/cf-service-operator/api/v1alpha1" + "github.com/sap/cf-service-operator/internal/config" "github.com/sap/cf-service-operator/internal/facade" "github.com/sap/cf-service-operator/internal/facade/facadefakes" @@ -65,6 +66,15 @@ const ( // (overridden by environment variable TEST_TIMEOUT) var timeout = 5 * time.Minute +// is resource cache enabled and cache timeout +var resourceCacheEnabled = false +var resourceCacheTimeout = 5 * time.Minute + +var cfg = &config.Config{ + IsResourceCacheEnabled: resourceCacheEnabled, + CacheTimeOut: resourceCacheTimeout.String(), +} + // interval used for polling custom resource var interval = 500 * time.Millisecond @@ -218,9 +228,10 @@ func addControllers(k8sManager ctrl.Manager) { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), ClusterResourceNamespace: testK8sNamespace, - ClientBuilder: func(organizationName string, url string, username string, password string) (facade.SpaceClient, error) { + ClientBuilder: func(organizationName string, url string, username string, password string, config config.Config) (facade.SpaceClient, error) { return fakeSpaceClient, nil }, + Config: cfg, } Expect(instanceReconciler.SetupWithManager(k8sManager)).To(Succeed()) diff --git a/internal/facade/client.go b/internal/facade/client.go index d1c2ffa..174f4c8 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -79,6 +79,7 @@ type OrganizationClient interface { AddDeveloper(ctx context.Context, guid string, username string) error AddManager(ctx context.Context, guid string, username string) error + //TODO: Add methods for managing space // AddSpaceInCache(key string, space *Space) // GetSpaceFromCache(key string) (*Space, bool) } @@ -99,6 +100,7 @@ type SpaceClient interface { FindServicePlan(ctx context.Context, serviceOfferingName string, servicePlanName string, spaceGuid string) (string, error) + //TODO: Add methods for managing service keys // AddInstanceInCache(key string, instance *Instance) // GetInstanceFromCache(key string) (*Instance, bool) // AddBindingInCache(key string, binding *Binding) @@ -145,7 +147,10 @@ func (c *Cache) GetCachedSpaces() map[string]*Space { func (c *Cache) SetResourceCacheEnabled(enabled bool) { c.isResourceCacheEnabled = enabled } -func (c *Cache) GetResourceCacheEnabled() bool { +func (c *Cache) IsResourceCacheEnabled() bool { + if c == nil { + return false + } return c.isResourceCacheEnabled } From 9d1041ce6be72c8302a529e38847ca8dc85db70d Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Thu, 22 Aug 2024 15:31:10 +0530 Subject: [PATCH 16/63] added licence header in config --- internal/config/config.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/config/config.go b/internal/config/config.go index fb1e77f..99bfc3f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,3 +1,8 @@ +/* +SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and cf-service-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + package config import ( From acfd78c14af9452ba1efefe89116268e4583cc4f Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Fri, 23 Aug 2024 19:00:42 +0530 Subject: [PATCH 17/63] added logic to remove the deleted instance from cache --- internal/cf/client.go | 72 +++++++++---------- internal/cf/instance.go | 17 ++--- internal/config/config.go | 1 + .../controllers/serviceinstance_controller.go | 4 +- internal/facade/client.go | 68 ++++++++++-------- .../facade/facadefakes/fake_space_client.go | 18 ++--- main.go | 3 +- 7 files changed, 96 insertions(+), 87 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index feb73ad..3162950 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -34,13 +34,13 @@ const ( type organizationClient struct { organizationName string client cfclient.Client - //resourcesCache *facade.Cache + //resourceCache *facade.Cache } type spaceClient struct { - spaceGuid string - client cfclient.Client - resourcesCache *facade.Cache + spaceGuid string + client cfclient.Client + resourceCache *facade.ResourceCache } type clientIdentifier struct { @@ -56,10 +56,10 @@ type clientCacheEntry struct { } var ( - cacheMutex = &sync.Mutex{} - clientCache = make(map[clientIdentifier]*clientCacheEntry) - cfCache *facade.Cache - refreshCacheMutex = &sync.Mutex{} + clientCacheMutex = &sync.Mutex{} + clientCache = make(map[clientIdentifier]*clientCacheEntry) + cfResourceCache *facade.ResourceCache + refreshResourceCacheMutex = &sync.Mutex{} ) func newOrganizationClient(organizationName string, url string, username string, password string) (*organizationClient, error) { @@ -90,11 +90,7 @@ func newOrganizationClient(organizationName string, url string, username string, if err != nil { return nil, err } - // isResourceCacheEnabled, _ := strconv.ParseBool(os.Getenv("RESOURCE_CACHE_ENABLED")) - // if isResourceCacheEnabled && c.resourcesCache == nil { - // c.resourcesCache = InitResourcesCache() - // c.populateResourcesCache() - // } + // TODO:Populate resource cache for ORg client return &organizationClient{organizationName: organizationName, client: *c}, nil } @@ -128,14 +124,12 @@ func newSpaceClient(spaceGuid string, url string, username string, password stri return nil, err } - spcClient := &spaceClient{spaceGuid: spaceGuid, client: *c} - - return spcClient, nil + return &spaceClient{spaceGuid: spaceGuid, client: *c}, nil } func NewOrganizationClient(organizationName string, url string, username string, password string) (facade.OrganizationClient, error) { - cacheMutex.Lock() - defer cacheMutex.Unlock() + clientCacheMutex.Lock() + defer clientCacheMutex.Unlock() // look up CF client in cache identifier := clientIdentifier{url: url, username: username} @@ -166,18 +160,18 @@ func NewOrganizationClient(organizationName string, url string, username string, } func NewSpaceClient(spaceGuid string, url string, username string, password string, config config.Config) (facade.SpaceClient, error) { - cacheMutex.Lock() - defer cacheMutex.Unlock() + clientCacheMutex.Lock() + defer clientCacheMutex.Unlock() // look up CF client in cache identifier := clientIdentifier{url: url, username: username} cacheEntry, isInCache := clientCache[identifier] var err error = nil - var spcClient *spaceClient = nil + var client *spaceClient = nil if isInCache { // re-use CF client from cache and wrap it as spaceClient - spcClient = &spaceClient{spaceGuid: spaceGuid, client: cacheEntry.client, resourcesCache: cfCache} + client = &spaceClient{spaceGuid: spaceGuid, client: cacheEntry.client, resourceCache: cfResourceCache} if cacheEntry.password != password { // password was rotated => delete client from cache and create a new one below delete(clientCache, identifier) @@ -187,27 +181,27 @@ func NewSpaceClient(spaceGuid string, url string, username string, password stri if !isInCache { // create new CF client and wrap it as spaceClient - spcClient, err = newSpaceClient(spaceGuid, url, username, password) + client, err = newSpaceClient(spaceGuid, url, username, password) if err == nil { // add CF client to cache - clientCache[identifier] = &clientCacheEntry{url: url, username: username, password: password, client: spcClient.client} + clientCache[identifier] = &clientCacheEntry{url: url, username: username, password: password, client: client.client} } } - if config.IsResourceCacheEnabled && spcClient.resourcesCache == nil { - spcClient.resourcesCache = facade.InitResourcesCache() - spcClient.resourcesCache.SetResourceCacheEnabled(config.IsResourceCacheEnabled) - spcClient.resourcesCache.SetCacheTimeOut(config.CacheTimeOut) - spcClient.populateResourcesCache() + if config.IsResourceCacheEnabled && client.resourceCache == nil { + client.resourceCache = facade.InitResourceCache() + client.resourceCache.SetResourceCacheEnabled(config.IsResourceCacheEnabled) + client.resourceCache.SetCacheTimeOut(config.CacheTimeOut) + client.populateResourceCache() } - return spcClient, err + return client, err } func NewSpaceHealthChecker(spaceGuid string, url string, username string, password string) (facade.SpaceHealthChecker, error) { - cacheMutex.Lock() - defer cacheMutex.Unlock() + clientCacheMutex.Lock() + defer clientCacheMutex.Unlock() // look up CF client in cache identifier := clientIdentifier{url: url, username: username} @@ -237,12 +231,12 @@ func NewSpaceHealthChecker(spaceGuid string, url string, username string, passwo return client, err } -func (c *spaceClient) populateResourcesCache() { +func (c *spaceClient) populateResourceCache() { // TODO: Create the space options // TODO: Add for loop for space - refreshCacheMutex.Lock() - defer refreshCacheMutex.Unlock() - if c.resourcesCache.IsCacheExpired() { + refreshResourceCacheMutex.Lock() + defer refreshResourceCacheMutex.Unlock() + if c.resourceCache.IsCacheExpired() { instanceOptions := cfclient.NewServiceInstanceListOptions() instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) instanceOptions.Page = 1 @@ -263,7 +257,7 @@ func (c *spaceClient) populateResourcesCache() { instance, err := InitInstance(serviceInstance) // instance is added to cache only if error is nil if err == nil { - c.resourcesCache.AddInstanceInCache(*serviceInstance.Metadata.Labels[labelOwner], instance) + c.resourceCache.AddInstanceInCache(*serviceInstance.Metadata.Labels[labelOwner], instance) } } @@ -274,8 +268,8 @@ func (c *spaceClient) populateResourcesCache() { pager.NextPage(instanceOptions) } - c.resourcesCache.SetLastCacheTime() - cfCache = c.resourcesCache + c.resourceCache.SetLastCacheTime() + cfResourceCache = c.resourceCache } // TODO: Add for loop for bindings } diff --git a/internal/cf/instance.go b/internal/cf/instance.go index ff9d7c6..8bfb3db 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -49,23 +49,23 @@ func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOpt // The function add the parameter values to the orphan cf instance, so that can be adopted. func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]string) (*facade.Instance, error) { - if c.resourcesCache.IsResourceCacheEnabled() { + if c.resourceCache.IsResourceCacheEnabled() { // Ensure resourcesCache is initialized - if c.resourcesCache == nil { - c.resourcesCache = facade.InitResourcesCache() + if c.resourceCache == nil { + c.resourceCache = facade.InitResourceCache() } // Attempt to retrieve instance from Cache var instanceInCache bool var instance *facade.Instance - if len(c.resourcesCache.GetCachedInstances()) != 0 { - if c.resourcesCache.IsCacheExpired() { + if len(c.resourceCache.GetCachedInstances()) != 0 { + if c.resourceCache.IsCacheExpired() { - c.populateResourcesCache() + c.populateResourceCache() } - instance, instanceInCache = c.resourcesCache.GetInstanceFromCache(instanceOpts["owner"]) + instance, instanceInCache = c.resourceCache.GetInstanceFromCache(instanceOpts["owner"]) } if instanceInCache { @@ -163,9 +163,10 @@ func (c *spaceClient) UpdateInstance(ctx context.Context, guid string, name stri return err } -func (c *spaceClient) DeleteInstance(ctx context.Context, guid string) error { +func (c *spaceClient) DeleteInstance(ctx context.Context, guid string, owner string) error { // TODO: return jobGUID to enable querying the job deletion status _, err := c.client.ServiceInstances.Delete(ctx, guid) + c.resourceCache.RemoveInstanceFromCache(owner) return err } diff --git a/internal/config/config.go b/internal/config/config.go index 99bfc3f..8a68f82 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -13,6 +13,7 @@ import ( type Config struct { //Resource cache is enabled or disabled + //TODO change it back to false after testing IsResourceCacheEnabled bool `env:"RESOURCE_CACHE_ENABLED" envDefault:"true"` //cache timeout in seconds,minutes or hours diff --git a/internal/controllers/serviceinstance_controller.go b/internal/controllers/serviceinstance_controller.go index c9d2f86..265ab07 100644 --- a/internal/controllers/serviceinstance_controller.go +++ b/internal/controllers/serviceinstance_controller.go @@ -315,7 +315,7 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ } else if recreateOnCreationFailure && (cfinstance.State == facade.InstanceStateCreatedFailed || cfinstance.State == facade.InstanceStateDeleteFailed) { // Re-create instance log.V(1).Info("Deleting instance for later re-creation") - if err := client.DeleteInstance(ctx, cfinstance.Guid); err != nil { + if err := client.DeleteInstance(ctx, cfinstance.Guid, cfinstance.Owner); err != nil { return ctrl.Result{}, RetryError } status.LastModifiedAt = &[]metav1.Time{metav1.Now()}[0] @@ -425,7 +425,7 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ } else { if cfinstance.State != facade.InstanceStateDeleting { log.V(1).Info("Deleting instance") - if err := client.DeleteInstance(ctx, cfinstance.Guid); err != nil { + if err := client.DeleteInstance(ctx, cfinstance.Guid, cfinstance.Owner); err != nil { return ctrl.Result{}, err } status.LastModifiedAt = &[]metav1.Time{metav1.Now()}[0] diff --git a/internal/facade/client.go b/internal/facade/client.go index 174f4c8..8295736 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -91,7 +91,7 @@ type SpaceClient interface { GetInstance(ctx context.Context, instanceOpts map[string]string) (*Instance, error) CreateInstance(ctx context.Context, name string, servicePlanGuid string, parameters map[string]interface{}, tags []string, owner string, generation int64) error UpdateInstance(ctx context.Context, guid string, name string, servicePlanGuid string, parameters map[string]interface{}, tags []string, generation int64) error - DeleteInstance(ctx context.Context, guid string) error + DeleteInstance(ctx context.Context, guid string, owner string) error GetBinding(ctx context.Context, bindingOpts map[string]string) (*Binding, error) CreateBinding(ctx context.Context, name string, serviceInstanceGuid string, parameters map[string]interface{}, owner string, generation int64) error @@ -101,16 +101,16 @@ type SpaceClient interface { FindServicePlan(ctx context.Context, serviceOfferingName string, servicePlanName string, spaceGuid string) (string, error) //TODO: Add methods for managing service keys - // AddInstanceInCache(key string, instance *Instance) - // GetInstanceFromCache(key string) (*Instance, bool) - // AddBindingInCache(key string, binding *Binding) - // GetBindingFromCache(key string) (*Binding, bool) + // AddInstanceToResourceCache(key string, instance *Instance) + // GetInstanceFromResourceCache(key string) (*Instance, bool) + // AddBindingToResourceCache(key string, binding *Binding) + // GetBindingFromResourceCache(key string) (*Binding, bool) } type SpaceClientBuilder func(string, string, string, string, config.Config) (SpaceClient, error) // Cache is a simple in-memory cache to store spaces, instances, and bindings -type Cache struct { +type ResourceCache struct { spaces map[string]*Space instances map[string]*Instance bindings map[string]*Binding @@ -121,8 +121,8 @@ type Cache struct { } // InitResourcesCache initializes a new cache -func InitResourcesCache() *Cache { - cache := &Cache{ +func InitResourceCache() *ResourceCache { + cache := &ResourceCache{ spaces: make(map[string]*Space), instances: make(map[string]*Instance), bindings: make(map[string]*Binding), @@ -131,23 +131,23 @@ func InitResourcesCache() *Cache { return cache } -func (c *Cache) GetCachedInstances() map[string]*Instance { +func (c *ResourceCache) GetCachedInstances() map[string]*Instance { return c.instances } -func (c *Cache) GetCachedBindings() map[string]*Binding { +func (c *ResourceCache) GetCachedBindings() map[string]*Binding { return c.bindings } -func (c *Cache) GetCachedSpaces() map[string]*Space { +func (c *ResourceCache) GetCachedSpaces() map[string]*Space { return c.spaces } // function to set the resource cache enabled flag from config -func (c *Cache) SetResourceCacheEnabled(enabled bool) { +func (c *ResourceCache) SetResourceCacheEnabled(enabled bool) { c.isResourceCacheEnabled = enabled } -func (c *Cache) IsResourceCacheEnabled() bool { +func (c *ResourceCache) IsResourceCacheEnabled() bool { if c == nil { return false } @@ -155,11 +155,11 @@ func (c *Cache) IsResourceCacheEnabled() bool { } // Function to set the resource cache enabled flag from config -func (c *Cache) GetCacheTimeOut() time.Duration { +func (c *ResourceCache) GetCacheTimeOut() time.Duration { return c.cacheTimeOut } -func (c *Cache) SetCacheTimeOut(timeOut string) { +func (c *ResourceCache) SetCacheTimeOut(timeOut string) { cacheTimeOut, err := time.ParseDuration(timeOut) if err != nil { fmt.Printf("Error parsing duration: %v\n", err) @@ -168,12 +168,8 @@ func (c *Cache) SetCacheTimeOut(timeOut string) { c.cacheTimeOut = cacheTimeOut } -// Function to set the Last cache time -func (c *Cache) GetLastCacheTime() time.Time { - return c.lastCacheTime -} - -func (c *Cache) IsCacheExpired() bool { +// Function to check if the cache is expired +func (c *ResourceCache) IsCacheExpired() bool { expirationTime := c.lastCacheTime.Add(c.cacheTimeOut) //expiryTime := time.Until(c.lastCacheTime) @@ -182,20 +178,22 @@ func (c *Cache) IsCacheExpired() bool { return time.Now().After(expirationTime) } -func (c *Cache) SetLastCacheTime() { + +// Function to set the last cache time +func (c *ResourceCache) SetLastCacheTime() { c.lastCacheTime = time.Now() fmt.Printf("Last cache time: %v\n", c.lastCacheTime) } // AddSpaceInCache stores a space in the cache -func (c *Cache) AddSpaceInCache(key string, space *Space) { +func (c *ResourceCache) AddSpaceInCache(key string, space *Space) { c.mutex.Lock() defer c.mutex.Unlock() c.spaces[key] = space } // GetSpaceFromCache retrieves a space from the cache -func (c *Cache) GetSpaceFromCache(key string) (*Space, bool) { +func (c *ResourceCache) GetSpaceFromCache(key string) (*Space, bool) { c.mutex.RLock() defer c.mutex.RUnlock() space, found := c.spaces[key] @@ -203,29 +201,43 @@ func (c *Cache) GetSpaceFromCache(key string) (*Space, bool) { } // AddInstanceInCache stores an instance in the cache -func (c *Cache) AddInstanceInCache(key string, instance *Instance) { +func (c *ResourceCache) AddInstanceInCache(key string, instance *Instance) { c.mutex.Lock() defer c.mutex.Unlock() c.instances[key] = instance } // GetInstanceFromCache retrieves an instance from the cache -func (c *Cache) GetInstanceFromCache(key string) (*Instance, bool) { +func (c *ResourceCache) GetInstanceFromCache(key string) (*Instance, bool) { c.mutex.RLock() defer c.mutex.RUnlock() instance, found := c.instances[key] return instance, found } +// RemoveInstanceFromCache removes an instance from the cache +// This is used when an instance is deleted +// The instance is removed from the cache to avoid stale data +// The instance is removed from the cache only if the instance is found in the cache +func (c *ResourceCache) RemoveInstanceFromCache(key string) { + c.mutex.Lock() + defer c.mutex.Unlock() + _, found := c.instances[key] + if found { + delete(c.instances, key) + } + +} + // AddBindingInCache stores a binding in the cache -func (c *Cache) AddBindingInCache(key string, binding *Binding) { +func (c *ResourceCache) AddBindingInCache(key string, binding *Binding) { c.mutex.Lock() defer c.mutex.Unlock() c.bindings[key] = binding } // GetBindingFromCache retrieves a binding from the cache -func (c *Cache) GetBindingFromCache(key string) (*Binding, bool) { +func (c *ResourceCache) GetBindingFromCache(key string) (*Binding, bool) { c.mutex.RLock() defer c.mutex.RUnlock() binding, found := c.bindings[key] diff --git a/internal/facade/facadefakes/fake_space_client.go b/internal/facade/facadefakes/fake_space_client.go index 5f9b070..acdb07a 100644 --- a/internal/facade/facadefakes/fake_space_client.go +++ b/internal/facade/facadefakes/fake_space_client.go @@ -58,11 +58,12 @@ type FakeSpaceClient struct { deleteBindingReturnsOnCall map[int]struct { result1 error } - DeleteInstanceStub func(context.Context, string) error + DeleteInstanceStub func(context.Context, string, string) error deleteInstanceMutex sync.RWMutex deleteInstanceArgsForCall []struct { arg1 context.Context arg2 string + arg3 string } deleteInstanceReturns struct { result1 error @@ -349,19 +350,20 @@ func (fake *FakeSpaceClient) DeleteBindingReturnsOnCall(i int, result1 error) { }{result1} } -func (fake *FakeSpaceClient) DeleteInstance(arg1 context.Context, arg2 string) error { +func (fake *FakeSpaceClient) DeleteInstance(arg1 context.Context, arg2 string, arg3 string) error { fake.deleteInstanceMutex.Lock() ret, specificReturn := fake.deleteInstanceReturnsOnCall[len(fake.deleteInstanceArgsForCall)] fake.deleteInstanceArgsForCall = append(fake.deleteInstanceArgsForCall, struct { arg1 context.Context arg2 string - }{arg1, arg2}) + arg3 string + }{arg1, arg2, arg3}) stub := fake.DeleteInstanceStub fakeReturns := fake.deleteInstanceReturns - fake.recordInvocation("DeleteInstance", []interface{}{arg1, arg2}) + fake.recordInvocation("DeleteInstance", []interface{}{arg1, arg2, arg3}) fake.deleteInstanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1 @@ -375,17 +377,17 @@ func (fake *FakeSpaceClient) DeleteInstanceCallCount() int { return len(fake.deleteInstanceArgsForCall) } -func (fake *FakeSpaceClient) DeleteInstanceCalls(stub func(context.Context, string) error) { +func (fake *FakeSpaceClient) DeleteInstanceCalls(stub func(context.Context, string, string) error) { fake.deleteInstanceMutex.Lock() defer fake.deleteInstanceMutex.Unlock() fake.DeleteInstanceStub = stub } -func (fake *FakeSpaceClient) DeleteInstanceArgsForCall(i int) (context.Context, string) { +func (fake *FakeSpaceClient) DeleteInstanceArgsForCall(i int) (context.Context, string, string) { fake.deleteInstanceMutex.RLock() defer fake.deleteInstanceMutex.RUnlock() argsForCall := fake.deleteInstanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *FakeSpaceClient) DeleteInstanceReturns(result1 error) { diff --git a/main.go b/main.go index 9a6a617..ad58e8b 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,6 @@ package main import ( "flag" - "fmt" "net" "os" "strconv" @@ -102,7 +101,7 @@ func main() { } cfg, err := config.Load() if err != nil { - fmt.Printf("failed to load config %v\n", err) + setupLog.Error(err, "failed to load config") os.Exit(1) } options := ctrl.Options{ From edc8ddbd511298db1d18cc08a3f372f830310f76 Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Mon, 26 Aug 2024 12:23:03 +0530 Subject: [PATCH 18/63] init instance to return owner for adopt instance scenario --- internal/cf/client.go | 2 +- internal/cf/instance.go | 19 ++++-- .../controllers/serviceinstance_controller.go | 59 ++++++++++--------- 3 files changed, 46 insertions(+), 34 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index 3162950..e6f1ae3 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -254,7 +254,7 @@ func (c *spaceClient) populateResourceCache() { // Cache the service instance for _, serviceInstance := range srvInstanes { // ... some caching logic - instance, err := InitInstance(serviceInstance) + instance, err := InitInstance(serviceInstance, nil) // instance is added to cache only if error is nil if err == nil { c.resourceCache.AddInstanceInCache(*serviceInstance.Metadata.Labels[labelOwner], instance) diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 8bfb3db..433826c 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -101,7 +101,7 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s serviceInstance.Metadata.Annotations[annotationParameterHash] = ¶meterHashValue } - return InitInstance(serviceInstance) + return InitInstance(serviceInstance, instanceOpts) } // Required parameters (may not be initial): name, servicePlanGuid, owner, generation @@ -170,7 +170,7 @@ func (c *spaceClient) DeleteInstance(ctx context.Context, guid string, owner str return err } -func InitInstance(serviceInstance *cfresource.ServiceInstance) (*facade.Instance, error) { +func InitInstance(serviceInstance *cfresource.ServiceInstance, instanceOpts map[string]string) (*facade.Instance, error) { guid := serviceInstance.GUID name := serviceInstance.Name servicePlanGuid := serviceInstance.Relationships.ServicePlan.Data.GUID @@ -203,12 +203,19 @@ func InitInstance(serviceInstance *cfresource.ServiceInstance) (*facade.Instance state = facade.InstanceStateUnknown } stateDescription := serviceInstance.LastOperation.Description + //if (instanceOpts["owner"] not nil then owner = instanceOpts["owner"] else owner = serviceInstance.Metadata.Labels[labelOwner] + owner := instanceOpts["owner"] + if owner == "" { + owner = *serviceInstance.Metadata.Labels[labelOwner] + } return &facade.Instance{ - Guid: guid, - Name: name, - ServicePlanGuid: servicePlanGuid, - Owner: *serviceInstance.Metadata.Labels[labelOwner], + Guid: guid, + Name: name, + ServicePlanGuid: servicePlanGuid, + //if (instanceOpts["owner"] not nil then owner = instanceOpts["owner"] else owner = serviceInstance.Metadata.Labels[labelOwner] + Owner: owner, + Generation: generation, ParameterHash: parameterHash, State: state, diff --git a/internal/controllers/serviceinstance_controller.go b/internal/controllers/serviceinstance_controller.go index 265ab07..25e389d 100644 --- a/internal/controllers/serviceinstance_controller.go +++ b/internal/controllers/serviceinstance_controller.go @@ -208,34 +208,39 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ if err != nil { return ctrl.Result{}, err } - - //Add parameters to adopt the orphaned instance - var parameterObjects []map[string]interface{} - paramMap := make(map[string]interface{}) - paramMap["parameter-hash"] = cfinstance.ParameterHash - paramMap["owner"] = cfinstance.Owner - parameterObjects = append(parameterObjects, paramMap) - parameters, err := mergeObjects(parameterObjects...) - if err != nil { - return ctrl.Result{}, errors.Wrap(err, "failed to unmarshal/merge parameters") - } - // update the orphaned cloud foundry instance - log.V(1).Info("Updating instance") - if err := client.UpdateInstance( - ctx, - cfinstance.Guid, - spec.Name, - "", - parameters, - nil, - serviceInstance.Generation, - ); err != nil { - return ctrl.Result{}, err + if cfinstance != nil { + //Add parameters to adopt the orphaned instance + var parameterObjects []map[string]interface{} + paramMap := make(map[string]interface{}) + // Ensure cfinstance.ParameterHash is not nil + if cfinstance.ParameterHash == "" { + return ctrl.Result{}, errors.Wrap(err, "ParameterHash is nil") + } + paramMap["parameter-hash"] = cfinstance.ParameterHash + paramMap["owner"] = cfinstance.Owner + parameterObjects = append(parameterObjects, paramMap) + parameters, err := mergeObjects(parameterObjects...) + if err != nil { + return ctrl.Result{}, errors.Wrap(err, "failed to unmarshal/merge parameters") + } + // update the orphaned cloud foundry instance + log.V(1).Info("Updating instance") + if err := client.UpdateInstance( + ctx, + cfinstance.Guid, + spec.Name, + "", + parameters, + nil, + serviceInstance.Generation, + ); err != nil { + return ctrl.Result{}, err + } + status.LastModifiedAt = &[]metav1.Time{metav1.Now()}[0] + // return the reconcile function to requeue inmediatly after the update + serviceInstance.SetReadyCondition(cfv1alpha1.ConditionUnknown, string(cfinstance.State), cfinstance.StateDescription) + return ctrl.Result{Requeue: true}, nil } - status.LastModifiedAt = &[]metav1.Time{metav1.Now()}[0] - // return the reconcile function to requeue inmediatly after the update - serviceInstance.SetReadyCondition(cfv1alpha1.ConditionUnknown, string(cfinstance.State), cfinstance.StateDescription) - return ctrl.Result{Requeue: true}, nil } } From 16e2d619df74cac40654326687b5b52ee9372e37 Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Wed, 28 Aug 2024 17:57:25 +0530 Subject: [PATCH 19/63] Code for update instance to cache --- internal/cf/instance.go | 44 ++++++++++++++++--- .../controllers/serviceinstance_controller.go | 22 +++++++--- internal/facade/client.go | 36 ++++++++++++++- .../facade/facadefakes/fake_space_client.go | 38 ++++++++-------- 4 files changed, 110 insertions(+), 30 deletions(-) diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 433826c..938ff93 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -58,17 +58,25 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s // Attempt to retrieve instance from Cache var instanceInCache bool var instance *facade.Instance + //TODO recheck this logic later + if c.resourceCache.IsCacheExpired() { + c.populateResourceCache() + } if len(c.resourceCache.GetCachedInstances()) != 0 { - if c.resourceCache.IsCacheExpired() { - c.populateResourceCache() + instance, instanceInCache = c.resourceCache.GetInstanceFromCache(instanceOpts["owner"]) + //TODO:remove later: get all instances and print + instances := c.resourceCache.GetCachedInstances() + for key, value := range instances { + fmt.Printf("Key: %s, Value: %v\n", key, value) } - instance, instanceInCache = c.resourceCache.GetInstanceFromCache(instanceOpts["owner"]) } if instanceInCache { + // TODO remove this printf later + fmt.Printf("Got the instance from Cache") return instance, nil } } @@ -131,7 +139,7 @@ func (c *spaceClient) CreateInstance(ctx context.Context, name string, servicePl // Required parameters (may not be initial): guid, generation // Optional parameters (may be initial): name, servicePlanGuid, parameters, tags -func (c *spaceClient) UpdateInstance(ctx context.Context, guid string, name string, servicePlanGuid string, parameters map[string]interface{}, tags []string, generation int64) error { +func (c *spaceClient) UpdateInstance(ctx context.Context, guid string, name string, owner string, servicePlanGuid string, parameters map[string]interface{}, tags []string, generation int64) error { req := cfresource.NewServiceInstanceManagedUpdate() if name != "" { req.WithName(name) @@ -160,13 +168,39 @@ func (c *spaceClient) UpdateInstance(ctx context.Context, guid string, name stri } _, _, err := c.client.ServiceInstances.UpdateManaged(ctx, guid, req) + if err == nil && c.resourceCache.IsResourceCacheEnabled() { + isUpdated := c.resourceCache.UpdateInstanceInCache(guid, name, owner, servicePlanGuid, parameters, generation) + + if !isUpdated { + //add instance to cache in case of orphan instance + instance := &facade.Instance{ + Guid: guid, + Name: name, + ServicePlanGuid: servicePlanGuid, + Owner: owner, + Generation: generation, + ParameterHash: facade.ObjectHash(parameters), + State: facade.InstanceStateReady, + StateDescription: "", + } + c.resourceCache.AddInstanceInCache(owner, instance) + + } + } + //TODO:remove later: get all instances and print + instances := c.resourceCache.GetCachedInstances() + for key, value := range instances { + fmt.Printf("Key: %s, Value: %v\n", key, value) + } return err } func (c *spaceClient) DeleteInstance(ctx context.Context, guid string, owner string) error { // TODO: return jobGUID to enable querying the job deletion status _, err := c.client.ServiceInstances.Delete(ctx, guid) - c.resourceCache.RemoveInstanceFromCache(owner) + if err == nil && c.resourceCache.IsResourceCacheEnabled() { + c.resourceCache.RemoveInstanceFromCache(owner) + } return err } diff --git a/internal/controllers/serviceinstance_controller.go b/internal/controllers/serviceinstance_controller.go index 25e389d..26b95c8 100644 --- a/internal/controllers/serviceinstance_controller.go +++ b/internal/controllers/serviceinstance_controller.go @@ -208,14 +208,10 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ if err != nil { return ctrl.Result{}, err } - if cfinstance != nil { + if cfinstance != nil && cfinstance.State == facade.InstanceStateReady { //Add parameters to adopt the orphaned instance var parameterObjects []map[string]interface{} paramMap := make(map[string]interface{}) - // Ensure cfinstance.ParameterHash is not nil - if cfinstance.ParameterHash == "" { - return ctrl.Result{}, errors.Wrap(err, "ParameterHash is nil") - } paramMap["parameter-hash"] = cfinstance.ParameterHash paramMap["owner"] = cfinstance.Owner parameterObjects = append(parameterObjects, paramMap) @@ -223,13 +219,22 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ if err != nil { return ctrl.Result{}, errors.Wrap(err, "failed to unmarshal/merge parameters") } + servicePlanGuid := cfinstance.ServicePlanGuid + if servicePlanGuid == "" { + log.V(1).Info("Searching service plan") + servicePlanGuid, err = client.FindServicePlan(ctx, spec.ServiceOfferingName, spec.ServicePlanName, spaceGuid) + if err != nil { + return ctrl.Result{}, err + } + } // update the orphaned cloud foundry instance log.V(1).Info("Updating instance") if err := client.UpdateInstance( ctx, cfinstance.Guid, spec.Name, - "", + string(serviceInstance.UID), + servicePlanGuid, parameters, nil, serviceInstance.Generation, @@ -240,7 +245,11 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ // return the reconcile function to requeue inmediatly after the update serviceInstance.SetReadyCondition(cfv1alpha1.ConditionUnknown, string(cfinstance.State), cfinstance.StateDescription) return ctrl.Result{Requeue: true}, nil + } else if cfinstance != nil && cfinstance.State != facade.InstanceStateReady { + //return the reconcile function to not reconcile and error message + return ctrl.Result{}, fmt.Errorf("orphaned instance is not ready to be adopted") } + } } @@ -357,6 +366,7 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ ctx, cfinstance.Guid, updateName, + string(serviceInstance.UID), updateServicePlanGuid, updateParameters, updateTags, diff --git a/internal/facade/client.go b/internal/facade/client.go index 8295736..f543307 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -90,7 +90,7 @@ type OrganizationClientBuilder func(string, string, string, string) (Organizatio type SpaceClient interface { GetInstance(ctx context.Context, instanceOpts map[string]string) (*Instance, error) CreateInstance(ctx context.Context, name string, servicePlanGuid string, parameters map[string]interface{}, tags []string, owner string, generation int64) error - UpdateInstance(ctx context.Context, guid string, name string, servicePlanGuid string, parameters map[string]interface{}, tags []string, generation int64) error + UpdateInstance(ctx context.Context, guid string, name string, owner string, servicePlanGuid string, parameters map[string]interface{}, tags []string, generation int64) error DeleteInstance(ctx context.Context, guid string, owner string) error GetBinding(ctx context.Context, bindingOpts map[string]string) (*Binding, error) @@ -229,6 +229,40 @@ func (c *ResourceCache) RemoveInstanceFromCache(key string) { } +// update the instance in the cache +func (c *ResourceCache) UpdateInstanceInCache(guid string, name string, owner string, servicePlanGuid string, parameters map[string]interface{}, generation int64) (status bool) { + c.mutex.Lock() + defer c.mutex.Unlock() + //update if the instance is found in the cache + //update all the struct variables if they are not nil or empty + instance, found := c.instances[owner] + if found { + if guid != "" { + instance.Guid = guid + } + if name != "" { + instance.Name = name + } + if servicePlanGuid != "" { + instance.ServicePlanGuid = servicePlanGuid + } + if parameters != nil { + instance.ParameterHash = ObjectHash(parameters) + } + if owner != "" { + instance.Owner = owner + } + instance.Generation = generation + c.instances[owner] = instance + return true + + } + //TODO:remove later: print all the instances in cache + fmt.Printf("Instances in cache: %v\n", c.instances) + return false + +} + // AddBindingInCache stores a binding in the cache func (c *ResourceCache) AddBindingInCache(key string, binding *Binding) { c.mutex.Lock() diff --git a/internal/facade/facadefakes/fake_space_client.go b/internal/facade/facadefakes/fake_space_client.go index acdb07a..68c2159 100644 --- a/internal/facade/facadefakes/fake_space_client.go +++ b/internal/facade/facadefakes/fake_space_client.go @@ -129,16 +129,17 @@ type FakeSpaceClient struct { updateBindingReturnsOnCall map[int]struct { result1 error } - UpdateInstanceStub func(context.Context, string, string, string, map[string]interface{}, []string, int64) error + UpdateInstanceStub func(context.Context, string, string, string, string, map[string]interface{}, []string, int64) error updateInstanceMutex sync.RWMutex updateInstanceArgsForCall []struct { arg1 context.Context arg2 string arg3 string arg4 string - arg5 map[string]interface{} - arg6 []string - arg7 int64 + arg5 string + arg6 map[string]interface{} + arg7 []string + arg8 int64 } updateInstanceReturns struct { result1 error @@ -674,11 +675,11 @@ func (fake *FakeSpaceClient) UpdateBindingReturnsOnCall(i int, result1 error) { }{result1} } -func (fake *FakeSpaceClient) UpdateInstance(arg1 context.Context, arg2 string, arg3 string, arg4 string, arg5 map[string]interface{}, arg6 []string, arg7 int64) error { - var arg6Copy []string - if arg6 != nil { - arg6Copy = make([]string, len(arg6)) - copy(arg6Copy, arg6) +func (fake *FakeSpaceClient) UpdateInstance(arg1 context.Context, arg2 string, arg3 string, arg4 string, arg5 string, arg6 map[string]interface{}, arg7 []string, arg8 int64) error { + var arg7Copy []string + if arg7 != nil { + arg7Copy = make([]string, len(arg7)) + copy(arg7Copy, arg7) } fake.updateInstanceMutex.Lock() ret, specificReturn := fake.updateInstanceReturnsOnCall[len(fake.updateInstanceArgsForCall)] @@ -687,16 +688,17 @@ func (fake *FakeSpaceClient) UpdateInstance(arg1 context.Context, arg2 string, a arg2 string arg3 string arg4 string - arg5 map[string]interface{} - arg6 []string - arg7 int64 - }{arg1, arg2, arg3, arg4, arg5, arg6Copy, arg7}) + arg5 string + arg6 map[string]interface{} + arg7 []string + arg8 int64 + }{arg1, arg2, arg3, arg4, arg5, arg6, arg7Copy, arg8}) stub := fake.UpdateInstanceStub fakeReturns := fake.updateInstanceReturns - fake.recordInvocation("UpdateInstance", []interface{}{arg1, arg2, arg3, arg4, arg5, arg6Copy, arg7}) + fake.recordInvocation("UpdateInstance", []interface{}{arg1, arg2, arg3, arg4, arg5, arg6, arg7Copy, arg8}) fake.updateInstanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3, arg4, arg5, arg6, arg7) + return stub(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) } if specificReturn { return ret.result1 @@ -710,17 +712,17 @@ func (fake *FakeSpaceClient) UpdateInstanceCallCount() int { return len(fake.updateInstanceArgsForCall) } -func (fake *FakeSpaceClient) UpdateInstanceCalls(stub func(context.Context, string, string, string, map[string]interface{}, []string, int64) error) { +func (fake *FakeSpaceClient) UpdateInstanceCalls(stub func(context.Context, string, string, string, string, map[string]interface{}, []string, int64) error) { fake.updateInstanceMutex.Lock() defer fake.updateInstanceMutex.Unlock() fake.UpdateInstanceStub = stub } -func (fake *FakeSpaceClient) UpdateInstanceArgsForCall(i int) (context.Context, string, string, string, map[string]interface{}, []string, int64) { +func (fake *FakeSpaceClient) UpdateInstanceArgsForCall(i int) (context.Context, string, string, string, string, map[string]interface{}, []string, int64) { fake.updateInstanceMutex.RLock() defer fake.updateInstanceMutex.RUnlock() argsForCall := fake.updateInstanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5, argsForCall.arg6, argsForCall.arg7 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5, argsForCall.arg6, argsForCall.arg7, argsForCall.arg8 } func (fake *FakeSpaceClient) UpdateInstanceReturns(result1 error) { From 061f6685d9c2949037c0d58073ddfc5829f2505c Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Thu, 29 Aug 2024 13:58:39 +0530 Subject: [PATCH 20/63] Added logging for testing --- internal/cf/client.go | 3 +++ internal/cf/instance.go | 25 +++++++++++-------------- internal/config/config.go | 3 +-- internal/facade/client.go | 17 +++++++++++++---- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index e6f1ae3..b88133f 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -237,6 +237,8 @@ func (c *spaceClient) populateResourceCache() { refreshResourceCacheMutex.Lock() defer refreshResourceCacheMutex.Unlock() if c.resourceCache.IsCacheExpired() { + //print cache is expired and populate the cache + fmt.Println("For the first or Cache is expired and populating the cache") instanceOptions := cfclient.NewServiceInstanceListOptions() instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) instanceOptions.Page = 1 @@ -248,6 +250,7 @@ func (c *spaceClient) populateResourceCache() { for { srvInstanes, pager, err := c.client.ServiceInstances.List(ctx, instanceOptions) if err != nil { + //TODO:revisit to see how to handle if error occurs log.Fatalf("Error listing service instances: %s", err) } diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 938ff93..f634808 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -60,26 +60,25 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s var instance *facade.Instance //TODO recheck this logic later if c.resourceCache.IsCacheExpired() { - + //TODO: remove later:Print cache is expited + fmt.Println("Cache is expired") c.populateResourceCache() } if len(c.resourceCache.GetCachedInstances()) != 0 { instance, instanceInCache = c.resourceCache.GetInstanceFromCache(instanceOpts["owner"]) - //TODO:remove later: get all instances and print - instances := c.resourceCache.GetCachedInstances() - for key, value := range instances { - fmt.Printf("Key: %s, Value: %v\n", key, value) - } + //TODO: remove later: print length of cache + fmt.Printf("Length of cache: %d\n", len(c.resourceCache.GetCachedInstances())) } if instanceInCache { - // TODO remove this printf later - fmt.Printf("Got the instance from Cache") return instance, nil } } + //TODO:remove later:Print not found in cache or cache is empty or instance not found + fmt.Println("Not found in cache or cache is empty or instance not found in cf") + // Attempt to retrieve instance from Cloud Foundry var serviceInstance *cfresource.ServiceInstance @@ -186,12 +185,10 @@ func (c *spaceClient) UpdateInstance(ctx context.Context, guid string, name stri c.resourceCache.AddInstanceInCache(owner, instance) } + //TODO:remove later: print instance added in cache and print the instance + fmt.Println("Instance added or updated in cache from update instance function") } - //TODO:remove later: get all instances and print - instances := c.resourceCache.GetCachedInstances() - for key, value := range instances { - fmt.Printf("Key: %s, Value: %v\n", key, value) - } + return err } @@ -199,7 +196,7 @@ func (c *spaceClient) DeleteInstance(ctx context.Context, guid string, owner str // TODO: return jobGUID to enable querying the job deletion status _, err := c.client.ServiceInstances.Delete(ctx, guid) if err == nil && c.resourceCache.IsResourceCacheEnabled() { - c.resourceCache.RemoveInstanceFromCache(owner) + c.resourceCache.DeleteInstanceFromCache(owner) } return err } diff --git a/internal/config/config.go b/internal/config/config.go index 8a68f82..7fdbc98 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -13,8 +13,7 @@ import ( type Config struct { //Resource cache is enabled or disabled - //TODO change it back to false after testing - IsResourceCacheEnabled bool `env:"RESOURCE_CACHE_ENABLED" envDefault:"true"` + IsResourceCacheEnabled bool `env:"RESOURCE_CACHE_ENABLED" envDefault:"false"` //cache timeout in seconds,minutes or hours CacheTimeOut string `env:"CACHE_TIMEOUT" envDefault:"1m"` diff --git a/internal/facade/client.go b/internal/facade/client.go index f543307..3c2a3dd 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -172,7 +172,6 @@ func (c *ResourceCache) SetCacheTimeOut(timeOut string) { func (c *ResourceCache) IsCacheExpired() bool { expirationTime := c.lastCacheTime.Add(c.cacheTimeOut) - //expiryTime := time.Until(c.lastCacheTime) fmt.Printf("Expiry time: %v\n", expirationTime) fmt.Printf("Cache timeout: %v\n", c.cacheTimeOut) return time.Now().After(expirationTime) @@ -205,6 +204,8 @@ func (c *ResourceCache) AddInstanceInCache(key string, instance *Instance) { c.mutex.Lock() defer c.mutex.Unlock() c.instances[key] = instance + // TODO :remove later:addedinstance to cache and print the instance + fmt.Printf("Added instance to cache: %v\n", instance) } // GetInstanceFromCache retrieves an instance from the cache @@ -212,6 +213,8 @@ func (c *ResourceCache) GetInstanceFromCache(key string) (*Instance, bool) { c.mutex.RLock() defer c.mutex.RUnlock() instance, found := c.instances[key] + // TODO :remove later: remove this printf later + fmt.Printf("Got the instance from Cache: %v", instance) return instance, found } @@ -219,13 +222,17 @@ func (c *ResourceCache) GetInstanceFromCache(key string) (*Instance, bool) { // This is used when an instance is deleted // The instance is removed from the cache to avoid stale data // The instance is removed from the cache only if the instance is found in the cache -func (c *ResourceCache) RemoveInstanceFromCache(key string) { +func (c *ResourceCache) DeleteInstanceFromCache(key string) { c.mutex.Lock() defer c.mutex.Unlock() _, found := c.instances[key] if found { delete(c.instances, key) + //TODO:remove later: print cache found and deleted + fmt.Println("Cache found and deleted") } + //TODO:remove later: print cache not found + fmt.Println("Cache not found to delete") } @@ -254,11 +261,13 @@ func (c *ResourceCache) UpdateInstanceInCache(guid string, name string, owner st } instance.Generation = generation c.instances[owner] = instance + //TODO:remove later:print updated instance + fmt.Printf("Updated cache instance: %v\n", instance) return true } - //TODO:remove later: print all the instances in cache - fmt.Printf("Instances in cache: %v\n", c.instances) + //TODO:remove later: print cache not found + fmt.Println("Cache not found to update") return false } From 80b6a849adb0cb00bc880ff6b66d6a483ecc560f Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Mon, 2 Sep 2024 18:11:15 +0530 Subject: [PATCH 21/63] tests for resource cache --- internal/cf/client.go | 1 + internal/cf/client_test.go | 39 +++- internal/cf/instance.go | 4 + internal/facade/client.go | 182 +-------------- .../facade/facadefakes/fake_space_client.go | 65 ++++++ internal/facade/resourcecache.go | 195 ++++++++++++++++ internal/facade/resourcecache_test.go | 209 ++++++++++++++++++ 7 files changed, 508 insertions(+), 187 deletions(-) create mode 100644 internal/facade/resourcecache.go create mode 100644 internal/facade/resourcecache_test.go diff --git a/internal/cf/client.go b/internal/cf/client.go index b88133f..d6b454b 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -34,6 +34,7 @@ const ( type organizationClient struct { organizationName string client cfclient.Client + // TODO: for org client //resourceCache *facade.Cache } diff --git a/internal/cf/client_test.go b/internal/cf/client_test.go index fd63a20..8957a3d 100644 --- a/internal/cf/client_test.go +++ b/internal/cf/client_test.go @@ -36,6 +36,7 @@ const ( spacesURI = "/v3/spaces" serviceInstancesURI = "/v3/service_instances" uaaURI = "/uaa/oauth/token" + label_selector = "service-operator.cf.cs.sap.com" ) type Token struct { @@ -51,7 +52,7 @@ func TestCFClient(t *testing.T) { } // is resource cache enabled and cache timeout -var resourceCacheEnabled = false +var resourceCacheEnabled = true var resourceCacheTimeout = 5 * time.Minute var cfg = &config.Config{ @@ -240,7 +241,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) - Expect(server.ReceivedRequests()).To(HaveLen(1)) + Expect(server.ReceivedRequests()).To(HaveLen(3)) }) It("should be able to query some space", func() { @@ -259,7 +260,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[2].URL.Path).To(Equal(serviceInstancesURI)) - Expect(server.ReceivedRequests()).To(HaveLen(3)) + Expect(server.ReceivedRequests()).To(HaveLen(4)) // verify metrics metricsList, err := metrics.Registry.Gather() @@ -298,7 +299,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[3].URL.Path).To(Equal(serviceInstancesURI)) - Expect(server.ReceivedRequests()).To(HaveLen(4)) + Expect(server.ReceivedRequests()).To(HaveLen(5)) }) It("should be able to query two different spaces", func() { @@ -314,7 +315,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) // Get instance Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(Owner)) + Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(label_selector)) // test space 2 spaceClient2, err2 := NewSpaceClient(SpaceName2, url, Username, Password, *cfg) @@ -323,7 +324,7 @@ var _ = Describe("CF Client tests", Ordered, func() { // no discovery of UAA endpoint or oAuth token here due to caching // Get instance Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring(Owner2)) + Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring(label_selector)) }) It("should register prometheus metrics for OrgClient", func() { @@ -366,6 +367,32 @@ var _ = Describe("CF Client tests", Ordered, func() { // for debugging: write metrics to file // prometheus.WriteToTextfile("metrics.txt", metrics.Registry) }) + It("should initialize resource cache and populate cache", func() { + // Enable resource cache in config + cfg.IsResourceCacheEnabled = true + + spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *cfg) + Expect(err).To(BeNil()) + Expect(spaceClient).ToNot(BeNil()) + + // Discover UAA endpoint + Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) + // Get new oAuth token + Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) + Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) + // Populate cache + Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(label_selector)) + // Verify that the cache is initialized + cache := spaceClient.GetResourceCache() + Expect(cache).ToNot(BeNil()) + Expect(cache.IsResourceCacheEnabled()).To(BeTrue()) + + // Verify that no additional requests are made to the server + Expect(server.ReceivedRequests()).To(HaveLen(3)) + + }) }) }) diff --git a/internal/cf/instance.go b/internal/cf/instance.go index f634808..d2381e0 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -253,3 +253,7 @@ func InitInstance(serviceInstance *cfresource.ServiceInstance, instanceOpts map[ StateDescription: stateDescription, }, nil } + +func (c *spaceClient) GetResourceCache() *facade.ResourceCache { + return c.resourceCache +} diff --git a/internal/facade/client.go b/internal/facade/client.go index 3c2a3dd..4ebb043 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -7,9 +7,6 @@ package facade import ( "context" - "fmt" - "sync" - "time" "github.com/sap/cf-service-operator/internal/config" ) @@ -100,6 +97,7 @@ type SpaceClient interface { FindServicePlan(ctx context.Context, serviceOfferingName string, servicePlanName string, spaceGuid string) (string, error) + GetResourceCache() *ResourceCache //TODO: Add methods for managing service keys // AddInstanceToResourceCache(key string, instance *Instance) // GetInstanceFromResourceCache(key string) (*Instance, bool) @@ -108,181 +106,3 @@ type SpaceClient interface { } type SpaceClientBuilder func(string, string, string, string, config.Config) (SpaceClient, error) - -// Cache is a simple in-memory cache to store spaces, instances, and bindings -type ResourceCache struct { - spaces map[string]*Space - instances map[string]*Instance - bindings map[string]*Binding - mutex sync.RWMutex - lastCacheTime time.Time - cacheTimeOut time.Duration - isResourceCacheEnabled bool -} - -// InitResourcesCache initializes a new cache -func InitResourceCache() *ResourceCache { - cache := &ResourceCache{ - spaces: make(map[string]*Space), - instances: make(map[string]*Instance), - bindings: make(map[string]*Binding), - cacheTimeOut: 5 * time.Minute, - } - return cache -} - -func (c *ResourceCache) GetCachedInstances() map[string]*Instance { - return c.instances -} - -func (c *ResourceCache) GetCachedBindings() map[string]*Binding { - return c.bindings -} - -func (c *ResourceCache) GetCachedSpaces() map[string]*Space { - return c.spaces -} - -// function to set the resource cache enabled flag from config -func (c *ResourceCache) SetResourceCacheEnabled(enabled bool) { - c.isResourceCacheEnabled = enabled -} -func (c *ResourceCache) IsResourceCacheEnabled() bool { - if c == nil { - return false - } - return c.isResourceCacheEnabled -} - -// Function to set the resource cache enabled flag from config -func (c *ResourceCache) GetCacheTimeOut() time.Duration { - return c.cacheTimeOut -} - -func (c *ResourceCache) SetCacheTimeOut(timeOut string) { - cacheTimeOut, err := time.ParseDuration(timeOut) - if err != nil { - fmt.Printf("Error parsing duration: %v\n", err) - return - } - c.cacheTimeOut = cacheTimeOut -} - -// Function to check if the cache is expired -func (c *ResourceCache) IsCacheExpired() bool { - - expirationTime := c.lastCacheTime.Add(c.cacheTimeOut) - fmt.Printf("Expiry time: %v\n", expirationTime) - fmt.Printf("Cache timeout: %v\n", c.cacheTimeOut) - return time.Now().After(expirationTime) - -} - -// Function to set the last cache time -func (c *ResourceCache) SetLastCacheTime() { - c.lastCacheTime = time.Now() - fmt.Printf("Last cache time: %v\n", c.lastCacheTime) -} - -// AddSpaceInCache stores a space in the cache -func (c *ResourceCache) AddSpaceInCache(key string, space *Space) { - c.mutex.Lock() - defer c.mutex.Unlock() - c.spaces[key] = space -} - -// GetSpaceFromCache retrieves a space from the cache -func (c *ResourceCache) GetSpaceFromCache(key string) (*Space, bool) { - c.mutex.RLock() - defer c.mutex.RUnlock() - space, found := c.spaces[key] - return space, found -} - -// AddInstanceInCache stores an instance in the cache -func (c *ResourceCache) AddInstanceInCache(key string, instance *Instance) { - c.mutex.Lock() - defer c.mutex.Unlock() - c.instances[key] = instance - // TODO :remove later:addedinstance to cache and print the instance - fmt.Printf("Added instance to cache: %v\n", instance) -} - -// GetInstanceFromCache retrieves an instance from the cache -func (c *ResourceCache) GetInstanceFromCache(key string) (*Instance, bool) { - c.mutex.RLock() - defer c.mutex.RUnlock() - instance, found := c.instances[key] - // TODO :remove later: remove this printf later - fmt.Printf("Got the instance from Cache: %v", instance) - return instance, found -} - -// RemoveInstanceFromCache removes an instance from the cache -// This is used when an instance is deleted -// The instance is removed from the cache to avoid stale data -// The instance is removed from the cache only if the instance is found in the cache -func (c *ResourceCache) DeleteInstanceFromCache(key string) { - c.mutex.Lock() - defer c.mutex.Unlock() - _, found := c.instances[key] - if found { - delete(c.instances, key) - //TODO:remove later: print cache found and deleted - fmt.Println("Cache found and deleted") - } - //TODO:remove later: print cache not found - fmt.Println("Cache not found to delete") - -} - -// update the instance in the cache -func (c *ResourceCache) UpdateInstanceInCache(guid string, name string, owner string, servicePlanGuid string, parameters map[string]interface{}, generation int64) (status bool) { - c.mutex.Lock() - defer c.mutex.Unlock() - //update if the instance is found in the cache - //update all the struct variables if they are not nil or empty - instance, found := c.instances[owner] - if found { - if guid != "" { - instance.Guid = guid - } - if name != "" { - instance.Name = name - } - if servicePlanGuid != "" { - instance.ServicePlanGuid = servicePlanGuid - } - if parameters != nil { - instance.ParameterHash = ObjectHash(parameters) - } - if owner != "" { - instance.Owner = owner - } - instance.Generation = generation - c.instances[owner] = instance - //TODO:remove later:print updated instance - fmt.Printf("Updated cache instance: %v\n", instance) - return true - - } - //TODO:remove later: print cache not found - fmt.Println("Cache not found to update") - return false - -} - -// AddBindingInCache stores a binding in the cache -func (c *ResourceCache) AddBindingInCache(key string, binding *Binding) { - c.mutex.Lock() - defer c.mutex.Unlock() - c.bindings[key] = binding -} - -// GetBindingFromCache retrieves a binding from the cache -func (c *ResourceCache) GetBindingFromCache(key string) (*Binding, bool) { - c.mutex.RLock() - defer c.mutex.RUnlock() - binding, found := c.bindings[key] - return binding, found -} diff --git a/internal/facade/facadefakes/fake_space_client.go b/internal/facade/facadefakes/fake_space_client.go index 68c2159..ec663c6 100644 --- a/internal/facade/facadefakes/fake_space_client.go +++ b/internal/facade/facadefakes/fake_space_client.go @@ -115,6 +115,16 @@ type FakeSpaceClient struct { result1 *facade.Instance result2 error } + GetResourceCacheStub func() *facade.ResourceCache + getResourceCacheMutex sync.RWMutex + getResourceCacheArgsForCall []struct { + } + getResourceCacheReturns struct { + result1 *facade.ResourceCache + } + getResourceCacheReturnsOnCall map[int]struct { + result1 *facade.ResourceCache + } UpdateBindingStub func(context.Context, string, int64, map[string]interface{}) error updateBindingMutex sync.RWMutex updateBindingArgsForCall []struct { @@ -611,6 +621,59 @@ func (fake *FakeSpaceClient) GetInstanceReturnsOnCall(i int, result1 *facade.Ins }{result1, result2} } +func (fake *FakeSpaceClient) GetResourceCache() *facade.ResourceCache { + fake.getResourceCacheMutex.Lock() + ret, specificReturn := fake.getResourceCacheReturnsOnCall[len(fake.getResourceCacheArgsForCall)] + fake.getResourceCacheArgsForCall = append(fake.getResourceCacheArgsForCall, struct { + }{}) + stub := fake.GetResourceCacheStub + fakeReturns := fake.getResourceCacheReturns + fake.recordInvocation("GetResourceCache", []interface{}{}) + fake.getResourceCacheMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeSpaceClient) GetResourceCacheCallCount() int { + fake.getResourceCacheMutex.RLock() + defer fake.getResourceCacheMutex.RUnlock() + return len(fake.getResourceCacheArgsForCall) +} + +func (fake *FakeSpaceClient) GetResourceCacheCalls(stub func() *facade.ResourceCache) { + fake.getResourceCacheMutex.Lock() + defer fake.getResourceCacheMutex.Unlock() + fake.GetResourceCacheStub = stub +} + +func (fake *FakeSpaceClient) GetResourceCacheReturns(result1 *facade.ResourceCache) { + fake.getResourceCacheMutex.Lock() + defer fake.getResourceCacheMutex.Unlock() + fake.GetResourceCacheStub = nil + fake.getResourceCacheReturns = struct { + result1 *facade.ResourceCache + }{result1} +} + +func (fake *FakeSpaceClient) GetResourceCacheReturnsOnCall(i int, result1 *facade.ResourceCache) { + fake.getResourceCacheMutex.Lock() + defer fake.getResourceCacheMutex.Unlock() + fake.GetResourceCacheStub = nil + if fake.getResourceCacheReturnsOnCall == nil { + fake.getResourceCacheReturnsOnCall = make(map[int]struct { + result1 *facade.ResourceCache + }) + } + fake.getResourceCacheReturnsOnCall[i] = struct { + result1 *facade.ResourceCache + }{result1} +} + func (fake *FakeSpaceClient) UpdateBinding(arg1 context.Context, arg2 string, arg3 int64, arg4 map[string]interface{}) error { fake.updateBindingMutex.Lock() ret, specificReturn := fake.updateBindingReturnsOnCall[len(fake.updateBindingArgsForCall)] @@ -765,6 +828,8 @@ func (fake *FakeSpaceClient) Invocations() map[string][][]interface{} { defer fake.getBindingMutex.RUnlock() fake.getInstanceMutex.RLock() defer fake.getInstanceMutex.RUnlock() + fake.getResourceCacheMutex.RLock() + defer fake.getResourceCacheMutex.RUnlock() fake.updateBindingMutex.RLock() defer fake.updateBindingMutex.RUnlock() fake.updateInstanceMutex.RLock() diff --git a/internal/facade/resourcecache.go b/internal/facade/resourcecache.go new file mode 100644 index 0000000..111ad0b --- /dev/null +++ b/internal/facade/resourcecache.go @@ -0,0 +1,195 @@ +/* +SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and cf-service-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +package facade + +import ( + "fmt" + "sync" + "time" +) + +// Cache is a simple in-memory cache to store spaces, instances, and bindings +type ResourceCache struct { + spaces map[string]*Space + instances map[string]*Instance + bindings map[string]*Binding + mutex sync.RWMutex + lastCacheTime time.Time + cacheTimeOut time.Duration + isResourceCacheEnabled bool +} + +// InitResourcesCache initializes a new cache +func InitResourceCache() *ResourceCache { + cache := &ResourceCache{ + spaces: make(map[string]*Space), + instances: make(map[string]*Instance), + bindings: make(map[string]*Binding), + cacheTimeOut: 5 * time.Minute, + } + return cache +} + +// Get resource cache +func (c *ResourceCache) GetResourceCache() *ResourceCache { + return c +} + +func (c *ResourceCache) GetCachedInstances() map[string]*Instance { + return c.instances +} + +func (c *ResourceCache) GetCachedBindings() map[string]*Binding { + return c.bindings +} + +func (c *ResourceCache) GetCachedSpaces() map[string]*Space { + return c.spaces +} + +// function to set the resource cache enabled flag from config +func (c *ResourceCache) SetResourceCacheEnabled(enabled bool) { + c.isResourceCacheEnabled = enabled +} +func (c *ResourceCache) IsResourceCacheEnabled() bool { + if c == nil { + return false + } + return c.isResourceCacheEnabled +} + +// Function to set the resource cache enabled flag from config +func (c *ResourceCache) GetCacheTimeOut() time.Duration { + return c.cacheTimeOut +} + +func (c *ResourceCache) SetCacheTimeOut(timeOut string) { + cacheTimeOut, err := time.ParseDuration(timeOut) + if err != nil { + fmt.Printf("Error parsing duration: %v\n", err) + return + } + c.cacheTimeOut = cacheTimeOut +} + +// Function to check if the cache is expired +func (c *ResourceCache) IsCacheExpired() bool { + + expirationTime := c.lastCacheTime.Add(c.cacheTimeOut) + fmt.Printf("Expiry time: %v\n", expirationTime) + fmt.Printf("Cache timeout: %v\n", c.cacheTimeOut) + return time.Now().After(expirationTime) + +} + +// Function to set the last cache time +func (c *ResourceCache) SetLastCacheTime() { + c.lastCacheTime = time.Now() + fmt.Printf("Last cache time: %v\n", c.lastCacheTime) +} + +// AddSpaceInCache stores a space in the cache +func (c *ResourceCache) AddSpaceInCache(key string, space *Space) { + c.mutex.Lock() + defer c.mutex.Unlock() + c.spaces[key] = space +} + +// GetSpaceFromCache retrieves a space from the cache +func (c *ResourceCache) GetSpaceFromCache(key string) (*Space, bool) { + c.mutex.RLock() + defer c.mutex.RUnlock() + space, found := c.spaces[key] + return space, found +} + +// AddInstanceInCache stores an instance in the cache +func (c *ResourceCache) AddInstanceInCache(key string, instance *Instance) { + c.mutex.Lock() + defer c.mutex.Unlock() + c.instances[key] = instance + // TODO :remove later:addedinstance to cache and print the instance + fmt.Printf("Added instance to cache: %v\n", instance) +} + +// GetInstanceFromCache retrieves an instance from the cache +func (c *ResourceCache) GetInstanceFromCache(key string) (*Instance, bool) { + c.mutex.RLock() + defer c.mutex.RUnlock() + instance, found := c.instances[key] + // TODO :remove later: remove this printf later + fmt.Printf("Got the instance from Cache: %v", instance) + return instance, found +} + +// RemoveInstanceFromCache removes an instance from the cache +// This is used when an instance is deleted +// The instance is removed from the cache to avoid stale data +// The instance is removed from the cache only if the instance is found in the cache +func (c *ResourceCache) DeleteInstanceFromCache(key string) { + c.mutex.Lock() + defer c.mutex.Unlock() + _, found := c.instances[key] + if found { + delete(c.instances, key) + //TODO:remove later: print cache found and deleted + fmt.Println("Cache found and deleted") + } + //TODO:remove later: print cache not found + fmt.Println("Cache not found to delete") + +} + +// update the instance in the cache +func (c *ResourceCache) UpdateInstanceInCache(guid string, name string, owner string, servicePlanGuid string, parameters map[string]interface{}, generation int64) (status bool) { + c.mutex.Lock() + defer c.mutex.Unlock() + //update if the instance is found in the cache + //update all the struct variables if they are not nil or empty + instance, found := c.instances[owner] + if found { + if guid != "" { + instance.Guid = guid + } + if name != "" { + instance.Name = name + } + if servicePlanGuid != "" { + instance.ServicePlanGuid = servicePlanGuid + } + if parameters != nil { + instance.ParameterHash = ObjectHash(parameters) + } + if owner != "" { + instance.Owner = owner + } + instance.Generation = generation + c.instances[owner] = instance + //TODO:remove later:print updated instance + fmt.Printf("Updated cache instance: %v\n", instance) + return true + + } + //TODO:remove later: print cache not found + fmt.Println("Cache not found to update") + return false + +} + +// AddBindingInCache stores a binding in the cache +func (c *ResourceCache) AddBindingInCache(key string, binding *Binding) { + c.mutex.Lock() + defer c.mutex.Unlock() + c.bindings[key] = binding +} + +// GetBindingFromCache retrieves a binding from the cache +func (c *ResourceCache) GetBindingFromCache(key string) (*Binding, bool) { + c.mutex.RLock() + defer c.mutex.RUnlock() + binding, found := c.bindings[key] + return binding, found +} diff --git a/internal/facade/resourcecache_test.go b/internal/facade/resourcecache_test.go new file mode 100644 index 0000000..94c2312 --- /dev/null +++ b/internal/facade/resourcecache_test.go @@ -0,0 +1,209 @@ +/* +SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and cf-service-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +package facade + +import ( + "strconv" + "sync" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestFacade(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Facade Suite") +} + +var _ = Describe("ResourceCache", func() { + var cache *ResourceCache + var instance *Instance + var wg sync.WaitGroup + concurrencyLevel := 100 + + BeforeEach(func() { + cache = InitResourceCache() + instance = &Instance{ + Guid: "guid1", + Name: "name1", + Owner: "owner1", + ServicePlanGuid: "plan1", + ParameterHash: "hash1", + Generation: 1, + } + }) + + Context("basic CRUD operations", func() { + It("should add, get, update, and delete an instance in the cache", func() { + // Add instance + Ownerkey := "owner1" + cache.AddInstanceInCache(Ownerkey, instance) + + // Get instance + retrievedInstance, found := cache.GetInstanceFromCache(Ownerkey) + Expect(found).To(BeTrue()) + Expect(retrievedInstance).To(Equal(instance)) + + // Update instance + updatedInstance := &Instance{ + Guid: "guid1", + Name: "updatedName", + Owner: "owner1", + ServicePlanGuid: "updatedPlan", + ParameterHash: "hash1", + Generation: 2, + } + cache.UpdateInstanceInCache("guid1", "updatedName", "owner1", "updatedPlan", nil, 2) + retrievedInstance, found = cache.GetInstanceFromCache(Ownerkey) + Expect(found).To(BeTrue()) + Expect(retrievedInstance).To(Equal(updatedInstance)) + + // Delete instance + cache.DeleteInstanceFromCache(Ownerkey) + _, found = cache.GetInstanceFromCache(Ownerkey) + Expect(found).To(BeFalse()) + }) + }) + + Context("edge cases", func() { + It("should handle adding an instance with an existing key", func() { + cache.AddInstanceInCache("owner1", instance) + cache.AddInstanceInCache("owner1", instance) + retrievedInstance, found := cache.GetInstanceFromCache("owner1") + Expect(found).To(BeTrue()) + Expect(retrievedInstance).To(Equal(instance)) + }) + + It("should handle updating a non-existent instance", func() { + cache.UpdateInstanceInCache("nonExistentGuid", "name", "owner", "plan", nil, 1) + _, found := cache.GetInstanceFromCache("nonExistentGuid") + Expect(found).To(BeFalse()) + }) + + It("should handle deleting a non-existent instance", func() { + cache.DeleteInstanceFromCache("nonExistentKey") + _, found := cache.GetInstanceFromCache("nonExistentKey") + Expect(found).To(BeFalse()) + }) + }) + + Context("concurrent operations", func() { + It("should handle concurrent AddInstanceInCache", func() { + for i := 0; i < concurrencyLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + cache.AddInstanceInCache("key"+strconv.Itoa(i), instance) + }(i) + } + wg.Wait() + }) + + It("should handle concurrent GetInstanceFromCache", func() { + for i := 0; i < concurrencyLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + cache.GetInstanceFromCache("key" + strconv.Itoa(i)) + }(i) + } + wg.Wait() + }) + + It("should handle concurrent UpdateInstanceInCache", func() { + for i := 0; i < concurrencyLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + cache.UpdateInstanceInCache("guid"+strconv.Itoa(i), "name"+strconv.Itoa(i), "owner1", "plan"+strconv.Itoa(i), nil, int64(i)) + }(i) + } + wg.Wait() + }) + + It("should handle concurrent DeleteInstanceFromCache", func() { + for i := 0; i < concurrencyLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + cache.DeleteInstanceFromCache("key" + strconv.Itoa(i)) + }(i) + } + wg.Wait() + + // Verify final state + for i := 0; i < concurrencyLevel; i++ { + _, found := cache.GetInstanceFromCache("key" + strconv.Itoa(i)) + Expect(found).To(BeFalse(), "Expected instance to be deleted from cache") + } + }) + + It("should handle high load", func() { + highLoadLevel := 1000 + + for i := 0; i < highLoadLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + cache.AddInstanceInCache("key"+strconv.Itoa(i), instance) + }(i) + } + + wg.Wait() + + for i := 0; i < highLoadLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + cache.GetInstanceFromCache("key" + strconv.Itoa(i)) + }(i) + } + + wg.Wait() + + for i := 0; i < highLoadLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + cache.DeleteInstanceFromCache("key" + strconv.Itoa(i)) + }(i) + } + + wg.Wait() + + // Verify final state + for i := 0; i < highLoadLevel; i++ { + _, found := cache.GetInstanceFromCache("key" + strconv.Itoa(i)) + Expect(found).To(BeFalse(), "Expected instance to be deleted from cache") + } + }) + + It("should maintain data integrity during concurrent operations", func() { + for i := 0; i < concurrencyLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + cache.AddInstanceInCache("key"+strconv.Itoa(i), instance) + }(i) + } + + wg.Wait() + + for i := 0; i < concurrencyLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + retrievedInstance, found := cache.GetInstanceFromCache("key" + strconv.Itoa(i)) + Expect(found).To(BeTrue()) + Expect(retrievedInstance).To(Equal(instance)) + }(i) + } + + wg.Wait() + }) + }) +}) From e7507537d1c92ff88d33e84ce592701a7444c324 Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Tue, 3 Sep 2024 12:24:20 +0530 Subject: [PATCH 22/63] refactored the code --- internal/cf/client_test.go | 4 -- internal/cf/instance.go | 26 ++++---- internal/{facade => cf}/resourcecache.go | 60 ++++++++--------- internal/{facade => cf}/resourcecache_test.go | 63 +++++++++--------- internal/facade/client.go | 1 - .../facade/facadefakes/fake_space_client.go | 65 ------------------- 6 files changed, 74 insertions(+), 145 deletions(-) rename internal/{facade => cf}/resourcecache.go (69%) rename internal/{facade => cf}/resourcecache_test.go (69%) diff --git a/internal/cf/client_test.go b/internal/cf/client_test.go index 8957a3d..9bc6e21 100644 --- a/internal/cf/client_test.go +++ b/internal/cf/client_test.go @@ -384,10 +384,6 @@ var _ = Describe("CF Client tests", Ordered, func() { // Populate cache Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(label_selector)) - // Verify that the cache is initialized - cache := spaceClient.GetResourceCache() - Expect(cache).ToNot(BeNil()) - Expect(cache.IsResourceCacheEnabled()).To(BeTrue()) // Verify that no additional requests are made to the server Expect(server.ReceivedRequests()).To(HaveLen(3)) diff --git a/internal/cf/instance.go b/internal/cf/instance.go index d2381e0..b581846 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -49,26 +49,26 @@ func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOpt // The function add the parameter values to the orphan cf instance, so that can be adopted. func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]string) (*facade.Instance, error) { - if c.resourceCache.IsResourceCacheEnabled() { + if c.resourceCache.checkResourceCacheEnabled() { // Ensure resourcesCache is initialized if c.resourceCache == nil { - c.resourceCache = facade.InitResourceCache() + c.resourceCache = initResourceCache() } // Attempt to retrieve instance from Cache var instanceInCache bool var instance *facade.Instance //TODO recheck this logic later - if c.resourceCache.IsCacheExpired() { + if c.resourceCache.isCacheExpired() { //TODO: remove later:Print cache is expited fmt.Println("Cache is expired") c.populateResourceCache() } - if len(c.resourceCache.GetCachedInstances()) != 0 { + if len(c.resourceCache.getCachedInstances()) != 0 { - instance, instanceInCache = c.resourceCache.GetInstanceFromCache(instanceOpts["owner"]) + instance, instanceInCache = c.resourceCache.getInstanceFromCache(instanceOpts["owner"]) //TODO: remove later: print length of cache - fmt.Printf("Length of cache: %d\n", len(c.resourceCache.GetCachedInstances())) + fmt.Printf("Length of cache: %d\n", len(c.resourceCache.getCachedInstances())) } @@ -167,8 +167,8 @@ func (c *spaceClient) UpdateInstance(ctx context.Context, guid string, name stri } _, _, err := c.client.ServiceInstances.UpdateManaged(ctx, guid, req) - if err == nil && c.resourceCache.IsResourceCacheEnabled() { - isUpdated := c.resourceCache.UpdateInstanceInCache(guid, name, owner, servicePlanGuid, parameters, generation) + if err == nil && c.resourceCache.checkResourceCacheEnabled() { + isUpdated := c.resourceCache.updateInstanceInCache(guid, name, owner, servicePlanGuid, parameters, generation) if !isUpdated { //add instance to cache in case of orphan instance @@ -182,7 +182,7 @@ func (c *spaceClient) UpdateInstance(ctx context.Context, guid string, name stri State: facade.InstanceStateReady, StateDescription: "", } - c.resourceCache.AddInstanceInCache(owner, instance) + c.resourceCache.addInstanceInCache(owner, instance) } //TODO:remove later: print instance added in cache and print the instance @@ -195,8 +195,8 @@ func (c *spaceClient) UpdateInstance(ctx context.Context, guid string, name stri func (c *spaceClient) DeleteInstance(ctx context.Context, guid string, owner string) error { // TODO: return jobGUID to enable querying the job deletion status _, err := c.client.ServiceInstances.Delete(ctx, guid) - if err == nil && c.resourceCache.IsResourceCacheEnabled() { - c.resourceCache.DeleteInstanceFromCache(owner) + if err == nil && c.resourceCache.checkResourceCacheEnabled() { + c.resourceCache.deleteInstanceFromCache(owner) } return err } @@ -253,7 +253,3 @@ func InitInstance(serviceInstance *cfresource.ServiceInstance, instanceOpts map[ StateDescription: stateDescription, }, nil } - -func (c *spaceClient) GetResourceCache() *facade.ResourceCache { - return c.resourceCache -} diff --git a/internal/facade/resourcecache.go b/internal/cf/resourcecache.go similarity index 69% rename from internal/facade/resourcecache.go rename to internal/cf/resourcecache.go index 111ad0b..29fd925 100644 --- a/internal/facade/resourcecache.go +++ b/internal/cf/resourcecache.go @@ -3,19 +3,21 @@ SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and cf-service-o SPDX-License-Identifier: Apache-2.0 */ -package facade +package cf import ( "fmt" "sync" "time" + + "github.com/sap/cf-service-operator/internal/facade" ) // Cache is a simple in-memory cache to store spaces, instances, and bindings -type ResourceCache struct { - spaces map[string]*Space - instances map[string]*Instance - bindings map[string]*Binding +type resourceCache struct { + spaces map[string]*facade.Space + instances map[string]*facade.Instance + bindings map[string]*facade.Binding mutex sync.RWMutex lastCacheTime time.Time cacheTimeOut time.Duration @@ -23,38 +25,38 @@ type ResourceCache struct { } // InitResourcesCache initializes a new cache -func InitResourceCache() *ResourceCache { - cache := &ResourceCache{ - spaces: make(map[string]*Space), - instances: make(map[string]*Instance), - bindings: make(map[string]*Binding), +func initResourceCache() *resourceCache { + cache := &resourceCache{ + spaces: make(map[string]*facade.Space), + instances: make(map[string]*facade.Instance), + bindings: make(map[string]*facade.Binding), cacheTimeOut: 5 * time.Minute, } return cache } // Get resource cache -func (c *ResourceCache) GetResourceCache() *ResourceCache { +func (c *resourceCache) getresourceCache() *resourceCache { return c } -func (c *ResourceCache) GetCachedInstances() map[string]*Instance { +func (c *resourceCache) getCachedInstances() map[string]*facade.Instance { return c.instances } -func (c *ResourceCache) GetCachedBindings() map[string]*Binding { +func (c *resourceCache) getCachedBindings() map[string]*facade.Binding { return c.bindings } -func (c *ResourceCache) GetCachedSpaces() map[string]*Space { +func (c *resourceCache) getCachedSpaces() map[string]*facade.Space { return c.spaces } // function to set the resource cache enabled flag from config -func (c *ResourceCache) SetResourceCacheEnabled(enabled bool) { +func (c *resourceCache) setResourceCacheEnabled(enabled bool) { c.isResourceCacheEnabled = enabled } -func (c *ResourceCache) IsResourceCacheEnabled() bool { +func (c *resourceCache) checkResourceCacheEnabled() bool { if c == nil { return false } @@ -62,11 +64,11 @@ func (c *ResourceCache) IsResourceCacheEnabled() bool { } // Function to set the resource cache enabled flag from config -func (c *ResourceCache) GetCacheTimeOut() time.Duration { +func (c *resourceCache) getCacheTimeOut() time.Duration { return c.cacheTimeOut } -func (c *ResourceCache) SetCacheTimeOut(timeOut string) { +func (c *resourceCache) setCacheTimeOut(timeOut string) { cacheTimeOut, err := time.ParseDuration(timeOut) if err != nil { fmt.Printf("Error parsing duration: %v\n", err) @@ -76,7 +78,7 @@ func (c *ResourceCache) SetCacheTimeOut(timeOut string) { } // Function to check if the cache is expired -func (c *ResourceCache) IsCacheExpired() bool { +func (c *resourceCache) isCacheExpired() bool { expirationTime := c.lastCacheTime.Add(c.cacheTimeOut) fmt.Printf("Expiry time: %v\n", expirationTime) @@ -86,20 +88,20 @@ func (c *ResourceCache) IsCacheExpired() bool { } // Function to set the last cache time -func (c *ResourceCache) SetLastCacheTime() { +func (c *resourceCache) setLastCacheTime() { c.lastCacheTime = time.Now() fmt.Printf("Last cache time: %v\n", c.lastCacheTime) } // AddSpaceInCache stores a space in the cache -func (c *ResourceCache) AddSpaceInCache(key string, space *Space) { +func (c *resourceCache) addSpaceInCache(key string, space *facade.Space) { c.mutex.Lock() defer c.mutex.Unlock() c.spaces[key] = space } // GetSpaceFromCache retrieves a space from the cache -func (c *ResourceCache) GetSpaceFromCache(key string) (*Space, bool) { +func (c *resourceCache) getSpaceFromCache(key string) (*facade.Space, bool) { c.mutex.RLock() defer c.mutex.RUnlock() space, found := c.spaces[key] @@ -107,7 +109,7 @@ func (c *ResourceCache) GetSpaceFromCache(key string) (*Space, bool) { } // AddInstanceInCache stores an instance in the cache -func (c *ResourceCache) AddInstanceInCache(key string, instance *Instance) { +func (c *resourceCache) addInstanceInCache(key string, instance *facade.Instance) { c.mutex.Lock() defer c.mutex.Unlock() c.instances[key] = instance @@ -116,7 +118,7 @@ func (c *ResourceCache) AddInstanceInCache(key string, instance *Instance) { } // GetInstanceFromCache retrieves an instance from the cache -func (c *ResourceCache) GetInstanceFromCache(key string) (*Instance, bool) { +func (c *resourceCache) getInstanceFromCache(key string) (*facade.Instance, bool) { c.mutex.RLock() defer c.mutex.RUnlock() instance, found := c.instances[key] @@ -129,7 +131,7 @@ func (c *ResourceCache) GetInstanceFromCache(key string) (*Instance, bool) { // This is used when an instance is deleted // The instance is removed from the cache to avoid stale data // The instance is removed from the cache only if the instance is found in the cache -func (c *ResourceCache) DeleteInstanceFromCache(key string) { +func (c *resourceCache) deleteInstanceFromCache(key string) { c.mutex.Lock() defer c.mutex.Unlock() _, found := c.instances[key] @@ -144,7 +146,7 @@ func (c *ResourceCache) DeleteInstanceFromCache(key string) { } // update the instance in the cache -func (c *ResourceCache) UpdateInstanceInCache(guid string, name string, owner string, servicePlanGuid string, parameters map[string]interface{}, generation int64) (status bool) { +func (c *resourceCache) updateInstanceInCache(guid string, name string, owner string, servicePlanGuid string, parameters map[string]interface{}, generation int64) (status bool) { c.mutex.Lock() defer c.mutex.Unlock() //update if the instance is found in the cache @@ -161,7 +163,7 @@ func (c *ResourceCache) UpdateInstanceInCache(guid string, name string, owner st instance.ServicePlanGuid = servicePlanGuid } if parameters != nil { - instance.ParameterHash = ObjectHash(parameters) + instance.ParameterHash = facade.ObjectHash(parameters) } if owner != "" { instance.Owner = owner @@ -180,14 +182,14 @@ func (c *ResourceCache) UpdateInstanceInCache(guid string, name string, owner st } // AddBindingInCache stores a binding in the cache -func (c *ResourceCache) AddBindingInCache(key string, binding *Binding) { +func (c *resourceCache) addBindingInCache(key string, binding *facade.Binding) { c.mutex.Lock() defer c.mutex.Unlock() c.bindings[key] = binding } // GetBindingFromCache retrieves a binding from the cache -func (c *ResourceCache) GetBindingFromCache(key string) (*Binding, bool) { +func (c *resourceCache) getBindingFromCache(key string) (*facade.Binding, bool) { c.mutex.RLock() defer c.mutex.RUnlock() binding, found := c.bindings[key] diff --git a/internal/facade/resourcecache_test.go b/internal/cf/resourcecache_test.go similarity index 69% rename from internal/facade/resourcecache_test.go rename to internal/cf/resourcecache_test.go index 94c2312..c8e3425 100644 --- a/internal/facade/resourcecache_test.go +++ b/internal/cf/resourcecache_test.go @@ -3,7 +3,7 @@ SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and cf-service-o SPDX-License-Identifier: Apache-2.0 */ -package facade +package cf import ( "strconv" @@ -12,22 +12,23 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/sap/cf-service-operator/internal/facade" ) func TestFacade(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Facade Suite") + RunSpecs(t, "ResourceCache Suite") } var _ = Describe("ResourceCache", func() { - var cache *ResourceCache - var instance *Instance + var cache *resourceCache + var instance *facade.Instance var wg sync.WaitGroup concurrencyLevel := 100 BeforeEach(func() { - cache = InitResourceCache() - instance = &Instance{ + cache = initResourceCache() + instance = &facade.Instance{ Guid: "guid1", Name: "name1", Owner: "owner1", @@ -41,15 +42,15 @@ var _ = Describe("ResourceCache", func() { It("should add, get, update, and delete an instance in the cache", func() { // Add instance Ownerkey := "owner1" - cache.AddInstanceInCache(Ownerkey, instance) + cache.addInstanceInCache(Ownerkey, instance) // Get instance - retrievedInstance, found := cache.GetInstanceFromCache(Ownerkey) + retrievedInstance, found := cache.getInstanceFromCache(Ownerkey) Expect(found).To(BeTrue()) Expect(retrievedInstance).To(Equal(instance)) // Update instance - updatedInstance := &Instance{ + updatedInstance := &facade.Instance{ Guid: "guid1", Name: "updatedName", Owner: "owner1", @@ -57,36 +58,36 @@ var _ = Describe("ResourceCache", func() { ParameterHash: "hash1", Generation: 2, } - cache.UpdateInstanceInCache("guid1", "updatedName", "owner1", "updatedPlan", nil, 2) - retrievedInstance, found = cache.GetInstanceFromCache(Ownerkey) + cache.updateInstanceInCache("guid1", "updatedName", "owner1", "updatedPlan", nil, 2) + retrievedInstance, found = cache.getInstanceFromCache(Ownerkey) Expect(found).To(BeTrue()) Expect(retrievedInstance).To(Equal(updatedInstance)) // Delete instance - cache.DeleteInstanceFromCache(Ownerkey) - _, found = cache.GetInstanceFromCache(Ownerkey) + cache.deleteInstanceFromCache(Ownerkey) + _, found = cache.getInstanceFromCache(Ownerkey) Expect(found).To(BeFalse()) }) }) Context("edge cases", func() { It("should handle adding an instance with an existing key", func() { - cache.AddInstanceInCache("owner1", instance) - cache.AddInstanceInCache("owner1", instance) - retrievedInstance, found := cache.GetInstanceFromCache("owner1") + cache.addInstanceInCache("owner1", instance) + cache.addInstanceInCache("owner1", instance) + retrievedInstance, found := cache.getInstanceFromCache("owner1") Expect(found).To(BeTrue()) Expect(retrievedInstance).To(Equal(instance)) }) It("should handle updating a non-existent instance", func() { - cache.UpdateInstanceInCache("nonExistentGuid", "name", "owner", "plan", nil, 1) - _, found := cache.GetInstanceFromCache("nonExistentGuid") + cache.updateInstanceInCache("nonExistentGuid", "name", "owner", "plan", nil, 1) + _, found := cache.getInstanceFromCache("nonExistentGuid") Expect(found).To(BeFalse()) }) It("should handle deleting a non-existent instance", func() { - cache.DeleteInstanceFromCache("nonExistentKey") - _, found := cache.GetInstanceFromCache("nonExistentKey") + cache.deleteInstanceFromCache("nonExistentKey") + _, found := cache.getInstanceFromCache("nonExistentKey") Expect(found).To(BeFalse()) }) }) @@ -97,7 +98,7 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - cache.AddInstanceInCache("key"+strconv.Itoa(i), instance) + cache.addInstanceInCache("key"+strconv.Itoa(i), instance) }(i) } wg.Wait() @@ -108,7 +109,7 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - cache.GetInstanceFromCache("key" + strconv.Itoa(i)) + cache.getInstanceFromCache("key" + strconv.Itoa(i)) }(i) } wg.Wait() @@ -119,7 +120,7 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - cache.UpdateInstanceInCache("guid"+strconv.Itoa(i), "name"+strconv.Itoa(i), "owner1", "plan"+strconv.Itoa(i), nil, int64(i)) + cache.updateInstanceInCache("guid"+strconv.Itoa(i), "name"+strconv.Itoa(i), "owner1", "plan"+strconv.Itoa(i), nil, int64(i)) }(i) } wg.Wait() @@ -130,14 +131,14 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - cache.DeleteInstanceFromCache("key" + strconv.Itoa(i)) + cache.deleteInstanceFromCache("key" + strconv.Itoa(i)) }(i) } wg.Wait() // Verify final state for i := 0; i < concurrencyLevel; i++ { - _, found := cache.GetInstanceFromCache("key" + strconv.Itoa(i)) + _, found := cache.getInstanceFromCache("key" + strconv.Itoa(i)) Expect(found).To(BeFalse(), "Expected instance to be deleted from cache") } }) @@ -149,7 +150,7 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - cache.AddInstanceInCache("key"+strconv.Itoa(i), instance) + cache.addInstanceInCache("key"+strconv.Itoa(i), instance) }(i) } @@ -159,7 +160,7 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - cache.GetInstanceFromCache("key" + strconv.Itoa(i)) + cache.getInstanceFromCache("key" + strconv.Itoa(i)) }(i) } @@ -169,7 +170,7 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - cache.DeleteInstanceFromCache("key" + strconv.Itoa(i)) + cache.deleteInstanceFromCache("key" + strconv.Itoa(i)) }(i) } @@ -177,7 +178,7 @@ var _ = Describe("ResourceCache", func() { // Verify final state for i := 0; i < highLoadLevel; i++ { - _, found := cache.GetInstanceFromCache("key" + strconv.Itoa(i)) + _, found := cache.getInstanceFromCache("key" + strconv.Itoa(i)) Expect(found).To(BeFalse(), "Expected instance to be deleted from cache") } }) @@ -187,7 +188,7 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - cache.AddInstanceInCache("key"+strconv.Itoa(i), instance) + cache.addInstanceInCache("key"+strconv.Itoa(i), instance) }(i) } @@ -197,7 +198,7 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - retrievedInstance, found := cache.GetInstanceFromCache("key" + strconv.Itoa(i)) + retrievedInstance, found := cache.getInstanceFromCache("key" + strconv.Itoa(i)) Expect(found).To(BeTrue()) Expect(retrievedInstance).To(Equal(instance)) }(i) diff --git a/internal/facade/client.go b/internal/facade/client.go index 4ebb043..3250119 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -97,7 +97,6 @@ type SpaceClient interface { FindServicePlan(ctx context.Context, serviceOfferingName string, servicePlanName string, spaceGuid string) (string, error) - GetResourceCache() *ResourceCache //TODO: Add methods for managing service keys // AddInstanceToResourceCache(key string, instance *Instance) // GetInstanceFromResourceCache(key string) (*Instance, bool) diff --git a/internal/facade/facadefakes/fake_space_client.go b/internal/facade/facadefakes/fake_space_client.go index ec663c6..68c2159 100644 --- a/internal/facade/facadefakes/fake_space_client.go +++ b/internal/facade/facadefakes/fake_space_client.go @@ -115,16 +115,6 @@ type FakeSpaceClient struct { result1 *facade.Instance result2 error } - GetResourceCacheStub func() *facade.ResourceCache - getResourceCacheMutex sync.RWMutex - getResourceCacheArgsForCall []struct { - } - getResourceCacheReturns struct { - result1 *facade.ResourceCache - } - getResourceCacheReturnsOnCall map[int]struct { - result1 *facade.ResourceCache - } UpdateBindingStub func(context.Context, string, int64, map[string]interface{}) error updateBindingMutex sync.RWMutex updateBindingArgsForCall []struct { @@ -621,59 +611,6 @@ func (fake *FakeSpaceClient) GetInstanceReturnsOnCall(i int, result1 *facade.Ins }{result1, result2} } -func (fake *FakeSpaceClient) GetResourceCache() *facade.ResourceCache { - fake.getResourceCacheMutex.Lock() - ret, specificReturn := fake.getResourceCacheReturnsOnCall[len(fake.getResourceCacheArgsForCall)] - fake.getResourceCacheArgsForCall = append(fake.getResourceCacheArgsForCall, struct { - }{}) - stub := fake.GetResourceCacheStub - fakeReturns := fake.getResourceCacheReturns - fake.recordInvocation("GetResourceCache", []interface{}{}) - fake.getResourceCacheMutex.Unlock() - if stub != nil { - return stub() - } - if specificReturn { - return ret.result1 - } - return fakeReturns.result1 -} - -func (fake *FakeSpaceClient) GetResourceCacheCallCount() int { - fake.getResourceCacheMutex.RLock() - defer fake.getResourceCacheMutex.RUnlock() - return len(fake.getResourceCacheArgsForCall) -} - -func (fake *FakeSpaceClient) GetResourceCacheCalls(stub func() *facade.ResourceCache) { - fake.getResourceCacheMutex.Lock() - defer fake.getResourceCacheMutex.Unlock() - fake.GetResourceCacheStub = stub -} - -func (fake *FakeSpaceClient) GetResourceCacheReturns(result1 *facade.ResourceCache) { - fake.getResourceCacheMutex.Lock() - defer fake.getResourceCacheMutex.Unlock() - fake.GetResourceCacheStub = nil - fake.getResourceCacheReturns = struct { - result1 *facade.ResourceCache - }{result1} -} - -func (fake *FakeSpaceClient) GetResourceCacheReturnsOnCall(i int, result1 *facade.ResourceCache) { - fake.getResourceCacheMutex.Lock() - defer fake.getResourceCacheMutex.Unlock() - fake.GetResourceCacheStub = nil - if fake.getResourceCacheReturnsOnCall == nil { - fake.getResourceCacheReturnsOnCall = make(map[int]struct { - result1 *facade.ResourceCache - }) - } - fake.getResourceCacheReturnsOnCall[i] = struct { - result1 *facade.ResourceCache - }{result1} -} - func (fake *FakeSpaceClient) UpdateBinding(arg1 context.Context, arg2 string, arg3 int64, arg4 map[string]interface{}) error { fake.updateBindingMutex.Lock() ret, specificReturn := fake.updateBindingReturnsOnCall[len(fake.updateBindingArgsForCall)] @@ -828,8 +765,6 @@ func (fake *FakeSpaceClient) Invocations() map[string][][]interface{} { defer fake.getBindingMutex.RUnlock() fake.getInstanceMutex.RLock() defer fake.getInstanceMutex.RUnlock() - fake.getResourceCacheMutex.RLock() - defer fake.getResourceCacheMutex.RUnlock() fake.updateBindingMutex.RLock() defer fake.updateBindingMutex.RUnlock() fake.updateInstanceMutex.RLock() From b50e1a6c482abc9b931be7f88dfd5fd366e442e7 Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Tue, 3 Sep 2024 13:17:28 +0530 Subject: [PATCH 23/63] cf/client.go changes for resource cache --- internal/cf/client.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index d6b454b..871ac40 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -34,14 +34,14 @@ const ( type organizationClient struct { organizationName string client cfclient.Client - // TODO: for org client + // TP //resourceCache *facade.Cache } type spaceClient struct { spaceGuid string client cfclient.Client - resourceCache *facade.ResourceCache + resourceCache *resourceCache } type clientIdentifier struct { @@ -59,7 +59,7 @@ type clientCacheEntry struct { var ( clientCacheMutex = &sync.Mutex{} clientCache = make(map[clientIdentifier]*clientCacheEntry) - cfResourceCache *facade.ResourceCache + cfResourceCache *resourceCache refreshResourceCacheMutex = &sync.Mutex{} ) @@ -190,9 +190,9 @@ func NewSpaceClient(spaceGuid string, url string, username string, password stri } if config.IsResourceCacheEnabled && client.resourceCache == nil { - client.resourceCache = facade.InitResourceCache() - client.resourceCache.SetResourceCacheEnabled(config.IsResourceCacheEnabled) - client.resourceCache.SetCacheTimeOut(config.CacheTimeOut) + client.resourceCache = initResourceCache() + client.resourceCache.setResourceCacheEnabled(config.IsResourceCacheEnabled) + client.resourceCache.setCacheTimeOut(config.CacheTimeOut) client.populateResourceCache() } @@ -237,7 +237,7 @@ func (c *spaceClient) populateResourceCache() { // TODO: Add for loop for space refreshResourceCacheMutex.Lock() defer refreshResourceCacheMutex.Unlock() - if c.resourceCache.IsCacheExpired() { + if c.resourceCache.isCacheExpired() { //print cache is expired and populate the cache fmt.Println("For the first or Cache is expired and populating the cache") instanceOptions := cfclient.NewServiceInstanceListOptions() @@ -261,7 +261,7 @@ func (c *spaceClient) populateResourceCache() { instance, err := InitInstance(serviceInstance, nil) // instance is added to cache only if error is nil if err == nil { - c.resourceCache.AddInstanceInCache(*serviceInstance.Metadata.Labels[labelOwner], instance) + c.resourceCache.addInstanceInCache(*serviceInstance.Metadata.Labels[labelOwner], instance) } } @@ -272,7 +272,7 @@ func (c *spaceClient) populateResourceCache() { pager.NextPage(instanceOptions) } - c.resourceCache.SetLastCacheTime() + c.resourceCache.setLastCacheTime() cfResourceCache = c.resourceCache } // TODO: Add for loop for bindings From e327ccd15529d1dce60d5dc295b6e1c95c621629 Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Tue, 3 Sep 2024 19:59:04 +0530 Subject: [PATCH 24/63] Client tests are added for populate resource cache --- internal/cf/client.go | 3 +- internal/cf/client_test.go | 197 +++++++++++++++++++++++++----- internal/cf/resourcecache.go | 2 - internal/cf/resourcecache_test.go | 4 +- 4 files changed, 168 insertions(+), 38 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index 871ac40..07b0203 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -243,8 +243,7 @@ func (c *spaceClient) populateResourceCache() { instanceOptions := cfclient.NewServiceInstanceListOptions() instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) instanceOptions.Page = 1 - instanceOptions.PerPage = 500 - //instanceOptions.OrganizationGUIDs.EqualTo("21dc8fd6-ea17-49df-99e9-cacf57b479fc") + instanceOptions.PerPage = 5000 ctx := context.Background() // populate instance cache diff --git a/internal/cf/client_test.go b/internal/cf/client_test.go index 9bc6e21..b08a210 100644 --- a/internal/cf/client_test.go +++ b/internal/cf/client_test.go @@ -6,6 +6,7 @@ package cf import ( "context" + "net/http" "testing" "time" @@ -36,7 +37,7 @@ const ( spacesURI = "/v3/spaces" serviceInstancesURI = "/v3/service_instances" uaaURI = "/uaa/oauth/token" - label_selector = "service-operator.cf.cs.sap.com" + labelSelector = "service-operator.cf.cs.sap.com" ) type Token struct { @@ -51,13 +52,46 @@ func TestCFClient(t *testing.T) { RunSpecs(t, "CF Client Test Suite") } -// is resource cache enabled and cache timeout -var resourceCacheEnabled = true -var resourceCacheTimeout = 5 * time.Minute +func String(s string) *string { + return &s +} +// is resource cache enabled and cache timeout var cfg = &config.Config{ - IsResourceCacheEnabled: resourceCacheEnabled, - CacheTimeOut: resourceCacheTimeout.String(), + IsResourceCacheEnabled: false, + CacheTimeOut: "5m", +} + +// Fake response for service instances +var fakeServiceInstances = cfResource.ServiceInstanceList{ + Resources: []*cfResource.ServiceInstance{ + { + GUID: "test-instance-guid-1", + Name: "test-instance-name-1", + Tags: []string{}, + LastOperation: cfResource.LastOperation{ + Type: "create", + State: "succeeded", + Description: "", + }, + Relationships: cfResource.ServiceInstanceRelationships{ + ServicePlan: &cfResource.ToOneRelationship{ + Data: &cfResource.Relationship{ + GUID: "test-instance-service_plan-1", + }, + }, + }, + Metadata: &cfResource.Metadata{ + Labels: map[string]*string{ + "service-operator.cf.cs.sap.com/owner": String("testOwner"), + }, + Annotations: map[string]*string{ + "service-operator.cf.cs.sap.com/generation": String("1"), + "service-operator.cf.cs.sap.com/parameter-hash": String("74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b"), + }, + }, + }, + }, } // ----------------------------------------------------------------------------------------------- @@ -222,6 +256,11 @@ var _ = Describe("CF Client tests", Ordered, func() { metrics.Registry = prometheus.NewRegistry() server.Reset() + // Create a new configuration for each test + cfg = &config.Config{ + IsResourceCacheEnabled: false, + CacheTimeOut: "5m", + } // Register handlers server.RouteToHandler("GET", "/", ghttp.CombineHandlers( ghttp.RespondWithJSONEncodedPtr(&statusCode, &rootResult), @@ -241,7 +280,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) - Expect(server.ReceivedRequests()).To(HaveLen(3)) + Expect(server.ReceivedRequests()).To(HaveLen(1)) }) It("should be able to query some space", func() { @@ -260,7 +299,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[2].URL.Path).To(Equal(serviceInstancesURI)) - Expect(server.ReceivedRequests()).To(HaveLen(4)) + Expect(server.ReceivedRequests()).To(HaveLen(3)) // verify metrics metricsList, err := metrics.Registry.Gather() @@ -299,7 +338,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[3].URL.Path).To(Equal(serviceInstancesURI)) - Expect(server.ReceivedRequests()).To(HaveLen(5)) + Expect(server.ReceivedRequests()).To(HaveLen(4)) }) It("should be able to query two different spaces", func() { @@ -315,7 +354,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) // Get instance Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(label_selector)) + Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(Owner)) // test space 2 spaceClient2, err2 := NewSpaceClient(SpaceName2, url, Username, Password, *cfg) @@ -324,7 +363,7 @@ var _ = Describe("CF Client tests", Ordered, func() { // no discovery of UAA endpoint or oAuth token here due to caching // Get instance Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring(label_selector)) + Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring(Owner2)) }) It("should register prometheus metrics for OrgClient", func() { @@ -367,28 +406,122 @@ var _ = Describe("CF Client tests", Ordered, func() { // for debugging: write metrics to file // prometheus.WriteToTextfile("metrics.txt", metrics.Registry) }) - It("should initialize resource cache and populate cache", func() { - // Enable resource cache in config - cfg.IsResourceCacheEnabled = true - - spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *cfg) - Expect(err).To(BeNil()) - Expect(spaceClient).ToNot(BeNil()) - - // Discover UAA endpoint - Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) - // Get new oAuth token - Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) - Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) - // Populate cache - Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(label_selector)) - - // Verify that no additional requests are made to the server - Expect(server.ReceivedRequests()).To(HaveLen(3)) + Context("Populate resource cache tests", func() { + + It("should initialize resource cache for the first time and populate resource cache again on cache expiry", func() { + // Enable resource cache in config + cfg.IsResourceCacheEnabled = true + cfg.CacheTimeOut = "5s" + + server.RouteToHandler("GET", serviceInstancesURI, ghttp.CombineHandlers( + ghttp.RespondWithJSONEncodedPtr(&statusCode, &fakeServiceInstances), + )) + + spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *cfg) + Expect(err).To(BeNil()) + Expect(spaceClient).ToNot(BeNil()) + + // Verify resource cache used and no additional requests expected + spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + + // Make another request after cache expired and verify that cache is repopulated + time.Sleep(10 * time.Second) + spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + Expect(server.ReceivedRequests()).To(HaveLen(4)) + + // Discover UAA endpoint + Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) + // Get new oAuth token + Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) + Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) + // Populate cache + Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(serviceInstancesURI)) + // Populate cache on expiry + Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(serviceInstancesURI)) + Expect(server.ReceivedRequests()[2].RequestURI).NotTo(ContainSubstring(Owner)) + + }) + + It("should not initialize resource cache if disabled in config", func() { + // Enable resource cache in config + cfg.IsResourceCacheEnabled = false + + server.RouteToHandler("GET", serviceInstancesURI, ghttp.CombineHandlers( + ghttp.RespondWithJSONEncodedPtr(&statusCode, &fakeServiceInstances), + )) + + spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *cfg) + Expect(err).To(BeNil()) + Expect(spaceClient).ToNot(BeNil()) + + // Verify resource cache not used and request expected + spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + + Expect(server.ReceivedRequests()).To(HaveLen(3)) + + // Discover UAA endpoint + Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) + // Get new oAuth token + Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) + Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) + // Get instance + Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(Owner)) + }) + + It("Delete instance from cache", func() { + // Enable resource cache in config + cfg.IsResourceCacheEnabled = true + cfg.CacheTimeOut = "5m" + + // Route to handler for GET request + server.RouteToHandler("GET", serviceInstancesURI, ghttp.CombineHandlers( + ghttp.RespondWithJSONEncodedPtr(&statusCode, &fakeServiceInstances), + )) + + // Route to handler for DELETE request + server.RouteToHandler("DELETE", serviceInstancesURI+"/test-instance-guid-1", ghttp.CombineHandlers( + ghttp.RespondWith(http.StatusAccepted, nil), + )) + + spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *cfg) + Expect(err).To(BeNil()) + Expect(spaceClient).ToNot(BeNil()) + + // Verify resource cache used and no additional requests expected + spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + + // Delete instance from cache + err = spaceClient.DeleteInstance(ctx, "test-instance-guid-1", Owner) + Expect(err).To(BeNil()) + + // Get instance from cache should return empty + spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + Expect(server.ReceivedRequests()).To(HaveLen(5)) + + // Discover UAA endpoint + Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) + // Get new oAuth token + Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) + Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) + // populate cache + Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(serviceInstancesURI)) + Expect(server.ReceivedRequests()[2].RequestURI).NotTo(ContainSubstring(Owner)) + //Delete instance from cache + Expect(server.ReceivedRequests()[3].Method).To(Equal("DELETE")) + Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring("test-instance-guid-1")) + //get instance from cache + Expect(server.ReceivedRequests()[4].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[4].RequestURI).To(ContainSubstring(Owner)) + + }) }) - }) }) diff --git a/internal/cf/resourcecache.go b/internal/cf/resourcecache.go index 29fd925..0212af1 100644 --- a/internal/cf/resourcecache.go +++ b/internal/cf/resourcecache.go @@ -140,8 +140,6 @@ func (c *resourceCache) deleteInstanceFromCache(key string) { //TODO:remove later: print cache found and deleted fmt.Println("Cache found and deleted") } - //TODO:remove later: print cache not found - fmt.Println("Cache not found to delete") } diff --git a/internal/cf/resourcecache_test.go b/internal/cf/resourcecache_test.go index c8e3425..09b444e 100644 --- a/internal/cf/resourcecache_test.go +++ b/internal/cf/resourcecache_test.go @@ -17,14 +17,14 @@ import ( func TestFacade(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "ResourceCache Suite") + //RunSpecs(t, "ResourceCache Suite") } var _ = Describe("ResourceCache", func() { var cache *resourceCache var instance *facade.Instance var wg sync.WaitGroup - concurrencyLevel := 100 + concurrencyLevel := 20 BeforeEach(func() { cache = initResourceCache() From 4e28306959cb83a2e7b82ef2878a1feb6c5910d6 Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Tue, 3 Sep 2024 21:00:19 +0530 Subject: [PATCH 25/63] small refactoring the code --- internal/cf/instance.go | 14 ++-- internal/cf/resourcecache.go | 124 ++++++++++++++--------------- internal/controllers/suite_test.go | 8 +- internal/facade/client.go | 10 --- 4 files changed, 67 insertions(+), 89 deletions(-) diff --git a/internal/cf/instance.go b/internal/cf/instance.go index b581846..9559a2e 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -56,9 +56,6 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s } // Attempt to retrieve instance from Cache - var instanceInCache bool - var instance *facade.Instance - //TODO recheck this logic later if c.resourceCache.isCacheExpired() { //TODO: remove later:Print cache is expited fmt.Println("Cache is expired") @@ -66,15 +63,16 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s } if len(c.resourceCache.getCachedInstances()) != 0 { - instance, instanceInCache = c.resourceCache.getInstanceFromCache(instanceOpts["owner"]) - //TODO: remove later: print length of cache + instance, instanceInCache := c.resourceCache.getInstanceFromCache(instanceOpts["owner"]) + // TODO: remove later: print length of cache fmt.Printf("Length of cache: %d\n", len(c.resourceCache.getCachedInstances())) - } + if instanceInCache { + return instance, nil + } - if instanceInCache { - return instance, nil } + } //TODO:remove later:Print not found in cache or cache is empty or instance not found fmt.Println("Not found in cache or cache is empty or instance not found in cf") diff --git a/internal/cf/resourcecache.go b/internal/cf/resourcecache.go index 0212af1..032b059 100644 --- a/internal/cf/resourcecache.go +++ b/internal/cf/resourcecache.go @@ -13,7 +13,9 @@ import ( "github.com/sap/cf-service-operator/internal/facade" ) -// Cache is a simple in-memory cache to store spaces, instances, and bindings +// Cache is a simple in-memory cache to store spaces, instances, and bindings using a map with a mutex +// The cache is used to store the resources to avoid making multiple calls to the CF API +// key is the owner of the instance which is kubernetes UID and value is the instance type resourceCache struct { spaces map[string]*facade.Space instances map[string]*facade.Instance @@ -35,39 +37,7 @@ func initResourceCache() *resourceCache { return cache } -// Get resource cache -func (c *resourceCache) getresourceCache() *resourceCache { - return c -} - -func (c *resourceCache) getCachedInstances() map[string]*facade.Instance { - return c.instances -} - -func (c *resourceCache) getCachedBindings() map[string]*facade.Binding { - return c.bindings -} - -func (c *resourceCache) getCachedSpaces() map[string]*facade.Space { - return c.spaces -} - -// function to set the resource cache enabled flag from config -func (c *resourceCache) setResourceCacheEnabled(enabled bool) { - c.isResourceCacheEnabled = enabled -} -func (c *resourceCache) checkResourceCacheEnabled() bool { - if c == nil { - return false - } - return c.isResourceCacheEnabled -} - -// Function to set the resource cache enabled flag from config -func (c *resourceCache) getCacheTimeOut() time.Duration { - return c.cacheTimeOut -} - +// Function to set the resource cache timeout from config func (c *resourceCache) setCacheTimeOut(timeOut string) { cacheTimeOut, err := time.ParseDuration(timeOut) if err != nil { @@ -93,19 +63,15 @@ func (c *resourceCache) setLastCacheTime() { fmt.Printf("Last cache time: %v\n", c.lastCacheTime) } -// AddSpaceInCache stores a space in the cache -func (c *resourceCache) addSpaceInCache(key string, space *facade.Space) { - c.mutex.Lock() - defer c.mutex.Unlock() - c.spaces[key] = space +// function to set the resource cache enabled flag from config +func (c *resourceCache) setResourceCacheEnabled(enabled bool) { + c.isResourceCacheEnabled = enabled } - -// GetSpaceFromCache retrieves a space from the cache -func (c *resourceCache) getSpaceFromCache(key string) (*facade.Space, bool) { - c.mutex.RLock() - defer c.mutex.RUnlock() - space, found := c.spaces[key] - return space, found +func (c *resourceCache) checkResourceCacheEnabled() bool { + if c == nil { + return false + } + return c.isResourceCacheEnabled } // AddInstanceInCache stores an instance in the cache @@ -134,12 +100,9 @@ func (c *resourceCache) getInstanceFromCache(key string) (*facade.Instance, bool func (c *resourceCache) deleteInstanceFromCache(key string) { c.mutex.Lock() defer c.mutex.Unlock() - _, found := c.instances[key] - if found { - delete(c.instances, key) - //TODO:remove later: print cache found and deleted - fmt.Println("Cache found and deleted") - } + delete(c.instances, key) + // TODO :remove later: remove this printf later + fmt.Println("Cache found and deleted") } @@ -179,17 +142,50 @@ func (c *resourceCache) updateInstanceInCache(guid string, name string, owner st } -// AddBindingInCache stores a binding in the cache -func (c *resourceCache) addBindingInCache(key string, binding *facade.Binding) { - c.mutex.Lock() - defer c.mutex.Unlock() - c.bindings[key] = binding +func (c *resourceCache) getCachedInstances() map[string]*facade.Instance { + return c.instances } -// GetBindingFromCache retrieves a binding from the cache -func (c *resourceCache) getBindingFromCache(key string) (*facade.Binding, bool) { - c.mutex.RLock() - defer c.mutex.RUnlock() - binding, found := c.bindings[key] - return binding, found -} +//TODO:Uncomment on functionality completion +// // AddSpaceInCache stores a space in the cache +// func (c *resourceCache) addSpaceInCache(key string, space *facade.Space) { +// c.mutex.Lock() +// defer c.mutex.Unlock() +// c.spaces[key] = space +// } + +// // GetSpaceFromCache retrieves a space from the cache +// func (c *resourceCache) getSpaceFromCache(key string) (*facade.Space, bool) { +// c.mutex.RLock() +// defer c.mutex.RUnlock() +// space, found := c.spaces[key] +// return space, found +// } + +// // AddBindingInCache stores a binding in the cache +// func (c *resourceCache) addBindingInCache(key string, binding *facade.Binding) { +// c.mutex.Lock() +// defer c.mutex.Unlock() +// c.bindings[key] = binding +// } + +// // GetBindingFromCache retrieves a binding from the cache +// func (c *resourceCache) getBindingFromCache(key string) (*facade.Binding, bool) { +// c.mutex.RLock() +// defer c.mutex.RUnlock() +// binding, found := c.bindings[key] +// return binding, found +// } + +// // Get resource cache +// func (c *resourceCache) getresourceCache() *resourceCache { +// return c +// } + +// func (c *resourceCache) getCachedBindings() map[string]*facade.Binding { +// return c.bindings +// } + +// func (c *resourceCache) getCachedSpaces() map[string]*facade.Space { +// return c.spaces +// } diff --git a/internal/controllers/suite_test.go b/internal/controllers/suite_test.go index ca98edc..3ea2d05 100644 --- a/internal/controllers/suite_test.go +++ b/internal/controllers/suite_test.go @@ -67,13 +67,7 @@ const ( var timeout = 5 * time.Minute // is resource cache enabled and cache timeout -var resourceCacheEnabled = false -var resourceCacheTimeout = 5 * time.Minute - -var cfg = &config.Config{ - IsResourceCacheEnabled: resourceCacheEnabled, - CacheTimeOut: resourceCacheTimeout.String(), -} +var cfg = &config.Config{} // interval used for polling custom resource var interval = 500 * time.Millisecond diff --git a/internal/facade/client.go b/internal/facade/client.go index 3250119..5b3e179 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -75,10 +75,6 @@ type OrganizationClient interface { AddAuditor(ctx context.Context, guid string, username string) error AddDeveloper(ctx context.Context, guid string, username string) error AddManager(ctx context.Context, guid string, username string) error - - //TODO: Add methods for managing space - // AddSpaceInCache(key string, space *Space) - // GetSpaceFromCache(key string) (*Space, bool) } type OrganizationClientBuilder func(string, string, string, string) (OrganizationClient, error) @@ -96,12 +92,6 @@ type SpaceClient interface { DeleteBinding(ctx context.Context, guid string) error FindServicePlan(ctx context.Context, serviceOfferingName string, servicePlanName string, spaceGuid string) (string, error) - - //TODO: Add methods for managing service keys - // AddInstanceToResourceCache(key string, instance *Instance) - // GetInstanceFromResourceCache(key string) (*Instance, bool) - // AddBindingToResourceCache(key string, binding *Binding) - // GetBindingFromResourceCache(key string) (*Binding, bool) } type SpaceClientBuilder func(string, string, string, string, config.Config) (SpaceClient, error) From c5e2fd4e5f8074029f36713e26309ad6e0b0bbb5 Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Wed, 4 Sep 2024 16:11:22 +0530 Subject: [PATCH 26/63] refactoring to populate resource cache function and few small changes --- internal/cf/client.go | 59 +++++++++++++++++++++--------------- internal/cf/instance.go | 26 ++++++---------- internal/cf/resourcecache.go | 24 +++++---------- internal/config/config.go | 3 ++ 4 files changed, 54 insertions(+), 58 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index 07b0203..8ad5416 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -13,6 +13,7 @@ import ( cfclient "github.com/cloudfoundry-community/go-cfclient/v3/client" cfconfig "github.com/cloudfoundry-community/go-cfclient/v3/config" + cfresource "github.com/cloudfoundry-community/go-cfclient/v3/resource" "sigs.k8s.io/controller-runtime/pkg/metrics" "github.com/sap/cf-service-operator/internal/config" @@ -232,47 +233,55 @@ func NewSpaceHealthChecker(spaceGuid string, url string, username string, passwo return client, err } +// populateResourceCache populates the resource cache by fetching All service instances matching the owner label key +// from the Cloud Foundry API and storing them in an in-memory cache. This function +// ensures that the cache is refreshed if it is expired. It uses concurrency to +// initialize and cache service instances efficiently. +// TODO: Extend logic to cache space and bindings func (c *spaceClient) populateResourceCache() { - // TODO: Create the space options - // TODO: Add for loop for space + refreshResourceCacheMutex.Lock() defer refreshResourceCacheMutex.Unlock() + if c.resourceCache.isCacheExpired() { - //print cache is expired and populate the cache - fmt.Println("For the first or Cache is expired and populating the cache") instanceOptions := cfclient.NewServiceInstanceListOptions() instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) - instanceOptions.Page = 1 - instanceOptions.PerPage = 5000 ctx := context.Background() - // populate instance cache - for { - srvInstanes, pager, err := c.client.ServiceInstances.List(ctx, instanceOptions) - if err != nil { - //TODO:revisit to see how to handle if error occurs - log.Fatalf("Error listing service instances: %s", err) - } - - // Cache the service instance - for _, serviceInstance := range srvInstanes { - // ... some caching logic + //TODO:check if List method with paging option can be used instead of ListAll if in case of large number of instances/performance issues + srvInstances, err := c.client.ServiceInstances.ListAll(ctx, instanceOptions) + if err != nil { + // reset the cache to nil in case of error + log.Printf("Error listing service instances: %s", err) + c.resourceCache.instances = make(map[string]*facade.Instance) + c.resourceCache.setLastCacheTime() + cfResourceCache = c.resourceCache + return + } + + var wg sync.WaitGroup + + // Cache the service instance + for _, serviceInstance := range srvInstances { + wg.Add(1) + go func(serviceInstance *cfresource.ServiceInstance) { + defer wg.Done() instance, err := InitInstance(serviceInstance, nil) // instance is added to cache only if error is nil if err == nil { c.resourceCache.addInstanceInCache(*serviceInstance.Metadata.Labels[labelOwner], instance) + } else { + log.Printf("Error initializing instance: %s", err) } - } + }(serviceInstance) + } - if !pager.HasNextPage() { - fmt.Printf("No more pages\n") - break - } + // Wait for all goroutines to finish + wg.Wait() - pager.NextPage(instanceOptions) - } c.resourceCache.setLastCacheTime() cfResourceCache = c.resourceCache + } - // TODO: Add for loop for bindings + } diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 9559a2e..e15bf77 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -44,27 +44,22 @@ func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOpt // GetInstance returns the instance with the given instanceOpts["owner"] or instanceOpts["name"]. // If instanceOpts["name"] is empty, the instance with the given instanceOpts["owner"] is returned. // If instanceOpts["name"] is not empty, the instance with the given Name is returned for orphan instances. -// If no instance is found, nil is returned. -// If multiple instances are found, an error is returned. -// The function add the parameter values to the orphan cf instance, so that can be adopted. +// If resource cache is enabled, the instance is first searched in the cache. +// If the instance is not found in the cache, it is searched in Cloud Foundry. func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]string) (*facade.Instance, error) { if c.resourceCache.checkResourceCacheEnabled() { - // Ensure resourcesCache is initialized - if c.resourceCache == nil { - c.resourceCache = initResourceCache() - } // Attempt to retrieve instance from Cache if c.resourceCache.isCacheExpired() { - //TODO: remove later:Print cache is expited + //TODO: remove after internal review fmt.Println("Cache is expired") c.populateResourceCache() } if len(c.resourceCache.getCachedInstances()) != 0 { instance, instanceInCache := c.resourceCache.getInstanceFromCache(instanceOpts["owner"]) - // TODO: remove later: print length of cache + //TODO: remove after internal review fmt.Printf("Length of cache: %d\n", len(c.resourceCache.getCachedInstances())) if instanceInCache { @@ -74,8 +69,6 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s } } - //TODO:remove later:Print not found in cache or cache is empty or instance not found - fmt.Println("Not found in cache or cache is empty or instance not found in cf") // Attempt to retrieve instance from Cloud Foundry var serviceInstance *cfresource.ServiceInstance @@ -165,6 +158,8 @@ func (c *spaceClient) UpdateInstance(ctx context.Context, guid string, name stri } _, _, err := c.client.ServiceInstances.UpdateManaged(ctx, guid, req) + + // Update instance in cache if err == nil && c.resourceCache.checkResourceCacheEnabled() { isUpdated := c.resourceCache.updateInstanceInCache(guid, name, owner, servicePlanGuid, parameters, generation) @@ -183,8 +178,6 @@ func (c *spaceClient) UpdateInstance(ctx context.Context, guid string, name stri c.resourceCache.addInstanceInCache(owner, instance) } - //TODO:remove later: print instance added in cache and print the instance - fmt.Println("Instance added or updated in cache from update instance function") } return err @@ -193,6 +186,8 @@ func (c *spaceClient) UpdateInstance(ctx context.Context, guid string, name stri func (c *spaceClient) DeleteInstance(ctx context.Context, guid string, owner string) error { // TODO: return jobGUID to enable querying the job deletion status _, err := c.client.ServiceInstances.Delete(ctx, guid) + + // Delete instance from cache if err == nil && c.resourceCache.checkResourceCacheEnabled() { c.resourceCache.deleteInstanceFromCache(owner) } @@ -232,7 +227,7 @@ func InitInstance(serviceInstance *cfresource.ServiceInstance, instanceOpts map[ state = facade.InstanceStateUnknown } stateDescription := serviceInstance.LastOperation.Description - //if (instanceOpts["owner"] not nil then owner = instanceOpts["owner"] else owner = serviceInstance.Metadata.Labels[labelOwner] + owner := instanceOpts["owner"] if owner == "" { owner = *serviceInstance.Metadata.Labels[labelOwner] @@ -242,8 +237,7 @@ func InitInstance(serviceInstance *cfresource.ServiceInstance, instanceOpts map[ Guid: guid, Name: name, ServicePlanGuid: servicePlanGuid, - //if (instanceOpts["owner"] not nil then owner = instanceOpts["owner"] else owner = serviceInstance.Metadata.Labels[labelOwner] - Owner: owner, + Owner: owner, Generation: generation, ParameterHash: parameterHash, diff --git a/internal/cf/resourcecache.go b/internal/cf/resourcecache.go index 032b059..2f2ec7f 100644 --- a/internal/cf/resourcecache.go +++ b/internal/cf/resourcecache.go @@ -7,6 +7,7 @@ package cf import ( "fmt" + "log" "sync" "time" @@ -41,7 +42,8 @@ func initResourceCache() *resourceCache { func (c *resourceCache) setCacheTimeOut(timeOut string) { cacheTimeOut, err := time.ParseDuration(timeOut) if err != nil { - fmt.Printf("Error parsing duration: %v\n", err) + log.Printf("Error parsing duration: %v\n", err) + c.cacheTimeOut = 1 * time.Minute return } c.cacheTimeOut = cacheTimeOut @@ -51,8 +53,6 @@ func (c *resourceCache) setCacheTimeOut(timeOut string) { func (c *resourceCache) isCacheExpired() bool { expirationTime := c.lastCacheTime.Add(c.cacheTimeOut) - fmt.Printf("Expiry time: %v\n", expirationTime) - fmt.Printf("Cache timeout: %v\n", c.cacheTimeOut) return time.Now().After(expirationTime) } @@ -60,7 +60,7 @@ func (c *resourceCache) isCacheExpired() bool { // Function to set the last cache time func (c *resourceCache) setLastCacheTime() { c.lastCacheTime = time.Now() - fmt.Printf("Last cache time: %v\n", c.lastCacheTime) + log.Printf("Last cache time: %v\n", c.lastCacheTime) } // function to set the resource cache enabled flag from config @@ -69,6 +69,7 @@ func (c *resourceCache) setResourceCacheEnabled(enabled bool) { } func (c *resourceCache) checkResourceCacheEnabled() bool { if c == nil { + log.Println("Resource cache is nil") return false } return c.isResourceCacheEnabled @@ -79,8 +80,6 @@ func (c *resourceCache) addInstanceInCache(key string, instance *facade.Instance c.mutex.Lock() defer c.mutex.Unlock() c.instances[key] = instance - // TODO :remove later:addedinstance to cache and print the instance - fmt.Printf("Added instance to cache: %v\n", instance) } // GetInstanceFromCache retrieves an instance from the cache @@ -88,21 +87,16 @@ func (c *resourceCache) getInstanceFromCache(key string) (*facade.Instance, bool c.mutex.RLock() defer c.mutex.RUnlock() instance, found := c.instances[key] - // TODO :remove later: remove this printf later + // TODO :remove After internal review fmt.Printf("Got the instance from Cache: %v", instance) return instance, found } -// RemoveInstanceFromCache removes an instance from the cache -// This is used when an instance is deleted -// The instance is removed from the cache to avoid stale data -// The instance is removed from the cache only if the instance is found in the cache +// delete the instance from the cache func (c *resourceCache) deleteInstanceFromCache(key string) { c.mutex.Lock() defer c.mutex.Unlock() delete(c.instances, key) - // TODO :remove later: remove this printf later - fmt.Println("Cache found and deleted") } @@ -131,13 +125,9 @@ func (c *resourceCache) updateInstanceInCache(guid string, name string, owner st } instance.Generation = generation c.instances[owner] = instance - //TODO:remove later:print updated instance - fmt.Printf("Updated cache instance: %v\n", instance) return true } - //TODO:remove later: print cache not found - fmt.Println("Cache not found to update") return false } diff --git a/internal/config/config.go b/internal/config/config.go index 7fdbc98..80914e6 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,6 +6,8 @@ SPDX-License-Identifier: Apache-2.0 package config import ( + "log" + "github.com/caarlos0/env/v11" ) @@ -23,6 +25,7 @@ type Config struct { func Load() (*Config, error) { cfg := &Config{} if err := env.Parse(cfg); err != nil { + log.Printf("Error parsing environment variables: %v\n", err) return nil, err } From 070ad27bc0101bc230ac5d98fd0991088c24f598 Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:33:25 +0200 Subject: [PATCH 27/63] update populateResourceCache --- internal/cf/client.go | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index 8ad5416..60e5a00 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -233,13 +233,12 @@ func NewSpaceHealthChecker(spaceGuid string, url string, username string, passwo return client, err } -// populateResourceCache populates the resource cache by fetching All service instances matching the owner label key -// from the Cloud Foundry API and storing them in an in-memory cache. This function -// ensures that the cache is refreshed if it is expired. It uses concurrency to -// initialize and cache service instances efficiently. +// populateResourceCache populates the resource cache by fetching All service instances matching +// the owner label key from the Cloud Foundry API and storing them in an in-memory cache. +// This function ensures that the cache is refreshed if it is expired. +// It uses concurrency to initialize and cache service instances efficiently. // TODO: Extend logic to cache space and bindings func (c *spaceClient) populateResourceCache() { - refreshResourceCacheMutex.Lock() defer refreshResourceCacheMutex.Unlock() @@ -249,7 +248,7 @@ func (c *spaceClient) populateResourceCache() { ctx := context.Background() //TODO:check if List method with paging option can be used instead of ListAll if in case of large number of instances/performance issues - srvInstances, err := c.client.ServiceInstances.ListAll(ctx, instanceOptions) + cfInstances, err := c.client.ServiceInstances.ListAll(ctx, instanceOptions) if err != nil { // reset the cache to nil in case of error log.Printf("Error listing service instances: %s", err) @@ -259,29 +258,23 @@ func (c *spaceClient) populateResourceCache() { return } - var wg sync.WaitGroup - - // Cache the service instance - for _, serviceInstance := range srvInstances { - wg.Add(1) - go func(serviceInstance *cfresource.ServiceInstance) { - defer wg.Done() - instance, err := InitInstance(serviceInstance, nil) - // instance is added to cache only if error is nil - if err == nil { - c.resourceCache.addInstanceInCache(*serviceInstance.Metadata.Labels[labelOwner], instance) + // add service instances to cache concurrently + var waitGroup sync.WaitGroup + for _, cfInstance := range cfInstances { + waitGroup.Add(1) + go func(cfInstance *cfresource.ServiceInstance) { + defer waitGroup.Done() + if instance, err := InitInstance(cfInstance, nil); err == nil { + c.resourceCache.addInstanceInCache(*cfInstance.Metadata.Labels[labelOwner], instance) } else { log.Printf("Error initializing instance: %s", err) } - }(serviceInstance) + }(cfInstance) } - - // Wait for all goroutines to finish - wg.Wait() + waitGroup.Wait() c.resourceCache.setLastCacheTime() cfResourceCache = c.resourceCache - } } From 8fdff6d0bac910e4cfa45d26ab2e4e2b2bfa9a88 Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Thu, 5 Sep 2024 13:20:02 +0200 Subject: [PATCH 28/63] improve testing of expectations --- internal/cf/client_test.go | 168 +++++++++++++++++++------------------ 1 file changed, 85 insertions(+), 83 deletions(-) diff --git a/internal/cf/client_test.go b/internal/cf/client_test.go index b08a210..d74d157 100644 --- a/internal/cf/client_test.go +++ b/internal/cf/client_test.go @@ -56,13 +56,13 @@ func String(s string) *string { return &s } -// is resource cache enabled and cache timeout -var cfg = &config.Config{ +// configuration for CF client +var clientConfig = &config.Config{ IsResourceCacheEnabled: false, CacheTimeOut: "5m", } -// Fake response for service instances +// fake response for service instances var fakeServiceInstances = cfResource.ServiceInstanceList{ Resources: []*cfResource.ServiceInstance{ { @@ -143,15 +143,18 @@ var _ = Describe("CF Client tests", Ordered, func() { server.Reset() // Register handlers + // - Fake response for discover UAA endpoint server.RouteToHandler("GET", "/", ghttp.CombineHandlers( ghttp.RespondWithJSONEncodedPtr(&statusCode, &rootResult), )) - server.RouteToHandler("GET", spacesURI, ghttp.CombineHandlers( - ghttp.RespondWithJSONEncodedPtr(&statusCode, &rootResult), - )) + // - Fake response for new oAuth token server.RouteToHandler("POST", uaaURI, ghttp.CombineHandlers( ghttp.RespondWithJSONEncodedPtr(&statusCode, &tokenResult), )) + // - Fake response for get service instance + server.RouteToHandler("GET", spacesURI, ghttp.CombineHandlers( + ghttp.RespondWithJSONEncodedPtr(&statusCode, &rootResult), + )) }) It("should create OrgClient", func() { @@ -257,24 +260,28 @@ var _ = Describe("CF Client tests", Ordered, func() { server.Reset() // Create a new configuration for each test - cfg = &config.Config{ + clientConfig = &config.Config{ IsResourceCacheEnabled: false, CacheTimeOut: "5m", } + // Register handlers + // - Fake response for discover UAA endpoint server.RouteToHandler("GET", "/", ghttp.CombineHandlers( ghttp.RespondWithJSONEncodedPtr(&statusCode, &rootResult), )) - server.RouteToHandler("GET", serviceInstancesURI, ghttp.CombineHandlers( - ghttp.RespondWithJSONEncodedPtr(&statusCode, &rootResult), - )) + // - Fake response for new oAuth token server.RouteToHandler("POST", uaaURI, ghttp.CombineHandlers( ghttp.RespondWithJSONEncodedPtr(&statusCode, &tokenResult), )) + // - Fake response for get service instance + server.RouteToHandler("GET", serviceInstancesURI, ghttp.CombineHandlers( + ghttp.RespondWithJSONEncodedPtr(&statusCode, &fakeServiceInstances), + )) }) It("should create SpaceClient", func() { - NewSpaceClient(OrgName, url, Username, Password, *cfg) + NewSpaceClient(OrgName, url, Username, Password, *clientConfig) // Discover UAA endpoint Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) @@ -284,7 +291,7 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should be able to query some space", func() { - spaceClient, err := NewSpaceClient(OrgName, url, Username, Password, *cfg) + spaceClient, err := NewSpaceClient(OrgName, url, Username, Password, *clientConfig) Expect(err).To(BeNil()) spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) @@ -316,11 +323,11 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should be able to query some space twice", func() { - spaceClient, err := NewSpaceClient(OrgName, url, Username, Password, *cfg) + spaceClient, err := NewSpaceClient(OrgName, url, Username, Password, *clientConfig) Expect(err).To(BeNil()) spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - spaceClient, err = NewSpaceClient(OrgName, url, Username, Password, *cfg) + spaceClient, err = NewSpaceClient(OrgName, url, Username, Password, *clientConfig) Expect(err).To(BeNil()) spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) @@ -343,7 +350,7 @@ var _ = Describe("CF Client tests", Ordered, func() { It("should be able to query two different spaces", func() { // test space 1 - spaceClient1, err1 := NewSpaceClient(SpaceName, url, Username, Password, *cfg) + spaceClient1, err1 := NewSpaceClient(SpaceName, url, Username, Password, *clientConfig) Expect(err1).To(BeNil()) spaceClient1.GetInstance(ctx, map[string]string{"owner": Owner}) // Discover UAA endpoint @@ -357,7 +364,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(Owner)) // test space 2 - spaceClient2, err2 := NewSpaceClient(SpaceName2, url, Username, Password, *cfg) + spaceClient2, err2 := NewSpaceClient(SpaceName2, url, Username, Password, *clientConfig) Expect(err2).To(BeNil()) spaceClient2.GetInstance(ctx, map[string]string{"owner": Owner2}) // no discovery of UAA endpoint or oAuth token here due to caching @@ -386,7 +393,7 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should register prometheus metrics for SpaceClient", func() { - spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *cfg) + spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *clientConfig) Expect(err).To(BeNil()) Expect(spaceClient).ToNot(BeNil()) @@ -409,118 +416,113 @@ var _ = Describe("CF Client tests", Ordered, func() { Context("Populate resource cache tests", func() { - It("should initialize resource cache for the first time and populate resource cache again on cache expiry", func() { + It("should initialize resource cache after start and on cache expiry", func() { // Enable resource cache in config - cfg.IsResourceCacheEnabled = true - cfg.CacheTimeOut = "5s" + clientConfig.IsResourceCacheEnabled = true + clientConfig.CacheTimeOut = "5s" // short duration for fast test - server.RouteToHandler("GET", serviceInstancesURI, ghttp.CombineHandlers( - ghttp.RespondWithJSONEncodedPtr(&statusCode, &fakeServiceInstances), - )) - - spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *cfg) + // Create client + spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *clientConfig) Expect(err).To(BeNil()) Expect(spaceClient).ToNot(BeNil()) - // Verify resource cache used and no additional requests expected - spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - - // Make another request after cache expired and verify that cache is repopulated - time.Sleep(10 * time.Second) - spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - Expect(server.ReceivedRequests()).To(HaveLen(4)) - - // Discover UAA endpoint + // Verify resource cache is populated during client creation + Expect(server.ReceivedRequests()).To(HaveLen(3)) + // - Discover UAA endpoint Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) - // Get new oAuth token + // - Get new oAuth token Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) - // Populate cache - Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(serviceInstancesURI)) - // Populate cache on expiry + // - Populate cache Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(serviceInstancesURI)) - Expect(server.ReceivedRequests()[2].RequestURI).NotTo(ContainSubstring(Owner)) + // Make a request and verify that cache is used and no additional requests expected + spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + Expect(server.ReceivedRequests()).To(HaveLen(3)) // still same as above + + // Make another request after cache expired and verify that cache is repopulated + time.Sleep(10 * time.Second) + spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + Expect(server.ReceivedRequests()).To(HaveLen(4)) // one more request to repopulate cache + Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring(serviceInstancesURI)) + Expect(server.ReceivedRequests()[3].RequestURI).NotTo(ContainSubstring(Owner)) }) It("should not initialize resource cache if disabled in config", func() { - // Enable resource cache in config - cfg.IsResourceCacheEnabled = false - - server.RouteToHandler("GET", serviceInstancesURI, ghttp.CombineHandlers( - ghttp.RespondWithJSONEncodedPtr(&statusCode, &fakeServiceInstances), - )) + // Disable resource cache in config + clientConfig.IsResourceCacheEnabled = false - spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *cfg) + // Create client + spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *clientConfig) Expect(err).To(BeNil()) Expect(spaceClient).ToNot(BeNil()) - // Verify resource cache not used and request expected - spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - - Expect(server.ReceivedRequests()).To(HaveLen(3)) - - // Discover UAA endpoint + // Verify resource cache is NOT populated during client creation + // - Discover UAA endpoint + Expect(server.ReceivedRequests()).To(HaveLen(1)) Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) - // Get new oAuth token + + // Make request and verify cache is NOT used + spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + Expect(server.ReceivedRequests()).To(HaveLen(3)) // one more request to get instance + // - Get new oAuth token Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) - // Get instance + // - Get instance Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(Owner)) }) It("Delete instance from cache", func() { // Enable resource cache in config - cfg.IsResourceCacheEnabled = true - cfg.CacheTimeOut = "5m" - - // Route to handler for GET request - server.RouteToHandler("GET", serviceInstancesURI, ghttp.CombineHandlers( - ghttp.RespondWithJSONEncodedPtr(&statusCode, &fakeServiceInstances), - )) + clientConfig.IsResourceCacheEnabled = true + clientConfig.CacheTimeOut = "5m" // Route to handler for DELETE request - server.RouteToHandler("DELETE", serviceInstancesURI+"/test-instance-guid-1", ghttp.CombineHandlers( - ghttp.RespondWith(http.StatusAccepted, nil), - )) + server.RouteToHandler("DELETE", + serviceInstancesURI+"/test-instance-guid-1", + ghttp.CombineHandlers(ghttp.RespondWith(http.StatusAccepted, nil))) - spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *cfg) + // Create client + spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *clientConfig) Expect(err).To(BeNil()) Expect(spaceClient).ToNot(BeNil()) - // Verify resource cache used and no additional requests expected - spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - - // Delete instance from cache - err = spaceClient.DeleteInstance(ctx, "test-instance-guid-1", Owner) - Expect(err).To(BeNil()) - - // Get instance from cache should return empty - spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - Expect(server.ReceivedRequests()).To(HaveLen(5)) - - // Discover UAA endpoint + // Verify resource cache is populated during client creation + Expect(server.ReceivedRequests()).To(HaveLen(3)) + // - Discover UAA endpoint Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) - // Get new oAuth token + // - Get new oAuth token Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) - // populate cache + // - Populate cache Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(serviceInstancesURI)) Expect(server.ReceivedRequests()[2].RequestURI).NotTo(ContainSubstring(Owner)) - //Delete instance from cache + + // Make a request and verify that cache is used and no additional requests expected + spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + Expect(server.ReceivedRequests()).To(HaveLen(3)) // still same as above + + // Delete instance from cache + err = spaceClient.DeleteInstance(ctx, "test-instance-guid-1", Owner) + Expect(err).To(BeNil()) + Expect(server.ReceivedRequests()).To(HaveLen(4)) + // - Delete instance from cache Expect(server.ReceivedRequests()[3].Method).To(Equal("DELETE")) Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring("test-instance-guid-1")) - //get instance from cache + + // Get instance from cache should return empty + spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + Expect(server.ReceivedRequests()).To(HaveLen(5)) + // - get instance from cache Expect(server.ReceivedRequests()[4].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[4].RequestURI).To(ContainSubstring(Owner)) - }) }) }) From d8f0bdb56fc1bebe0321419e9edfb79b9586c597 Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Thu, 5 Sep 2024 13:40:11 +0200 Subject: [PATCH 29/63] minor improvements --- internal/cf/instance.go | 53 ++++++++++++++++------------------------- 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/internal/cf/instance.go b/internal/cf/instance.go index e15bf77..74ee8df 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -47,55 +47,46 @@ func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOpt // If resource cache is enabled, the instance is first searched in the cache. // If the instance is not found in the cache, it is searched in Cloud Foundry. func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]string) (*facade.Instance, error) { - if c.resourceCache.checkResourceCacheEnabled() { - - // Attempt to retrieve instance from Cache + // Attempt to retrieve instance from cache if c.resourceCache.isCacheExpired() { //TODO: remove after internal review fmt.Println("Cache is expired") c.populateResourceCache() } if len(c.resourceCache.getCachedInstances()) != 0 { - instance, instanceInCache := c.resourceCache.getInstanceFromCache(instanceOpts["owner"]) //TODO: remove after internal review fmt.Printf("Length of cache: %d\n", len(c.resourceCache.getCachedInstances())) - if instanceInCache { return instance, nil } - } - } // Attempt to retrieve instance from Cloud Foundry - var serviceInstance *cfresource.ServiceInstance - var filterOpts instanceFilter if instanceOpts["name"] != "" { filterOpts = &instanceFilterName{name: instanceOpts["name"]} } else { filterOpts = &instanceFilterOwner{owner: instanceOpts["owner"]} } - listOpts := filterOpts.getListOptions() - serviceInstances, err := c.client.ServiceInstances.ListAll(ctx, listOpts) + serviceInstances, err := c.client.ServiceInstances.ListAll(ctx, filterOpts.getListOptions()) if err != nil { return nil, fmt.Errorf("failed to list service instances: %w", err) } if len(serviceInstances) == 0 { - return nil, nil + return nil, nil // instance not found } else if len(serviceInstances) > 1 { return nil, errors.New(fmt.Sprintf("found multiple service instances with owner: %s", instanceOpts["owner"])) } - serviceInstance = serviceInstances[0] + serviceInstance := serviceInstances[0] // add parameter values to the orphan cf instance if instanceOpts["name"] != "" { generationvalue := "0" - serviceInstance.Metadata.Annotations[annotationGeneration] = &generationvalue parameterHashValue := "0" + serviceInstance.Metadata.Annotations[annotationGeneration] = &generationvalue serviceInstance.Metadata.Annotations[annotationParameterHash] = ¶meterHashValue } @@ -164,7 +155,7 @@ func (c *spaceClient) UpdateInstance(ctx context.Context, guid string, name stri isUpdated := c.resourceCache.updateInstanceInCache(guid, name, owner, servicePlanGuid, parameters, generation) if !isUpdated { - //add instance to cache in case of orphan instance + // add instance to cache in case of orphan instance instance := &facade.Instance{ Guid: guid, Name: name, @@ -176,7 +167,6 @@ func (c *spaceClient) UpdateInstance(ctx context.Context, guid string, name stri StateDescription: "", } c.resourceCache.addInstanceInCache(owner, instance) - } } @@ -191,18 +181,22 @@ func (c *spaceClient) DeleteInstance(ctx context.Context, guid string, owner str if err == nil && c.resourceCache.checkResourceCacheEnabled() { c.resourceCache.deleteInstanceFromCache(owner) } + return err } +// InitInstance wraps cfclient.ServiceInstance as a facade.Instance. func InitInstance(serviceInstance *cfresource.ServiceInstance, instanceOpts map[string]string) (*facade.Instance, error) { - guid := serviceInstance.GUID - name := serviceInstance.Name - servicePlanGuid := serviceInstance.Relationships.ServicePlan.Data.GUID generation, err := strconv.ParseInt(*serviceInstance.Metadata.Annotations[annotationGeneration], 10, 64) if err != nil { return nil, errors.Wrap(err, "error parsing service instance generation") } - parameterHash := *serviceInstance.Metadata.Annotations[annotationParameterHash] + + owner := instanceOpts["owner"] + if owner == "" { + owner = *serviceInstance.Metadata.Labels[labelOwner] + } + var state facade.InstanceState switch serviceInstance.LastOperation.Type + ":" + serviceInstance.LastOperation.State { case "create:in progress": @@ -226,22 +220,15 @@ func InitInstance(serviceInstance *cfresource.ServiceInstance, instanceOpts map[ default: state = facade.InstanceStateUnknown } - stateDescription := serviceInstance.LastOperation.Description - - owner := instanceOpts["owner"] - if owner == "" { - owner = *serviceInstance.Metadata.Labels[labelOwner] - } return &facade.Instance{ - Guid: guid, - Name: name, - ServicePlanGuid: servicePlanGuid, - Owner: owner, - + Guid: serviceInstance.GUID, + Name: serviceInstance.Name, + ServicePlanGuid: serviceInstance.Relationships.ServicePlan.Data.GUID, + Owner: owner, Generation: generation, - ParameterHash: parameterHash, + ParameterHash: *serviceInstance.Metadata.Annotations[annotationParameterHash], State: state, - StateDescription: stateDescription, + StateDescription: serviceInstance.LastOperation.Description, }, nil } From b03b198678e361a5e960cd77932f492324e41082 Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Thu, 5 Sep 2024 13:48:49 +0200 Subject: [PATCH 30/63] improve documentation in resourcecache --- internal/cf/resourcecache.go | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/internal/cf/resourcecache.go b/internal/cf/resourcecache.go index 2f2ec7f..d77ab56 100644 --- a/internal/cf/resourcecache.go +++ b/internal/cf/resourcecache.go @@ -14,9 +14,11 @@ import ( "github.com/sap/cf-service-operator/internal/facade" ) -// Cache is a simple in-memory cache to store spaces, instances, and bindings using a map with a mutex -// The cache is used to store the resources to avoid making multiple calls to the CF API -// key is the owner of the instance which is kubernetes UID and value is the instance +// The resource cache is a simple in-memory cache to store CF resources like spaces, instances and +// bindings using a map protected by a mutex. +// The resource cache is used to avoid making multiple calls to the CF API and avoid rate limits. +// The map uses the owner of the instance (which is Kubernetes UID) as key and the service instance +// as value. type resourceCache struct { spaces map[string]*facade.Space instances map[string]*facade.Instance @@ -38,7 +40,7 @@ func initResourceCache() *resourceCache { return cache } -// Function to set the resource cache timeout from config +// setCacheTimeOut sets the timeout used for expiration of the cache func (c *resourceCache) setCacheTimeOut(timeOut string) { cacheTimeOut, err := time.ParseDuration(timeOut) if err != nil { @@ -49,24 +51,24 @@ func (c *resourceCache) setCacheTimeOut(timeOut string) { c.cacheTimeOut = cacheTimeOut } -// Function to check if the cache is expired +// isCacheExpired checks if the cache is already expired func (c *resourceCache) isCacheExpired() bool { - expirationTime := c.lastCacheTime.Add(c.cacheTimeOut) return time.Now().After(expirationTime) - } -// Function to set the last cache time +// setLastCacheTime sets the time of the last cache expiration re-population func (c *resourceCache) setLastCacheTime() { c.lastCacheTime = time.Now() log.Printf("Last cache time: %v\n", c.lastCacheTime) } -// function to set the resource cache enabled flag from config +// setResourceCacheEnabled enables or disables the resource cahce func (c *resourceCache) setResourceCacheEnabled(enabled bool) { c.isResourceCacheEnabled = enabled } + +// checkResourceCacheEnabled checks if the resource cache is enabled (object might be nil) func (c *resourceCache) checkResourceCacheEnabled() bool { if c == nil { log.Println("Resource cache is nil") @@ -75,14 +77,14 @@ func (c *resourceCache) checkResourceCacheEnabled() bool { return c.isResourceCacheEnabled } -// AddInstanceInCache stores an instance in the cache +// addInstanceInCache stores an instance in the cache func (c *resourceCache) addInstanceInCache(key string, instance *facade.Instance) { c.mutex.Lock() defer c.mutex.Unlock() c.instances[key] = instance } -// GetInstanceFromCache retrieves an instance from the cache +// getInstanceFromCache retrieves an instance from the cache func (c *resourceCache) getInstanceFromCache(key string) (*facade.Instance, bool) { c.mutex.RLock() defer c.mutex.RUnlock() @@ -92,7 +94,7 @@ func (c *resourceCache) getInstanceFromCache(key string) (*facade.Instance, bool return instance, found } -// delete the instance from the cache +// deleteInstanceFromCache deletes an instance from the cache func (c *resourceCache) deleteInstanceFromCache(key string) { c.mutex.Lock() defer c.mutex.Unlock() @@ -100,7 +102,7 @@ func (c *resourceCache) deleteInstanceFromCache(key string) { } -// update the instance in the cache +// updateInstanceInCache updates an instance in the cache func (c *resourceCache) updateInstanceInCache(guid string, name string, owner string, servicePlanGuid string, parameters map[string]interface{}, generation int64) (status bool) { c.mutex.Lock() defer c.mutex.Unlock() From 554832ede673b659704dd8a55d1089fa634257a9 Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Thu, 5 Sep 2024 13:51:24 +0200 Subject: [PATCH 31/63] minor improvements to config --- internal/config/config.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index 80914e6..cb7c2de 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -13,11 +13,10 @@ import ( // Config defines the configuration keys type Config struct { - - //Resource cache is enabled or disabled + // Resource cache is enabled or disabled IsResourceCacheEnabled bool `env:"RESOURCE_CACHE_ENABLED" envDefault:"false"` - //cache timeout in seconds,minutes or hours + // Timeout for resource cache in seconds, minutes or hours CacheTimeOut string `env:"CACHE_TIMEOUT" envDefault:"1m"` } From f86cf7671fcf8c1cb15d1c42a65165bc4d5991d4 Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Fri, 6 Sep 2024 16:06:10 +0530 Subject: [PATCH 32/63] Modified resource cache tests --- internal/cf/resourcecache_test.go | 127 ++++++++++++------------------ 1 file changed, 52 insertions(+), 75 deletions(-) diff --git a/internal/cf/resourcecache_test.go b/internal/cf/resourcecache_test.go index 09b444e..f8dfa0c 100644 --- a/internal/cf/resourcecache_test.go +++ b/internal/cf/resourcecache_test.go @@ -72,16 +72,24 @@ var _ = Describe("ResourceCache", func() { Context("edge cases", func() { It("should handle adding an instance with an existing key", func() { + instance2 := &facade.Instance{ + Guid: "guid2", + Name: "name2", + Owner: "owner1", + ServicePlanGuid: "plan1", + ParameterHash: "hash1", + Generation: 1, + } cache.addInstanceInCache("owner1", instance) - cache.addInstanceInCache("owner1", instance) + cache.addInstanceInCache("owner1", instance2) retrievedInstance, found := cache.getInstanceFromCache("owner1") Expect(found).To(BeTrue()) - Expect(retrievedInstance).To(Equal(instance)) + Expect(retrievedInstance).To(Equal(instance2)) }) It("should handle updating a non-existent instance", func() { cache.updateInstanceInCache("nonExistentGuid", "name", "owner", "plan", nil, 1) - _, found := cache.getInstanceFromCache("nonExistentGuid") + _, found := cache.getInstanceFromCache("owner") Expect(found).To(BeFalse()) }) @@ -92,119 +100,88 @@ var _ = Describe("ResourceCache", func() { }) }) - Context("concurrent operations", func() { - It("should handle concurrent AddInstanceInCache", func() { + Context("concurrent CRUD operations, data integrity and load test", func() { + It("should handle concurrent ", func() { for i := 0; i < concurrencyLevel; i++ { wg.Add(1) go func(i int) { defer wg.Done() + instance := &facade.Instance{ + Guid: "guid" + strconv.Itoa(i), + Name: "name" + strconv.Itoa(i), + Owner: "key" + strconv.Itoa(i), + ServicePlanGuid: "plan" + strconv.Itoa(i), + ParameterHash: "hash", + Generation: 1, + } cache.addInstanceInCache("key"+strconv.Itoa(i), instance) }(i) } wg.Wait() - }) - It("should handle concurrent GetInstanceFromCache", func() { + // Verify that all instances have been added to the cache for i := 0; i < concurrencyLevel; i++ { wg.Add(1) go func(i int) { defer wg.Done() - cache.getInstanceFromCache("key" + strconv.Itoa(i)) + key := "key" + strconv.Itoa(i) + instance := &facade.Instance{ + Guid: "guid" + strconv.Itoa(i), + Name: "name" + strconv.Itoa(i), + Owner: "key" + strconv.Itoa(i), + ServicePlanGuid: "plan" + strconv.Itoa(i), + ParameterHash: "hash", + Generation: 1, + } + retrievedInstance, found := cache.getInstanceFromCache(key) + Expect(found).To(BeTrue(), "Instance should be found in cache for key: %s", key) + Expect(retrievedInstance).To(Equal(instance), "Retrieved instance should match the added instance for key: %s", key) }(i) } wg.Wait() - }) - It("should handle concurrent UpdateInstanceInCache", func() { + // Concurrently update instances in the cache for i := 0; i < concurrencyLevel; i++ { wg.Add(1) go func(i int) { defer wg.Done() - cache.updateInstanceInCache("guid"+strconv.Itoa(i), "name"+strconv.Itoa(i), "owner1", "plan"+strconv.Itoa(i), nil, int64(i)) + cache.updateInstanceInCache("guid"+strconv.Itoa(i), "updatedInstance"+strconv.Itoa(i), "key"+strconv.Itoa(i), "plan"+strconv.Itoa(i), nil, 1) }(i) } wg.Wait() - }) - It("should handle concurrent DeleteInstanceFromCache", func() { + // Verify that all instances have been updated in the cache for i := 0; i < concurrencyLevel; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - cache.deleteInstanceFromCache("key" + strconv.Itoa(i)) - }(i) + key := "key" + strconv.Itoa(i) + expectedInstance := &facade.Instance{ + Guid: "guid" + strconv.Itoa(i), + Name: "updatedInstance" + strconv.Itoa(i), + Owner: key, + ServicePlanGuid: "plan" + strconv.Itoa(i), + ParameterHash: "hash", + Generation: 1, + } + retrievedInstance, found := cache.getInstanceFromCache(key) + + Expect(found).To(BeTrue(), "Instance should be found in cache for key: %s", key) + Expect(retrievedInstance).To(Equal(expectedInstance), "Retrieved instance should match the updated instance for key: %s", key) } - wg.Wait() - // Verify final state + // Concurrently delete instances from the cache for i := 0; i < concurrencyLevel; i++ { - _, found := cache.getInstanceFromCache("key" + strconv.Itoa(i)) - Expect(found).To(BeFalse(), "Expected instance to be deleted from cache") - } - }) - - It("should handle high load", func() { - highLoadLevel := 1000 - - for i := 0; i < highLoadLevel; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - cache.addInstanceInCache("key"+strconv.Itoa(i), instance) - }(i) - } - - wg.Wait() - - for i := 0; i < highLoadLevel; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - cache.getInstanceFromCache("key" + strconv.Itoa(i)) - }(i) - } - - wg.Wait() - - for i := 0; i < highLoadLevel; i++ { wg.Add(1) go func(i int) { defer wg.Done() cache.deleteInstanceFromCache("key" + strconv.Itoa(i)) }(i) } - wg.Wait() // Verify final state - for i := 0; i < highLoadLevel; i++ { + for i := 0; i < concurrencyLevel; i++ { _, found := cache.getInstanceFromCache("key" + strconv.Itoa(i)) Expect(found).To(BeFalse(), "Expected instance to be deleted from cache") } }) - - It("should maintain data integrity during concurrent operations", func() { - for i := 0; i < concurrencyLevel; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - cache.addInstanceInCache("key"+strconv.Itoa(i), instance) - }(i) - } - - wg.Wait() - - for i := 0; i < concurrencyLevel; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - retrievedInstance, found := cache.getInstanceFromCache("key" + strconv.Itoa(i)) - Expect(found).To(BeTrue()) - Expect(retrievedInstance).To(Equal(instance)) - }(i) - } - - wg.Wait() - }) }) }) From 6d642c88e2cb6d1155adf473fcfa5b1afb2a0f6b Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Tue, 10 Sep 2024 17:50:02 +0530 Subject: [PATCH 33/63] Added caching logic for space and binding --- internal/cf/binding.go | 132 ++++++---- internal/cf/client.go | 208 ++++++++++++--- internal/cf/instance.go | 8 +- internal/cf/resourcecache.go | 237 +++++++++++++----- internal/cf/resourcecache_test.go | 10 +- internal/cf/space.go | 57 +++-- .../controllers/servicebinding_controller.go | 59 +++-- internal/controllers/space_controller.go | 4 +- internal/controllers/suite_test.go | 2 +- internal/facade/client.go | 6 +- .../facade/facadefakes/fake_space_client.go | 44 ++-- main.go | 2 + 12 files changed, 557 insertions(+), 212 deletions(-) diff --git a/internal/cf/binding.go b/internal/cf/binding.go index f7f66c2..1655427 100644 --- a/internal/cf/binding.go +++ b/internal/cf/binding.go @@ -48,6 +48,24 @@ func (bo *bindingFilterOwner) getListOptions() *cfclient.ServiceCredentialBindin // If multiple bindings are found, an error is returned. // The function add the parameter values to the orphan cf binding, so that can be adopted. func (c *spaceClient) GetBinding(ctx context.Context, bindingOpts map[string]string) (*facade.Binding, error) { + if c.resourceCache.checkResourceCacheEnabled() { + // Attempt to retrieve binding from cache + if c.resourceCache.isCacheExpired("serviceBindings") { + //TODO: remove after internal review + fmt.Println("Cache is expired") + populateResourceCache[*spaceClient](c, "serviceBindings") + } + if len(c.resourceCache.getCachedBindings()) != 0 { + binding, bindingInCache := c.resourceCache.getBindingFromCache(bindingOpts["owner"]) + //TODO: remove after internal review + fmt.Printf("Length of cache: %d\n", len(c.resourceCache.getCachedBindings())) + if bindingInCache { + return binding, nil + } + } + } + + // Attempt to retrieve binding from Cloud Foundry var filterOpts bindingFilter if bindingOpts["name"] != "" { filterOpts = &bindingFilterName{name: bindingOpts["name"]} @@ -75,6 +93,73 @@ func (c *spaceClient) GetBinding(ctx context.Context, bindingOpts map[string]str parameterHashValue := "0" serviceBinding.Metadata.Annotations[annotationParameterHash] = ¶meterHashValue } + return c.InitBinding(ctx, serviceBinding, bindingOpts) +} + +// Required parameters (may not be initial): name, serviceInstanceGuid, owner, generation +// Optional parameters (may be initial): parameters +func (c *spaceClient) CreateBinding(ctx context.Context, name string, serviceInstanceGuid string, parameters map[string]interface{}, owner string, generation int64) error { + req := cfresource.NewServiceCredentialBindingCreateKey(serviceInstanceGuid, name) + if parameters != nil { + jsonParameters, err := json.Marshal(parameters) + if err != nil { + return err + } + req.WithJSONParameters(string(jsonParameters)) + } + req.Metadata = cfresource.NewMetadata(). + WithLabel(labelPrefix, labelKeyOwner, owner). + WithAnnotation(annotationPrefix, annotationKeyGeneration, strconv.FormatInt(generation, 10)). + WithAnnotation(annotationPrefix, annotationKeyParameterHash, facade.ObjectHash(parameters)) + + _, _, err := c.client.ServiceCredentialBindings.Create(ctx, req) + return err +} + +// Required parameters (may not be initial): guid, generation +func (c *spaceClient) UpdateBinding(ctx context.Context, guid string, owner string, generation int64, parameters map[string]interface{}) error { + // TODO: why is there no cfresource.NewServiceCredentialBindingUpdate() method ? + req := &cfresource.ServiceCredentialBindingUpdate{} + req.Metadata = cfresource.NewMetadata(). + WithAnnotation(annotationPrefix, annotationKeyGeneration, strconv.FormatInt(generation, 10)) + + if parameters != nil { + req.Metadata.WithAnnotation(annotationPrefix, annotationKeyParameterHash, facade.ObjectHash(parameters)) + if parameters["owner"] != nil { + req.Metadata.WithLabel(labelPrefix, labelKeyOwner, parameters["owner"].(string)) + } + } + _, err := c.client.ServiceCredentialBindings.Update(ctx, guid, req) + // Update binding in cache + if err == nil && c.resourceCache.checkResourceCacheEnabled() { + isUpdated := c.resourceCache.updateBindingInCache(owner, parameters, generation) + + if !isUpdated { + //add the binding to cache if it is not found + binding, err := c.GetBinding(ctx, map[string]string{"owner": owner}) + if err != nil { + return err + } + c.resourceCache.addBindingInCache(owner, binding) + } + + } + return err +} + +func (c *spaceClient) DeleteBinding(ctx context.Context, guid string, owner string) error { + + err := c.client.ServiceCredentialBindings.Delete(ctx, guid) + + // Delete binding from cache + if c.resourceCache.checkResourceCacheEnabled() { + c.resourceCache.deleteBindingFromCache(owner) + } + + return err +} + +func (c *spaceClient) InitBinding(ctx context.Context, serviceBinding *cfresource.ServiceCredentialBinding, bindingOpts map[string]string) (*facade.Binding, error) { guid := serviceBinding.GUID name := serviceBinding.Name @@ -110,11 +195,15 @@ func (c *spaceClient) GetBinding(ctx context.Context, bindingOpts map[string]str } credentials = details.Credentials } + owner := bindingOpts["owner"] + if owner == "" { + owner = *serviceBinding.Metadata.Labels[labelOwner] + } return &facade.Binding{ Guid: guid, Name: name, - Owner: bindingOpts["owner"], + Owner: owner, Generation: generation, ParameterHash: parameterHash, State: state, @@ -122,44 +211,3 @@ func (c *spaceClient) GetBinding(ctx context.Context, bindingOpts map[string]str Credentials: credentials, }, nil } - -// Required parameters (may not be initial): name, serviceInstanceGuid, owner, generation -// Optional parameters (may be initial): parameters -func (c *spaceClient) CreateBinding(ctx context.Context, name string, serviceInstanceGuid string, parameters map[string]interface{}, owner string, generation int64) error { - req := cfresource.NewServiceCredentialBindingCreateKey(serviceInstanceGuid, name) - if parameters != nil { - jsonParameters, err := json.Marshal(parameters) - if err != nil { - return err - } - req.WithJSONParameters(string(jsonParameters)) - } - req.Metadata = cfresource.NewMetadata(). - WithLabel(labelPrefix, labelKeyOwner, owner). - WithAnnotation(annotationPrefix, annotationKeyGeneration, strconv.FormatInt(generation, 10)). - WithAnnotation(annotationPrefix, annotationKeyParameterHash, facade.ObjectHash(parameters)) - - _, _, err := c.client.ServiceCredentialBindings.Create(ctx, req) - return err -} - -// Required parameters (may not be initial): guid, generation -func (c *spaceClient) UpdateBinding(ctx context.Context, guid string, generation int64, parameters map[string]interface{}) error { - // TODO: why is there no cfresource.NewServiceCredentialBindingUpdate() method ? - req := &cfresource.ServiceCredentialBindingUpdate{} - req.Metadata = cfresource.NewMetadata(). - WithAnnotation(annotationPrefix, annotationKeyGeneration, strconv.FormatInt(generation, 10)) - - if parameters != nil { - req.Metadata.WithAnnotation(annotationPrefix, annotationKeyParameterHash, facade.ObjectHash(parameters)) - if parameters["owner"] != nil { - req.Metadata.WithLabel(labelPrefix, labelKeyOwner, parameters["owner"].(string)) - } - } - _, err := c.client.ServiceCredentialBindings.Update(ctx, guid, req) - return err -} - -func (c *spaceClient) DeleteBinding(ctx context.Context, guid string) error { - return c.client.ServiceCredentialBindings.Delete(ctx, guid) -} diff --git a/internal/cf/client.go b/internal/cf/client.go index 60e5a00..b2624bf 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -35,8 +35,7 @@ const ( type organizationClient struct { organizationName string client cfclient.Client - // TP - //resourceCache *facade.Cache + resourceCache *resourceCache } type spaceClient struct { @@ -58,12 +57,22 @@ type clientCacheEntry struct { } var ( - clientCacheMutex = &sync.Mutex{} - clientCache = make(map[clientIdentifier]*clientCacheEntry) - cfResourceCache *resourceCache - refreshResourceCacheMutex = &sync.Mutex{} + clientCacheMutex = &sync.Mutex{} + clientCache = make(map[clientIdentifier]*clientCacheEntry) + cfResourceCache *resourceCache + refreshServiceInstanceResourceCacheMutex = &sync.Mutex{} + cfResourceCacheMutex = &sync.Mutex{} // Add this line + refreshSpaceResourceCacheMutex = &sync.Mutex{} + refreshServiceBindingResourceCacheMutex = &sync.Mutex{} ) +func initAndConfigureResourceCache(config config.Config) *resourceCache { + cache := initResourceCache() + cache.setResourceCacheEnabled(config.IsResourceCacheEnabled) + cache.setCacheTimeOut(config.CacheTimeOut) + return cache +} + func newOrganizationClient(organizationName string, url string, username string, password string) (*organizationClient, error) { if organizationName == "" { return nil, fmt.Errorf("missing or empty organization name") @@ -129,7 +138,7 @@ func newSpaceClient(spaceGuid string, url string, username string, password stri return &spaceClient{spaceGuid: spaceGuid, client: *c}, nil } -func NewOrganizationClient(organizationName string, url string, username string, password string) (facade.OrganizationClient, error) { +func NewOrganizationClient(organizationName string, url string, username string, password string, config config.Config) (facade.OrganizationClient, error) { clientCacheMutex.Lock() defer clientCacheMutex.Unlock() @@ -141,7 +150,7 @@ func NewOrganizationClient(organizationName string, url string, username string, var client *organizationClient = nil if isInCache { // re-use CF client and wrap it as organizationClient - client = &organizationClient{organizationName: organizationName, client: cacheEntry.client} + client = &organizationClient{organizationName: organizationName, client: cacheEntry.client, resourceCache: cfResourceCache} if cacheEntry.password != password { // password was rotated => delete client from cache and create a new one below delete(clientCache, identifier) @@ -158,6 +167,12 @@ func NewOrganizationClient(organizationName string, url string, username string, } } + if config.IsResourceCacheEnabled && client.resourceCache == nil { + client.resourceCache = initAndConfigureResourceCache(config) + populateResourceCache[*organizationClient](client, "spaces") + cfResourceCache = client.resourceCache + } + return client, err } @@ -191,10 +206,10 @@ func NewSpaceClient(spaceGuid string, url string, username string, password stri } if config.IsResourceCacheEnabled && client.resourceCache == nil { - client.resourceCache = initResourceCache() - client.resourceCache.setResourceCacheEnabled(config.IsResourceCacheEnabled) - client.resourceCache.setCacheTimeOut(config.CacheTimeOut) - client.populateResourceCache() + client.resourceCache = initAndConfigureResourceCache(config) + populateResourceCache[*spaceClient](client, "serviceInstances") + populateResourceCache[*spaceClient](client, "serviceBindings") + cfResourceCache = client.resourceCache } return client, err @@ -233,32 +248,60 @@ func NewSpaceHealthChecker(spaceGuid string, url string, username string, passwo return client, err } -// populateResourceCache populates the resource cache by fetching All service instances matching +type ResourceClient[T any] interface { + populateServiceInstances(ctx context.Context) error + populateServiceBindings(ctx context.Context) error + populateSpaces(ctx context.Context) error + resetCache(resourceType string) + getResourceCache() *resourceCache + setResourceCache(cache *resourceCache) +} + +// populateResourceCache populates the resource cache by fetching all resources matching // the owner label key from the Cloud Foundry API and storing them in an in-memory cache. // This function ensures that the cache is refreshed if it is expired. -// It uses concurrency to initialize and cache service instances efficiently. -// TODO: Extend logic to cache space and bindings -func (c *spaceClient) populateResourceCache() { - refreshResourceCacheMutex.Lock() - defer refreshResourceCacheMutex.Unlock() +// It uses concurrency to initialize and cache resources efficiently. +func populateResourceCache[T any](c ResourceClient[T], resourceType string) { + cfResourceCacheMutex.Lock() + defer cfResourceCacheMutex.Unlock() + ctx := context.Background() + var err error + + switch resourceType { + case "serviceInstances": + err = c.populateServiceInstances(ctx) + case "spaces": + err = c.populateSpaces(ctx) + case "serviceBindings": + err = c.populateServiceBindings(ctx) + default: + //TODO: populate for all resource types?? + log.Printf("Unknown resource type: %s", resourceType) + return + } - if c.resourceCache.isCacheExpired() { + if err != nil { + // reset the cache to nil in case of error + log.Printf("Error populating %s: %s", resourceType, err) + c.resetCache(resourceType) + return + } + c.setResourceCache(c.getResourceCache()) +} + +func (c *spaceClient) populateServiceInstances(ctx context.Context) error { + refreshServiceInstanceResourceCacheMutex.Lock() + defer refreshServiceInstanceResourceCacheMutex.Unlock() + + if c.resourceCache.isCacheExpired("serviceInstances") { instanceOptions := cfclient.NewServiceInstanceListOptions() instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) - ctx := context.Background() - //TODO:check if List method with paging option can be used instead of ListAll if in case of large number of instances/performance issues cfInstances, err := c.client.ServiceInstances.ListAll(ctx, instanceOptions) if err != nil { - // reset the cache to nil in case of error - log.Printf("Error listing service instances: %s", err) - c.resourceCache.instances = make(map[string]*facade.Instance) - c.resourceCache.setLastCacheTime() - cfResourceCache = c.resourceCache - return + return err } - // add service instances to cache concurrently var waitGroup sync.WaitGroup for _, cfInstance := range cfInstances { waitGroup.Add(1) @@ -272,9 +315,114 @@ func (c *spaceClient) populateResourceCache() { }(cfInstance) } waitGroup.Wait() + c.resourceCache.setLastCacheTime("serviceInstances") + } + + return nil + +} + +func (c *spaceClient) populateServiceBindings(ctx context.Context) error { + refreshServiceBindingResourceCacheMutex.Lock() + defer refreshServiceBindingResourceCacheMutex.Unlock() + + if c.resourceCache.isCacheExpired("serviceBindings") { + bindingOptions := cfclient.NewServiceCredentialBindingListOptions() + bindingOptions.ListOptions.LabelSelector.EqualTo(labelOwner) + + cfBindings, err := c.client.ServiceCredentialBindings.ListAll(ctx, bindingOptions) + if err != nil { + return err + } + + var waitGroup sync.WaitGroup + for _, cfBinding := range cfBindings { + waitGroup.Add(1) + go func(cfBinding *cfresource.ServiceCredentialBinding) { + defer waitGroup.Done() + if binding, err := c.InitBinding(ctx, cfBinding, nil); err == nil { + c.resourceCache.addBindingInCache(*cfBinding.Metadata.Labels[labelOwner], binding) + } else { + log.Printf("Error initializing binding: %s", err) + } + }(cfBinding) + } + waitGroup.Wait() + c.resourceCache.setLastCacheTime("serviceBindings") + } + + return nil +} + +func (c *spaceClient) populateSpaces(ctx context.Context) error { + return nil +} + +// populateSpaces populates the space cache by fetching all spaces matching the owner label key from the Cloud Foundry API +// and storing them in an in-memory cache having GUID as key. This function ensures that the cache is refreshed if it is expired. +func (c *organizationClient) populateSpaces(ctx context.Context) error { + refreshSpaceResourceCacheMutex.Lock() + defer refreshSpaceResourceCacheMutex.Unlock() + + if c.resourceCache.isCacheExpired("spaces") { + spaceOptions := cfclient.NewSpaceListOptions() + spaceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) + + cfSpaces, err := c.client.Spaces.ListAll(ctx, spaceOptions) + if err != nil { + return err + } - c.resourceCache.setLastCacheTime() - cfResourceCache = c.resourceCache + var waitGroup sync.WaitGroup + for _, cfSpace := range cfSpaces { + waitGroup.Add(1) + go func(cfSpace *cfresource.Space) { + defer waitGroup.Done() + if binding, err := InitSpace(cfSpace, ""); err == nil { + c.resourceCache.addSpaceInCache(cfSpace.GUID, binding) + } else { + log.Printf("Error initializing space: %s", err) + } + }(cfSpace) + } + waitGroup.Wait() + c.resourceCache.setLastCacheTime("spaces") } + return nil +} + +func (c *spaceClient) getResourceCache() *resourceCache { + return c.resourceCache +} + +func (c *spaceClient) setResourceCache(cache *resourceCache) { + c.resourceCache = cache +} + +func (c *spaceClient) resetCache(resourceType string) { + // Implementation for resetting the cache + c.resourceCache.resetCache(resourceType) +} + +func (c *organizationClient) getResourceCache() *resourceCache { + return c.resourceCache +} + +func (c *organizationClient) setResourceCache(cache *resourceCache) { + c.resourceCache = cache +} + +func (c *organizationClient) resetCache(resourceType string) { + // Implementation for resetting the cache + c.resourceCache.resetCache(resourceType) +} + +func (c *organizationClient) populateServiceInstances(ctx context.Context) error { + return nil + +} + +func (c *organizationClient) populateServiceBindings(ctx context.Context) error { + return nil } diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 74ee8df..1a54bc3 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -49,10 +49,10 @@ func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOpt func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]string) (*facade.Instance, error) { if c.resourceCache.checkResourceCacheEnabled() { // Attempt to retrieve instance from cache - if c.resourceCache.isCacheExpired() { + if c.resourceCache.isCacheExpired("serviceInstances") { //TODO: remove after internal review fmt.Println("Cache is expired") - c.populateResourceCache() + populateResourceCache[*spaceClient](c, "serviceInstances") } if len(c.resourceCache.getCachedInstances()) != 0 { instance, instanceInCache := c.resourceCache.getInstanceFromCache(instanceOpts["owner"]) @@ -152,7 +152,7 @@ func (c *spaceClient) UpdateInstance(ctx context.Context, guid string, name stri // Update instance in cache if err == nil && c.resourceCache.checkResourceCacheEnabled() { - isUpdated := c.resourceCache.updateInstanceInCache(guid, name, owner, servicePlanGuid, parameters, generation) + isUpdated := c.resourceCache.updateInstanceInCache(owner, servicePlanGuid, parameters, generation) if !isUpdated { // add instance to cache in case of orphan instance @@ -178,7 +178,7 @@ func (c *spaceClient) DeleteInstance(ctx context.Context, guid string, owner str _, err := c.client.ServiceInstances.Delete(ctx, guid) // Delete instance from cache - if err == nil && c.resourceCache.checkResourceCacheEnabled() { + if c.resourceCache.checkResourceCacheEnabled() { c.resourceCache.deleteInstanceFromCache(owner) } diff --git a/internal/cf/resourcecache.go b/internal/cf/resourcecache.go index d77ab56..63bd5e4 100644 --- a/internal/cf/resourcecache.go +++ b/internal/cf/resourcecache.go @@ -20,24 +20,33 @@ import ( // The map uses the owner of the instance (which is Kubernetes UID) as key and the service instance // as value. type resourceCache struct { - spaces map[string]*facade.Space - instances map[string]*facade.Instance - bindings map[string]*facade.Binding - mutex sync.RWMutex - lastCacheTime time.Time - cacheTimeOut time.Duration - isResourceCacheEnabled bool + spaces map[string]*facade.Space + instances map[string]*facade.Instance + bindings map[string]*facade.Binding + mutex sync.RWMutex + cacheTimeOut time.Duration + lastServiceInstanceCacheTime time.Time + lastSpaceCacheTime time.Time + lastServiceBindingCacheTime time.Time + isResourceCacheEnabled bool } +var ( + cacheInstance *resourceCache + cacheInstanceOnce sync.Once +) + // InitResourcesCache initializes a new cache func initResourceCache() *resourceCache { - cache := &resourceCache{ - spaces: make(map[string]*facade.Space), - instances: make(map[string]*facade.Instance), - bindings: make(map[string]*facade.Binding), - cacheTimeOut: 5 * time.Minute, - } - return cache + cacheInstanceOnce.Do(func() { + cacheInstance = &resourceCache{ + spaces: make(map[string]*facade.Space), + instances: make(map[string]*facade.Instance), + bindings: make(map[string]*facade.Binding), + cacheTimeOut: 5 * time.Minute, + } + }) + return cacheInstance } // setCacheTimeOut sets the timeout used for expiration of the cache @@ -51,16 +60,48 @@ func (c *resourceCache) setCacheTimeOut(timeOut string) { c.cacheTimeOut = cacheTimeOut } -// isCacheExpired checks if the cache is already expired -func (c *resourceCache) isCacheExpired() bool { - expirationTime := c.lastCacheTime.Add(c.cacheTimeOut) +// // isCacheExpired checks if the cache is already expired +// +// func (c *resourceCache) isCacheExpired() bool { +// expirationTime := c.lastCacheTime.Add(c.cacheTimeOut) +// return time.Now().After(expirationTime) +// } +func (c *resourceCache) isCacheExpired(resourceType string) bool { + var expirationTime time.Time + switch resourceType { + case "serviceInstances": + expirationTime = c.lastServiceInstanceCacheTime.Add(c.cacheTimeOut) + case "spaces": + expirationTime = c.lastSpaceCacheTime.Add(c.cacheTimeOut) + case "serviceBindings": + expirationTime = c.lastServiceBindingCacheTime.Add(c.cacheTimeOut) + default: + return true + } return time.Now().After(expirationTime) } -// setLastCacheTime sets the time of the last cache expiration re-population -func (c *resourceCache) setLastCacheTime() { - c.lastCacheTime = time.Now() - log.Printf("Last cache time: %v\n", c.lastCacheTime) +// // setLastCacheTime sets the time of the last cache expiration re-population +// func (c *resourceCache) setLastCacheTime() { +// c.lastCacheTime = time.Now() +// log.Printf("Last cache time: %v\n", c.lastCacheTime) +// } + +func (c *resourceCache) setLastCacheTime(resourceType string) { + now := time.Now() + switch resourceType { + case "serviceInstances": + c.lastServiceInstanceCacheTime = now + case "spaces": + c.lastSpaceCacheTime = now + case "serviceBindings": + c.lastServiceBindingCacheTime = now + default: + c.lastServiceInstanceCacheTime = now + c.lastSpaceCacheTime = now + c.lastServiceBindingCacheTime = now + } + log.Printf("Last cache time for %s: %v\n", resourceType, now) } // setResourceCacheEnabled enables or disables the resource cahce @@ -103,29 +144,22 @@ func (c *resourceCache) deleteInstanceFromCache(key string) { } // updateInstanceInCache updates an instance in the cache -func (c *resourceCache) updateInstanceInCache(guid string, name string, owner string, servicePlanGuid string, parameters map[string]interface{}, generation int64) (status bool) { +func (c *resourceCache) updateInstanceInCache(owner string, servicePlanGuid string, parameters map[string]interface{}, generation int64) (status bool) { c.mutex.Lock() defer c.mutex.Unlock() //update if the instance is found in the cache //update all the struct variables if they are not nil or empty instance, found := c.instances[owner] if found { - if guid != "" { - instance.Guid = guid - } - if name != "" { - instance.Name = name - } if servicePlanGuid != "" { instance.ServicePlanGuid = servicePlanGuid } if parameters != nil { instance.ParameterHash = facade.ObjectHash(parameters) } - if owner != "" { - instance.Owner = owner + if generation != 0 { + instance.Generation = generation } - instance.Generation = generation c.instances[owner] = instance return true @@ -138,46 +172,119 @@ func (c *resourceCache) getCachedInstances() map[string]*facade.Instance { return c.instances } -//TODO:Uncomment on functionality completion -// // AddSpaceInCache stores a space in the cache -// func (c *resourceCache) addSpaceInCache(key string, space *facade.Space) { -// c.mutex.Lock() -// defer c.mutex.Unlock() -// c.spaces[key] = space -// } +// getBindingFromCache retrieves binding from the cache +func (c *resourceCache) getCachedBindings() map[string]*facade.Binding { + return c.bindings +} -// // GetSpaceFromCache retrieves a space from the cache -// func (c *resourceCache) getSpaceFromCache(key string) (*facade.Space, bool) { -// c.mutex.RLock() -// defer c.mutex.RUnlock() -// space, found := c.spaces[key] -// return space, found -// } +// addBindingInCache stores binding in the cache +func (c *resourceCache) addBindingInCache(key string, binding *facade.Binding) { + c.mutex.Lock() + defer c.mutex.Unlock() + c.bindings[key] = binding +} + +// getBindingFromCache retrieves binding from the cache +func (c *resourceCache) getBindingFromCache(key string) (*facade.Binding, bool) { + c.mutex.RLock() + defer c.mutex.RUnlock() + binding, found := c.bindings[key] + // TODO :remove After internal review + fmt.Printf("Got the binding from Cache: %v", binding) + return binding, found +} + +// deleteBindingFromCache deletes binding from the cache +func (c *resourceCache) deleteBindingFromCache(key string) { + c.mutex.Lock() + defer c.mutex.Unlock() + delete(c.bindings, key) + +} -// // AddBindingInCache stores a binding in the cache -// func (c *resourceCache) addBindingInCache(key string, binding *facade.Binding) { +// updateBindingInCache updates an binding in the cache +func (c *resourceCache) updateBindingInCache(owner string, parameters map[string]interface{}, generation int64) (status bool) { + c.mutex.Lock() + defer c.mutex.Unlock() + //update if the instance is found in the cache + //update all the struct variables if they are not nil or empty + binding, found := c.bindings[owner] + if found { + if parameters != nil { + binding.ParameterHash = facade.ObjectHash(parameters) + } + if generation != 0 { + binding.Generation = generation + } + c.bindings[owner] = binding + return true + + } + return false + +} + +// AddSpaceInCache stores a space in the cache +func (c *resourceCache) addSpaceInCache(key string, space *facade.Space) { + c.mutex.Lock() + defer c.mutex.Unlock() + c.spaces[key] = space +} + +// GetSpaceFromCache retrieves a space from the cache +func (c *resourceCache) getSpaceFromCache(key string) (*facade.Space, bool) { + c.mutex.RLock() + defer c.mutex.RUnlock() + space, found := c.spaces[key] + return space, found +} + +// deleteSpaceFromCache deletes space from the cache +func (c *resourceCache) deleteSpaceFromCache(key string) { + c.mutex.Lock() + defer c.mutex.Unlock() + delete(c.spaces, key) + +} + +// getCachedSpaces retrieves spaces from the cache +func (c *resourceCache) getCachedSpaces() map[string]*facade.Space { + return c.spaces +} + +// // updateSpaceInCache updates an space in the cache +// func (c *resourceCache) updateSpaceInCache(owner string, generation int64) (status bool) { // c.mutex.Lock() // defer c.mutex.Unlock() -// c.bindings[key] = binding -// } +// //update if the space is found in the cache +// //update all the struct variables if they are not nil or empty +// space, found := c.spaces[owner] +// if found { +// if generation != 0 { +// space.Generation = generation +// } +// c.spaces[owner] = space +// return true -// // GetBindingFromCache retrieves a binding from the cache -// func (c *resourceCache) getBindingFromCache(key string) (*facade.Binding, bool) { -// c.mutex.RLock() -// defer c.mutex.RUnlock() -// binding, found := c.bindings[key] -// return binding, found -// } +// } +// return false -// // Get resource cache -// func (c *resourceCache) getresourceCache() *resourceCache { -// return c // } -// func (c *resourceCache) getCachedBindings() map[string]*facade.Binding { -// return c.bindings -// } +// reset cache of a specific resource type and last cache time +func (c *resourceCache) resetCache(resourceType string) { + switch resourceType { + case "serviceInstances": + c.instances = make(map[string]*facade.Instance) + c.lastServiceInstanceCacheTime = time.Now() + case "spaces": + c.spaces = make(map[string]*facade.Space) + c.lastSpaceCacheTime = time.Now() + case "serviceBindings": + c.bindings = make(map[string]*facade.Binding) + c.lastServiceBindingCacheTime = time.Now() + default: + log.Printf("Unknown resource type: %s", resourceType) -// func (c *resourceCache) getCachedSpaces() map[string]*facade.Space { -// return c.spaces -// } + } +} diff --git a/internal/cf/resourcecache_test.go b/internal/cf/resourcecache_test.go index f8dfa0c..4a28c2b 100644 --- a/internal/cf/resourcecache_test.go +++ b/internal/cf/resourcecache_test.go @@ -52,13 +52,13 @@ var _ = Describe("ResourceCache", func() { // Update instance updatedInstance := &facade.Instance{ Guid: "guid1", - Name: "updatedName", + Name: "name1", Owner: "owner1", ServicePlanGuid: "updatedPlan", ParameterHash: "hash1", Generation: 2, } - cache.updateInstanceInCache("guid1", "updatedName", "owner1", "updatedPlan", nil, 2) + cache.updateInstanceInCache("owner1", "updatedPlan", nil, 2) retrievedInstance, found = cache.getInstanceFromCache(Ownerkey) Expect(found).To(BeTrue()) Expect(retrievedInstance).To(Equal(updatedInstance)) @@ -88,7 +88,7 @@ var _ = Describe("ResourceCache", func() { }) It("should handle updating a non-existent instance", func() { - cache.updateInstanceInCache("nonExistentGuid", "name", "owner", "plan", nil, 1) + cache.updateInstanceInCache("owner", "plan", nil, 1) _, found := cache.getInstanceFromCache("owner") Expect(found).To(BeFalse()) }) @@ -145,7 +145,7 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - cache.updateInstanceInCache("guid"+strconv.Itoa(i), "updatedInstance"+strconv.Itoa(i), "key"+strconv.Itoa(i), "plan"+strconv.Itoa(i), nil, 1) + cache.updateInstanceInCache("key"+strconv.Itoa(i), "plan"+strconv.Itoa(i), nil, 1) }(i) } wg.Wait() @@ -155,7 +155,7 @@ var _ = Describe("ResourceCache", func() { key := "key" + strconv.Itoa(i) expectedInstance := &facade.Instance{ Guid: "guid" + strconv.Itoa(i), - Name: "updatedInstance" + strconv.Itoa(i), + Name: "name" + strconv.Itoa(i), Owner: key, ServicePlanGuid: "plan" + strconv.Itoa(i), ParameterHash: "hash", diff --git a/internal/cf/space.go b/internal/cf/space.go index 909e818..31b17cd 100644 --- a/internal/cf/space.go +++ b/internal/cf/space.go @@ -12,12 +12,29 @@ import ( cfclient "github.com/cloudfoundry-community/go-cfclient/v3/client" cfresource "github.com/cloudfoundry-community/go-cfclient/v3/resource" - "github.com/pkg/errors" "github.com/sap/cf-service-operator/internal/facade" ) func (c *organizationClient) GetSpace(ctx context.Context, owner string) (*facade.Space, error) { + if c.resourceCache.checkResourceCacheEnabled() { + // Attempt to retrieve space from cache + if c.resourceCache.isCacheExpired("spaces") { + //TODO: remove after internal review + fmt.Println("Cache is expired") + populateResourceCache[*organizationClient](c, "spaces") + } + if len(c.resourceCache.getCachedSpaces()) != 0 { + space, spaceInCache := c.resourceCache.getSpaceFromCache(owner) + //TODO: remove after internal review + fmt.Printf("Length of cache: %d\n", len(c.resourceCache.getCachedSpaces())) + if spaceInCache { + return space, nil + } + } + } + + //Attempt to retrieve space from Cloud Foundry listOpts := cfclient.NewSpaceListOptions() listOpts.LabelSelector.EqualTo(labelPrefix + "/" + labelKeyOwner + "=" + owner) spaces, err := c.client.Spaces.ListAll(ctx, listOpts) @@ -31,20 +48,7 @@ func (c *organizationClient) GetSpace(ctx context.Context, owner string) (*facad return nil, fmt.Errorf("found multiple spaces with owner: %s", owner) } space := spaces[0] - - guid := space.GUID - name := space.Name - generation, err := strconv.ParseInt(*space.Metadata.Annotations[annotationGeneration], 10, 64) - if err != nil { - return nil, errors.Wrap(err, "error parsing space generation") - } - - return &facade.Space{ - Guid: guid, - Name: name, - Owner: owner, - Generation: generation, - }, nil + return InitSpace(space, owner) } // Required parameters (may not be initial): name, owner, generation @@ -88,6 +92,11 @@ func (c *organizationClient) UpdateSpace(ctx context.Context, guid string, name func (c *organizationClient) DeleteSpace(ctx context.Context, guid string) error { _, err := c.client.Spaces.Delete(ctx, guid) + + // Delete space from cache + if c.resourceCache.checkResourceCacheEnabled() { + c.resourceCache.deleteSpaceFromCache(guid) + } return err } @@ -127,3 +136,21 @@ func (c *organizationClient) AddDeveloper(ctx context.Context, guid string, user func (c *organizationClient) AddManager(ctx context.Context, guid string, username string) error { return nil } + +// Initspace wraps cfclient.Space as a facade.Space +func InitSpace(space *cfresource.Space, owner string) (*facade.Space, error) { + + guid := space.GUID + name := space.Name + generation, err := strconv.ParseInt(*space.Metadata.Annotations[annotationGeneration], 10, 64) + if err != nil { + return nil, err + } + + return &facade.Space{ + Guid: guid, + Name: name, + Owner: owner, + Generation: generation, + }, err +} diff --git a/internal/controllers/servicebinding_controller.go b/internal/controllers/servicebinding_controller.go index aa69b5b..c693f91 100644 --- a/internal/controllers/servicebinding_controller.go +++ b/internal/controllers/servicebinding_controller.go @@ -194,32 +194,38 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque if err != nil { return ctrl.Result{}, err } + if cfbinding != nil && cfbinding.State == facade.BindingStateReady { + //Add parameters to adopt the orphaned binding + var parameterObjects []map[string]interface{} + paramMap := make(map[string]interface{}) + paramMap["parameter-hash"] = cfbinding.ParameterHash + paramMap["owner"] = cfbinding.Owner + parameterObjects = append(parameterObjects, paramMap) + parameters, err := mergeObjects(parameterObjects...) + if err != nil { + return ctrl.Result{}, errors.Wrap(err, "failed to unmarshal/merge parameters") + } + // update the orphaned cloud foundry service binding + log.V(1).Info("Updating binding") + if err := client.UpdateBinding( + ctx, + cfbinding.Guid, + cfbinding.Owner, + serviceBinding.Generation, + parameters, + ); err != nil { + return ctrl.Result{}, err + } + status.LastModifiedAt = &[]metav1.Time{metav1.Now()}[0] - //Add parameters to adopt the orphaned binding - var parameterObjects []map[string]interface{} - paramMap := make(map[string]interface{}) - paramMap["parameter-hash"] = cfbinding.ParameterHash - paramMap["owner"] = cfbinding.Owner - parameterObjects = append(parameterObjects, paramMap) - parameters, err := mergeObjects(parameterObjects...) - if err != nil { - return ctrl.Result{}, errors.Wrap(err, "failed to unmarshal/merge parameters") - } - // update the orphaned cloud foundry service binding - log.V(1).Info("Updating binding") - if err := client.UpdateBinding( - ctx, - cfbinding.Guid, - serviceBinding.Generation, - parameters, - ); err != nil { - return ctrl.Result{}, err - } - status.LastModifiedAt = &[]metav1.Time{metav1.Now()}[0] + // return the reconcile function to requeue inmediatly after the update + serviceBinding.SetReadyCondition(cfv1alpha1.ConditionUnknown, string(cfbinding.State), cfbinding.StateDescription) + return ctrl.Result{Requeue: true}, nil + } else if cfbinding != nil && cfbinding.State != facade.BindingStateReady { - // return the reconcile function to requeue inmediatly after the update - serviceBinding.SetReadyCondition(cfv1alpha1.ConditionUnknown, string(cfbinding.State), cfbinding.StateDescription) - return ctrl.Result{Requeue: true}, nil + //return the reconcile function to not reconcile and error message + return ctrl.Result{}, fmt.Errorf("orphaned instance is not ready to be adopted") + } } } @@ -298,7 +304,7 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque cfbinding.State == facade.BindingStateCreatedFailed || cfbinding.State == facade.BindingStateDeleteFailed { // Re-create binding (unfortunately, cloud foundry does not support binding updates, other than metadata) log.V(1).Info("Deleting binding for later re-creation") - if err := client.DeleteBinding(ctx, cfbinding.Guid); err != nil { + if err := client.DeleteBinding(ctx, cfbinding.Guid, cfbinding.Owner); err != nil { return ctrl.Result{}, err } status.LastModifiedAt = &[]metav1.Time{metav1.Now()}[0] @@ -311,6 +317,7 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque if err := client.UpdateBinding( ctx, cfbinding.Guid, + cfbinding.Owner, serviceBinding.Generation, nil, ); err != nil { @@ -404,7 +411,7 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque } else { if cfbinding.State != facade.BindingStateDeleting { log.V(1).Info("Deleting binding") - if err := client.DeleteBinding(ctx, cfbinding.Guid); err != nil { + if err := client.DeleteBinding(ctx, cfbinding.Guid, cfbinding.Owner); err != nil { return ctrl.Result{}, err } status.LastModifiedAt = &[]metav1.Time{metav1.Now()}[0] diff --git a/internal/controllers/space_controller.go b/internal/controllers/space_controller.go index 161036b..5aa672e 100644 --- a/internal/controllers/space_controller.go +++ b/internal/controllers/space_controller.go @@ -23,6 +23,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" cfv1alpha1 "github.com/sap/cf-service-operator/api/v1alpha1" + "github.com/sap/cf-service-operator/internal/config" "github.com/sap/cf-service-operator/internal/facade" ) @@ -46,6 +47,7 @@ type SpaceReconciler struct { ClusterResourceNamespace string ClientBuilder facade.OrganizationClientBuilder HealthCheckerBuilder facade.SpaceHealthCheckerBuilder + Config *config.Config } // +kubebuilder:rbac:groups=cf.cs.sap.com,resources=clusterspaces,verbs=get;list;watch;update @@ -147,7 +149,7 @@ func (r *SpaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resu password = string(secret.Data["password"]) } - client, err = r.ClientBuilder(spec.OrganizationName, url, username, password) + client, err = r.ClientBuilder(spec.OrganizationName, url, username, password, *r.Config) if err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to build the client from secret %s", secretName) } diff --git a/internal/controllers/suite_test.go b/internal/controllers/suite_test.go index 3ea2d05..9bdfc7f 100644 --- a/internal/controllers/suite_test.go +++ b/internal/controllers/suite_test.go @@ -208,7 +208,7 @@ func addControllers(k8sManager ctrl.Manager) { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), ClusterResourceNamespace: testK8sNamespace, - ClientBuilder: func(organizationName string, url string, username string, password string) (facade.OrganizationClient, error) { + ClientBuilder: func(organizationName string, url string, username string, password string, config config.Config) (facade.OrganizationClient, error) { return fakeOrgClient, nil }, HealthCheckerBuilder: func(spaceGuid string, url string, username string, password string) (facade.SpaceHealthChecker, error) { diff --git a/internal/facade/client.go b/internal/facade/client.go index 5b3e179..80c649c 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -77,7 +77,7 @@ type OrganizationClient interface { AddManager(ctx context.Context, guid string, username string) error } -type OrganizationClientBuilder func(string, string, string, string) (OrganizationClient, error) +type OrganizationClientBuilder func(string, string, string, string, config.Config) (OrganizationClient, error) //counterfeiter:generate . SpaceClient type SpaceClient interface { @@ -88,8 +88,8 @@ type SpaceClient interface { GetBinding(ctx context.Context, bindingOpts map[string]string) (*Binding, error) CreateBinding(ctx context.Context, name string, serviceInstanceGuid string, parameters map[string]interface{}, owner string, generation int64) error - UpdateBinding(ctx context.Context, guid string, generation int64, parameters map[string]interface{}) error - DeleteBinding(ctx context.Context, guid string) error + UpdateBinding(ctx context.Context, guid string, owner string, generation int64, parameters map[string]interface{}) error + DeleteBinding(ctx context.Context, guid string, owner string) error FindServicePlan(ctx context.Context, serviceOfferingName string, servicePlanName string, spaceGuid string) (string, error) } diff --git a/internal/facade/facadefakes/fake_space_client.go b/internal/facade/facadefakes/fake_space_client.go index 68c2159..57816e4 100644 --- a/internal/facade/facadefakes/fake_space_client.go +++ b/internal/facade/facadefakes/fake_space_client.go @@ -46,11 +46,12 @@ type FakeSpaceClient struct { createInstanceReturnsOnCall map[int]struct { result1 error } - DeleteBindingStub func(context.Context, string) error + DeleteBindingStub func(context.Context, string, string) error deleteBindingMutex sync.RWMutex deleteBindingArgsForCall []struct { arg1 context.Context arg2 string + arg3 string } deleteBindingReturns struct { result1 error @@ -115,13 +116,14 @@ type FakeSpaceClient struct { result1 *facade.Instance result2 error } - UpdateBindingStub func(context.Context, string, int64, map[string]interface{}) error + UpdateBindingStub func(context.Context, string, string, int64, map[string]interface{}) error updateBindingMutex sync.RWMutex updateBindingArgsForCall []struct { arg1 context.Context arg2 string - arg3 int64 - arg4 map[string]interface{} + arg3 string + arg4 int64 + arg5 map[string]interface{} } updateBindingReturns struct { result1 error @@ -289,19 +291,20 @@ func (fake *FakeSpaceClient) CreateInstanceReturnsOnCall(i int, result1 error) { }{result1} } -func (fake *FakeSpaceClient) DeleteBinding(arg1 context.Context, arg2 string) error { +func (fake *FakeSpaceClient) DeleteBinding(arg1 context.Context, arg2 string, arg3 string) error { fake.deleteBindingMutex.Lock() ret, specificReturn := fake.deleteBindingReturnsOnCall[len(fake.deleteBindingArgsForCall)] fake.deleteBindingArgsForCall = append(fake.deleteBindingArgsForCall, struct { arg1 context.Context arg2 string - }{arg1, arg2}) + arg3 string + }{arg1, arg2, arg3}) stub := fake.DeleteBindingStub fakeReturns := fake.deleteBindingReturns - fake.recordInvocation("DeleteBinding", []interface{}{arg1, arg2}) + fake.recordInvocation("DeleteBinding", []interface{}{arg1, arg2, arg3}) fake.deleteBindingMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1 @@ -315,17 +318,17 @@ func (fake *FakeSpaceClient) DeleteBindingCallCount() int { return len(fake.deleteBindingArgsForCall) } -func (fake *FakeSpaceClient) DeleteBindingCalls(stub func(context.Context, string) error) { +func (fake *FakeSpaceClient) DeleteBindingCalls(stub func(context.Context, string, string) error) { fake.deleteBindingMutex.Lock() defer fake.deleteBindingMutex.Unlock() fake.DeleteBindingStub = stub } -func (fake *FakeSpaceClient) DeleteBindingArgsForCall(i int) (context.Context, string) { +func (fake *FakeSpaceClient) DeleteBindingArgsForCall(i int) (context.Context, string, string) { fake.deleteBindingMutex.RLock() defer fake.deleteBindingMutex.RUnlock() argsForCall := fake.deleteBindingArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *FakeSpaceClient) DeleteBindingReturns(result1 error) { @@ -611,21 +614,22 @@ func (fake *FakeSpaceClient) GetInstanceReturnsOnCall(i int, result1 *facade.Ins }{result1, result2} } -func (fake *FakeSpaceClient) UpdateBinding(arg1 context.Context, arg2 string, arg3 int64, arg4 map[string]interface{}) error { +func (fake *FakeSpaceClient) UpdateBinding(arg1 context.Context, arg2 string, arg3 string, arg4 int64, arg5 map[string]interface{}) error { fake.updateBindingMutex.Lock() ret, specificReturn := fake.updateBindingReturnsOnCall[len(fake.updateBindingArgsForCall)] fake.updateBindingArgsForCall = append(fake.updateBindingArgsForCall, struct { arg1 context.Context arg2 string - arg3 int64 - arg4 map[string]interface{} - }{arg1, arg2, arg3, arg4}) + arg3 string + arg4 int64 + arg5 map[string]interface{} + }{arg1, arg2, arg3, arg4, arg5}) stub := fake.UpdateBindingStub fakeReturns := fake.updateBindingReturns - fake.recordInvocation("UpdateBinding", []interface{}{arg1, arg2, arg3, arg4}) + fake.recordInvocation("UpdateBinding", []interface{}{arg1, arg2, arg3, arg4, arg5}) fake.updateBindingMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3, arg4) + return stub(arg1, arg2, arg3, arg4, arg5) } if specificReturn { return ret.result1 @@ -639,17 +643,17 @@ func (fake *FakeSpaceClient) UpdateBindingCallCount() int { return len(fake.updateBindingArgsForCall) } -func (fake *FakeSpaceClient) UpdateBindingCalls(stub func(context.Context, string, int64, map[string]interface{}) error) { +func (fake *FakeSpaceClient) UpdateBindingCalls(stub func(context.Context, string, string, int64, map[string]interface{}) error) { fake.updateBindingMutex.Lock() defer fake.updateBindingMutex.Unlock() fake.UpdateBindingStub = stub } -func (fake *FakeSpaceClient) UpdateBindingArgsForCall(i int) (context.Context, string, int64, map[string]interface{}) { +func (fake *FakeSpaceClient) UpdateBindingArgsForCall(i int) (context.Context, string, string, int64, map[string]interface{}) { fake.updateBindingMutex.RLock() defer fake.updateBindingMutex.RUnlock() argsForCall := fake.updateBindingArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 } func (fake *FakeSpaceClient) UpdateBindingReturns(result1 error) { diff --git a/main.go b/main.go index ad58e8b..ea7038f 100644 --- a/main.go +++ b/main.go @@ -145,6 +145,7 @@ func main() { ClusterResourceNamespace: clusterResourceNamespace, ClientBuilder: cf.NewOrganizationClient, HealthCheckerBuilder: cf.NewSpaceHealthChecker, + Config: cfg, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Space") os.Exit(1) @@ -156,6 +157,7 @@ func main() { ClusterResourceNamespace: clusterResourceNamespace, ClientBuilder: cf.NewOrganizationClient, HealthCheckerBuilder: cf.NewSpaceHealthChecker, + Config: cfg, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ClusterSpace") os.Exit(1) From 353411518a31608b7c97f57b2b2d39485cfa4981 Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Tue, 10 Sep 2024 21:00:11 +0530 Subject: [PATCH 34/63] interface to manage populate cache for all resources --- internal/cf/client.go | 128 +++++++++++++++++++---------------- internal/cf/client_test.go | 14 ++-- internal/cf/resourcecache.go | 48 ++++++------- internal/cf/space.go | 4 ++ 4 files changed, 101 insertions(+), 93 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index b2624bf..4bb12e4 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -61,16 +61,22 @@ var ( clientCache = make(map[clientIdentifier]*clientCacheEntry) cfResourceCache *resourceCache refreshServiceInstanceResourceCacheMutex = &sync.Mutex{} - cfResourceCacheMutex = &sync.Mutex{} // Add this line refreshSpaceResourceCacheMutex = &sync.Mutex{} refreshServiceBindingResourceCacheMutex = &sync.Mutex{} ) +var ( + cacheInstance *resourceCache + cacheInstanceOnce sync.Once +) + func initAndConfigureResourceCache(config config.Config) *resourceCache { - cache := initResourceCache() - cache.setResourceCacheEnabled(config.IsResourceCacheEnabled) - cache.setCacheTimeOut(config.CacheTimeOut) - return cache + cacheInstanceOnce.Do(func() { + cacheInstance = initResourceCache() + cacheInstance.setResourceCacheEnabled(config.IsResourceCacheEnabled) + cacheInstance.setCacheTimeOut(config.CacheTimeOut) + }) + return cacheInstance } func newOrganizationClient(organizationName string, url string, username string, password string) (*organizationClient, error) { @@ -169,7 +175,7 @@ func NewOrganizationClient(organizationName string, url string, username string, if config.IsResourceCacheEnabled && client.resourceCache == nil { client.resourceCache = initAndConfigureResourceCache(config) - populateResourceCache[*organizationClient](client, "spaces") + populateResourceCache(client, spaces) cfResourceCache = client.resourceCache } @@ -207,8 +213,8 @@ func NewSpaceClient(spaceGuid string, url string, username string, password stri if config.IsResourceCacheEnabled && client.resourceCache == nil { client.resourceCache = initAndConfigureResourceCache(config) - populateResourceCache[*spaceClient](client, "serviceInstances") - populateResourceCache[*spaceClient](client, "serviceBindings") + populateResourceCache(client, serviceInstances) + populateResourceCache(client, serviceBindings) cfResourceCache = client.resourceCache } @@ -248,36 +254,70 @@ func NewSpaceHealthChecker(spaceGuid string, url string, username string, passwo return client, err } -type ResourceClient[T any] interface { +type ResourceServicesClient[T any] interface { populateServiceInstances(ctx context.Context) error populateServiceBindings(ctx context.Context) error + manageResourceCache +} + +type ResourceSpaceClient[T any] interface { populateSpaces(ctx context.Context) error - resetCache(resourceType string) - getResourceCache() *resourceCache - setResourceCache(cache *resourceCache) + manageResourceCache +} + +type manageResourceCache interface { + resetCache(resourceType cacheResourceType) } // populateResourceCache populates the resource cache by fetching all resources matching // the owner label key from the Cloud Foundry API and storing them in an in-memory cache. // This function ensures that the cache is refreshed if it is expired. // It uses concurrency to initialize and cache resources efficiently. -func populateResourceCache[T any](c ResourceClient[T], resourceType string) { - cfResourceCacheMutex.Lock() - defer cfResourceCacheMutex.Unlock() +// func populateResourceCache[T any](c ResourceClient[T], resourceType string) { +// cfResourceCacheMutex.Lock() +// defer cfResourceCacheMutex.Unlock() +// ctx := context.Background() +// var err error + +// switch resourceType { +// case "serviceInstances": +// err = c.populateServiceInstances(ctx) +// case "spaces": +// err = c.populateSpaces(ctx) +// case "serviceBindings": +// err = c.populateServiceBindings(ctx) +// default: +// //TODO: populate for all resource types?? +// log.Printf("Unknown resource type: %s", resourceType) +// return +// } + +// if err != nil { +// // reset the cache to nil in case of error +// log.Printf("Error populating %s: %s", resourceType, err) +// c.resetCache(resourceType) +// return +// } +// c.setResourceCache(c.getResourceCache()) +// } + +func populateResourceCache[T manageResourceCache](c T, resourceType cacheResourceType) { ctx := context.Background() var err error switch resourceType { - case "serviceInstances": - err = c.populateServiceInstances(ctx) - case "spaces": - err = c.populateSpaces(ctx) - case "serviceBindings": - err = c.populateServiceBindings(ctx) - default: - //TODO: populate for all resource types?? - log.Printf("Unknown resource type: %s", resourceType) - return + case serviceInstances: + if client, ok := any(c).(ResourceServicesClient[T]); ok { + err = client.populateServiceInstances(ctx) + } + case spaces: + if client, ok := any(c).(ResourceSpaceClient[T]); ok { + err = client.populateSpaces(ctx) + } + case serviceBindings: + if client, ok := any(c).(ResourceServicesClient[T]); ok { + err = client.populateServiceBindings(ctx) + } } if err != nil { @@ -286,7 +326,6 @@ func populateResourceCache[T any](c ResourceClient[T], resourceType string) { c.resetCache(resourceType) return } - c.setResourceCache(c.getResourceCache()) } func (c *spaceClient) populateServiceInstances(ctx context.Context) error { @@ -354,12 +393,6 @@ func (c *spaceClient) populateServiceBindings(ctx context.Context) error { return nil } -func (c *spaceClient) populateSpaces(ctx context.Context) error { - return nil -} - -// populateSpaces populates the space cache by fetching all spaces matching the owner label key from the Cloud Foundry API -// and storing them in an in-memory cache having GUID as key. This function ensures that the cache is refreshed if it is expired. func (c *organizationClient) populateSpaces(ctx context.Context) error { refreshSpaceResourceCacheMutex.Lock() defer refreshSpaceResourceCacheMutex.Unlock() @@ -379,7 +412,7 @@ func (c *organizationClient) populateSpaces(ctx context.Context) error { go func(cfSpace *cfresource.Space) { defer waitGroup.Done() if binding, err := InitSpace(cfSpace, ""); err == nil { - c.resourceCache.addSpaceInCache(cfSpace.GUID, binding) + c.resourceCache.addSpaceInCache(*cfSpace.Metadata.Labels[labelOwner], binding) } else { log.Printf("Error initializing space: %s", err) } @@ -392,37 +425,12 @@ func (c *organizationClient) populateSpaces(ctx context.Context) error { return nil } -func (c *spaceClient) getResourceCache() *resourceCache { - return c.resourceCache -} - -func (c *spaceClient) setResourceCache(cache *resourceCache) { - c.resourceCache = cache -} - -func (c *spaceClient) resetCache(resourceType string) { +func (c *spaceClient) resetCache(resourceType cacheResourceType) { // Implementation for resetting the cache c.resourceCache.resetCache(resourceType) } -func (c *organizationClient) getResourceCache() *resourceCache { - return c.resourceCache -} - -func (c *organizationClient) setResourceCache(cache *resourceCache) { - c.resourceCache = cache -} - -func (c *organizationClient) resetCache(resourceType string) { +func (c *organizationClient) resetCache(resourceType cacheResourceType) { // Implementation for resetting the cache c.resourceCache.resetCache(resourceType) } - -func (c *organizationClient) populateServiceInstances(ctx context.Context) error { - return nil - -} - -func (c *organizationClient) populateServiceBindings(ctx context.Context) error { - return nil -} diff --git a/internal/cf/client_test.go b/internal/cf/client_test.go index d74d157..216338d 100644 --- a/internal/cf/client_test.go +++ b/internal/cf/client_test.go @@ -158,7 +158,7 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should create OrgClient", func() { - NewOrganizationClient(OrgName, url, Username, Password) + NewOrganizationClient(OrgName, url, Username, Password, *clientConfig) // Discover UAA endpoint Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) @@ -168,7 +168,7 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should be able to query some org", func() { - orgClient, err := NewOrganizationClient(OrgName, url, Username, Password) + orgClient, err := NewOrganizationClient(OrgName, url, Username, Password, *clientConfig) Expect(err).To(BeNil()) orgClient.GetSpace(ctx, Owner) @@ -200,11 +200,11 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should be able to query some org twice", func() { - orgClient, err := NewOrganizationClient(OrgName, url, Username, Password) + orgClient, err := NewOrganizationClient(OrgName, url, Username, Password, *clientConfig) Expect(err).To(BeNil()) orgClient.GetSpace(ctx, Owner) - orgClient, err = NewOrganizationClient(OrgName, url, Username, Password) + orgClient, err = NewOrganizationClient(OrgName, url, Username, Password, *clientConfig) Expect(err).To(BeNil()) orgClient.GetSpace(ctx, Owner) @@ -227,7 +227,7 @@ var _ = Describe("CF Client tests", Ordered, func() { It("should be able to query two different orgs", func() { // test org 1 - orgClient1, err1 := NewOrganizationClient(OrgName, url, Username, Password) + orgClient1, err1 := NewOrganizationClient(OrgName, url, Username, Password, *clientConfig) Expect(err1).To(BeNil()) orgClient1.GetSpace(ctx, Owner) // Discover UAA endpoint @@ -241,7 +241,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(Owner)) // test org 2 - orgClient2, err2 := NewOrganizationClient(OrgName2, url, Username, Password) + orgClient2, err2 := NewOrganizationClient(OrgName2, url, Username, Password, *clientConfig) Expect(err2).To(BeNil()) orgClient2.GetSpace(ctx, Owner2) // no discovery of UAA endpoint or oAuth token here due to caching @@ -374,7 +374,7 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should register prometheus metrics for OrgClient", func() { - orgClient, err := NewOrganizationClient(OrgName, url, Username, Password) + orgClient, err := NewOrganizationClient(OrgName, url, Username, Password, *clientConfig) Expect(err).To(BeNil()) Expect(orgClient).ToNot(BeNil()) diff --git a/internal/cf/resourcecache.go b/internal/cf/resourcecache.go index 63bd5e4..80fe56a 100644 --- a/internal/cf/resourcecache.go +++ b/internal/cf/resourcecache.go @@ -31,22 +31,24 @@ type resourceCache struct { isResourceCacheEnabled bool } -var ( - cacheInstance *resourceCache - cacheInstanceOnce sync.Once +type cacheResourceType string + +const ( + serviceInstances cacheResourceType = "serviceInstances" + spaces cacheResourceType = "spaces" + serviceBindings cacheResourceType = "serviceBindings" ) // InitResourcesCache initializes a new cache func initResourceCache() *resourceCache { - cacheInstanceOnce.Do(func() { - cacheInstance = &resourceCache{ - spaces: make(map[string]*facade.Space), - instances: make(map[string]*facade.Instance), - bindings: make(map[string]*facade.Binding), - cacheTimeOut: 5 * time.Minute, - } - }) - return cacheInstance + + cache := &resourceCache{ + spaces: make(map[string]*facade.Space), + instances: make(map[string]*facade.Instance), + bindings: make(map[string]*facade.Binding), + } + + return cache } // setCacheTimeOut sets the timeout used for expiration of the cache @@ -66,14 +68,14 @@ func (c *resourceCache) setCacheTimeOut(timeOut string) { // expirationTime := c.lastCacheTime.Add(c.cacheTimeOut) // return time.Now().After(expirationTime) // } -func (c *resourceCache) isCacheExpired(resourceType string) bool { +func (c *resourceCache) isCacheExpired(resourceType cacheResourceType) bool { var expirationTime time.Time switch resourceType { - case "serviceInstances": + case serviceInstances: expirationTime = c.lastServiceInstanceCacheTime.Add(c.cacheTimeOut) - case "spaces": + case spaces: expirationTime = c.lastSpaceCacheTime.Add(c.cacheTimeOut) - case "serviceBindings": + case serviceBindings: expirationTime = c.lastServiceBindingCacheTime.Add(c.cacheTimeOut) default: return true @@ -96,10 +98,6 @@ func (c *resourceCache) setLastCacheTime(resourceType string) { c.lastSpaceCacheTime = now case "serviceBindings": c.lastServiceBindingCacheTime = now - default: - c.lastServiceInstanceCacheTime = now - c.lastSpaceCacheTime = now - c.lastServiceBindingCacheTime = now } log.Printf("Last cache time for %s: %v\n", resourceType, now) } @@ -272,19 +270,17 @@ func (c *resourceCache) getCachedSpaces() map[string]*facade.Space { // } // reset cache of a specific resource type and last cache time -func (c *resourceCache) resetCache(resourceType string) { +func (c *resourceCache) resetCache(resourceType cacheResourceType) { switch resourceType { - case "serviceInstances": + case serviceInstances: c.instances = make(map[string]*facade.Instance) c.lastServiceInstanceCacheTime = time.Now() - case "spaces": + case spaces: c.spaces = make(map[string]*facade.Space) c.lastSpaceCacheTime = time.Now() - case "serviceBindings": + case serviceBindings: c.bindings = make(map[string]*facade.Binding) c.lastServiceBindingCacheTime = time.Now() - default: - log.Printf("Unknown resource type: %s", resourceType) } } diff --git a/internal/cf/space.go b/internal/cf/space.go index 31b17cd..b9dfbc3 100644 --- a/internal/cf/space.go +++ b/internal/cf/space.go @@ -147,6 +147,10 @@ func InitSpace(space *cfresource.Space, owner string) (*facade.Space, error) { return nil, err } + if owner == "" { + owner = *space.Metadata.Labels[labelOwner] + } + return &facade.Space{ Guid: guid, Name: name, From 03806e9fae8c45fc0423d0678e81a31aeff26b39 Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Wed, 11 Sep 2024 13:27:16 +0530 Subject: [PATCH 35/63] add logging for testing --- internal/cf/binding.go | 3 + internal/cf/client.go | 59 ++-- internal/cf/client_test.go | 251 +++++++++--------- internal/cf/instance.go | 3 + internal/cf/resourcecache.go | 26 +- internal/cf/space.go | 3 + .../controllers/servicebinding_controller.go | 2 +- .../controllers/serviceinstance_controller.go | 2 +- internal/controllers/space_controller.go | 2 +- internal/controllers/suite_test.go | 4 +- internal/facade/client.go | 4 +- 11 files changed, 180 insertions(+), 179 deletions(-) diff --git a/internal/cf/binding.go b/internal/cf/binding.go index 1655427..a73b284 100644 --- a/internal/cf/binding.go +++ b/internal/cf/binding.go @@ -65,6 +65,9 @@ func (c *spaceClient) GetBinding(ctx context.Context, bindingOpts map[string]str } } + // TODO :remove After internal review + fmt.Println("get binding from CF only in case of cache is of or binding not found in cache or creation case") + // Attempt to retrieve binding from Cloud Foundry var filterOpts bindingFilter if bindingOpts["name"] != "" { diff --git a/internal/cf/client.go b/internal/cf/client.go index 4bb12e4..07c9037 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -70,9 +70,11 @@ var ( cacheInstanceOnce sync.Once ) -func initAndConfigureResourceCache(config config.Config) *resourceCache { +func initAndConfigureResourceCache(config *config.Config) *resourceCache { cacheInstanceOnce.Do(func() { cacheInstance = initResourceCache() + //TODO:Remove later:print cache initialized + fmt.Printf("Resource cache initialized") cacheInstance.setResourceCacheEnabled(config.IsResourceCacheEnabled) cacheInstance.setCacheTimeOut(config.CacheTimeOut) }) @@ -107,7 +109,6 @@ func newOrganizationClient(organizationName string, url string, username string, if err != nil { return nil, err } - // TODO:Populate resource cache for ORg client return &organizationClient{organizationName: organizationName, client: *c}, nil } @@ -144,7 +145,7 @@ func newSpaceClient(spaceGuid string, url string, username string, password stri return &spaceClient{spaceGuid: spaceGuid, client: *c}, nil } -func NewOrganizationClient(organizationName string, url string, username string, password string, config config.Config) (facade.OrganizationClient, error) { +func NewOrganizationClient(organizationName string, url string, username string, password string, config *config.Config) (facade.OrganizationClient, error) { clientCacheMutex.Lock() defer clientCacheMutex.Unlock() @@ -182,7 +183,7 @@ func NewOrganizationClient(organizationName string, url string, username string, return client, err } -func NewSpaceClient(spaceGuid string, url string, username string, password string, config config.Config) (facade.SpaceClient, error) { +func NewSpaceClient(spaceGuid string, url string, username string, password string, config *config.Config) (facade.SpaceClient, error) { clientCacheMutex.Lock() defer clientCacheMutex.Unlock() @@ -269,38 +270,6 @@ type manageResourceCache interface { resetCache(resourceType cacheResourceType) } -// populateResourceCache populates the resource cache by fetching all resources matching -// the owner label key from the Cloud Foundry API and storing them in an in-memory cache. -// This function ensures that the cache is refreshed if it is expired. -// It uses concurrency to initialize and cache resources efficiently. -// func populateResourceCache[T any](c ResourceClient[T], resourceType string) { -// cfResourceCacheMutex.Lock() -// defer cfResourceCacheMutex.Unlock() -// ctx := context.Background() -// var err error - -// switch resourceType { -// case "serviceInstances": -// err = c.populateServiceInstances(ctx) -// case "spaces": -// err = c.populateSpaces(ctx) -// case "serviceBindings": -// err = c.populateServiceBindings(ctx) -// default: -// //TODO: populate for all resource types?? -// log.Printf("Unknown resource type: %s", resourceType) -// return -// } - -// if err != nil { -// // reset the cache to nil in case of error -// log.Printf("Error populating %s: %s", resourceType, err) -// c.resetCache(resourceType) -// return -// } -// c.setResourceCache(c.getResourceCache()) -// } - func populateResourceCache[T manageResourceCache](c T, resourceType cacheResourceType) { ctx := context.Background() var err error @@ -309,14 +278,20 @@ func populateResourceCache[T manageResourceCache](c T, resourceType cacheResourc case serviceInstances: if client, ok := any(c).(ResourceServicesClient[T]); ok { err = client.populateServiceInstances(ctx) + //TODO:remove later + fmt.Println("populate service instance finished") } case spaces: if client, ok := any(c).(ResourceSpaceClient[T]); ok { err = client.populateSpaces(ctx) + //TODO:remove later + fmt.Println("populate space finished") } case serviceBindings: if client, ok := any(c).(ResourceServicesClient[T]); ok { err = client.populateServiceBindings(ctx) + //TODO:remove later + fmt.Println("populate service binding finished") } } @@ -332,7 +307,7 @@ func (c *spaceClient) populateServiceInstances(ctx context.Context) error { refreshServiceInstanceResourceCacheMutex.Lock() defer refreshServiceInstanceResourceCacheMutex.Unlock() - if c.resourceCache.isCacheExpired("serviceInstances") { + if c.resourceCache.isCacheExpired(serviceInstances) { instanceOptions := cfclient.NewServiceInstanceListOptions() instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) @@ -354,7 +329,7 @@ func (c *spaceClient) populateServiceInstances(ctx context.Context) error { }(cfInstance) } waitGroup.Wait() - c.resourceCache.setLastCacheTime("serviceInstances") + c.resourceCache.setLastCacheTime(serviceInstances) } return nil @@ -365,7 +340,7 @@ func (c *spaceClient) populateServiceBindings(ctx context.Context) error { refreshServiceBindingResourceCacheMutex.Lock() defer refreshServiceBindingResourceCacheMutex.Unlock() - if c.resourceCache.isCacheExpired("serviceBindings") { + if c.resourceCache.isCacheExpired(serviceBindings) { bindingOptions := cfclient.NewServiceCredentialBindingListOptions() bindingOptions.ListOptions.LabelSelector.EqualTo(labelOwner) @@ -387,7 +362,7 @@ func (c *spaceClient) populateServiceBindings(ctx context.Context) error { }(cfBinding) } waitGroup.Wait() - c.resourceCache.setLastCacheTime("serviceBindings") + c.resourceCache.setLastCacheTime(serviceBindings) } return nil @@ -397,7 +372,7 @@ func (c *organizationClient) populateSpaces(ctx context.Context) error { refreshSpaceResourceCacheMutex.Lock() defer refreshSpaceResourceCacheMutex.Unlock() - if c.resourceCache.isCacheExpired("spaces") { + if c.resourceCache.isCacheExpired(spaces) { spaceOptions := cfclient.NewSpaceListOptions() spaceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) @@ -419,7 +394,7 @@ func (c *organizationClient) populateSpaces(ctx context.Context) error { }(cfSpace) } waitGroup.Wait() - c.resourceCache.setLastCacheTime("spaces") + c.resourceCache.setLastCacheTime(spaces) } return nil diff --git a/internal/cf/client_test.go b/internal/cf/client_test.go index 216338d..ff045a6 100644 --- a/internal/cf/client_test.go +++ b/internal/cf/client_test.go @@ -6,7 +6,6 @@ package cf import ( "context" - "net/http" "testing" "time" @@ -158,7 +157,7 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should create OrgClient", func() { - NewOrganizationClient(OrgName, url, Username, Password, *clientConfig) + NewOrganizationClient(OrgName, url, Username, Password, clientConfig) // Discover UAA endpoint Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) @@ -168,7 +167,7 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should be able to query some org", func() { - orgClient, err := NewOrganizationClient(OrgName, url, Username, Password, *clientConfig) + orgClient, err := NewOrganizationClient(OrgName, url, Username, Password, clientConfig) Expect(err).To(BeNil()) orgClient.GetSpace(ctx, Owner) @@ -200,11 +199,11 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should be able to query some org twice", func() { - orgClient, err := NewOrganizationClient(OrgName, url, Username, Password, *clientConfig) + orgClient, err := NewOrganizationClient(OrgName, url, Username, Password, clientConfig) Expect(err).To(BeNil()) orgClient.GetSpace(ctx, Owner) - orgClient, err = NewOrganizationClient(OrgName, url, Username, Password, *clientConfig) + orgClient, err = NewOrganizationClient(OrgName, url, Username, Password, clientConfig) Expect(err).To(BeNil()) orgClient.GetSpace(ctx, Owner) @@ -227,7 +226,7 @@ var _ = Describe("CF Client tests", Ordered, func() { It("should be able to query two different orgs", func() { // test org 1 - orgClient1, err1 := NewOrganizationClient(OrgName, url, Username, Password, *clientConfig) + orgClient1, err1 := NewOrganizationClient(OrgName, url, Username, Password, clientConfig) Expect(err1).To(BeNil()) orgClient1.GetSpace(ctx, Owner) // Discover UAA endpoint @@ -241,7 +240,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(Owner)) // test org 2 - orgClient2, err2 := NewOrganizationClient(OrgName2, url, Username, Password, *clientConfig) + orgClient2, err2 := NewOrganizationClient(OrgName2, url, Username, Password, clientConfig) Expect(err2).To(BeNil()) orgClient2.GetSpace(ctx, Owner2) // no discovery of UAA endpoint or oAuth token here due to caching @@ -281,7 +280,7 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should create SpaceClient", func() { - NewSpaceClient(OrgName, url, Username, Password, *clientConfig) + NewSpaceClient(OrgName, url, Username, Password, clientConfig) // Discover UAA endpoint Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) @@ -291,7 +290,7 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should be able to query some space", func() { - spaceClient, err := NewSpaceClient(OrgName, url, Username, Password, *clientConfig) + spaceClient, err := NewSpaceClient(OrgName, url, Username, Password, clientConfig) Expect(err).To(BeNil()) spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) @@ -323,11 +322,11 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should be able to query some space twice", func() { - spaceClient, err := NewSpaceClient(OrgName, url, Username, Password, *clientConfig) + spaceClient, err := NewSpaceClient(OrgName, url, Username, Password, clientConfig) Expect(err).To(BeNil()) spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - spaceClient, err = NewSpaceClient(OrgName, url, Username, Password, *clientConfig) + spaceClient, err = NewSpaceClient(OrgName, url, Username, Password, clientConfig) Expect(err).To(BeNil()) spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) @@ -350,7 +349,7 @@ var _ = Describe("CF Client tests", Ordered, func() { It("should be able to query two different spaces", func() { // test space 1 - spaceClient1, err1 := NewSpaceClient(SpaceName, url, Username, Password, *clientConfig) + spaceClient1, err1 := NewSpaceClient(SpaceName, url, Username, Password, clientConfig) Expect(err1).To(BeNil()) spaceClient1.GetInstance(ctx, map[string]string{"owner": Owner}) // Discover UAA endpoint @@ -364,7 +363,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(Owner)) // test space 2 - spaceClient2, err2 := NewSpaceClient(SpaceName2, url, Username, Password, *clientConfig) + spaceClient2, err2 := NewSpaceClient(SpaceName2, url, Username, Password, clientConfig) Expect(err2).To(BeNil()) spaceClient2.GetInstance(ctx, map[string]string{"owner": Owner2}) // no discovery of UAA endpoint or oAuth token here due to caching @@ -374,7 +373,7 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should register prometheus metrics for OrgClient", func() { - orgClient, err := NewOrganizationClient(OrgName, url, Username, Password, *clientConfig) + orgClient, err := NewOrganizationClient(OrgName, url, Username, Password, clientConfig) Expect(err).To(BeNil()) Expect(orgClient).ToNot(BeNil()) @@ -393,7 +392,7 @@ var _ = Describe("CF Client tests", Ordered, func() { }) It("should register prometheus metrics for SpaceClient", func() { - spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *clientConfig) + spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, clientConfig) Expect(err).To(BeNil()) Expect(spaceClient).ToNot(BeNil()) @@ -414,116 +413,116 @@ var _ = Describe("CF Client tests", Ordered, func() { // prometheus.WriteToTextfile("metrics.txt", metrics.Registry) }) - Context("Populate resource cache tests", func() { - - It("should initialize resource cache after start and on cache expiry", func() { - // Enable resource cache in config - clientConfig.IsResourceCacheEnabled = true - clientConfig.CacheTimeOut = "5s" // short duration for fast test - - // Create client - spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *clientConfig) - Expect(err).To(BeNil()) - Expect(spaceClient).ToNot(BeNil()) - - // Verify resource cache is populated during client creation - Expect(server.ReceivedRequests()).To(HaveLen(3)) - // - Discover UAA endpoint - Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) - // - Get new oAuth token - Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) - Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) - // - Populate cache - Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(serviceInstancesURI)) - - // Make a request and verify that cache is used and no additional requests expected - spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - Expect(server.ReceivedRequests()).To(HaveLen(3)) // still same as above - - // Make another request after cache expired and verify that cache is repopulated - time.Sleep(10 * time.Second) - spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - Expect(server.ReceivedRequests()).To(HaveLen(4)) // one more request to repopulate cache - Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring(serviceInstancesURI)) - Expect(server.ReceivedRequests()[3].RequestURI).NotTo(ContainSubstring(Owner)) - }) - - It("should not initialize resource cache if disabled in config", func() { - // Disable resource cache in config - clientConfig.IsResourceCacheEnabled = false - - // Create client - spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *clientConfig) - Expect(err).To(BeNil()) - Expect(spaceClient).ToNot(BeNil()) - - // Verify resource cache is NOT populated during client creation - // - Discover UAA endpoint - Expect(server.ReceivedRequests()).To(HaveLen(1)) - Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) - - // Make request and verify cache is NOT used - spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - Expect(server.ReceivedRequests()).To(HaveLen(3)) // one more request to get instance - // - Get new oAuth token - Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) - Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) - // - Get instance - Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(Owner)) - }) - - It("Delete instance from cache", func() { - // Enable resource cache in config - clientConfig.IsResourceCacheEnabled = true - clientConfig.CacheTimeOut = "5m" - - // Route to handler for DELETE request - server.RouteToHandler("DELETE", - serviceInstancesURI+"/test-instance-guid-1", - ghttp.CombineHandlers(ghttp.RespondWith(http.StatusAccepted, nil))) - - // Create client - spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *clientConfig) - Expect(err).To(BeNil()) - Expect(spaceClient).ToNot(BeNil()) - - // Verify resource cache is populated during client creation - Expect(server.ReceivedRequests()).To(HaveLen(3)) - // - Discover UAA endpoint - Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) - // - Get new oAuth token - Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) - Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) - // - Populate cache - Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(serviceInstancesURI)) - Expect(server.ReceivedRequests()[2].RequestURI).NotTo(ContainSubstring(Owner)) - - // Make a request and verify that cache is used and no additional requests expected - spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - Expect(server.ReceivedRequests()).To(HaveLen(3)) // still same as above - - // Delete instance from cache - err = spaceClient.DeleteInstance(ctx, "test-instance-guid-1", Owner) - Expect(err).To(BeNil()) - Expect(server.ReceivedRequests()).To(HaveLen(4)) - // - Delete instance from cache - Expect(server.ReceivedRequests()[3].Method).To(Equal("DELETE")) - Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring("test-instance-guid-1")) - - // Get instance from cache should return empty - spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - Expect(server.ReceivedRequests()).To(HaveLen(5)) - // - get instance from cache - Expect(server.ReceivedRequests()[4].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[4].RequestURI).To(ContainSubstring(Owner)) - }) - }) + // Context("Populate resource cache tests", func() { + + // It("should initialize resource cache after start and on cache expiry", func() { + // // Enable resource cache in config + // clientConfig.IsResourceCacheEnabled = true + // clientConfig.CacheTimeOut = "5s" // short duration for fast test + + // // Create client + // spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *clientConfig) + // Expect(err).To(BeNil()) + // Expect(spaceClient).ToNot(BeNil()) + + // // Verify resource cache is populated during client creation + // Expect(server.ReceivedRequests()).To(HaveLen(3)) + // // - Discover UAA endpoint + // Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) + // Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) + // // - Get new oAuth token + // Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) + // Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) + // // - Populate cache + // Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) + // Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(serviceInstancesURI)) + + // // Make a request and verify that cache is used and no additional requests expected + // spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + // Expect(server.ReceivedRequests()).To(HaveLen(3)) // still same as above + + // // Make another request after cache expired and verify that cache is repopulated + // time.Sleep(10 * time.Second) + // spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + // Expect(server.ReceivedRequests()).To(HaveLen(4)) // one more request to repopulate cache + // Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) + // Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring(serviceInstancesURI)) + // Expect(server.ReceivedRequests()[3].RequestURI).NotTo(ContainSubstring(Owner)) + // }) + + // It("should not initialize resource cache if disabled in config", func() { + // // Disable resource cache in config + // clientConfig.IsResourceCacheEnabled = false + + // // Create client + // spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *clientConfig) + // Expect(err).To(BeNil()) + // Expect(spaceClient).ToNot(BeNil()) + + // // Verify resource cache is NOT populated during client creation + // // - Discover UAA endpoint + // Expect(server.ReceivedRequests()).To(HaveLen(1)) + // Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) + // Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) + + // // Make request and verify cache is NOT used + // spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + // Expect(server.ReceivedRequests()).To(HaveLen(3)) // one more request to get instance + // // - Get new oAuth token + // Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) + // Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) + // // - Get instance + // Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) + // Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(Owner)) + // }) + + // It("Delete instance from cache", func() { + // // Enable resource cache in config + // clientConfig.IsResourceCacheEnabled = true + // clientConfig.CacheTimeOut = "5m" + + // // Route to handler for DELETE request + // server.RouteToHandler("DELETE", + // serviceInstancesURI+"/test-instance-guid-1", + // ghttp.CombineHandlers(ghttp.RespondWith(http.StatusAccepted, nil))) + + // // Create client + // spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *clientConfig) + // Expect(err).To(BeNil()) + // Expect(spaceClient).ToNot(BeNil()) + + // // Verify resource cache is populated during client creation + // Expect(server.ReceivedRequests()).To(HaveLen(3)) + // // - Discover UAA endpoint + // Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) + // Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) + // // - Get new oAuth token + // Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) + // Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) + // // - Populate cache + // Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) + // Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(serviceInstancesURI)) + // Expect(server.ReceivedRequests()[2].RequestURI).NotTo(ContainSubstring(Owner)) + + // // Make a request and verify that cache is used and no additional requests expected + // spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + // Expect(server.ReceivedRequests()).To(HaveLen(3)) // still same as above + + // // Delete instance from cache + // err = spaceClient.DeleteInstance(ctx, "test-instance-guid-1", Owner) + // Expect(err).To(BeNil()) + // Expect(server.ReceivedRequests()).To(HaveLen(4)) + // // - Delete instance from cache + // Expect(server.ReceivedRequests()[3].Method).To(Equal("DELETE")) + // Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring("test-instance-guid-1")) + + // // Get instance from cache should return empty + // spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + // Expect(server.ReceivedRequests()).To(HaveLen(5)) + // // - get instance from cache + // Expect(server.ReceivedRequests()[4].Method).To(Equal("GET")) + // Expect(server.ReceivedRequests()[4].RequestURI).To(ContainSubstring(Owner)) + // }) + // }) }) }) diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 1a54bc3..80277d4 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -64,6 +64,9 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s } } + // TODO :remove After internal review + fmt.Println("get instance from CF only in case of cache is of or instance not found in cache or creation case") + // Attempt to retrieve instance from Cloud Foundry var filterOpts instanceFilter if instanceOpts["name"] != "" { diff --git a/internal/cf/resourcecache.go b/internal/cf/resourcecache.go index 80fe56a..5c69033 100644 --- a/internal/cf/resourcecache.go +++ b/internal/cf/resourcecache.go @@ -89,17 +89,19 @@ func (c *resourceCache) isCacheExpired(resourceType cacheResourceType) bool { // log.Printf("Last cache time: %v\n", c.lastCacheTime) // } -func (c *resourceCache) setLastCacheTime(resourceType string) { +func (c *resourceCache) setLastCacheTime(resourceType cacheResourceType) { now := time.Now() switch resourceType { - case "serviceInstances": + case serviceInstances: c.lastServiceInstanceCacheTime = now - case "spaces": + case spaces: c.lastSpaceCacheTime = now - case "serviceBindings": + case serviceBindings: c.lastServiceBindingCacheTime = now } log.Printf("Last cache time for %s: %v\n", resourceType, now) + //TODO:remove later + fmt.Printf("Last cache time for %s: %v\n", resourceType, now) } // setResourceCacheEnabled enables or disables the resource cahce @@ -120,6 +122,8 @@ func (c *resourceCache) checkResourceCacheEnabled() bool { func (c *resourceCache) addInstanceInCache(key string, instance *facade.Instance) { c.mutex.Lock() defer c.mutex.Unlock() + // TODO :remove After internal review + fmt.Printf("Added the instance to Cache: %v", instance) c.instances[key] = instance } @@ -138,6 +142,8 @@ func (c *resourceCache) deleteInstanceFromCache(key string) { c.mutex.Lock() defer c.mutex.Unlock() delete(c.instances, key) + // TODO :remove After internal review + fmt.Printf("deleted the instance from Cache: %v", key) } @@ -180,6 +186,8 @@ func (c *resourceCache) addBindingInCache(key string, binding *facade.Binding) { c.mutex.Lock() defer c.mutex.Unlock() c.bindings[key] = binding + // TODO :remove After internal review + fmt.Printf("Added the binding to Cache: %v", binding) } // getBindingFromCache retrieves binding from the cache @@ -197,6 +205,8 @@ func (c *resourceCache) deleteBindingFromCache(key string) { c.mutex.Lock() defer c.mutex.Unlock() delete(c.bindings, key) + // TODO :remove After internal review + fmt.Printf("Added the binding to Cache: %v", key) } @@ -227,6 +237,8 @@ func (c *resourceCache) addSpaceInCache(key string, space *facade.Space) { c.mutex.Lock() defer c.mutex.Unlock() c.spaces[key] = space + // TODO :remove After internal review + fmt.Printf("Added the space to Cache: %v", space) } // GetSpaceFromCache retrieves a space from the cache @@ -234,6 +246,8 @@ func (c *resourceCache) getSpaceFromCache(key string) (*facade.Space, bool) { c.mutex.RLock() defer c.mutex.RUnlock() space, found := c.spaces[key] + // TODO :remove After internal review + fmt.Printf("Got the space from Cache: %v", space) return space, found } @@ -242,6 +256,8 @@ func (c *resourceCache) deleteSpaceFromCache(key string) { c.mutex.Lock() defer c.mutex.Unlock() delete(c.spaces, key) + // TODO :remove After internal review + fmt.Printf("Deleted the space from Cache: %v", key) } @@ -271,6 +287,8 @@ func (c *resourceCache) getCachedSpaces() map[string]*facade.Space { // reset cache of a specific resource type and last cache time func (c *resourceCache) resetCache(resourceType cacheResourceType) { + + fmt.Printf("reset requested for %v", resourceType) switch resourceType { case serviceInstances: c.instances = make(map[string]*facade.Instance) diff --git a/internal/cf/space.go b/internal/cf/space.go index b9dfbc3..8cd25c5 100644 --- a/internal/cf/space.go +++ b/internal/cf/space.go @@ -34,6 +34,9 @@ func (c *organizationClient) GetSpace(ctx context.Context, owner string) (*facad } } + // TODO :remove After internal review + fmt.Println("get space from CF only in case of cache is of or space not found in cache or creation case") + //Attempt to retrieve space from Cloud Foundry listOpts := cfclient.NewSpaceListOptions() listOpts.LabelSelector.EqualTo(labelPrefix + "/" + labelKeyOwner + "=" + owner) diff --git a/internal/controllers/servicebinding_controller.go b/internal/controllers/servicebinding_controller.go index c693f91..ee5630a 100644 --- a/internal/controllers/servicebinding_controller.go +++ b/internal/controllers/servicebinding_controller.go @@ -170,7 +170,7 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque // Build cloud foundry client var client facade.SpaceClient if spaceGuid != "" { - client, err = r.ClientBuilder(spaceGuid, string(spaceSecret.Data["url"]), string(spaceSecret.Data["username"]), string(spaceSecret.Data["password"]), *r.Config) + client, err = r.ClientBuilder(spaceGuid, string(spaceSecret.Data["url"]), string(spaceSecret.Data["username"]), string(spaceSecret.Data["password"]), r.Config) if err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to build the client from secret %s", spaceSecretName) } diff --git a/internal/controllers/serviceinstance_controller.go b/internal/controllers/serviceinstance_controller.go index 26b95c8..3aad34e 100644 --- a/internal/controllers/serviceinstance_controller.go +++ b/internal/controllers/serviceinstance_controller.go @@ -184,7 +184,7 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ // Build cloud foundry client var client facade.SpaceClient if spaceGuid != "" { - client, err = r.ClientBuilder(spaceGuid, string(spaceSecret.Data["url"]), string(spaceSecret.Data["username"]), string(spaceSecret.Data["password"]), *r.Config) + client, err = r.ClientBuilder(spaceGuid, string(spaceSecret.Data["url"]), string(spaceSecret.Data["username"]), string(spaceSecret.Data["password"]), r.Config) if err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to build the client from secret %s", spaceSecretName) } diff --git a/internal/controllers/space_controller.go b/internal/controllers/space_controller.go index 5aa672e..51b7652 100644 --- a/internal/controllers/space_controller.go +++ b/internal/controllers/space_controller.go @@ -149,7 +149,7 @@ func (r *SpaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resu password = string(secret.Data["password"]) } - client, err = r.ClientBuilder(spec.OrganizationName, url, username, password, *r.Config) + client, err = r.ClientBuilder(spec.OrganizationName, url, username, password, r.Config) if err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to build the client from secret %s", secretName) } diff --git a/internal/controllers/suite_test.go b/internal/controllers/suite_test.go index 9bdfc7f..63e07f1 100644 --- a/internal/controllers/suite_test.go +++ b/internal/controllers/suite_test.go @@ -208,7 +208,7 @@ func addControllers(k8sManager ctrl.Manager) { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), ClusterResourceNamespace: testK8sNamespace, - ClientBuilder: func(organizationName string, url string, username string, password string, config config.Config) (facade.OrganizationClient, error) { + ClientBuilder: func(organizationName string, url string, username string, password string, config *config.Config) (facade.OrganizationClient, error) { return fakeOrgClient, nil }, HealthCheckerBuilder: func(spaceGuid string, url string, username string, password string) (facade.SpaceHealthChecker, error) { @@ -222,7 +222,7 @@ func addControllers(k8sManager ctrl.Manager) { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), ClusterResourceNamespace: testK8sNamespace, - ClientBuilder: func(organizationName string, url string, username string, password string, config config.Config) (facade.SpaceClient, error) { + ClientBuilder: func(organizationName string, url string, username string, password string, config *config.Config) (facade.SpaceClient, error) { return fakeSpaceClient, nil }, Config: cfg, diff --git a/internal/facade/client.go b/internal/facade/client.go index 80c649c..38c2550 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -77,7 +77,7 @@ type OrganizationClient interface { AddManager(ctx context.Context, guid string, username string) error } -type OrganizationClientBuilder func(string, string, string, string, config.Config) (OrganizationClient, error) +type OrganizationClientBuilder func(string, string, string, string, *config.Config) (OrganizationClient, error) //counterfeiter:generate . SpaceClient type SpaceClient interface { @@ -94,4 +94,4 @@ type SpaceClient interface { FindServicePlan(ctx context.Context, serviceOfferingName string, servicePlanName string, spaceGuid string) (string, error) } -type SpaceClientBuilder func(string, string, string, string, config.Config) (SpaceClient, error) +type SpaceClientBuilder func(string, string, string, string, *config.Config) (SpaceClient, error) From cc67ad6d6fd77768c08c1a7107cd4924d7bf2895 Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Fri, 13 Sep 2024 00:03:00 +0530 Subject: [PATCH 36/63] client and unit tests for cache --- internal/cf/binding.go | 6 +- internal/cf/client.go | 6 + internal/cf/client_test.go | 381 +++++++++++++----- internal/cf/instance.go | 4 +- internal/cf/resourcecache.go | 87 ++-- internal/cf/resourcecache_test.go | 365 +++++++++++++++-- internal/cf/space.go | 27 +- internal/controllers/space_controller.go | 3 +- internal/facade/client.go | 4 +- .../facadefakes/fake_organization_client.go | 40 +- 10 files changed, 713 insertions(+), 210 deletions(-) diff --git a/internal/cf/binding.go b/internal/cf/binding.go index a73b284..9c9c836 100644 --- a/internal/cf/binding.go +++ b/internal/cf/binding.go @@ -50,10 +50,10 @@ func (bo *bindingFilterOwner) getListOptions() *cfclient.ServiceCredentialBindin func (c *spaceClient) GetBinding(ctx context.Context, bindingOpts map[string]string) (*facade.Binding, error) { if c.resourceCache.checkResourceCacheEnabled() { // Attempt to retrieve binding from cache - if c.resourceCache.isCacheExpired("serviceBindings") { + if c.resourceCache.isCacheExpired(serviceBindings) { //TODO: remove after internal review - fmt.Println("Cache is expired") - populateResourceCache[*spaceClient](c, "serviceBindings") + fmt.Println("Cache is expired for binding") + populateResourceCache[*spaceClient](c, serviceBindings) } if len(c.resourceCache.getCachedBindings()) != 0 { binding, bindingInCache := c.resourceCache.getBindingFromCache(bindingOpts["owner"]) diff --git a/internal/cf/client.go b/internal/cf/client.go index 07c9037..00d5a7c 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -312,6 +312,8 @@ func (c *spaceClient) populateServiceInstances(ctx context.Context) error { instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) cfInstances, err := c.client.ServiceInstances.ListAll(ctx, instanceOptions) + //TODO:remove later after review + fmt.Println("populate instance cache called") if err != nil { return err } @@ -345,6 +347,8 @@ func (c *spaceClient) populateServiceBindings(ctx context.Context) error { bindingOptions.ListOptions.LabelSelector.EqualTo(labelOwner) cfBindings, err := c.client.ServiceCredentialBindings.ListAll(ctx, bindingOptions) + //TODO:remove later after review + fmt.Println("populate service binding cache called") if err != nil { return err } @@ -376,6 +380,8 @@ func (c *organizationClient) populateSpaces(ctx context.Context) error { spaceOptions := cfclient.NewSpaceListOptions() spaceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) + //TODO:remove later after review + fmt.Println("populate Space cache called") cfSpaces, err := c.client.Spaces.ListAll(ctx, spaceOptions) if err != nil { return err diff --git a/internal/cf/client_test.go b/internal/cf/client_test.go index ff045a6..3940922 100644 --- a/internal/cf/client_test.go +++ b/internal/cf/client_test.go @@ -6,6 +6,7 @@ package cf import ( "context" + "net/http" "testing" "time" @@ -35,6 +36,8 @@ const ( spacesURI = "/v3/spaces" serviceInstancesURI = "/v3/service_instances" + spaceURI = "/v3/spaces" + serviceBindingURI = "/v3/service_credential_bindings" uaaURI = "/uaa/oauth/token" labelSelector = "service-operator.cf.cs.sap.com" ) @@ -93,6 +96,53 @@ var fakeServiceInstances = cfResource.ServiceInstanceList{ }, } +var fakeServiceBindings = cfResource.ServiceCredentialBindingList{ + Resources: []*cfResource.ServiceCredentialBinding{ + { + GUID: "test-binding-guid-1", + Name: "test-binding-name-1", + LastOperation: cfResource.LastOperation{ + Type: "create", + State: "succeeded", + Description: "", + }, + Metadata: &cfResource.Metadata{ + Labels: map[string]*string{ + "service-operator.cf.cs.sap.com/owner": String("testOwner"), + }, + Annotations: map[string]*string{ + "service-operator.cf.cs.sap.com/generation": String("1"), + "service-operator.cf.cs.sap.com/parameter-hash": String("74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b"), + }, + }, + }, + }, +} + +var fakeBingdingDetails = cfResource.ServiceCredentialBindingDetails{ + Credentials: map[string]interface{}{ + "key": "value", + }, +} + +var fakeSpaces = cfResource.SpaceList{ + Resources: []*cfResource.Space{ + { + GUID: "test-space-guid-1", + Name: "test-space-name-1", + Metadata: &cfResource.Metadata{ + Labels: map[string]*string{ + "service-operator.cf.cs.sap.com/owner": String("testOwner"), + }, + Annotations: map[string]*string{ + "service-operator.cf.cs.sap.com/generation": String("1"), + "service-operator.cf.cs.sap.com/parameter-hash": String("74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b"), + }, + }, + }, + }, +} + // ----------------------------------------------------------------------------------------------- // Tests // ----------------------------------------------------------------------------------------------- @@ -141,6 +191,12 @@ var _ = Describe("CF Client tests", Ordered, func() { metrics.Registry = prometheus.NewRegistry() server.Reset() + // Create a new configuration for each test + clientConfig = &config.Config{ + IsResourceCacheEnabled: false, + CacheTimeOut: "5m", + } + // Register handlers // - Fake response for discover UAA endpoint server.RouteToHandler("GET", "/", ghttp.CombineHandlers( @@ -154,6 +210,10 @@ var _ = Describe("CF Client tests", Ordered, func() { server.RouteToHandler("GET", spacesURI, ghttp.CombineHandlers( ghttp.RespondWithJSONEncodedPtr(&statusCode, &rootResult), )) + // - Fake response for get service instance + server.RouteToHandler("GET", spaceURI, ghttp.CombineHandlers( + ghttp.RespondWithJSONEncodedPtr(&statusCode, &fakeSpaces), + )) }) It("should create OrgClient", func() { @@ -249,6 +309,87 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring(Owner2)) }) + It("should not initialize resource cache if disabled in config", func() { + // Disable resource cache in config + clientConfig.IsResourceCacheEnabled = false + + // Create client + orgClient, err := NewOrganizationClient(OrgName, url, Username, Password, clientConfig) + Expect(err).To(BeNil()) + Expect(orgClient).ToNot(BeNil()) + + // Verify resource cache is NOT populated during client creation + // - Discover UAA endpoint + Expect(server.ReceivedRequests()).To(HaveLen(1)) + Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) + + // Make request and verify cache is NOT used + orgClient.GetSpace(ctx, Owner) + Expect(server.ReceivedRequests()).To(HaveLen(3)) // one more request to get instance + // - Get new oAuth token + Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) + Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) + // - Get space + Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(Owner)) + }) + + It("should initialize/manage resource cache after start and on cache expiry", func() { + // Enable resource cache in config + clientConfig.IsResourceCacheEnabled = true + clientConfig.CacheTimeOut = "5s" // short duration for fast test + + // // Route to handler for DELETE request + server.RouteToHandler("DELETE", + spaceURI+"/test-space-guid-1", + ghttp.CombineHandlers(ghttp.RespondWith(http.StatusAccepted, nil))) + + // Create client + orgClient, err := NewOrganizationClient(OrgName, url, Username, Password, clientConfig) + Expect(err).To(BeNil()) + Expect(orgClient).ToNot(BeNil()) + + // Verify resource cache is populated during client creation + Expect(server.ReceivedRequests()).To(HaveLen(3)) + // - Discover UAA endpoint + Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) + // - Get new oAuth token + Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) + Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) + // - Populate cache with spaces + Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(spaceURI)) + + // Make a request and verify that cache is used and no additional requests expected + orgClient.GetSpace(ctx, Owner) + Expect(server.ReceivedRequests()).To(HaveLen(3)) // still same as above + + // Make another request after cache expired and verify that cache is repopulated + time.Sleep(10 * time.Second) + orgClient.GetSpace(ctx, Owner) + Expect(server.ReceivedRequests()).To(HaveLen(4)) // one more request to repopulate cache + Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring(spaceURI)) + Expect(server.ReceivedRequests()[3].RequestURI).NotTo(ContainSubstring(Owner)) + + // Delete space from cache + err = orgClient.DeleteSpace(ctx, Owner, "test-space-guid-1") + Expect(err).To(BeNil()) + Expect(server.ReceivedRequests()).To(HaveLen(5)) + // - Delete space from cache + Expect(server.ReceivedRequests()[4].Method).To(Equal("DELETE")) + Expect(server.ReceivedRequests()[4].RequestURI).To(ContainSubstring("test-space-guid-1")) + + // Get space from cache should return empty + orgClient.GetSpace(ctx, Owner) + Expect(server.ReceivedRequests()).To(HaveLen(6)) + // - Get call to cf to get the space + Expect(server.ReceivedRequests()[5].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[5].RequestURI).To(ContainSubstring(Owner)) + }) + }) Describe("NewSpaceClient", func() { @@ -277,6 +418,16 @@ var _ = Describe("CF Client tests", Ordered, func() { server.RouteToHandler("GET", serviceInstancesURI, ghttp.CombineHandlers( ghttp.RespondWithJSONEncodedPtr(&statusCode, &fakeServiceInstances), )) + + // - Fake response for get service binding + server.RouteToHandler("GET", serviceBindingURI, ghttp.CombineHandlers( + ghttp.RespondWithJSONEncodedPtr(&statusCode, &fakeServiceBindings), + )) + + // - Fake response for get service binding + server.RouteToHandler("GET", serviceBindingURI+"/"+fakeServiceBindings.Resources[0].GUID+"/details", ghttp.CombineHandlers( + ghttp.RespondWithJSONEncodedPtr(&statusCode, &fakeBingdingDetails), + )) }) It("should create SpaceClient", func() { @@ -413,116 +564,124 @@ var _ = Describe("CF Client tests", Ordered, func() { // prometheus.WriteToTextfile("metrics.txt", metrics.Registry) }) - // Context("Populate resource cache tests", func() { - - // It("should initialize resource cache after start and on cache expiry", func() { - // // Enable resource cache in config - // clientConfig.IsResourceCacheEnabled = true - // clientConfig.CacheTimeOut = "5s" // short duration for fast test - - // // Create client - // spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *clientConfig) - // Expect(err).To(BeNil()) - // Expect(spaceClient).ToNot(BeNil()) - - // // Verify resource cache is populated during client creation - // Expect(server.ReceivedRequests()).To(HaveLen(3)) - // // - Discover UAA endpoint - // Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) - // Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) - // // - Get new oAuth token - // Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) - // Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) - // // - Populate cache - // Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) - // Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(serviceInstancesURI)) - - // // Make a request and verify that cache is used and no additional requests expected - // spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - // Expect(server.ReceivedRequests()).To(HaveLen(3)) // still same as above - - // // Make another request after cache expired and verify that cache is repopulated - // time.Sleep(10 * time.Second) - // spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - // Expect(server.ReceivedRequests()).To(HaveLen(4)) // one more request to repopulate cache - // Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) - // Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring(serviceInstancesURI)) - // Expect(server.ReceivedRequests()[3].RequestURI).NotTo(ContainSubstring(Owner)) - // }) - - // It("should not initialize resource cache if disabled in config", func() { - // // Disable resource cache in config - // clientConfig.IsResourceCacheEnabled = false - - // // Create client - // spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *clientConfig) - // Expect(err).To(BeNil()) - // Expect(spaceClient).ToNot(BeNil()) - - // // Verify resource cache is NOT populated during client creation - // // - Discover UAA endpoint - // Expect(server.ReceivedRequests()).To(HaveLen(1)) - // Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) - // Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) - - // // Make request and verify cache is NOT used - // spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - // Expect(server.ReceivedRequests()).To(HaveLen(3)) // one more request to get instance - // // - Get new oAuth token - // Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) - // Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) - // // - Get instance - // Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) - // Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(Owner)) - // }) - - // It("Delete instance from cache", func() { - // // Enable resource cache in config - // clientConfig.IsResourceCacheEnabled = true - // clientConfig.CacheTimeOut = "5m" - - // // Route to handler for DELETE request - // server.RouteToHandler("DELETE", - // serviceInstancesURI+"/test-instance-guid-1", - // ghttp.CombineHandlers(ghttp.RespondWith(http.StatusAccepted, nil))) - - // // Create client - // spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, *clientConfig) - // Expect(err).To(BeNil()) - // Expect(spaceClient).ToNot(BeNil()) - - // // Verify resource cache is populated during client creation - // Expect(server.ReceivedRequests()).To(HaveLen(3)) - // // - Discover UAA endpoint - // Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) - // Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) - // // - Get new oAuth token - // Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) - // Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) - // // - Populate cache - // Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) - // Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(serviceInstancesURI)) - // Expect(server.ReceivedRequests()[2].RequestURI).NotTo(ContainSubstring(Owner)) - - // // Make a request and verify that cache is used and no additional requests expected - // spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - // Expect(server.ReceivedRequests()).To(HaveLen(3)) // still same as above - - // // Delete instance from cache - // err = spaceClient.DeleteInstance(ctx, "test-instance-guid-1", Owner) - // Expect(err).To(BeNil()) - // Expect(server.ReceivedRequests()).To(HaveLen(4)) - // // - Delete instance from cache - // Expect(server.ReceivedRequests()[3].Method).To(Equal("DELETE")) - // Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring("test-instance-guid-1")) - - // // Get instance from cache should return empty - // spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - // Expect(server.ReceivedRequests()).To(HaveLen(5)) - // // - get instance from cache - // Expect(server.ReceivedRequests()[4].Method).To(Equal("GET")) - // Expect(server.ReceivedRequests()[4].RequestURI).To(ContainSubstring(Owner)) - // }) - // }) + It("should not initialize resource cache if disabled in config", func() { + // Disable resource cache in config + clientConfig.IsResourceCacheEnabled = false + + // Create client + spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, clientConfig) + Expect(err).To(BeNil()) + Expect(spaceClient).ToNot(BeNil()) + + // Verify resource cache is NOT populated during client creation + // - Discover UAA endpoint + Expect(server.ReceivedRequests()).To(HaveLen(1)) + Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) + + // Make request and verify cache is NOT used + spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + Expect(server.ReceivedRequests()).To(HaveLen(3)) // one more request to get instance + // - Get new oAuth token + Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) + Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) + // - Get instance + Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(Owner)) + }) + + It("should initialize resource cache after start and on cache expiry", func() { + // Enable resource cache in config + clientConfig.IsResourceCacheEnabled = true + clientConfig.CacheTimeOut = "8s" // short duration for fast test + + //resource cache will be initialized only only once, so we need to wait till the cache expiry from previous test + time.Sleep(10 * time.Second) + + // Route to handler for DELETE request + server.RouteToHandler("DELETE", + serviceInstancesURI+"/test-instance-guid-1", + ghttp.CombineHandlers(ghttp.RespondWith(http.StatusAccepted, nil))) + + // Route to handler for DELETE request + server.RouteToHandler("DELETE", + serviceBindingURI+"/test-binding-guid-1", + ghttp.CombineHandlers(ghttp.RespondWith(http.StatusAccepted, nil))) + + // Create client + spaceClient, err := NewSpaceClient(SpaceName, url, Username, Password, clientConfig) + Expect(err).To(BeNil()) + Expect(spaceClient).ToNot(BeNil()) + + // Verify resource cache is populated during client creation + Expect(server.ReceivedRequests()).To(HaveLen(5)) + // - Discover UAA endpoint + Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) + // - Get new oAuth token + Expect(server.ReceivedRequests()[1].Method).To(Equal("POST")) + Expect(server.ReceivedRequests()[1].URL.Path).To(Equal(uaaURI)) + // - Populate cache with instances + Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(serviceInstancesURI)) + // - Populate cache with bindings + Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring(serviceBindingURI)) + // -get the binding details + Expect(server.ReceivedRequests()[4].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[4].RequestURI).To(ContainSubstring(serviceBindingURI + "/" + fakeServiceBindings.Resources[0].GUID + "/details")) + + // Make a request and verify that cache is used and no additional requests expected + spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + Expect(server.ReceivedRequests()).To(HaveLen(5)) // still same as above + + // Make another request after cache expired and verify that cache is repopulated + time.Sleep(10 * time.Second) + spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + spaceClient.GetBinding(ctx, map[string]string{"owner": Owner}) + Expect(server.ReceivedRequests()).To(HaveLen(8)) // one more request to repopulate cache + Expect(server.ReceivedRequests()[5].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[5].RequestURI).To(ContainSubstring(serviceInstancesURI)) + Expect(server.ReceivedRequests()[5].RequestURI).NotTo(ContainSubstring(Owner)) + Expect(server.ReceivedRequests()[6].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[6].RequestURI).To(ContainSubstring(serviceBindingURI)) + Expect(server.ReceivedRequests()[6].RequestURI).NotTo(ContainSubstring(Owner)) + Expect(server.ReceivedRequests()[7].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[7].RequestURI).To(ContainSubstring(serviceBindingURI + "/" + fakeServiceBindings.Resources[0].GUID + "/details")) + + // Delete instance from cache + err = spaceClient.DeleteInstance(ctx, "test-instance-guid-1", Owner) + Expect(err).To(BeNil()) + Expect(server.ReceivedRequests()).To(HaveLen(9)) + // - Delete instance from cache + Expect(server.ReceivedRequests()[8].Method).To(Equal("DELETE")) + Expect(server.ReceivedRequests()[8].RequestURI).To(ContainSubstring("test-instance-guid-1")) + + // Get instance from cache should return empty + spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) + Expect(server.ReceivedRequests()).To(HaveLen(10)) + // - Get call to cf to get the instance + Expect(server.ReceivedRequests()[9].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[9].RequestURI).To(ContainSubstring(Owner)) + + // Delete binding from cache + err = spaceClient.DeleteBinding(ctx, "test-binding-guid-1", Owner) + Expect(err).To(BeNil()) + Expect(server.ReceivedRequests()).To(HaveLen(11)) + // - Delete binding from cache + Expect(server.ReceivedRequests()[10].Method).To(Equal("DELETE")) + Expect(server.ReceivedRequests()[10].RequestURI).To(ContainSubstring("test-binding-guid-1")) + + // Get binding from cache should return empty + spaceClient.GetBinding(ctx, map[string]string{"owner": Owner}) + Expect(server.ReceivedRequests()).To(HaveLen(13)) + // - Get call to cf to get the binding + Expect(server.ReceivedRequests()[11].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[11].RequestURI).To(ContainSubstring(Owner)) + Expect(server.ReceivedRequests()[12].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[12].RequestURI).To(ContainSubstring(serviceBindingURI + "/" + fakeServiceBindings.Resources[0].GUID + "/details")) + + }) + }) }) diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 80277d4..80db819 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -51,7 +51,7 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s // Attempt to retrieve instance from cache if c.resourceCache.isCacheExpired("serviceInstances") { //TODO: remove after internal review - fmt.Println("Cache is expired") + fmt.Println("Cache is expired for instance so populating cache") populateResourceCache[*spaceClient](c, "serviceInstances") } if len(c.resourceCache.getCachedInstances()) != 0 { @@ -155,7 +155,7 @@ func (c *spaceClient) UpdateInstance(ctx context.Context, guid string, name stri // Update instance in cache if err == nil && c.resourceCache.checkResourceCacheEnabled() { - isUpdated := c.resourceCache.updateInstanceInCache(owner, servicePlanGuid, parameters, generation) + isUpdated := c.resourceCache.updateInstanceInCache(name, owner, servicePlanGuid, parameters, generation) if !isUpdated { // add instance to cache in case of orphan instance diff --git a/internal/cf/resourcecache.go b/internal/cf/resourcecache.go index 5c69033..359e66f 100644 --- a/internal/cf/resourcecache.go +++ b/internal/cf/resourcecache.go @@ -69,26 +69,28 @@ func (c *resourceCache) setCacheTimeOut(timeOut string) { // return time.Now().After(expirationTime) // } func (c *resourceCache) isCacheExpired(resourceType cacheResourceType) bool { - var expirationTime time.Time + var lastCacheTime time.Time switch resourceType { case serviceInstances: - expirationTime = c.lastServiceInstanceCacheTime.Add(c.cacheTimeOut) + lastCacheTime = c.lastServiceInstanceCacheTime case spaces: - expirationTime = c.lastSpaceCacheTime.Add(c.cacheTimeOut) + lastCacheTime = c.lastSpaceCacheTime case serviceBindings: - expirationTime = c.lastServiceBindingCacheTime.Add(c.cacheTimeOut) - default: + lastCacheTime = c.lastServiceBindingCacheTime + } + + // Ensure lastCacheTime is properly initialized + if lastCacheTime.IsZero() { return true } + + expirationTime := lastCacheTime.Add(c.cacheTimeOut) + //TODO:remove later + fmt.Printf("Expiration time for %s: %v\n", resourceType, expirationTime) + return time.Now().After(expirationTime) } -// // setLastCacheTime sets the time of the last cache expiration re-population -// func (c *resourceCache) setLastCacheTime() { -// c.lastCacheTime = time.Now() -// log.Printf("Last cache time: %v\n", c.lastCacheTime) -// } - func (c *resourceCache) setLastCacheTime(resourceType cacheResourceType) { now := time.Now() switch resourceType { @@ -123,7 +125,7 @@ func (c *resourceCache) addInstanceInCache(key string, instance *facade.Instance c.mutex.Lock() defer c.mutex.Unlock() // TODO :remove After internal review - fmt.Printf("Added the instance to Cache: %v", instance) + fmt.Printf("Added the instance to Cache: %v \n", instance) c.instances[key] = instance } @@ -133,7 +135,7 @@ func (c *resourceCache) getInstanceFromCache(key string) (*facade.Instance, bool defer c.mutex.RUnlock() instance, found := c.instances[key] // TODO :remove After internal review - fmt.Printf("Got the instance from Cache: %v", instance) + fmt.Printf("Got the instance from Cache: %v \n", instance) return instance, found } @@ -143,18 +145,21 @@ func (c *resourceCache) deleteInstanceFromCache(key string) { defer c.mutex.Unlock() delete(c.instances, key) // TODO :remove After internal review - fmt.Printf("deleted the instance from Cache: %v", key) + fmt.Printf("deleted the instance from Cache: %v \n", key) } // updateInstanceInCache updates an instance in the cache -func (c *resourceCache) updateInstanceInCache(owner string, servicePlanGuid string, parameters map[string]interface{}, generation int64) (status bool) { +func (c *resourceCache) updateInstanceInCache(owner string, name string, servicePlanGuid string, parameters map[string]interface{}, generation int64) (status bool) { c.mutex.Lock() defer c.mutex.Unlock() //update if the instance is found in the cache //update all the struct variables if they are not nil or empty instance, found := c.instances[owner] if found { + if name != "" { + instance.Name = name + } if servicePlanGuid != "" { instance.ServicePlanGuid = servicePlanGuid } @@ -187,7 +192,7 @@ func (c *resourceCache) addBindingInCache(key string, binding *facade.Binding) { defer c.mutex.Unlock() c.bindings[key] = binding // TODO :remove After internal review - fmt.Printf("Added the binding to Cache: %v", binding) + fmt.Printf("Added the binding to Cache: %v \n", binding) } // getBindingFromCache retrieves binding from the cache @@ -196,7 +201,7 @@ func (c *resourceCache) getBindingFromCache(key string) (*facade.Binding, bool) defer c.mutex.RUnlock() binding, found := c.bindings[key] // TODO :remove After internal review - fmt.Printf("Got the binding from Cache: %v", binding) + fmt.Printf("Got the binding from Cache: %v \n", binding) return binding, found } @@ -206,7 +211,7 @@ func (c *resourceCache) deleteBindingFromCache(key string) { defer c.mutex.Unlock() delete(c.bindings, key) // TODO :remove After internal review - fmt.Printf("Added the binding to Cache: %v", key) + fmt.Printf("Added the binding to Cache: %v \n", key) } @@ -238,7 +243,7 @@ func (c *resourceCache) addSpaceInCache(key string, space *facade.Space) { defer c.mutex.Unlock() c.spaces[key] = space // TODO :remove After internal review - fmt.Printf("Added the space to Cache: %v", space) + fmt.Printf("Added the space to Cache: %v \n", space) } // GetSpaceFromCache retrieves a space from the cache @@ -247,7 +252,7 @@ func (c *resourceCache) getSpaceFromCache(key string) (*facade.Space, bool) { defer c.mutex.RUnlock() space, found := c.spaces[key] // TODO :remove After internal review - fmt.Printf("Got the space from Cache: %v", space) + fmt.Printf("Got the space from Cache: %v \n", space) return space, found } @@ -257,7 +262,7 @@ func (c *resourceCache) deleteSpaceFromCache(key string) { defer c.mutex.Unlock() delete(c.spaces, key) // TODO :remove After internal review - fmt.Printf("Deleted the space from Cache: %v", key) + fmt.Printf("Deleted the space from Cache: %v \n", key) } @@ -266,29 +271,31 @@ func (c *resourceCache) getCachedSpaces() map[string]*facade.Space { return c.spaces } -// // updateSpaceInCache updates an space in the cache -// func (c *resourceCache) updateSpaceInCache(owner string, generation int64) (status bool) { -// c.mutex.Lock() -// defer c.mutex.Unlock() -// //update if the space is found in the cache -// //update all the struct variables if they are not nil or empty -// space, found := c.spaces[owner] -// if found { -// if generation != 0 { -// space.Generation = generation -// } -// c.spaces[owner] = space -// return true - -// } -// return false - -// } +// updateSpaceInCache updates an space in the cache +func (c *resourceCache) updateSpaceInCache(owner string, name string, generation int64) (status bool) { + c.mutex.Lock() + defer c.mutex.Unlock() + //update if the space is found in the cache + //update all the struct variables if they are not nil or empty + space, found := c.spaces[owner] + if found { + if name != "" { + space.Name = name + } + if generation != 0 { + space.Generation = generation + } + c.spaces[owner] = space + return true + } + return false + +} // reset cache of a specific resource type and last cache time func (c *resourceCache) resetCache(resourceType cacheResourceType) { - fmt.Printf("reset requested for %v", resourceType) + fmt.Printf("reset requested for %v \n", resourceType) switch resourceType { case serviceInstances: c.instances = make(map[string]*facade.Instance) diff --git a/internal/cf/resourcecache_test.go b/internal/cf/resourcecache_test.go index 4a28c2b..a7c871a 100644 --- a/internal/cf/resourcecache_test.go +++ b/internal/cf/resourcecache_test.go @@ -23,25 +23,45 @@ func TestFacade(t *testing.T) { var _ = Describe("ResourceCache", func() { var cache *resourceCache var instance *facade.Instance + var binding *facade.Binding + var space *facade.Space var wg sync.WaitGroup concurrencyLevel := 20 BeforeEach(func() { cache = initResourceCache() + instance = &facade.Instance{ Guid: "guid1", Name: "name1", - Owner: "owner1", + Owner: "instanceOwner1", ServicePlanGuid: "plan1", ParameterHash: "hash1", Generation: 1, } + + binding = &facade.Binding{ + Guid: "guid1", + Name: "name1", + Owner: "bindingOwner1", + ParameterHash: "hash1", + Generation: 1, + State: facade.BindingStateReady, + StateDescription: "", + } + + space = &facade.Space{ + Guid: "guid1", + Name: "name1", + Owner: "spaceOwner1", + Generation: 1, + } }) - Context("basic CRUD operations", func() { + Context("basic instance CRUD operations", func() { It("should add, get, update, and delete an instance in the cache", func() { // Add instance - Ownerkey := "owner1" + Ownerkey := "instanceOwner1" cache.addInstanceInCache(Ownerkey, instance) // Get instance @@ -52,13 +72,13 @@ var _ = Describe("ResourceCache", func() { // Update instance updatedInstance := &facade.Instance{ Guid: "guid1", - Name: "name1", - Owner: "owner1", + Name: "updatedInstanceName", + Owner: "instanceOwner1", ServicePlanGuid: "updatedPlan", ParameterHash: "hash1", Generation: 2, } - cache.updateInstanceInCache("owner1", "updatedPlan", nil, 2) + cache.updateInstanceInCache("instanceOwner1", "updatedInstanceName", "updatedPlan", nil, 2) retrievedInstance, found = cache.getInstanceFromCache(Ownerkey) Expect(found).To(BeTrue()) Expect(retrievedInstance).To(Equal(updatedInstance)) @@ -70,37 +90,37 @@ var _ = Describe("ResourceCache", func() { }) }) - Context("edge cases", func() { + Context("instance edge cases", func() { It("should handle adding an instance with an existing key", func() { instance2 := &facade.Instance{ Guid: "guid2", Name: "name2", - Owner: "owner1", - ServicePlanGuid: "plan1", - ParameterHash: "hash1", + Owner: "instanceOwner1", + ServicePlanGuid: "plan2", + ParameterHash: "hash2", Generation: 1, } - cache.addInstanceInCache("owner1", instance) - cache.addInstanceInCache("owner1", instance2) - retrievedInstance, found := cache.getInstanceFromCache("owner1") + cache.addInstanceInCache("instanceOwner1", instance) + cache.addInstanceInCache("instanceOwner1", instance2) + retrievedInstance, found := cache.getInstanceFromCache("instanceOwner1") Expect(found).To(BeTrue()) Expect(retrievedInstance).To(Equal(instance2)) }) It("should handle updating a non-existent instance", func() { - cache.updateInstanceInCache("owner", "plan", nil, 1) + cache.updateInstanceInCache("nonExistentOwner", "name", "plan", nil, 1) _, found := cache.getInstanceFromCache("owner") Expect(found).To(BeFalse()) }) It("should handle deleting a non-existent instance", func() { - cache.deleteInstanceFromCache("nonExistentKey") - _, found := cache.getInstanceFromCache("nonExistentKey") + cache.deleteInstanceFromCache("nonExistentOwner") + _, found := cache.getInstanceFromCache("nonExistentOwner") Expect(found).To(BeFalse()) }) }) - Context("concurrent CRUD operations, data integrity and load test", func() { + Context("concurrent instance CRUD operations, data integrity and load test", func() { It("should handle concurrent ", func() { for i := 0; i < concurrencyLevel; i++ { wg.Add(1) @@ -109,12 +129,12 @@ var _ = Describe("ResourceCache", func() { instance := &facade.Instance{ Guid: "guid" + strconv.Itoa(i), Name: "name" + strconv.Itoa(i), - Owner: "key" + strconv.Itoa(i), + Owner: "instanceOwner" + strconv.Itoa(i), ServicePlanGuid: "plan" + strconv.Itoa(i), ParameterHash: "hash", Generation: 1, } - cache.addInstanceInCache("key"+strconv.Itoa(i), instance) + cache.addInstanceInCache("instanceOwner"+strconv.Itoa(i), instance) }(i) } wg.Wait() @@ -124,11 +144,11 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - key := "key" + strconv.Itoa(i) + key := "instanceOwner" + strconv.Itoa(i) instance := &facade.Instance{ Guid: "guid" + strconv.Itoa(i), Name: "name" + strconv.Itoa(i), - Owner: "key" + strconv.Itoa(i), + Owner: "instanceOwner" + strconv.Itoa(i), ServicePlanGuid: "plan" + strconv.Itoa(i), ParameterHash: "hash", Generation: 1, @@ -145,19 +165,19 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - cache.updateInstanceInCache("key"+strconv.Itoa(i), "plan"+strconv.Itoa(i), nil, 1) + cache.updateInstanceInCache("instanceOwner"+strconv.Itoa(i), "updatedName"+strconv.Itoa(i), "updatedPlan"+strconv.Itoa(i), nil, 1) }(i) } wg.Wait() // Verify that all instances have been updated in the cache for i := 0; i < concurrencyLevel; i++ { - key := "key" + strconv.Itoa(i) + key := "instanceOwner" + strconv.Itoa(i) expectedInstance := &facade.Instance{ Guid: "guid" + strconv.Itoa(i), - Name: "name" + strconv.Itoa(i), + Name: "updatedName" + strconv.Itoa(i), Owner: key, - ServicePlanGuid: "plan" + strconv.Itoa(i), + ServicePlanGuid: "updatedPlan" + strconv.Itoa(i), ParameterHash: "hash", Generation: 1, } @@ -172,16 +192,307 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - cache.deleteInstanceFromCache("key" + strconv.Itoa(i)) + cache.deleteInstanceFromCache("instanceOwner" + strconv.Itoa(i)) }(i) } wg.Wait() // Verify final state for i := 0; i < concurrencyLevel; i++ { - _, found := cache.getInstanceFromCache("key" + strconv.Itoa(i)) + _, found := cache.getInstanceFromCache("instanceOwner" + strconv.Itoa(i)) Expect(found).To(BeFalse(), "Expected instance to be deleted from cache") } }) }) + + // tests for service binding cache + Context("basic binding CRUD operations", func() { + It("should add, get, update, and delete a binding in the cache", func() { + // Add binding + Ownerkey := "bindingOwner1" + cache.addBindingInCache(Ownerkey, binding) + + // Get binding + retrievedBinding, found := cache.getBindingFromCache(Ownerkey) + Expect(found).To(BeTrue()) + Expect(retrievedBinding).To(Equal(binding)) + + // Update binding + updatedBinding := &facade.Binding{ + Guid: "guid1", + Name: "name1", + Owner: "bindingOwner1", + ParameterHash: "hash1", + Generation: 2, + State: facade.BindingStateReady, + StateDescription: "", + } + cache.updateBindingInCache("bindingOwner1", nil, 2) + retrievedBinding, found = cache.getBindingFromCache(Ownerkey) + Expect(found).To(BeTrue()) + Expect(retrievedBinding).To(Equal(updatedBinding)) + + // Delete binding + cache.deleteBindingFromCache(Ownerkey) + _, found = cache.getBindingFromCache(Ownerkey) + Expect(found).To(BeFalse()) + }) + }) + + Context("edge cases", func() { + It("should handle adding a binding with an existing key", func() { + binding2 := &facade.Binding{ + Guid: "guid2", + Name: "newname2", + Owner: "bindingOwner1", + ParameterHash: "hash1", + Generation: 2, + State: facade.BindingStateReady, + StateDescription: "", + } + cache.addBindingInCache("bindingOwner1", binding) + cache.addBindingInCache("bindingOwner1", binding2) + retrievedBinding, found := cache.getBindingFromCache("bindingOwner1") + Expect(found).To(BeTrue()) + Expect(retrievedBinding).To(Equal(binding2)) + }) + + It("should handle updating a non-existent binding", func() { + cache.updateBindingInCache("nonExistOwner", nil, 1) + _, found := cache.getBindingFromCache("nonExistOwner") + Expect(found).To(BeFalse()) + }) + + It("should handle deleting a non-existent binding", func() { + cache.deleteBindingFromCache("nonExistOwner") + _, found := cache.getBindingFromCache("nonExistOwner") + Expect(found).To(BeFalse()) + }) + }) + + Context("concurrent CRUD operations, data integrity and load test", func() { + It("should handle concurrent ", func() { + for i := 0; i < concurrencyLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + binding := &facade.Binding{ + Guid: "guid" + strconv.Itoa(i), + Name: "name" + strconv.Itoa(i), + Owner: "bindingOwner" + strconv.Itoa(i), + ParameterHash: "hash", + Generation: 1, + State: facade.BindingStateReady, + StateDescription: "", + } + cache.addBindingInCache("bindingOwner"+strconv.Itoa(i), binding) + }(i) + } + wg.Wait() + + // Verify that all bindings have been added to the cache + for i := 0; i < concurrencyLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + key := "bindingOwner" + strconv.Itoa(i) + binding := &facade.Binding{ + Guid: "guid" + strconv.Itoa(i), + Name: "name" + strconv.Itoa(i), + Owner: "bindingOwner" + strconv.Itoa(i), + ParameterHash: "hash", + Generation: 1, + State: facade.BindingStateReady, + StateDescription: "", + } + retrievedBinding, found := cache.getBindingFromCache(key) + Expect(found).To(BeTrue(), "Binding should be found in cache for key: %s", key) + Expect(retrievedBinding).To(Equal(binding), "Retrieved binding should match the added binding for key: %s", key) + }(i) + } + wg.Wait() + + // Concurrently update bindings in the cache + for i := 0; i < concurrencyLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + cache.updateBindingInCache("bindingOwner"+strconv.Itoa(i), nil, 2) + }(i) + } + wg.Wait() + + // Verify that all bindings have been updated in the cache + for i := 0; i < concurrencyLevel; i++ { + key := "bindingOwner" + strconv.Itoa(i) + expectedBinding := &facade.Binding{ + Guid: "guid" + strconv.Itoa(i), + Name: "name" + strconv.Itoa(i), + Owner: key, + ParameterHash: "hash", + Generation: 2, + State: facade.BindingStateReady, + StateDescription: "", + } + retrievedBinding, found := cache.getBindingFromCache(key) + + Expect(found).To(BeTrue(), "Binding should be found in cache for key: %s", key) + Expect(retrievedBinding).To(Equal(expectedBinding), "Retrieved binding should match the updated binding for key: %s", key) + } + + // Concurrently delete bindings from the cache + for i := 0; i < concurrencyLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + cache.deleteBindingFromCache("bindingOwner" + strconv.Itoa(i)) + }(i) + } + wg.Wait() + + // Verify final state + for i := 0; i < concurrencyLevel; i++ { + _, found := cache.getBindingFromCache("bindingOwner" + strconv.Itoa(i)) + Expect(found).To(BeFalse(), "Expected binding to be deleted from cache") + } + }) + }) + + // tests for space cache + Context("basic CRUD operations", func() { + It("should add, get, update, and delete a space in the cache", func() { + // Add space + Ownerkey := "spaceOwner1" + cache.addSpaceInCache(Ownerkey, space) + + // Get space + retrievedSpace, found := cache.getSpaceFromCache(Ownerkey) + Expect(found).To(BeTrue()) + Expect(retrievedSpace).To(Equal(space)) + + // Update space + updatedSpace := &facade.Space{ + Guid: "guid1", + Name: "updatedname", + Owner: "spaceOwner1", + Generation: 2, + } + cache.updateSpaceInCache("spaceOwner1", "updatedname", 2) + retrievedSpace, found = cache.getSpaceFromCache(Ownerkey) + Expect(found).To(BeTrue()) + Expect(retrievedSpace).To(Equal(updatedSpace)) + + // Delete space + cache.deleteSpaceFromCache(Ownerkey) + _, found = cache.getSpaceFromCache(Ownerkey) + Expect(found).To(BeFalse()) + }) + }) + + Context("edge cases", func() { + It("should handle adding a space with an existing key", func() { + space2 := &facade.Space{ + Guid: "guid2", + Name: "name2", + Owner: "spaceOwner1", + Generation: 2, + } + cache.addSpaceInCache("spaceOwner1", space) + cache.addSpaceInCache("spaceOwner1", space2) + retrievedSpace, found := cache.getSpaceFromCache("spaceOwner1") + Expect(found).To(BeTrue()) + Expect(retrievedSpace).To(Equal(space2)) + }) + + It("should handle updating a non-existent space", func() { + cache.updateSpaceInCache("nonExistOwner", "name", 1) + _, found := cache.getSpaceFromCache("nonExistOwner") + Expect(found).To(BeFalse()) + }) + + It("should handle deleting a non-existent space", func() { + cache.deleteSpaceFromCache("nonExistOwner") + _, found := cache.getSpaceFromCache("nonExistOwner") + Expect(found).To(BeFalse()) + }) + }) + + Context("concurrent CRUD operations, data integrity and load test", func() { + It("should handle concurrent ", func() { + for i := 0; i < concurrencyLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + space := &facade.Space{ + Guid: "guid" + strconv.Itoa(i), + Name: "name" + strconv.Itoa(i), + Owner: "spaceOwner" + strconv.Itoa(i), + Generation: 1, + } + cache.addSpaceInCache("spaceOwner"+strconv.Itoa(i), space) + }(i) + } + wg.Wait() + + // Verify that all spaces have been added to the cache + for i := 0; i < concurrencyLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + key := "spaceOwner" + strconv.Itoa(i) + space := &facade.Space{ + Guid: "guid" + strconv.Itoa(i), + Name: "name" + strconv.Itoa(i), + Owner: "spaceOwner" + strconv.Itoa(i), + Generation: 1, + } + retrievedSpace, found := cache.getSpaceFromCache(key) + Expect(found).To(BeTrue(), "Space should be found in cache for key: %s", key) + Expect(retrievedSpace).To(Equal(space), "Retrieved space should match the added space for key: %s", key) + }(i) + } + wg.Wait() + + // Concurrently update spaces in the cache + for i := 0; i < concurrencyLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + cache.updateSpaceInCache("spaceOwner"+strconv.Itoa(i), "updatedname"+strconv.Itoa(i), 2) + }(i) + } + wg.Wait() + + // Verify that all spaces have been updated in the cache + for i := 0; i < concurrencyLevel; i++ { + key := "spaceOwner" + strconv.Itoa(i) + expectedSpace := &facade.Space{ + Guid: "guid" + strconv.Itoa(i), + Name: "updatedname" + strconv.Itoa(i), + Owner: key, + Generation: 2, + } + retrievedSpace, found := cache.getSpaceFromCache(key) + + Expect(found).To(BeTrue(), "Space should be found in cache for key: %s", key) + Expect(retrievedSpace).To(Equal(expectedSpace), "Retrieved space should match the updated space for key: %s", key) + } + + // Concurrently delete spaces from the cache + for i := 0; i < concurrencyLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + cache.deleteSpaceFromCache("spaceOwner" + strconv.Itoa(i)) + }(i) + } + wg.Wait() + + // Verify final state + for i := 0; i < concurrencyLevel; i++ { + _, found := cache.getSpaceFromCache("spaceOwner" + strconv.Itoa(i)) + Expect(found).To(BeFalse(), "Expected space to be deleted from cache") + } + }) + }) }) diff --git a/internal/cf/space.go b/internal/cf/space.go index 8cd25c5..ea22a87 100644 --- a/internal/cf/space.go +++ b/internal/cf/space.go @@ -19,10 +19,10 @@ import ( func (c *organizationClient) GetSpace(ctx context.Context, owner string) (*facade.Space, error) { if c.resourceCache.checkResourceCacheEnabled() { // Attempt to retrieve space from cache - if c.resourceCache.isCacheExpired("spaces") { + if c.resourceCache.isCacheExpired(spaces) { //TODO: remove after internal review - fmt.Println("Cache is expired") - populateResourceCache[*organizationClient](c, "spaces") + fmt.Println("Cache is expired for space") + populateResourceCache[*organizationClient](c, spaces) } if len(c.resourceCache.getCachedSpaces()) != 0 { space, spaceInCache := c.resourceCache.getSpaceFromCache(owner) @@ -80,7 +80,7 @@ func (c *organizationClient) CreateSpace(ctx context.Context, name string, owner // Required parameters (may not be initial): guid, generation // Optional parameters (may be initial): name -func (c *organizationClient) UpdateSpace(ctx context.Context, guid string, name string, generation int64) error { +func (c *organizationClient) UpdateSpace(ctx context.Context, guid string, name string, owner string, generation int64) error { // TODO: why is there no cfresource.NewSpaceUpdate() method ? req := &cfresource.SpaceUpdate{} if name != "" { @@ -90,15 +90,30 @@ func (c *organizationClient) UpdateSpace(ctx context.Context, guid string, name WithAnnotation(annotationPrefix, annotationKeyGeneration, strconv.FormatInt(generation, 10)) _, err := c.client.Spaces.Update(ctx, guid, req) + //update space in cache + if c.resourceCache.checkResourceCacheEnabled() { + isUpdated := c.resourceCache.updateSpaceInCache(owner, name, generation) + if !isUpdated { + //add space to cache + space := &facade.Space{ + Guid: guid, + Name: name, + Owner: owner, + Generation: generation, + } + c.resourceCache.addSpaceInCache(owner, space) + } + } + return err } -func (c *organizationClient) DeleteSpace(ctx context.Context, guid string) error { +func (c *organizationClient) DeleteSpace(ctx context.Context, owner string, guid string) error { _, err := c.client.Spaces.Delete(ctx, guid) // Delete space from cache if c.resourceCache.checkResourceCacheEnabled() { - c.resourceCache.deleteSpaceFromCache(guid) + c.resourceCache.deleteSpaceFromCache(owner) } return err } diff --git a/internal/controllers/space_controller.go b/internal/controllers/space_controller.go index 51b7652..b3d8b44 100644 --- a/internal/controllers/space_controller.go +++ b/internal/controllers/space_controller.go @@ -199,6 +199,7 @@ func (r *SpaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resu ctx, cfspace.Guid, updateName, + cfspace.Owner, space.GetGeneration(), ); err != nil { return ctrl.Result{}, err @@ -276,7 +277,7 @@ func (r *SpaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resu return ctrl.Result{}, nil } else { log.V(1).Info("Deleting space") - if err := client.DeleteSpace(ctx, cfspace.Guid); err != nil { + if err := client.DeleteSpace(ctx, cfspace.Owner, cfspace.Guid); err != nil { return ctrl.Result{}, err } status.LastModifiedAt = &[]metav1.Time{metav1.Now()}[0] diff --git a/internal/facade/client.go b/internal/facade/client.go index 38c2550..7eff4ab 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -70,8 +70,8 @@ const ( type OrganizationClient interface { GetSpace(ctx context.Context, owner string) (*Space, error) CreateSpace(ctx context.Context, name string, owner string, generation int64) error - UpdateSpace(ctx context.Context, guid string, name string, generation int64) error - DeleteSpace(ctx context.Context, guid string) error + UpdateSpace(ctx context.Context, guid string, name string, owner string, generation int64) error + DeleteSpace(ctx context.Context, owner string, guid string) error AddAuditor(ctx context.Context, guid string, username string) error AddDeveloper(ctx context.Context, guid string, username string) error AddManager(ctx context.Context, guid string, username string) error diff --git a/internal/facade/facadefakes/fake_organization_client.go b/internal/facade/facadefakes/fake_organization_client.go index c5df333..b8193e1 100644 --- a/internal/facade/facadefakes/fake_organization_client.go +++ b/internal/facade/facadefakes/fake_organization_client.go @@ -66,11 +66,12 @@ type FakeOrganizationClient struct { createSpaceReturnsOnCall map[int]struct { result1 error } - DeleteSpaceStub func(context.Context, string) error + DeleteSpaceStub func(context.Context, string, string) error deleteSpaceMutex sync.RWMutex deleteSpaceArgsForCall []struct { arg1 context.Context arg2 string + arg3 string } deleteSpaceReturns struct { result1 error @@ -92,13 +93,14 @@ type FakeOrganizationClient struct { result1 *facade.Space result2 error } - UpdateSpaceStub func(context.Context, string, string, int64) error + UpdateSpaceStub func(context.Context, string, string, string, int64) error updateSpaceMutex sync.RWMutex updateSpaceArgsForCall []struct { arg1 context.Context arg2 string arg3 string - arg4 int64 + arg4 string + arg5 int64 } updateSpaceReturns struct { result1 error @@ -363,19 +365,20 @@ func (fake *FakeOrganizationClient) CreateSpaceReturnsOnCall(i int, result1 erro }{result1} } -func (fake *FakeOrganizationClient) DeleteSpace(arg1 context.Context, arg2 string) error { +func (fake *FakeOrganizationClient) DeleteSpace(arg1 context.Context, arg2 string, arg3 string) error { fake.deleteSpaceMutex.Lock() ret, specificReturn := fake.deleteSpaceReturnsOnCall[len(fake.deleteSpaceArgsForCall)] fake.deleteSpaceArgsForCall = append(fake.deleteSpaceArgsForCall, struct { arg1 context.Context arg2 string - }{arg1, arg2}) + arg3 string + }{arg1, arg2, arg3}) stub := fake.DeleteSpaceStub fakeReturns := fake.deleteSpaceReturns - fake.recordInvocation("DeleteSpace", []interface{}{arg1, arg2}) + fake.recordInvocation("DeleteSpace", []interface{}{arg1, arg2, arg3}) fake.deleteSpaceMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1 @@ -389,17 +392,17 @@ func (fake *FakeOrganizationClient) DeleteSpaceCallCount() int { return len(fake.deleteSpaceArgsForCall) } -func (fake *FakeOrganizationClient) DeleteSpaceCalls(stub func(context.Context, string) error) { +func (fake *FakeOrganizationClient) DeleteSpaceCalls(stub func(context.Context, string, string) error) { fake.deleteSpaceMutex.Lock() defer fake.deleteSpaceMutex.Unlock() fake.DeleteSpaceStub = stub } -func (fake *FakeOrganizationClient) DeleteSpaceArgsForCall(i int) (context.Context, string) { +func (fake *FakeOrganizationClient) DeleteSpaceArgsForCall(i int) (context.Context, string, string) { fake.deleteSpaceMutex.RLock() defer fake.deleteSpaceMutex.RUnlock() argsForCall := fake.deleteSpaceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *FakeOrganizationClient) DeleteSpaceReturns(result1 error) { @@ -490,21 +493,22 @@ func (fake *FakeOrganizationClient) GetSpaceReturnsOnCall(i int, result1 *facade }{result1, result2} } -func (fake *FakeOrganizationClient) UpdateSpace(arg1 context.Context, arg2 string, arg3 string, arg4 int64) error { +func (fake *FakeOrganizationClient) UpdateSpace(arg1 context.Context, arg2 string, arg3 string, arg4 string, arg5 int64) error { fake.updateSpaceMutex.Lock() ret, specificReturn := fake.updateSpaceReturnsOnCall[len(fake.updateSpaceArgsForCall)] fake.updateSpaceArgsForCall = append(fake.updateSpaceArgsForCall, struct { arg1 context.Context arg2 string arg3 string - arg4 int64 - }{arg1, arg2, arg3, arg4}) + arg4 string + arg5 int64 + }{arg1, arg2, arg3, arg4, arg5}) stub := fake.UpdateSpaceStub fakeReturns := fake.updateSpaceReturns - fake.recordInvocation("UpdateSpace", []interface{}{arg1, arg2, arg3, arg4}) + fake.recordInvocation("UpdateSpace", []interface{}{arg1, arg2, arg3, arg4, arg5}) fake.updateSpaceMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3, arg4) + return stub(arg1, arg2, arg3, arg4, arg5) } if specificReturn { return ret.result1 @@ -518,17 +522,17 @@ func (fake *FakeOrganizationClient) UpdateSpaceCallCount() int { return len(fake.updateSpaceArgsForCall) } -func (fake *FakeOrganizationClient) UpdateSpaceCalls(stub func(context.Context, string, string, int64) error) { +func (fake *FakeOrganizationClient) UpdateSpaceCalls(stub func(context.Context, string, string, string, int64) error) { fake.updateSpaceMutex.Lock() defer fake.updateSpaceMutex.Unlock() fake.UpdateSpaceStub = stub } -func (fake *FakeOrganizationClient) UpdateSpaceArgsForCall(i int) (context.Context, string, string, int64) { +func (fake *FakeOrganizationClient) UpdateSpaceArgsForCall(i int) (context.Context, string, string, string, int64) { fake.updateSpaceMutex.RLock() defer fake.updateSpaceMutex.RUnlock() argsForCall := fake.updateSpaceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 } func (fake *FakeOrganizationClient) UpdateSpaceReturns(result1 error) { From 018d7341a9b748847e4f0d46768e4a1abe98e5f8 Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Mon, 16 Sep 2024 13:17:46 +0530 Subject: [PATCH 37/63] caching implementation and tests for space user role relationship --- internal/cf/binding.go | 2 +- internal/cf/client.go | 95 +++++++- internal/cf/client_test.go | 74 +++++-- internal/cf/instance.go | 2 +- internal/cf/resourcecache.go | 175 ++++++++++----- internal/cf/resourcecache_test.go | 354 +++++++++++++++++++----------- internal/cf/space.go | 19 +- 7 files changed, 504 insertions(+), 217 deletions(-) diff --git a/internal/cf/binding.go b/internal/cf/binding.go index 9c9c836..58c8868 100644 --- a/internal/cf/binding.go +++ b/internal/cf/binding.go @@ -53,7 +53,7 @@ func (c *spaceClient) GetBinding(ctx context.Context, bindingOpts map[string]str if c.resourceCache.isCacheExpired(serviceBindings) { //TODO: remove after internal review fmt.Println("Cache is expired for binding") - populateResourceCache[*spaceClient](c, serviceBindings) + populateResourceCache[*spaceClient](c, serviceBindings, "") } if len(c.resourceCache.getCachedBindings()) != 0 { binding, bindingInCache := c.resourceCache.getBindingFromCache(bindingOpts["owner"]) diff --git a/internal/cf/client.go b/internal/cf/client.go index 00d5a7c..7e6912d 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -9,6 +9,7 @@ import ( "context" "fmt" "log" + "strings" "sync" cfclient "github.com/cloudfoundry-community/go-cfclient/v3/client" @@ -63,6 +64,7 @@ var ( refreshServiceInstanceResourceCacheMutex = &sync.Mutex{} refreshSpaceResourceCacheMutex = &sync.Mutex{} refreshServiceBindingResourceCacheMutex = &sync.Mutex{} + refreshSpaceUserRoleCacheMutex = &sync.Mutex{} ) var ( @@ -176,7 +178,8 @@ func NewOrganizationClient(organizationName string, url string, username string, if config.IsResourceCacheEnabled && client.resourceCache == nil { client.resourceCache = initAndConfigureResourceCache(config) - populateResourceCache(client, spaces) + populateResourceCache(client, spaces, "") + populateResourceCache(client, spaceUserRoles, username) cfResourceCache = client.resourceCache } @@ -214,8 +217,8 @@ func NewSpaceClient(spaceGuid string, url string, username string, password stri if config.IsResourceCacheEnabled && client.resourceCache == nil { client.resourceCache = initAndConfigureResourceCache(config) - populateResourceCache(client, serviceInstances) - populateResourceCache(client, serviceBindings) + populateResourceCache(client, serviceInstances, "") + populateResourceCache(client, serviceBindings, "") cfResourceCache = client.resourceCache } @@ -263,6 +266,7 @@ type ResourceServicesClient[T any] interface { type ResourceSpaceClient[T any] interface { populateSpaces(ctx context.Context) error + populateSpaceUserRoleCache(ctx context.Context, username string) error manageResourceCache } @@ -270,7 +274,7 @@ type manageResourceCache interface { resetCache(resourceType cacheResourceType) } -func populateResourceCache[T manageResourceCache](c T, resourceType cacheResourceType) { +func populateResourceCache[T manageResourceCache](c T, resourceType cacheResourceType, username string) { ctx := context.Background() var err error @@ -293,6 +297,11 @@ func populateResourceCache[T manageResourceCache](c T, resourceType cacheResourc //TODO:remove later fmt.Println("populate service binding finished") } + case spaceUserRoles: + if client, ok := any(c).(ResourceSpaceClient[T]); ok { + err = client.populateSpaceUserRoleCache(ctx, username) + fmt.Println("populate service spaceUserRoles finished") + } } if err != nil { @@ -310,6 +319,8 @@ func (c *spaceClient) populateServiceInstances(ctx context.Context) error { if c.resourceCache.isCacheExpired(serviceInstances) { instanceOptions := cfclient.NewServiceInstanceListOptions() instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) + instanceOptions.Page = 1 + instanceOptions.PerPage = 5000 cfInstances, err := c.client.ServiceInstances.ListAll(ctx, instanceOptions) //TODO:remove later after review @@ -345,6 +356,8 @@ func (c *spaceClient) populateServiceBindings(ctx context.Context) error { if c.resourceCache.isCacheExpired(serviceBindings) { bindingOptions := cfclient.NewServiceCredentialBindingListOptions() bindingOptions.ListOptions.LabelSelector.EqualTo(labelOwner) + bindingOptions.Page = 1 + bindingOptions.PerPage = 5000 cfBindings, err := c.client.ServiceCredentialBindings.ListAll(ctx, bindingOptions) //TODO:remove later after review @@ -379,6 +392,8 @@ func (c *organizationClient) populateSpaces(ctx context.Context) error { if c.resourceCache.isCacheExpired(spaces) { spaceOptions := cfclient.NewSpaceListOptions() spaceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) + spaceOptions.Page = 1 + spaceOptions.PerPage = 5000 //TODO:remove later after review fmt.Println("populate Space cache called") @@ -406,12 +421,80 @@ func (c *organizationClient) populateSpaces(ctx context.Context) error { return nil } +func (c *organizationClient) populateSpaceUserRoleCache(ctx context.Context, username string) error { + refreshSpaceUserRoleCacheMutex.Lock() + defer refreshSpaceUserRoleCacheMutex.Unlock() + + if c.resourceCache.isCacheExpired(spaceUserRoles) { + spaceOptions := cfclient.NewSpaceListOptions() + spaceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) + spaceOptions.Page = 1 + spaceOptions.PerPage = 5000 + cfSpaces, err := c.client.Spaces.ListAll(ctx, spaceOptions) + if err != nil { + return err + } + if len(cfSpaces) == 0 { + return fmt.Errorf("no user spaces found") + } + var spaceGUIDs []string + for _, cfSpace := range cfSpaces { + spaceGUIDs = append(spaceGUIDs, cfSpace.GUID) + } + + userOptions := cfclient.NewUserListOptions() + userOptions.UserNames.EqualTo(username) + userOptions.Page = 1 + userOptions.PerPage = 5000 + + users, err := c.client.Users.ListAll(ctx, userOptions) + if err != nil { + return err + } + if len(users) == 0 { + return fmt.Errorf("found no user with name: %s", username) + } else if len(users) > 1 { + return fmt.Errorf("found multiple users with name: %s (this should not be possible, actually)", username) + } + user := users[0] + + roleListOpts := cfclient.NewRoleListOptions() + roleListOpts.SpaceGUIDs.EqualTo(strings.Join(spaceGUIDs, ",")) + roleListOpts.UserGUIDs.EqualTo(user.GUID) + roleListOpts.Types.EqualTo(cfresource.SpaceRoleDeveloper.String()) + roleListOpts.Page = 1 + roleListOpts.PerPage = 5000 + cfRoles, err := c.client.Roles.ListAll(ctx, roleListOpts) + if err != nil { + return err + } + + if len(cfRoles) == 0 { + return fmt.Errorf("no RoleSpaceUser relationship found") + } + var waitGroup sync.WaitGroup + for _, cfRole := range cfRoles { + waitGroup.Add(1) + go func(cfrole *cfresource.Role) { + defer waitGroup.Done() + c.resourceCache.addSpaceUserRoleInCache(cfrole.Relationships.Space.Data.GUID, cfrole.Relationships.User.Data.GUID, username, cfrole.Type) + }(cfRole) + } + waitGroup.Wait() + c.resourceCache.setLastCacheTime(spaceUserRoles) + } + + return nil +} + +// Implementation for resetting the cache func (c *spaceClient) resetCache(resourceType cacheResourceType) { - // Implementation for resetting the cache + c.resourceCache.resetCache(resourceType) } +// Implementation for resetting the cache func (c *organizationClient) resetCache(resourceType cacheResourceType) { - // Implementation for resetting the cache + c.resourceCache.resetCache(resourceType) } diff --git a/internal/cf/client_test.go b/internal/cf/client_test.go index 3940922..7332ecb 100644 --- a/internal/cf/client_test.go +++ b/internal/cf/client_test.go @@ -38,6 +38,8 @@ const ( serviceInstancesURI = "/v3/service_instances" spaceURI = "/v3/spaces" serviceBindingURI = "/v3/service_credential_bindings" + userURI = "v3/users" + roleURI = "v3/roles" uaaURI = "/uaa/oauth/token" labelSelector = "service-operator.cf.cs.sap.com" ) @@ -143,6 +145,36 @@ var fakeSpaces = cfResource.SpaceList{ }, } +var fakeUsers = cfResource.UserList{ + Resources: []*cfResource.User{ + { + GUID: "test-user-guid-1", + Username: "testUser", + }, + }, +} + +var fakeRoles = cfResource.RoleList{ + Resources: []*cfResource.Role{ + { + GUID: "test-role-guid-1", + Type: "test-role-type-1", + Relationships: cfResource.RoleSpaceUserOrganizationRelationships{ + User: cfResource.ToOneRelationship{ + Data: &cfResource.Relationship{ + GUID: "test-user-guid-1", + }, + }, + Space: cfResource.ToOneRelationship{ + Data: &cfResource.Relationship{ + GUID: "test-space-guid-1", + }, + }, + }, + }, + }, +} + // ----------------------------------------------------------------------------------------------- // Tests // ----------------------------------------------------------------------------------------------- @@ -214,6 +246,15 @@ var _ = Describe("CF Client tests", Ordered, func() { server.RouteToHandler("GET", spaceURI, ghttp.CombineHandlers( ghttp.RespondWithJSONEncodedPtr(&statusCode, &fakeSpaces), )) + // - Fake response for get users + server.RouteToHandler("GET", "/v3/users", ghttp.CombineHandlers( + ghttp.RespondWithJSONEncodedPtr(&statusCode, &fakeUsers), + )) + // - Fake response for get roles + server.RouteToHandler("GET", "/v3/roles", ghttp.CombineHandlers( + ghttp.RespondWithJSONEncodedPtr(&statusCode, &fakeRoles), + )) + }) It("should create OrgClient", func() { @@ -351,7 +392,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(orgClient).ToNot(BeNil()) // Verify resource cache is populated during client creation - Expect(server.ReceivedRequests()).To(HaveLen(3)) + Expect(server.ReceivedRequests()).To(HaveLen(6)) // - Discover UAA endpoint Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) @@ -361,33 +402,40 @@ var _ = Describe("CF Client tests", Ordered, func() { // - Populate cache with spaces Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(spaceURI)) + //- Populate cache with spaces,user and role + Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring(spaceURI)) + Expect(server.ReceivedRequests()[4].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[4].RequestURI).To(ContainSubstring(userURI)) + Expect(server.ReceivedRequests()[5].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[5].RequestURI).To(ContainSubstring(roleURI)) // Make a request and verify that cache is used and no additional requests expected orgClient.GetSpace(ctx, Owner) - Expect(server.ReceivedRequests()).To(HaveLen(3)) // still same as above + Expect(server.ReceivedRequests()).To(HaveLen(6)) // still same as above // Make another request after cache expired and verify that cache is repopulated time.Sleep(10 * time.Second) orgClient.GetSpace(ctx, Owner) - Expect(server.ReceivedRequests()).To(HaveLen(4)) // one more request to repopulate cache - Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring(spaceURI)) - Expect(server.ReceivedRequests()[3].RequestURI).NotTo(ContainSubstring(Owner)) + Expect(server.ReceivedRequests()).To(HaveLen(7)) // one more request to repopulate cache + Expect(server.ReceivedRequests()[6].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[6].RequestURI).To(ContainSubstring(spaceURI)) + Expect(server.ReceivedRequests()[6].RequestURI).NotTo(ContainSubstring(Owner)) // Delete space from cache err = orgClient.DeleteSpace(ctx, Owner, "test-space-guid-1") Expect(err).To(BeNil()) - Expect(server.ReceivedRequests()).To(HaveLen(5)) + Expect(server.ReceivedRequests()).To(HaveLen(8)) // - Delete space from cache - Expect(server.ReceivedRequests()[4].Method).To(Equal("DELETE")) - Expect(server.ReceivedRequests()[4].RequestURI).To(ContainSubstring("test-space-guid-1")) + Expect(server.ReceivedRequests()[7].Method).To(Equal("DELETE")) + Expect(server.ReceivedRequests()[7].RequestURI).To(ContainSubstring("test-space-guid-1")) // Get space from cache should return empty orgClient.GetSpace(ctx, Owner) - Expect(server.ReceivedRequests()).To(HaveLen(6)) + Expect(server.ReceivedRequests()).To(HaveLen(9)) // - Get call to cf to get the space - Expect(server.ReceivedRequests()[5].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[5].RequestURI).To(ContainSubstring(Owner)) + Expect(server.ReceivedRequests()[8].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[8].RequestURI).To(ContainSubstring(Owner)) }) }) @@ -593,7 +641,7 @@ var _ = Describe("CF Client tests", Ordered, func() { It("should initialize resource cache after start and on cache expiry", func() { // Enable resource cache in config clientConfig.IsResourceCacheEnabled = true - clientConfig.CacheTimeOut = "8s" // short duration for fast test + clientConfig.CacheTimeOut = "5s" // short duration for fast test //resource cache will be initialized only only once, so we need to wait till the cache expiry from previous test time.Sleep(10 * time.Second) diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 80db819..adf4aa5 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -52,7 +52,7 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s if c.resourceCache.isCacheExpired("serviceInstances") { //TODO: remove after internal review fmt.Println("Cache is expired for instance so populating cache") - populateResourceCache[*spaceClient](c, "serviceInstances") + populateResourceCache[*spaceClient](c, "serviceInstances", "") } if len(c.resourceCache.getCachedInstances()) != 0 { instance, instanceInCache := c.resourceCache.getInstanceFromCache(instanceOpts["owner"]) diff --git a/internal/cf/resourcecache.go b/internal/cf/resourcecache.go index 359e66f..074a75b 100644 --- a/internal/cf/resourcecache.go +++ b/internal/cf/resourcecache.go @@ -25,10 +25,19 @@ type resourceCache struct { bindings map[string]*facade.Binding mutex sync.RWMutex cacheTimeOut time.Duration - lastServiceInstanceCacheTime time.Time - lastSpaceCacheTime time.Time - lastServiceBindingCacheTime time.Time + serviceInstanceLastCacheTime time.Time + spaceLastaCheTime time.Time + serviceBindingLastCacheTime time.Time + spaceUserRoleLastCacheTime time.Time isResourceCacheEnabled bool + spaceUserRole map[string]*spaceUserRole +} + +type spaceUserRole struct { + user string + spaceGuid string + userGUID string + roleType string } type cacheResourceType string @@ -37,20 +46,36 @@ const ( serviceInstances cacheResourceType = "serviceInstances" spaces cacheResourceType = "spaces" serviceBindings cacheResourceType = "serviceBindings" + spaceUserRoles cacheResourceType = "spaceUserRole" ) // InitResourcesCache initializes a new cache func initResourceCache() *resourceCache { cache := &resourceCache{ - spaces: make(map[string]*facade.Space), - instances: make(map[string]*facade.Instance), - bindings: make(map[string]*facade.Binding), + spaces: make(map[string]*facade.Space), + instances: make(map[string]*facade.Instance), + bindings: make(map[string]*facade.Binding), + spaceUserRole: make(map[string]*spaceUserRole), } return cache } +// setResourceCacheEnabled enables or disables the resource cahce +func (c *resourceCache) setResourceCacheEnabled(enabled bool) { + c.isResourceCacheEnabled = enabled +} + +// checkResourceCacheEnabled checks if the resource cache is enabled (object might be nil) +func (c *resourceCache) checkResourceCacheEnabled() bool { + if c == nil { + log.Println("Resource cache is nil") + return false + } + return c.isResourceCacheEnabled +} + // setCacheTimeOut sets the timeout used for expiration of the cache func (c *resourceCache) setCacheTimeOut(timeOut string) { cacheTimeOut, err := time.ParseDuration(timeOut) @@ -62,21 +87,35 @@ func (c *resourceCache) setCacheTimeOut(timeOut string) { c.cacheTimeOut = cacheTimeOut } -// // isCacheExpired checks if the cache is already expired -// -// func (c *resourceCache) isCacheExpired() bool { -// expirationTime := c.lastCacheTime.Add(c.cacheTimeOut) -// return time.Now().After(expirationTime) -// } +// setLastCacheTime sets the last cache time for a specific resource type +func (c *resourceCache) setLastCacheTime(resourceType cacheResourceType) { + now := time.Now() + switch resourceType { + case serviceBindings: + c.serviceBindingLastCacheTime = now + case serviceInstances: + c.serviceInstanceLastCacheTime = now + case spaces: + c.spaceLastaCheTime = now + case spaceUserRoles: + c.spaceUserRoleLastCacheTime = now + } + //TODO:remove later + fmt.Printf("Last cache time for %s: %v\n", resourceType, now) +} + +// isCacheExpired checks if the cache is expired for a specific resource type func (c *resourceCache) isCacheExpired(resourceType cacheResourceType) bool { var lastCacheTime time.Time switch resourceType { case serviceInstances: - lastCacheTime = c.lastServiceInstanceCacheTime + lastCacheTime = c.serviceInstanceLastCacheTime case spaces: - lastCacheTime = c.lastSpaceCacheTime + lastCacheTime = c.spaceLastaCheTime case serviceBindings: - lastCacheTime = c.lastServiceBindingCacheTime + lastCacheTime = c.serviceBindingLastCacheTime + case spaceUserRoles: + lastCacheTime = c.spaceUserRoleLastCacheTime } // Ensure lastCacheTime is properly initialized @@ -86,38 +125,35 @@ func (c *resourceCache) isCacheExpired(resourceType cacheResourceType) bool { expirationTime := lastCacheTime.Add(c.cacheTimeOut) //TODO:remove later - fmt.Printf("Expiration time for %s: %v\n", resourceType, expirationTime) - - return time.Now().After(expirationTime) + fmt.Printf("Expiration time for %s: %v and last cached time: %v and timenow :%v\n", resourceType, expirationTime, lastCacheTime, time.Now()) + bool := time.Now().After(expirationTime) + return bool } -func (c *resourceCache) setLastCacheTime(resourceType cacheResourceType) { - now := time.Now() +// reset cache of a specific resource type and last cache time +func (c *resourceCache) resetCache(resourceType cacheResourceType) { + + fmt.Printf("reset requested for %v \n", resourceType) switch resourceType { case serviceInstances: - c.lastServiceInstanceCacheTime = now + c.instances = make(map[string]*facade.Instance) + c.serviceInstanceLastCacheTime = time.Now() case spaces: - c.lastSpaceCacheTime = now + c.spaces = make(map[string]*facade.Space) + c.spaceLastaCheTime = time.Now() case serviceBindings: - c.lastServiceBindingCacheTime = now - } - log.Printf("Last cache time for %s: %v\n", resourceType, now) - //TODO:remove later - fmt.Printf("Last cache time for %s: %v\n", resourceType, now) -} + c.bindings = make(map[string]*facade.Binding) + c.serviceBindingLastCacheTime = time.Now() + case spaceUserRoles: + c.spaceUserRole = make(map[string]*spaceUserRole) + c.spaceUserRoleLastCacheTime = time.Now() -// setResourceCacheEnabled enables or disables the resource cahce -func (c *resourceCache) setResourceCacheEnabled(enabled bool) { - c.isResourceCacheEnabled = enabled + } } -// checkResourceCacheEnabled checks if the resource cache is enabled (object might be nil) -func (c *resourceCache) checkResourceCacheEnabled() bool { - if c == nil { - log.Println("Resource cache is nil") - return false - } - return c.isResourceCacheEnabled +// getCachedInstances retrieves instances from the cache +func (c *resourceCache) getCachedInstances() map[string]*facade.Instance { + return c.instances } // addInstanceInCache stores an instance in the cache @@ -177,10 +213,6 @@ func (c *resourceCache) updateInstanceInCache(owner string, name string, service } -func (c *resourceCache) getCachedInstances() map[string]*facade.Instance { - return c.instances -} - // getBindingFromCache retrieves binding from the cache func (c *resourceCache) getCachedBindings() map[string]*facade.Binding { return c.bindings @@ -237,6 +269,11 @@ func (c *resourceCache) updateBindingInCache(owner string, parameters map[string } +// getCachedSpaces retrieves spaces from the cache +func (c *resourceCache) getCachedSpaces() map[string]*facade.Space { + return c.spaces +} + // AddSpaceInCache stores a space in the cache func (c *resourceCache) addSpaceInCache(key string, space *facade.Space) { c.mutex.Lock() @@ -266,11 +303,6 @@ func (c *resourceCache) deleteSpaceFromCache(key string) { } -// getCachedSpaces retrieves spaces from the cache -func (c *resourceCache) getCachedSpaces() map[string]*facade.Space { - return c.spaces -} - // updateSpaceInCache updates an space in the cache func (c *resourceCache) updateSpaceInCache(owner string, name string, generation int64) (status bool) { c.mutex.Lock() @@ -292,20 +324,41 @@ func (c *resourceCache) updateSpaceInCache(owner string, name string, generation } -// reset cache of a specific resource type and last cache time -func (c *resourceCache) resetCache(resourceType cacheResourceType) { - - fmt.Printf("reset requested for %v \n", resourceType) - switch resourceType { - case serviceInstances: - c.instances = make(map[string]*facade.Instance) - c.lastServiceInstanceCacheTime = time.Now() - case spaces: - c.spaces = make(map[string]*facade.Space) - c.lastSpaceCacheTime = time.Now() - case serviceBindings: - c.bindings = make(map[string]*facade.Binding) - c.lastServiceBindingCacheTime = time.Now() +// list all cached spaceuserroles in cache +func (c *resourceCache) getCachedSpaceUserRoles() map[string]*spaceUserRole { + return c.spaceUserRole +} +// add spaceuserrole to cache +func (c *resourceCache) addSpaceUserRoleInCache(spaceGuid string, userGuid string, username string, roleType string) { + c.mutex.Lock() + defer c.mutex.Unlock() + role := &spaceUserRole{ + user: username, + spaceGuid: spaceGuid, + userGUID: userGuid, + roleType: roleType, } + c.spaceUserRole[spaceGuid] = role + // TODO :remove After internal review + fmt.Printf("Added the space user role to Cache: %v \n", role) +} + +// get spaceuserrole in cache +func (c *resourceCache) getSpaceUserRoleFromCache(key string) (*spaceUserRole, bool) { + c.mutex.RLock() + defer c.mutex.RUnlock() + spaceUserRole, found := c.spaceUserRole[key] + // TODO :remove After internal review + fmt.Printf("Got the space user role from Cache: %v \n", spaceUserRole) + return spaceUserRole, found +} + +// delete spaceuserrole not present in the cache +func (c *resourceCache) deleteSpaceUserRoleFromCache(spaceGuid string) { + c.mutex.Lock() + defer c.mutex.Unlock() + delete(c.spaceUserRole, spaceGuid) + // TODO :remove After internal review + fmt.Printf("Deleted the space user role from Cache: %v \n", spaceGuid) } diff --git a/internal/cf/resourcecache_test.go b/internal/cf/resourcecache_test.go index a7c871a..be47454 100644 --- a/internal/cf/resourcecache_test.go +++ b/internal/cf/resourcecache_test.go @@ -25,6 +25,11 @@ var _ = Describe("ResourceCache", func() { var instance *facade.Instance var binding *facade.Binding var space *facade.Space + var testspaceUserRole *spaceUserRole + var testBindingOwnerkey string + var testInstanceOwnerkey string + var testSpaceOwnerKey string + var testSpaceGuidKey string var wg sync.WaitGroup concurrencyLevel := 20 @@ -32,109 +37,116 @@ var _ = Describe("ResourceCache", func() { cache = initResourceCache() instance = &facade.Instance{ - Guid: "guid1", - Name: "name1", - Owner: "instanceOwner1", - ServicePlanGuid: "plan1", - ParameterHash: "hash1", + Guid: "testGuid", + Name: "testName", + Owner: "testInstanceOwner", + ServicePlanGuid: "testPlan", + ParameterHash: "testHash", Generation: 1, } binding = &facade.Binding{ - Guid: "guid1", - Name: "name1", - Owner: "bindingOwner1", - ParameterHash: "hash1", + Guid: "testGuid", + Name: "testName", + Owner: "testBindingOwner", + ParameterHash: "testHash", Generation: 1, State: facade.BindingStateReady, StateDescription: "", } space = &facade.Space{ - Guid: "guid1", - Name: "name1", - Owner: "spaceOwner1", + Guid: "testGuid", + Name: "testName", + Owner: "testSpaceOwner", Generation: 1, } + + testspaceUserRole = &spaceUserRole{ + user: "testUsername", + spaceGuid: "testSpaceGuid", + userGUID: "testUserGuid", + roleType: "developer", + } + testInstanceOwnerkey = "testInstanceOwner" + testBindingOwnerkey = "testBindingOwner" + testSpaceOwnerKey = "testSpaceOwner" + testSpaceGuidKey = "testSpaceGuid" + }) - Context("basic instance CRUD operations", func() { + Context("service instance basic CRUD operation test cases", func() { It("should add, get, update, and delete an instance in the cache", func() { // Add instance - Ownerkey := "instanceOwner1" - cache.addInstanceInCache(Ownerkey, instance) + cache.addInstanceInCache(testInstanceOwnerkey, instance) // Get instance - retrievedInstance, found := cache.getInstanceFromCache(Ownerkey) + retrievedInstance, found := cache.getInstanceFromCache(testInstanceOwnerkey) Expect(found).To(BeTrue()) Expect(retrievedInstance).To(Equal(instance)) // Update instance updatedInstance := &facade.Instance{ - Guid: "guid1", + Guid: "testGuid", Name: "updatedInstanceName", - Owner: "instanceOwner1", + Owner: testInstanceOwnerkey, ServicePlanGuid: "updatedPlan", - ParameterHash: "hash1", + ParameterHash: "testHash", Generation: 2, } - cache.updateInstanceInCache("instanceOwner1", "updatedInstanceName", "updatedPlan", nil, 2) - retrievedInstance, found = cache.getInstanceFromCache(Ownerkey) + cache.updateInstanceInCache(testInstanceOwnerkey, "updatedInstanceName", "updatedPlan", nil, 2) + retrievedInstance, found = cache.getInstanceFromCache(testInstanceOwnerkey) Expect(found).To(BeTrue()) Expect(retrievedInstance).To(Equal(updatedInstance)) // Delete instance - cache.deleteInstanceFromCache(Ownerkey) - _, found = cache.getInstanceFromCache(Ownerkey) + cache.deleteInstanceFromCache(testInstanceOwnerkey) + _, found = cache.getInstanceFromCache(testInstanceOwnerkey) Expect(found).To(BeFalse()) }) - }) - Context("instance edge cases", func() { It("should handle adding an instance with an existing key", func() { instance2 := &facade.Instance{ - Guid: "guid2", - Name: "name2", - Owner: "instanceOwner1", - ServicePlanGuid: "plan2", - ParameterHash: "hash2", + Guid: "testguid2", + Name: "testname2", + Owner: testInstanceOwnerkey, + ServicePlanGuid: "testplan2", + ParameterHash: "testhash2", Generation: 1, } - cache.addInstanceInCache("instanceOwner1", instance) - cache.addInstanceInCache("instanceOwner1", instance2) - retrievedInstance, found := cache.getInstanceFromCache("instanceOwner1") + cache.addInstanceInCache(testInstanceOwnerkey, instance) + cache.addInstanceInCache(testInstanceOwnerkey, instance2) + retrievedInstance, found := cache.getInstanceFromCache(testInstanceOwnerkey) Expect(found).To(BeTrue()) Expect(retrievedInstance).To(Equal(instance2)) }) It("should handle updating a non-existent instance", func() { - cache.updateInstanceInCache("nonExistentOwner", "name", "plan", nil, 1) + cache.updateInstanceInCache("nonExistentInstanceOwner", "name", "plan", nil, 1) _, found := cache.getInstanceFromCache("owner") Expect(found).To(BeFalse()) }) It("should handle deleting a non-existent instance", func() { - cache.deleteInstanceFromCache("nonExistentOwner") - _, found := cache.getInstanceFromCache("nonExistentOwner") + cache.deleteInstanceFromCache("nonExistentInstanceOwner") + _, found := cache.getInstanceFromCache("nonExistentInstanceOwner") Expect(found).To(BeFalse()) }) - }) - Context("concurrent instance CRUD operations, data integrity and load test", func() { - It("should handle concurrent ", func() { + It("concurrent instance CRUD operations, data integrity and load test", func() { for i := 0; i < concurrencyLevel; i++ { wg.Add(1) go func(i int) { defer wg.Done() instance := &facade.Instance{ - Guid: "guid" + strconv.Itoa(i), - Name: "name" + strconv.Itoa(i), - Owner: "instanceOwner" + strconv.Itoa(i), - ServicePlanGuid: "plan" + strconv.Itoa(i), + Guid: "testGuid" + strconv.Itoa(i), + Name: "testName" + strconv.Itoa(i), + Owner: testInstanceOwnerkey + strconv.Itoa(i), + ServicePlanGuid: "testPlan" + strconv.Itoa(i), ParameterHash: "hash", Generation: 1, } - cache.addInstanceInCache("instanceOwner"+strconv.Itoa(i), instance) + cache.addInstanceInCache(testInstanceOwnerkey+strconv.Itoa(i), instance) }(i) } wg.Wait() @@ -144,12 +156,12 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - key := "instanceOwner" + strconv.Itoa(i) + key := testInstanceOwnerkey + strconv.Itoa(i) instance := &facade.Instance{ - Guid: "guid" + strconv.Itoa(i), - Name: "name" + strconv.Itoa(i), - Owner: "instanceOwner" + strconv.Itoa(i), - ServicePlanGuid: "plan" + strconv.Itoa(i), + Guid: "testGuid" + strconv.Itoa(i), + Name: "testName" + strconv.Itoa(i), + Owner: testInstanceOwnerkey + strconv.Itoa(i), + ServicePlanGuid: "testPlan" + strconv.Itoa(i), ParameterHash: "hash", Generation: 1, } @@ -165,16 +177,16 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - cache.updateInstanceInCache("instanceOwner"+strconv.Itoa(i), "updatedName"+strconv.Itoa(i), "updatedPlan"+strconv.Itoa(i), nil, 1) + cache.updateInstanceInCache(testInstanceOwnerkey+strconv.Itoa(i), "updatedName"+strconv.Itoa(i), "updatedPlan"+strconv.Itoa(i), nil, 1) }(i) } wg.Wait() // Verify that all instances have been updated in the cache for i := 0; i < concurrencyLevel; i++ { - key := "instanceOwner" + strconv.Itoa(i) + key := testInstanceOwnerkey + strconv.Itoa(i) expectedInstance := &facade.Instance{ - Guid: "guid" + strconv.Itoa(i), + Guid: "testGuid" + strconv.Itoa(i), Name: "updatedName" + strconv.Itoa(i), Owner: key, ServicePlanGuid: "updatedPlan" + strconv.Itoa(i), @@ -192,100 +204,94 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - cache.deleteInstanceFromCache("instanceOwner" + strconv.Itoa(i)) + cache.deleteInstanceFromCache(testInstanceOwnerkey + strconv.Itoa(i)) }(i) } wg.Wait() // Verify final state for i := 0; i < concurrencyLevel; i++ { - _, found := cache.getInstanceFromCache("instanceOwner" + strconv.Itoa(i)) + _, found := cache.getInstanceFromCache(testInstanceOwnerkey + strconv.Itoa(i)) Expect(found).To(BeFalse(), "Expected instance to be deleted from cache") } }) }) - // tests for service binding cache - Context("basic binding CRUD operations", func() { + Context("service binding basic CRUD operation test ", func() { It("should add, get, update, and delete a binding in the cache", func() { // Add binding - Ownerkey := "bindingOwner1" - cache.addBindingInCache(Ownerkey, binding) + cache.addBindingInCache(testBindingOwnerkey, binding) // Get binding - retrievedBinding, found := cache.getBindingFromCache(Ownerkey) + retrievedBinding, found := cache.getBindingFromCache(testBindingOwnerkey) Expect(found).To(BeTrue()) Expect(retrievedBinding).To(Equal(binding)) // Update binding updatedBinding := &facade.Binding{ - Guid: "guid1", - Name: "name1", - Owner: "bindingOwner1", - ParameterHash: "hash1", + Guid: "testGuid", + Name: "testName", + Owner: testBindingOwnerkey, + ParameterHash: "testHash", Generation: 2, State: facade.BindingStateReady, StateDescription: "", } - cache.updateBindingInCache("bindingOwner1", nil, 2) - retrievedBinding, found = cache.getBindingFromCache(Ownerkey) + cache.updateBindingInCache(testBindingOwnerkey, nil, 2) + retrievedBinding, found = cache.getBindingFromCache(testBindingOwnerkey) Expect(found).To(BeTrue()) Expect(retrievedBinding).To(Equal(updatedBinding)) // Delete binding - cache.deleteBindingFromCache(Ownerkey) - _, found = cache.getBindingFromCache(Ownerkey) + cache.deleteBindingFromCache(testBindingOwnerkey) + _, found = cache.getBindingFromCache(testBindingOwnerkey) Expect(found).To(BeFalse()) }) - }) - Context("edge cases", func() { It("should handle adding a binding with an existing key", func() { binding2 := &facade.Binding{ - Guid: "guid2", - Name: "newname2", - Owner: "bindingOwner1", - ParameterHash: "hash1", + Guid: "testGuid2", + Name: "testName2", + Owner: testBindingOwnerkey, + ParameterHash: "testHash", Generation: 2, State: facade.BindingStateReady, StateDescription: "", } - cache.addBindingInCache("bindingOwner1", binding) - cache.addBindingInCache("bindingOwner1", binding2) - retrievedBinding, found := cache.getBindingFromCache("bindingOwner1") + cache.addBindingInCache(testBindingOwnerkey, binding) + cache.addBindingInCache(testBindingOwnerkey, binding2) + retrievedBinding, found := cache.getBindingFromCache(testBindingOwnerkey) Expect(found).To(BeTrue()) Expect(retrievedBinding).To(Equal(binding2)) }) It("should handle updating a non-existent binding", func() { - cache.updateBindingInCache("nonExistOwner", nil, 1) - _, found := cache.getBindingFromCache("nonExistOwner") + cache.updateBindingInCache("nonExistBindingOwner", nil, 1) + _, found := cache.getBindingFromCache("nonExistBindingOwner") Expect(found).To(BeFalse()) }) It("should handle deleting a non-existent binding", func() { - cache.deleteBindingFromCache("nonExistOwner") - _, found := cache.getBindingFromCache("nonExistOwner") + cache.deleteBindingFromCache("nonExistBindingOwner") + _, found := cache.getBindingFromCache("nonExistBindingOwner") Expect(found).To(BeFalse()) }) - }) - Context("concurrent CRUD operations, data integrity and load test", func() { - It("should handle concurrent ", func() { + It("service binding concurrent CRUD operations, data integrity and load test ", func() { for i := 0; i < concurrencyLevel; i++ { wg.Add(1) go func(i int) { defer wg.Done() binding := &facade.Binding{ - Guid: "guid" + strconv.Itoa(i), - Name: "name" + strconv.Itoa(i), - Owner: "bindingOwner" + strconv.Itoa(i), - ParameterHash: "hash", + Guid: "testGuid" + strconv.Itoa(i), + Name: "testName" + strconv.Itoa(i), + Owner: testBindingOwnerkey + strconv.Itoa(i), + ParameterHash: "testhash", Generation: 1, State: facade.BindingStateReady, StateDescription: "", } - cache.addBindingInCache("bindingOwner"+strconv.Itoa(i), binding) + cache.addBindingInCache(testBindingOwnerkey+strconv.Itoa(i), binding) }(i) } wg.Wait() @@ -295,12 +301,12 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - key := "bindingOwner" + strconv.Itoa(i) + key := testBindingOwnerkey + strconv.Itoa(i) binding := &facade.Binding{ - Guid: "guid" + strconv.Itoa(i), - Name: "name" + strconv.Itoa(i), - Owner: "bindingOwner" + strconv.Itoa(i), - ParameterHash: "hash", + Guid: "testGuid" + strconv.Itoa(i), + Name: "testName" + strconv.Itoa(i), + Owner: testBindingOwnerkey + strconv.Itoa(i), + ParameterHash: "testhash", Generation: 1, State: facade.BindingStateReady, StateDescription: "", @@ -317,19 +323,19 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - cache.updateBindingInCache("bindingOwner"+strconv.Itoa(i), nil, 2) + cache.updateBindingInCache(testBindingOwnerkey+strconv.Itoa(i), nil, 2) }(i) } wg.Wait() // Verify that all bindings have been updated in the cache for i := 0; i < concurrencyLevel; i++ { - key := "bindingOwner" + strconv.Itoa(i) + key := testBindingOwnerkey + strconv.Itoa(i) expectedBinding := &facade.Binding{ - Guid: "guid" + strconv.Itoa(i), - Name: "name" + strconv.Itoa(i), + Guid: "testGuid" + strconv.Itoa(i), + Name: "testName" + strconv.Itoa(i), Owner: key, - ParameterHash: "hash", + ParameterHash: "testhash", Generation: 2, State: facade.BindingStateReady, StateDescription: "", @@ -345,80 +351,75 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - cache.deleteBindingFromCache("bindingOwner" + strconv.Itoa(i)) + cache.deleteBindingFromCache(testBindingOwnerkey + strconv.Itoa(i)) }(i) } wg.Wait() // Verify final state for i := 0; i < concurrencyLevel; i++ { - _, found := cache.getBindingFromCache("bindingOwner" + strconv.Itoa(i)) + _, found := cache.getBindingFromCache(testBindingOwnerkey + strconv.Itoa(i)) Expect(found).To(BeFalse(), "Expected binding to be deleted from cache") } }) }) // tests for space cache - Context("basic CRUD operations", func() { + Context("space basic CRUD operation test cases", func() { It("should add, get, update, and delete a space in the cache", func() { // Add space - Ownerkey := "spaceOwner1" - cache.addSpaceInCache(Ownerkey, space) + cache.addSpaceInCache(testSpaceOwnerKey, space) // Get space - retrievedSpace, found := cache.getSpaceFromCache(Ownerkey) + retrievedSpace, found := cache.getSpaceFromCache(testSpaceOwnerKey) Expect(found).To(BeTrue()) Expect(retrievedSpace).To(Equal(space)) // Update space updatedSpace := &facade.Space{ - Guid: "guid1", - Name: "updatedname", - Owner: "spaceOwner1", + Guid: "testGuid", + Name: "updatedName", + Owner: testSpaceOwnerKey, Generation: 2, } - cache.updateSpaceInCache("spaceOwner1", "updatedname", 2) - retrievedSpace, found = cache.getSpaceFromCache(Ownerkey) + cache.updateSpaceInCache(testSpaceOwnerKey, "updatedName", 2) + retrievedSpace, found = cache.getSpaceFromCache(testSpaceOwnerKey) Expect(found).To(BeTrue()) Expect(retrievedSpace).To(Equal(updatedSpace)) // Delete space - cache.deleteSpaceFromCache(Ownerkey) - _, found = cache.getSpaceFromCache(Ownerkey) + cache.deleteSpaceFromCache(testSpaceOwnerKey) + _, found = cache.getSpaceFromCache(testSpaceOwnerKey) Expect(found).To(BeFalse()) }) - }) - Context("edge cases", func() { It("should handle adding a space with an existing key", func() { space2 := &facade.Space{ - Guid: "guid2", - Name: "name2", - Owner: "spaceOwner1", + Guid: "testGuid", + Name: "testName", + Owner: testSpaceOwnerKey, Generation: 2, } - cache.addSpaceInCache("spaceOwner1", space) - cache.addSpaceInCache("spaceOwner1", space2) - retrievedSpace, found := cache.getSpaceFromCache("spaceOwner1") + cache.addSpaceInCache(testSpaceOwnerKey, space) + cache.addSpaceInCache(testSpaceOwnerKey, space2) + retrievedSpace, found := cache.getSpaceFromCache(testSpaceOwnerKey) Expect(found).To(BeTrue()) Expect(retrievedSpace).To(Equal(space2)) }) It("should handle updating a non-existent space", func() { - cache.updateSpaceInCache("nonExistOwner", "name", 1) - _, found := cache.getSpaceFromCache("nonExistOwner") + cache.updateSpaceInCache("nonExistSpaceOwner", "testname", 1) + _, found := cache.getSpaceFromCache("nonExistSpaceOwner") Expect(found).To(BeFalse()) }) It("should handle deleting a non-existent space", func() { - cache.deleteSpaceFromCache("nonExistOwner") - _, found := cache.getSpaceFromCache("nonExistOwner") + cache.deleteSpaceFromCache("nonExistSpaceOwner") + _, found := cache.getSpaceFromCache("nonExistSpaceOwner") Expect(found).To(BeFalse()) }) - }) - Context("concurrent CRUD operations, data integrity and load test", func() { - It("should handle concurrent ", func() { + It("concurrent CRUD operations, data integrity and load test", func() { for i := 0; i < concurrencyLevel; i++ { wg.Add(1) go func(i int) { @@ -426,10 +427,10 @@ var _ = Describe("ResourceCache", func() { space := &facade.Space{ Guid: "guid" + strconv.Itoa(i), Name: "name" + strconv.Itoa(i), - Owner: "spaceOwner" + strconv.Itoa(i), + Owner: testSpaceOwnerKey + strconv.Itoa(i), Generation: 1, } - cache.addSpaceInCache("spaceOwner"+strconv.Itoa(i), space) + cache.addSpaceInCache(testSpaceOwnerKey+strconv.Itoa(i), space) }(i) } wg.Wait() @@ -439,11 +440,11 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - key := "spaceOwner" + strconv.Itoa(i) + key := testSpaceOwnerKey + strconv.Itoa(i) space := &facade.Space{ Guid: "guid" + strconv.Itoa(i), Name: "name" + strconv.Itoa(i), - Owner: "spaceOwner" + strconv.Itoa(i), + Owner: testSpaceOwnerKey + strconv.Itoa(i), Generation: 1, } retrievedSpace, found := cache.getSpaceFromCache(key) @@ -458,14 +459,14 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - cache.updateSpaceInCache("spaceOwner"+strconv.Itoa(i), "updatedname"+strconv.Itoa(i), 2) + cache.updateSpaceInCache(testSpaceOwnerKey+strconv.Itoa(i), "updatedname"+strconv.Itoa(i), 2) }(i) } wg.Wait() // Verify that all spaces have been updated in the cache for i := 0; i < concurrencyLevel; i++ { - key := "spaceOwner" + strconv.Itoa(i) + key := testSpaceOwnerKey + strconv.Itoa(i) expectedSpace := &facade.Space{ Guid: "guid" + strconv.Itoa(i), Name: "updatedname" + strconv.Itoa(i), @@ -483,16 +484,101 @@ var _ = Describe("ResourceCache", func() { wg.Add(1) go func(i int) { defer wg.Done() - cache.deleteSpaceFromCache("spaceOwner" + strconv.Itoa(i)) + cache.deleteSpaceFromCache(testSpaceOwnerKey + strconv.Itoa(i)) }(i) } wg.Wait() // Verify final state for i := 0; i < concurrencyLevel; i++ { - _, found := cache.getSpaceFromCache("spaceOwner" + strconv.Itoa(i)) + _, found := cache.getSpaceFromCache(testSpaceOwnerKey + strconv.Itoa(i)) Expect(found).To(BeFalse(), "Expected space to be deleted from cache") } }) }) + + Context("space user role relationship basic CRUD operation test cases", func() { + It("should add, get, update, and delete a space user role in the cache", func() { + // Add space user role + + cache.addSpaceUserRoleInCache(testSpaceGuidKey, "testUserGuid", "testUsername", "developer") + + // Get space user role + retrievedSpaceUserRole, found := cache.getSpaceUserRoleFromCache(testSpaceGuidKey) + Expect(found).To(BeTrue()) + Expect(retrievedSpaceUserRole).To(Equal(testspaceUserRole)) + + // Delete space user role + cache.deleteSpaceUserRoleFromCache(testSpaceGuidKey) + _, found = cache.getSpaceUserRoleFromCache(testSpaceGuidKey) + Expect(found).To(BeFalse()) + }) + + It("should handle adding a space user role with an existing key", func() { + spaceUserRole2 := &spaceUserRole{ + user: "testUsername2", + spaceGuid: testSpaceGuidKey, + userGUID: "testUserGuid2", + roleType: "developer", + } + cache.addSpaceUserRoleInCache(testSpaceGuidKey, "testUserGuid", "testUsername", "developer") + cache.addSpaceUserRoleInCache(testSpaceGuidKey, "testUserGuid2", "testUsername2", "developer") + retrievedSpaceUserRole, found := cache.getSpaceUserRoleFromCache(testSpaceGuidKey) + Expect(found).To(BeTrue()) + Expect(retrievedSpaceUserRole).To(Equal(spaceUserRole2)) + + }) + + It("should handle deleting a non-existent space user role", func() { + cache.deleteSpaceUserRoleFromCache("nonExistSpaceUserRole") + _, found := cache.getSpaceUserRoleFromCache("nonExistSpaceUserRole") + Expect(found).To(BeFalse()) + }) + + It("concurrent CRUD operations, data integrity and load test", func() { + for i := 0; i < concurrencyLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + cache.addSpaceUserRoleInCache(testSpaceGuidKey+strconv.Itoa(i), "testUserGuid"+strconv.Itoa(i), "testUsername"+strconv.Itoa(i), "developer") + }(i) + } + wg.Wait() + + // Verify that all space user roles have been added to the cache + for i := 0; i < concurrencyLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + key := testSpaceGuidKey + strconv.Itoa(i) + expetcSpaceUserRole := &spaceUserRole{ + user: "testUsername" + strconv.Itoa(i), + spaceGuid: testSpaceGuidKey + strconv.Itoa(i), + userGUID: "testUserGuid" + strconv.Itoa(i), + roleType: "developer", + } + retrievedSpaceUserRole, found := cache.getSpaceUserRoleFromCache(key) + Expect(found).To(BeTrue(), "Space user role should be found in cache for key: %s", key) + Expect(retrievedSpaceUserRole).To(Equal(expetcSpaceUserRole), "Retrieved space user role should match the added space user role for key: %s", key) + }(i) + } + wg.Wait() + + // Concurrently delete space user roles from the cache + for i := 0; i < concurrencyLevel; i++ { + wg.Add(1) + go func(i int) { + defer wg.Done() + cache.deleteSpaceUserRoleFromCache(testSpaceGuidKey + strconv.Itoa(i)) + }(i) + } + wg.Wait() + + // Verify final state + for i := 0; i < concurrencyLevel; i++ { + _, found := cache.getSpaceUserRoleFromCache(testSpaceGuidKey + strconv.Itoa(i)) + Expect(found).To(BeFalse(), "Expected space user role to be deleted from cache") + } + }) + }) }) diff --git a/internal/cf/space.go b/internal/cf/space.go index ea22a87..0b3eee6 100644 --- a/internal/cf/space.go +++ b/internal/cf/space.go @@ -22,7 +22,7 @@ func (c *organizationClient) GetSpace(ctx context.Context, owner string) (*facad if c.resourceCache.isCacheExpired(spaces) { //TODO: remove after internal review fmt.Println("Cache is expired for space") - populateResourceCache[*organizationClient](c, spaces) + populateResourceCache[*organizationClient](c, spaces, "") } if len(c.resourceCache.getCachedSpaces()) != 0 { space, spaceInCache := c.resourceCache.getSpaceFromCache(owner) @@ -114,6 +114,7 @@ func (c *organizationClient) DeleteSpace(ctx context.Context, owner string, guid // Delete space from cache if c.resourceCache.checkResourceCacheEnabled() { c.resourceCache.deleteSpaceFromCache(owner) + c.resourceCache.deleteSpaceUserRoleFromCache(guid) } return err } @@ -123,6 +124,22 @@ func (c *organizationClient) AddAuditor(ctx context.Context, guid string, userna } func (c *organizationClient) AddDeveloper(ctx context.Context, guid string, username string) error { + + if c.resourceCache.checkResourceCacheEnabled() { + // Attempt to retrieve space user role from cache + if c.resourceCache.isCacheExpired(spaceUserRoles) { + // populateResourceCache[*organizationClient](c, spaceUserRoles) + populateResourceCache[*organizationClient](c, spaceUserRoles, username) + } + if len(c.resourceCache.getCachedSpaceUserRoles()) != 0 { + _, roleInCache := c.resourceCache.getSpaceUserRoleFromCache(guid) + if roleInCache { + return nil + } + } + } + + // Attempt to retrieve binding from Cloud Foundry userListOpts := cfclient.NewUserListOptions() userListOpts.UserNames.EqualTo(username) users, err := c.client.Users.ListAll(ctx, userListOpts) From b9b7062348a7eb4ac855ad0ff79f4bba578c6145 Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Mon, 16 Sep 2024 17:15:52 +0530 Subject: [PATCH 38/63] added todo for improvements --- internal/cf/client.go | 5 ++++- internal/cf/health.go | 1 + internal/cf/space.go | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index 7e6912d..28cdf4c 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -74,6 +74,7 @@ var ( func initAndConfigureResourceCache(config *config.Config) *resourceCache { cacheInstanceOnce.Do(func() { + //TODO: make this initialize cache for different testing purposes cacheInstance = initResourceCache() //TODO:Remove later:print cache initialized fmt.Printf("Resource cache initialized") @@ -391,6 +392,7 @@ func (c *organizationClient) populateSpaces(ctx context.Context) error { if c.resourceCache.isCacheExpired(spaces) { spaceOptions := cfclient.NewSpaceListOptions() + //TODO: check for existing spaces as label owner annotation wont be present spaceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) spaceOptions.Page = 1 spaceOptions.PerPage = 5000 @@ -424,9 +426,10 @@ func (c *organizationClient) populateSpaces(ctx context.Context) error { func (c *organizationClient) populateSpaceUserRoleCache(ctx context.Context, username string) error { refreshSpaceUserRoleCacheMutex.Lock() defer refreshSpaceUserRoleCacheMutex.Unlock() - + fmt.Println("populate Space cache called") if c.resourceCache.isCacheExpired(spaceUserRoles) { spaceOptions := cfclient.NewSpaceListOptions() + spaceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) spaceOptions.Page = 1 spaceOptions.PerPage = 5000 diff --git a/internal/cf/health.go b/internal/cf/health.go index a99f7dc..f3d710d 100644 --- a/internal/cf/health.go +++ b/internal/cf/health.go @@ -8,6 +8,7 @@ package cf import "context" func (c *spaceClient) Check(ctx context.Context) error { + //TODO:Need to check if caching needed here or code can be removed _, err := c.client.Spaces.Get(ctx, c.spaceGuid) if err != nil { return err diff --git a/internal/cf/space.go b/internal/cf/space.go index 0b3eee6..5149eeb 100644 --- a/internal/cf/space.go +++ b/internal/cf/space.go @@ -139,6 +139,7 @@ func (c *organizationClient) AddDeveloper(ctx context.Context, guid string, user } } + fmt.Printf("get space user role from CF only in case of cache is off or space user role not found in cache or creation case") // Attempt to retrieve binding from Cloud Foundry userListOpts := cfclient.NewUserListOptions() userListOpts.UserNames.EqualTo(username) From 2cb50089ff2e970b64833be0ab23c03512e2d2e4 Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Wed, 18 Sep 2024 14:09:28 +0200 Subject: [PATCH 39/63] Make DeleteSpace consistent with other methods --- internal/cf/client_test.go | 2 +- internal/cf/space.go | 2 +- internal/controllers/space_controller.go | 2 +- internal/facade/client.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/cf/client_test.go b/internal/cf/client_test.go index 7332ecb..1d9652a 100644 --- a/internal/cf/client_test.go +++ b/internal/cf/client_test.go @@ -423,7 +423,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(server.ReceivedRequests()[6].RequestURI).NotTo(ContainSubstring(Owner)) // Delete space from cache - err = orgClient.DeleteSpace(ctx, Owner, "test-space-guid-1") + err = orgClient.DeleteSpace(ctx, "test-space-guid-1", Owner) Expect(err).To(BeNil()) Expect(server.ReceivedRequests()).To(HaveLen(8)) // - Delete space from cache diff --git a/internal/cf/space.go b/internal/cf/space.go index 5149eeb..270143e 100644 --- a/internal/cf/space.go +++ b/internal/cf/space.go @@ -108,7 +108,7 @@ func (c *organizationClient) UpdateSpace(ctx context.Context, guid string, name return err } -func (c *organizationClient) DeleteSpace(ctx context.Context, owner string, guid string) error { +func (c *organizationClient) DeleteSpace(ctx context.Context, guid string, owner string) error { _, err := c.client.Spaces.Delete(ctx, guid) // Delete space from cache diff --git a/internal/controllers/space_controller.go b/internal/controllers/space_controller.go index b3d8b44..ae9cd6f 100644 --- a/internal/controllers/space_controller.go +++ b/internal/controllers/space_controller.go @@ -277,7 +277,7 @@ func (r *SpaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resu return ctrl.Result{}, nil } else { log.V(1).Info("Deleting space") - if err := client.DeleteSpace(ctx, cfspace.Owner, cfspace.Guid); err != nil { + if err := client.DeleteSpace(ctx, cfspace.Guid, cfspace.Owner); err != nil { return ctrl.Result{}, err } status.LastModifiedAt = &[]metav1.Time{metav1.Now()}[0] diff --git a/internal/facade/client.go b/internal/facade/client.go index 7eff4ab..4c65002 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -71,7 +71,7 @@ type OrganizationClient interface { GetSpace(ctx context.Context, owner string) (*Space, error) CreateSpace(ctx context.Context, name string, owner string, generation int64) error UpdateSpace(ctx context.Context, guid string, name string, owner string, generation int64) error - DeleteSpace(ctx context.Context, owner string, guid string) error + DeleteSpace(ctx context.Context, guid string, owner string) error AddAuditor(ctx context.Context, guid string, username string) error AddDeveloper(ctx context.Context, guid string, username string) error AddManager(ctx context.Context, guid string, username string) error From be24a05bd5b2fcab367e9e85c61e5fd438db64a0 Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Wed, 18 Sep 2024 14:34:11 +0200 Subject: [PATCH 40/63] add config also to SpaceReconciler --- internal/controllers/suite_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/controllers/suite_test.go b/internal/controllers/suite_test.go index 63e07f1..7fa5681 100644 --- a/internal/controllers/suite_test.go +++ b/internal/controllers/suite_test.go @@ -214,6 +214,7 @@ func addControllers(k8sManager ctrl.Manager) { HealthCheckerBuilder: func(spaceGuid string, url string, username string, password string) (facade.SpaceHealthChecker, error) { return fakeSpaceHealthChecker, nil }, + Config: cfg, } Expect(spaceReconciler.SetupWithManager(k8sManager)).To(Succeed()) From 12252c7fb4e7e5e190c5f50e5d61619bf874d16c Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:46:49 +0200 Subject: [PATCH 41/63] use consistent namings --- internal/cf/binding.go | 4 +- internal/cf/client.go | 44 +++-- internal/cf/resourcecache.go | 307 ++++++++++++++++++----------------- internal/cf/space.go | 8 +- 4 files changed, 189 insertions(+), 174 deletions(-) diff --git a/internal/cf/binding.go b/internal/cf/binding.go index 58c8868..6c605d6 100644 --- a/internal/cf/binding.go +++ b/internal/cf/binding.go @@ -50,10 +50,10 @@ func (bo *bindingFilterOwner) getListOptions() *cfclient.ServiceCredentialBindin func (c *spaceClient) GetBinding(ctx context.Context, bindingOpts map[string]string) (*facade.Binding, error) { if c.resourceCache.checkResourceCacheEnabled() { // Attempt to retrieve binding from cache - if c.resourceCache.isCacheExpired(serviceBindings) { + if c.resourceCache.isCacheExpired(bindingType) { //TODO: remove after internal review fmt.Println("Cache is expired for binding") - populateResourceCache[*spaceClient](c, serviceBindings, "") + populateResourceCache[*spaceClient](c, bindingType, "") } if len(c.resourceCache.getCachedBindings()) != 0 { binding, bindingInCache := c.resourceCache.getBindingFromCache(bindingOpts["owner"]) diff --git a/internal/cf/client.go b/internal/cf/client.go index 28cdf4c..5a47a61 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -179,8 +179,8 @@ func NewOrganizationClient(organizationName string, url string, username string, if config.IsResourceCacheEnabled && client.resourceCache == nil { client.resourceCache = initAndConfigureResourceCache(config) - populateResourceCache(client, spaces, "") - populateResourceCache(client, spaceUserRoles, username) + populateResourceCache(client, spaceType, "") + populateResourceCache(client, spaceUserRoleType, username) cfResourceCache = client.resourceCache } @@ -218,8 +218,8 @@ func NewSpaceClient(spaceGuid string, url string, username string, password stri if config.IsResourceCacheEnabled && client.resourceCache == nil { client.resourceCache = initAndConfigureResourceCache(config) - populateResourceCache(client, serviceInstances, "") - populateResourceCache(client, serviceBindings, "") + populateResourceCache(client, instanceType, "") + populateResourceCache(client, bindingType, "") cfResourceCache = client.resourceCache } @@ -280,25 +280,25 @@ func populateResourceCache[T manageResourceCache](c T, resourceType cacheResourc var err error switch resourceType { - case serviceInstances: + case bindingType: + if client, ok := any(c).(ResourceServicesClient[T]); ok { + err = client.populateServiceBindings(ctx) + //TODO:remove later + fmt.Println("populate service binding finished") + } + case instanceType: if client, ok := any(c).(ResourceServicesClient[T]); ok { err = client.populateServiceInstances(ctx) //TODO:remove later fmt.Println("populate service instance finished") } - case spaces: + case spaceType: if client, ok := any(c).(ResourceSpaceClient[T]); ok { err = client.populateSpaces(ctx) //TODO:remove later fmt.Println("populate space finished") } - case serviceBindings: - if client, ok := any(c).(ResourceServicesClient[T]); ok { - err = client.populateServiceBindings(ctx) - //TODO:remove later - fmt.Println("populate service binding finished") - } - case spaceUserRoles: + case spaceUserRoleType: if client, ok := any(c).(ResourceSpaceClient[T]); ok { err = client.populateSpaceUserRoleCache(ctx, username) fmt.Println("populate service spaceUserRoles finished") @@ -317,7 +317,7 @@ func (c *spaceClient) populateServiceInstances(ctx context.Context) error { refreshServiceInstanceResourceCacheMutex.Lock() defer refreshServiceInstanceResourceCacheMutex.Unlock() - if c.resourceCache.isCacheExpired(serviceInstances) { + if c.resourceCache.isCacheExpired(instanceType) { instanceOptions := cfclient.NewServiceInstanceListOptions() instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) instanceOptions.Page = 1 @@ -343,7 +343,7 @@ func (c *spaceClient) populateServiceInstances(ctx context.Context) error { }(cfInstance) } waitGroup.Wait() - c.resourceCache.setLastCacheTime(serviceInstances) + c.resourceCache.setLastCacheTime(instanceType) } return nil @@ -354,7 +354,7 @@ func (c *spaceClient) populateServiceBindings(ctx context.Context) error { refreshServiceBindingResourceCacheMutex.Lock() defer refreshServiceBindingResourceCacheMutex.Unlock() - if c.resourceCache.isCacheExpired(serviceBindings) { + if c.resourceCache.isCacheExpired(bindingType) { bindingOptions := cfclient.NewServiceCredentialBindingListOptions() bindingOptions.ListOptions.LabelSelector.EqualTo(labelOwner) bindingOptions.Page = 1 @@ -380,7 +380,7 @@ func (c *spaceClient) populateServiceBindings(ctx context.Context) error { }(cfBinding) } waitGroup.Wait() - c.resourceCache.setLastCacheTime(serviceBindings) + c.resourceCache.setLastCacheTime(bindingType) } return nil @@ -390,7 +390,7 @@ func (c *organizationClient) populateSpaces(ctx context.Context) error { refreshSpaceResourceCacheMutex.Lock() defer refreshSpaceResourceCacheMutex.Unlock() - if c.resourceCache.isCacheExpired(spaces) { + if c.resourceCache.isCacheExpired(spaceType) { spaceOptions := cfclient.NewSpaceListOptions() //TODO: check for existing spaces as label owner annotation wont be present spaceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) @@ -417,7 +417,7 @@ func (c *organizationClient) populateSpaces(ctx context.Context) error { }(cfSpace) } waitGroup.Wait() - c.resourceCache.setLastCacheTime(spaces) + c.resourceCache.setLastCacheTime(spaceType) } return nil @@ -427,7 +427,7 @@ func (c *organizationClient) populateSpaceUserRoleCache(ctx context.Context, use refreshSpaceUserRoleCacheMutex.Lock() defer refreshSpaceUserRoleCacheMutex.Unlock() fmt.Println("populate Space cache called") - if c.resourceCache.isCacheExpired(spaceUserRoles) { + if c.resourceCache.isCacheExpired(spaceUserRoleType) { spaceOptions := cfclient.NewSpaceListOptions() spaceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) @@ -484,7 +484,7 @@ func (c *organizationClient) populateSpaceUserRoleCache(ctx context.Context, use }(cfRole) } waitGroup.Wait() - c.resourceCache.setLastCacheTime(spaceUserRoles) + c.resourceCache.setLastCacheTime(spaceUserRoleType) } return nil @@ -492,12 +492,10 @@ func (c *organizationClient) populateSpaceUserRoleCache(ctx context.Context, use // Implementation for resetting the cache func (c *spaceClient) resetCache(resourceType cacheResourceType) { - c.resourceCache.resetCache(resourceType) } // Implementation for resetting the cache func (c *organizationClient) resetCache(resourceType cacheResourceType) { - c.resourceCache.resetCache(resourceType) } diff --git a/internal/cf/resourcecache.go b/internal/cf/resourcecache.go index 074a75b..ba7c0fe 100644 --- a/internal/cf/resourcecache.go +++ b/internal/cf/resourcecache.go @@ -20,17 +20,23 @@ import ( // The map uses the owner of the instance (which is Kubernetes UID) as key and the service instance // as value. type resourceCache struct { - spaces map[string]*facade.Space - instances map[string]*facade.Instance - bindings map[string]*facade.Binding - mutex sync.RWMutex - cacheTimeOut time.Duration - serviceInstanceLastCacheTime time.Time - spaceLastaCheTime time.Time - serviceBindingLastCacheTime time.Time - spaceUserRoleLastCacheTime time.Time - isResourceCacheEnabled bool - spaceUserRole map[string]*spaceUserRole + mutex sync.RWMutex + + // cache for each resource type + bindings map[string]*facade.Binding + instances map[string]*facade.Instance + spaces map[string]*facade.Space + spaceUserRoles map[string]*spaceUserRole + + // last cache time for each resource type + bindingLastCacheTime time.Time + instanceLastCacheTime time.Time + spaceLastCacheTime time.Time + spaceUserRoleLastCacheTime time.Time + + // configuration + cacheTimeOut time.Duration + isResourceCacheEnabled bool } type spaceUserRole struct { @@ -43,20 +49,19 @@ type spaceUserRole struct { type cacheResourceType string const ( - serviceInstances cacheResourceType = "serviceInstances" - spaces cacheResourceType = "spaces" - serviceBindings cacheResourceType = "serviceBindings" - spaceUserRoles cacheResourceType = "spaceUserRole" + bindingType cacheResourceType = "bindingType" + instanceType cacheResourceType = "instanceType" + spaceType cacheResourceType = "spaceType" + spaceUserRoleType cacheResourceType = "spaceUserRoleType" ) // InitResourcesCache initializes a new cache func initResourceCache() *resourceCache { - cache := &resourceCache{ - spaces: make(map[string]*facade.Space), - instances: make(map[string]*facade.Instance), - bindings: make(map[string]*facade.Binding), - spaceUserRole: make(map[string]*spaceUserRole), + bindings: make(map[string]*facade.Binding), + instances: make(map[string]*facade.Instance), + spaces: make(map[string]*facade.Space), + spaceUserRoles: make(map[string]*spaceUserRole), } return cache @@ -91,13 +96,13 @@ func (c *resourceCache) setCacheTimeOut(timeOut string) { func (c *resourceCache) setLastCacheTime(resourceType cacheResourceType) { now := time.Now() switch resourceType { - case serviceBindings: - c.serviceBindingLastCacheTime = now - case serviceInstances: - c.serviceInstanceLastCacheTime = now - case spaces: - c.spaceLastaCheTime = now - case spaceUserRoles: + case bindingType: + c.bindingLastCacheTime = now + case instanceType: + c.instanceLastCacheTime = now + case spaceType: + c.spaceLastCacheTime = now + case spaceUserRoleType: c.spaceUserRoleLastCacheTime = now } //TODO:remove later @@ -108,13 +113,13 @@ func (c *resourceCache) setLastCacheTime(resourceType cacheResourceType) { func (c *resourceCache) isCacheExpired(resourceType cacheResourceType) bool { var lastCacheTime time.Time switch resourceType { - case serviceInstances: - lastCacheTime = c.serviceInstanceLastCacheTime - case spaces: - lastCacheTime = c.spaceLastaCheTime - case serviceBindings: - lastCacheTime = c.serviceBindingLastCacheTime - case spaceUserRoles: + case bindingType: + lastCacheTime = c.bindingLastCacheTime + case instanceType: + lastCacheTime = c.instanceLastCacheTime + case spaceType: + lastCacheTime = c.spaceLastCacheTime + case spaceUserRoleType: lastCacheTime = c.spaceUserRoleLastCacheTime } @@ -126,142 +131,147 @@ func (c *resourceCache) isCacheExpired(resourceType cacheResourceType) bool { expirationTime := lastCacheTime.Add(c.cacheTimeOut) //TODO:remove later fmt.Printf("Expiration time for %s: %v and last cached time: %v and timenow :%v\n", resourceType, expirationTime, lastCacheTime, time.Now()) - bool := time.Now().After(expirationTime) - return bool + isExpired := time.Now().After(expirationTime) + return isExpired } // reset cache of a specific resource type and last cache time func (c *resourceCache) resetCache(resourceType cacheResourceType) { - fmt.Printf("reset requested for %v \n", resourceType) switch resourceType { - case serviceInstances: + case bindingType: + c.bindings = make(map[string]*facade.Binding) + c.bindingLastCacheTime = time.Now() + case instanceType: c.instances = make(map[string]*facade.Instance) - c.serviceInstanceLastCacheTime = time.Now() - case spaces: + c.instanceLastCacheTime = time.Now() + case spaceType: c.spaces = make(map[string]*facade.Space) - c.spaceLastaCheTime = time.Now() - case serviceBindings: - c.bindings = make(map[string]*facade.Binding) - c.serviceBindingLastCacheTime = time.Now() - case spaceUserRoles: - c.spaceUserRole = make(map[string]*spaceUserRole) + c.spaceLastCacheTime = time.Now() + case spaceUserRoleType: + c.spaceUserRoles = make(map[string]*spaceUserRole) c.spaceUserRoleLastCacheTime = time.Now() - } } -// getCachedInstances retrieves instances from the cache -func (c *resourceCache) getCachedInstances() map[string]*facade.Instance { - return c.instances -} +// ----------------------------------------------------------------------------------------------- +// Bindings +// ----------------------------------------------------------------------------------------------- -// addInstanceInCache stores an instance in the cache -func (c *resourceCache) addInstanceInCache(key string, instance *facade.Instance) { +// addBindingInCache stores a binding to the cache +func (c *resourceCache) addBindingInCache(key string, binding *facade.Binding) { c.mutex.Lock() defer c.mutex.Unlock() + c.bindings[key] = binding // TODO :remove After internal review - fmt.Printf("Added the instance to Cache: %v \n", instance) - c.instances[key] = instance -} - -// getInstanceFromCache retrieves an instance from the cache -func (c *resourceCache) getInstanceFromCache(key string) (*facade.Instance, bool) { - c.mutex.RLock() - defer c.mutex.RUnlock() - instance, found := c.instances[key] - // TODO :remove After internal review - fmt.Printf("Got the instance from Cache: %v \n", instance) - return instance, found + fmt.Printf("Added the binding to Cache: %v \n", binding) } -// deleteInstanceFromCache deletes an instance from the cache -func (c *resourceCache) deleteInstanceFromCache(key string) { +// deleteBindingFromCache deletes a specific binding from the cache +func (c *resourceCache) deleteBindingFromCache(key string) { c.mutex.Lock() defer c.mutex.Unlock() - delete(c.instances, key) + delete(c.bindings, key) // TODO :remove After internal review - fmt.Printf("deleted the instance from Cache: %v \n", key) + fmt.Printf("Added the binding to Cache: %v \n", key) } -// updateInstanceInCache updates an instance in the cache -func (c *resourceCache) updateInstanceInCache(owner string, name string, servicePlanGuid string, parameters map[string]interface{}, generation int64) (status bool) { +// getCachedBindings retrieves all bindings from the cache +func (c *resourceCache) getCachedBindings() map[string]*facade.Binding { + return c.bindings +} + +// getBindingFromCache retrieves a specific binding from the cache +func (c *resourceCache) getBindingFromCache(key string) (*facade.Binding, bool) { + c.mutex.RLock() + defer c.mutex.RUnlock() + binding, found := c.bindings[key] + // TODO :remove After internal review + fmt.Printf("Got the binding from Cache: %v \n", binding) + return binding, found +} + +// updateBindingInCache updates a specific binding in the cache +func (c *resourceCache) updateBindingInCache(owner string, parameters map[string]interface{}, generation int64) (status bool) { c.mutex.Lock() defer c.mutex.Unlock() //update if the instance is found in the cache //update all the struct variables if they are not nil or empty - instance, found := c.instances[owner] + binding, found := c.bindings[owner] if found { - if name != "" { - instance.Name = name - } - if servicePlanGuid != "" { - instance.ServicePlanGuid = servicePlanGuid - } if parameters != nil { - instance.ParameterHash = facade.ObjectHash(parameters) + binding.ParameterHash = facade.ObjectHash(parameters) } if generation != 0 { - instance.Generation = generation + binding.Generation = generation } - c.instances[owner] = instance + c.bindings[owner] = binding return true } return false - } -// getBindingFromCache retrieves binding from the cache -func (c *resourceCache) getCachedBindings() map[string]*facade.Binding { - return c.bindings -} +// ----------------------------------------------------------------------------------------------- +// Instances +// ----------------------------------------------------------------------------------------------- -// addBindingInCache stores binding in the cache -func (c *resourceCache) addBindingInCache(key string, binding *facade.Binding) { +// addInstanceInCache stores an instance to the cache +func (c *resourceCache) addInstanceInCache(key string, instance *facade.Instance) { c.mutex.Lock() defer c.mutex.Unlock() - c.bindings[key] = binding // TODO :remove After internal review - fmt.Printf("Added the binding to Cache: %v \n", binding) -} - -// getBindingFromCache retrieves binding from the cache -func (c *resourceCache) getBindingFromCache(key string) (*facade.Binding, bool) { - c.mutex.RLock() - defer c.mutex.RUnlock() - binding, found := c.bindings[key] - // TODO :remove After internal review - fmt.Printf("Got the binding from Cache: %v \n", binding) - return binding, found + fmt.Printf("Added the instance to Cache: %v \n", instance) + c.instances[key] = instance } -// deleteBindingFromCache deletes binding from the cache -func (c *resourceCache) deleteBindingFromCache(key string) { +// deleteInstanceFromCache deletes a specific instance from the cache +func (c *resourceCache) deleteInstanceFromCache(key string) { c.mutex.Lock() defer c.mutex.Unlock() - delete(c.bindings, key) + delete(c.instances, key) // TODO :remove After internal review - fmt.Printf("Added the binding to Cache: %v \n", key) + fmt.Printf("deleted the instance from Cache: %v \n", key) } -// updateBindingInCache updates an binding in the cache -func (c *resourceCache) updateBindingInCache(owner string, parameters map[string]interface{}, generation int64) (status bool) { +// getCachedInstances retrieves all instances from the cache +func (c *resourceCache) getCachedInstances() map[string]*facade.Instance { + return c.instances +} + +// getInstanceFromCache retrieves a specific instance from the cache +func (c *resourceCache) getInstanceFromCache(key string) (*facade.Instance, bool) { + c.mutex.RLock() + defer c.mutex.RUnlock() + instance, found := c.instances[key] + // TODO :remove After internal review + fmt.Printf("Got the instance from Cache: %v \n", instance) + return instance, found +} + +// updateInstanceInCache updates a specific instance in the cache +func (c *resourceCache) updateInstanceInCache(owner string, name string, servicePlanGuid string, parameters map[string]interface{}, generation int64) (status bool) { c.mutex.Lock() defer c.mutex.Unlock() //update if the instance is found in the cache //update all the struct variables if they are not nil or empty - binding, found := c.bindings[owner] + instance, found := c.instances[owner] if found { + if name != "" { + instance.Name = name + } + if servicePlanGuid != "" { + instance.ServicePlanGuid = servicePlanGuid + } if parameters != nil { - binding.ParameterHash = facade.ObjectHash(parameters) + instance.ParameterHash = facade.ObjectHash(parameters) } if generation != 0 { - binding.Generation = generation + instance.Generation = generation } - c.bindings[owner] = binding + c.instances[owner] = instance return true } @@ -269,12 +279,11 @@ func (c *resourceCache) updateBindingInCache(owner string, parameters map[string } -// getCachedSpaces retrieves spaces from the cache -func (c *resourceCache) getCachedSpaces() map[string]*facade.Space { - return c.spaces -} +// ----------------------------------------------------------------------------------------------- +// Spaces +// ----------------------------------------------------------------------------------------------- -// AddSpaceInCache stores a space in the cache +// addSpaceInCache stores a space to the cache func (c *resourceCache) addSpaceInCache(key string, space *facade.Space) { c.mutex.Lock() defer c.mutex.Unlock() @@ -283,17 +292,7 @@ func (c *resourceCache) addSpaceInCache(key string, space *facade.Space) { fmt.Printf("Added the space to Cache: %v \n", space) } -// GetSpaceFromCache retrieves a space from the cache -func (c *resourceCache) getSpaceFromCache(key string) (*facade.Space, bool) { - c.mutex.RLock() - defer c.mutex.RUnlock() - space, found := c.spaces[key] - // TODO :remove After internal review - fmt.Printf("Got the space from Cache: %v \n", space) - return space, found -} - -// deleteSpaceFromCache deletes space from the cache +// deleteSpaceFromCache deletes a specific space from the cache func (c *resourceCache) deleteSpaceFromCache(key string) { c.mutex.Lock() defer c.mutex.Unlock() @@ -303,7 +302,22 @@ func (c *resourceCache) deleteSpaceFromCache(key string) { } -// updateSpaceInCache updates an space in the cache +// getCachedSpaces retrieves all spaces from the cache +func (c *resourceCache) getCachedSpaces() map[string]*facade.Space { + return c.spaces +} + +// getSpaceFromCache retrieves a specific space from the cache +func (c *resourceCache) getSpaceFromCache(key string) (*facade.Space, bool) { + c.mutex.RLock() + defer c.mutex.RUnlock() + space, found := c.spaces[key] + // TODO :remove After internal review + fmt.Printf("Got the space from Cache: %v \n", space) + return space, found +} + +// updateSpaceInCache updates a specific space in the cache func (c *resourceCache) updateSpaceInCache(owner string, name string, generation int64) (status bool) { c.mutex.Lock() defer c.mutex.Unlock() @@ -321,15 +335,13 @@ func (c *resourceCache) updateSpaceInCache(owner string, name string, generation return true } return false - } -// list all cached spaceuserroles in cache -func (c *resourceCache) getCachedSpaceUserRoles() map[string]*spaceUserRole { - return c.spaceUserRole -} +// ----------------------------------------------------------------------------------------------- +// Space User Roles +// ----------------------------------------------------------------------------------------------- -// add spaceuserrole to cache +// addSpaceUserRoleInCache adds a specific spaceuserrole to the cache func (c *resourceCache) addSpaceUserRoleInCache(spaceGuid string, userGuid string, username string, roleType string) { c.mutex.Lock() defer c.mutex.Unlock() @@ -339,26 +351,31 @@ func (c *resourceCache) addSpaceUserRoleInCache(spaceGuid string, userGuid strin userGUID: userGuid, roleType: roleType, } - c.spaceUserRole[spaceGuid] = role + c.spaceUserRoles[spaceGuid] = role // TODO :remove After internal review fmt.Printf("Added the space user role to Cache: %v \n", role) } -// get spaceuserrole in cache +// deleteSpaceUserRoleFromCache deletes a specifc spaceuserrole from the cache +func (c *resourceCache) deleteSpaceUserRoleFromCache(spaceGuid string) { + c.mutex.Lock() + defer c.mutex.Unlock() + delete(c.spaceUserRoles, spaceGuid) + // TODO :remove After internal review + fmt.Printf("Deleted the space user role from Cache: %v \n", spaceGuid) +} + +// getCachedSpaceUserRoles lists all spaceuserroles from the cache +func (c *resourceCache) getCachedSpaceUserRoles() map[string]*spaceUserRole { + return c.spaceUserRoles +} + +// getSpaceUserRoleFromCache gets a specific spaceuserrole from the cache func (c *resourceCache) getSpaceUserRoleFromCache(key string) (*spaceUserRole, bool) { c.mutex.RLock() defer c.mutex.RUnlock() - spaceUserRole, found := c.spaceUserRole[key] + spaceUserRole, found := c.spaceUserRoles[key] // TODO :remove After internal review fmt.Printf("Got the space user role from Cache: %v \n", spaceUserRole) return spaceUserRole, found } - -// delete spaceuserrole not present in the cache -func (c *resourceCache) deleteSpaceUserRoleFromCache(spaceGuid string) { - c.mutex.Lock() - defer c.mutex.Unlock() - delete(c.spaceUserRole, spaceGuid) - // TODO :remove After internal review - fmt.Printf("Deleted the space user role from Cache: %v \n", spaceGuid) -} diff --git a/internal/cf/space.go b/internal/cf/space.go index 270143e..77578c4 100644 --- a/internal/cf/space.go +++ b/internal/cf/space.go @@ -19,10 +19,10 @@ import ( func (c *organizationClient) GetSpace(ctx context.Context, owner string) (*facade.Space, error) { if c.resourceCache.checkResourceCacheEnabled() { // Attempt to retrieve space from cache - if c.resourceCache.isCacheExpired(spaces) { + if c.resourceCache.isCacheExpired(spaceType) { //TODO: remove after internal review fmt.Println("Cache is expired for space") - populateResourceCache[*organizationClient](c, spaces, "") + populateResourceCache[*organizationClient](c, spaceType, "") } if len(c.resourceCache.getCachedSpaces()) != 0 { space, spaceInCache := c.resourceCache.getSpaceFromCache(owner) @@ -127,9 +127,9 @@ func (c *organizationClient) AddDeveloper(ctx context.Context, guid string, user if c.resourceCache.checkResourceCacheEnabled() { // Attempt to retrieve space user role from cache - if c.resourceCache.isCacheExpired(spaceUserRoles) { + if c.resourceCache.isCacheExpired(spaceUserRoleType) { // populateResourceCache[*organizationClient](c, spaceUserRoles) - populateResourceCache[*organizationClient](c, spaceUserRoles, username) + populateResourceCache[*organizationClient](c, spaceUserRoleType, username) } if len(c.resourceCache.getCachedSpaceUserRoles()) != 0 { _, roleInCache := c.resourceCache.getSpaceUserRoleFromCache(guid) From 277feef3d39afc8ff9cf73c1cb8e915be397b25f Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Wed, 18 Sep 2024 17:38:00 +0200 Subject: [PATCH 42/63] remove debug log, harmonize coding --- internal/cf/binding.go | 57 ++++++++++++++++++----------------------- internal/cf/client.go | 2 +- internal/cf/health.go | 2 +- internal/cf/instance.go | 32 +++++++++-------------- internal/cf/space.go | 44 +++++++++++++------------------ 5 files changed, 57 insertions(+), 80 deletions(-) diff --git a/internal/cf/binding.go b/internal/cf/binding.go index 6c605d6..67dc94b 100644 --- a/internal/cf/binding.go +++ b/internal/cf/binding.go @@ -49,26 +49,19 @@ func (bo *bindingFilterOwner) getListOptions() *cfclient.ServiceCredentialBindin // The function add the parameter values to the orphan cf binding, so that can be adopted. func (c *spaceClient) GetBinding(ctx context.Context, bindingOpts map[string]string) (*facade.Binding, error) { if c.resourceCache.checkResourceCacheEnabled() { - // Attempt to retrieve binding from cache + // attempt to retrieve binding from cache if c.resourceCache.isCacheExpired(bindingType) { - //TODO: remove after internal review - fmt.Println("Cache is expired for binding") populateResourceCache[*spaceClient](c, bindingType, "") } if len(c.resourceCache.getCachedBindings()) != 0 { - binding, bindingInCache := c.resourceCache.getBindingFromCache(bindingOpts["owner"]) - //TODO: remove after internal review - fmt.Printf("Length of cache: %d\n", len(c.resourceCache.getCachedBindings())) - if bindingInCache { + binding, inCache := c.resourceCache.getBindingFromCache(bindingOpts["owner"]) + if inCache { return binding, nil } } } - // TODO :remove After internal review - fmt.Println("get binding from CF only in case of cache is of or binding not found in cache or creation case") - - // Attempt to retrieve binding from Cloud Foundry + // attempt to retrieve binding from Cloud Foundry var filterOpts bindingFilter if bindingOpts["name"] != "" { filterOpts = &bindingFilterName{name: bindingOpts["name"]} @@ -86,16 +79,16 @@ func (c *spaceClient) GetBinding(ctx context.Context, bindingOpts map[string]str } else if len(serviceBindings) > 1 { return nil, errors.New(fmt.Sprintf("found multiple service bindings with owner: %s", bindingOpts["owner"])) } - serviceBinding := serviceBindings[0] - // add parameter values to the cf orphan binding + // add parameter values to the orphaned binding in Cloud Foundry if bindingOpts["name"] != "" { generationvalue := "0" - serviceBinding.Metadata.Annotations[annotationGeneration] = &generationvalue parameterHashValue := "0" + serviceBinding.Metadata.Annotations[annotationGeneration] = &generationvalue serviceBinding.Metadata.Annotations[annotationParameterHash] = ¶meterHashValue } + return c.InitBinding(ctx, serviceBinding, bindingOpts) } @@ -133,12 +126,13 @@ func (c *spaceClient) UpdateBinding(ctx context.Context, guid string, owner stri } } _, err := c.client.ServiceCredentialBindings.Update(ctx, guid, req) - // Update binding in cache + + // update binding in cache if err == nil && c.resourceCache.checkResourceCacheEnabled() { isUpdated := c.resourceCache.updateBindingInCache(owner, parameters, generation) - if !isUpdated { - //add the binding to cache if it is not found + // add binding to cache if it is not found + // TODO: why getting binding here but not instance in CreateInstance() ? binding, err := c.GetBinding(ctx, map[string]string{"owner": owner}) if err != nil { return err @@ -151,26 +145,28 @@ func (c *spaceClient) UpdateBinding(ctx context.Context, guid string, owner stri } func (c *spaceClient) DeleteBinding(ctx context.Context, guid string, owner string) error { - err := c.client.ServiceCredentialBindings.Delete(ctx, guid) - // Delete binding from cache - if c.resourceCache.checkResourceCacheEnabled() { + // delete binding from cache + if err == nil && c.resourceCache.checkResourceCacheEnabled() { c.resourceCache.deleteBindingFromCache(owner) } return err } +// InitBinding wraps cfclient.ServiceCredentialBinding as a facade.Binding. func (c *spaceClient) InitBinding(ctx context.Context, serviceBinding *cfresource.ServiceCredentialBinding, bindingOpts map[string]string) (*facade.Binding, error) { - - guid := serviceBinding.GUID - name := serviceBinding.Name generation, err := strconv.ParseInt(*serviceBinding.Metadata.Annotations[annotationGeneration], 10, 64) if err != nil { return nil, errors.Wrap(err, "error parsing service binding generation") } - parameterHash := *serviceBinding.Metadata.Annotations[annotationParameterHash] + + owner := bindingOpts["owner"] + if owner == "" { + owner = *serviceBinding.Metadata.Labels[labelOwner] + } + var state facade.BindingState switch serviceBinding.LastOperation.Type + ":" + serviceBinding.LastOperation.State { case "create:in progress": @@ -188,7 +184,8 @@ func (c *spaceClient) InitBinding(ctx context.Context, serviceBinding *cfresourc default: state = facade.BindingStateUnknown } - stateDescription := serviceBinding.LastOperation.Description + + guid := serviceBinding.GUID var credentials map[string]interface{} if state == facade.BindingStateReady { @@ -198,19 +195,15 @@ func (c *spaceClient) InitBinding(ctx context.Context, serviceBinding *cfresourc } credentials = details.Credentials } - owner := bindingOpts["owner"] - if owner == "" { - owner = *serviceBinding.Metadata.Labels[labelOwner] - } return &facade.Binding{ Guid: guid, - Name: name, + Name: serviceBinding.Name, Owner: owner, Generation: generation, - ParameterHash: parameterHash, + ParameterHash: *serviceBinding.Metadata.Annotations[annotationParameterHash], State: state, - StateDescription: stateDescription, + StateDescription: serviceBinding.LastOperation.Description, Credentials: credentials, }, nil } diff --git a/internal/cf/client.go b/internal/cf/client.go index 5a47a61..7d3f95e 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -335,7 +335,7 @@ func (c *spaceClient) populateServiceInstances(ctx context.Context) error { waitGroup.Add(1) go func(cfInstance *cfresource.ServiceInstance) { defer waitGroup.Done() - if instance, err := InitInstance(cfInstance, nil); err == nil { + if instance, err := c.InitInstance(cfInstance, nil); err == nil { c.resourceCache.addInstanceInCache(*cfInstance.Metadata.Labels[labelOwner], instance) } else { log.Printf("Error initializing instance: %s", err) diff --git a/internal/cf/health.go b/internal/cf/health.go index f3d710d..7e3fc0c 100644 --- a/internal/cf/health.go +++ b/internal/cf/health.go @@ -8,7 +8,7 @@ package cf import "context" func (c *spaceClient) Check(ctx context.Context) error { - //TODO:Need to check if caching needed here or code can be removed + // TODO: Need to check if caching needed here or code can be removed _, err := c.client.Spaces.Get(ctx, c.spaceGuid) if err != nil { return err diff --git a/internal/cf/instance.go b/internal/cf/instance.go index adf4aa5..61f5ca1 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -48,26 +48,19 @@ func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOpt // If the instance is not found in the cache, it is searched in Cloud Foundry. func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]string) (*facade.Instance, error) { if c.resourceCache.checkResourceCacheEnabled() { - // Attempt to retrieve instance from cache - if c.resourceCache.isCacheExpired("serviceInstances") { - //TODO: remove after internal review - fmt.Println("Cache is expired for instance so populating cache") - populateResourceCache[*spaceClient](c, "serviceInstances", "") + // attempt to retrieve instance from cache + if c.resourceCache.isCacheExpired(instanceType) { + populateResourceCache[*spaceClient](c, instanceType, "") } if len(c.resourceCache.getCachedInstances()) != 0 { - instance, instanceInCache := c.resourceCache.getInstanceFromCache(instanceOpts["owner"]) - //TODO: remove after internal review - fmt.Printf("Length of cache: %d\n", len(c.resourceCache.getCachedInstances())) - if instanceInCache { + instance, inCache := c.resourceCache.getInstanceFromCache(instanceOpts["owner"]) + if inCache { return instance, nil } } } - // TODO :remove After internal review - fmt.Println("get instance from CF only in case of cache is of or instance not found in cache or creation case") - - // Attempt to retrieve instance from Cloud Foundry + // attempt to retrieve instance from Cloud Foundry var filterOpts instanceFilter if instanceOpts["name"] != "" { filterOpts = &instanceFilterName{name: instanceOpts["name"]} @@ -85,7 +78,7 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s } serviceInstance := serviceInstances[0] - // add parameter values to the orphan cf instance + // add parameter values to the orphaned instance in Cloud Foundry if instanceOpts["name"] != "" { generationvalue := "0" parameterHashValue := "0" @@ -93,7 +86,7 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s serviceInstance.Metadata.Annotations[annotationParameterHash] = ¶meterHashValue } - return InitInstance(serviceInstance, instanceOpts) + return c.InitInstance(serviceInstance, instanceOpts) } // Required parameters (may not be initial): name, servicePlanGuid, owner, generation @@ -153,10 +146,9 @@ func (c *spaceClient) UpdateInstance(ctx context.Context, guid string, name stri _, _, err := c.client.ServiceInstances.UpdateManaged(ctx, guid, req) - // Update instance in cache + // update instance in cache if err == nil && c.resourceCache.checkResourceCacheEnabled() { isUpdated := c.resourceCache.updateInstanceInCache(name, owner, servicePlanGuid, parameters, generation) - if !isUpdated { // add instance to cache in case of orphan instance instance := &facade.Instance{ @@ -180,8 +172,8 @@ func (c *spaceClient) DeleteInstance(ctx context.Context, guid string, owner str // TODO: return jobGUID to enable querying the job deletion status _, err := c.client.ServiceInstances.Delete(ctx, guid) - // Delete instance from cache - if c.resourceCache.checkResourceCacheEnabled() { + // delete instance from cache + if err == nil && c.resourceCache.checkResourceCacheEnabled() { c.resourceCache.deleteInstanceFromCache(owner) } @@ -189,7 +181,7 @@ func (c *spaceClient) DeleteInstance(ctx context.Context, guid string, owner str } // InitInstance wraps cfclient.ServiceInstance as a facade.Instance. -func InitInstance(serviceInstance *cfresource.ServiceInstance, instanceOpts map[string]string) (*facade.Instance, error) { +func (c *spaceClient) InitInstance(serviceInstance *cfresource.ServiceInstance, instanceOpts map[string]string) (*facade.Instance, error) { generation, err := strconv.ParseInt(*serviceInstance.Metadata.Annotations[annotationGeneration], 10, 64) if err != nil { return nil, errors.Wrap(err, "error parsing service instance generation") diff --git a/internal/cf/space.go b/internal/cf/space.go index 77578c4..1c4f331 100644 --- a/internal/cf/space.go +++ b/internal/cf/space.go @@ -18,26 +18,19 @@ import ( func (c *organizationClient) GetSpace(ctx context.Context, owner string) (*facade.Space, error) { if c.resourceCache.checkResourceCacheEnabled() { - // Attempt to retrieve space from cache + // attempt to retrieve space from cache if c.resourceCache.isCacheExpired(spaceType) { - //TODO: remove after internal review - fmt.Println("Cache is expired for space") populateResourceCache[*organizationClient](c, spaceType, "") } if len(c.resourceCache.getCachedSpaces()) != 0 { - space, spaceInCache := c.resourceCache.getSpaceFromCache(owner) - //TODO: remove after internal review - fmt.Printf("Length of cache: %d\n", len(c.resourceCache.getCachedSpaces())) - if spaceInCache { + space, inCache := c.resourceCache.getSpaceFromCache(owner) + if inCache { return space, nil } } } - // TODO :remove After internal review - fmt.Println("get space from CF only in case of cache is of or space not found in cache or creation case") - - //Attempt to retrieve space from Cloud Foundry + // attempt to retrieve space from Cloud Foundry listOpts := cfclient.NewSpaceListOptions() listOpts.LabelSelector.EqualTo(labelPrefix + "/" + labelKeyOwner + "=" + owner) spaces, err := c.client.Spaces.ListAll(ctx, listOpts) @@ -51,6 +44,7 @@ func (c *organizationClient) GetSpace(ctx context.Context, owner string) (*facad return nil, fmt.Errorf("found multiple spaces with owner: %s", owner) } space := spaces[0] + return InitSpace(space, owner) } @@ -90,11 +84,12 @@ func (c *organizationClient) UpdateSpace(ctx context.Context, guid string, name WithAnnotation(annotationPrefix, annotationKeyGeneration, strconv.FormatInt(generation, 10)) _, err := c.client.Spaces.Update(ctx, guid, req) - //update space in cache - if c.resourceCache.checkResourceCacheEnabled() { + + // update space in cache + if err == nil && c.resourceCache.checkResourceCacheEnabled() { isUpdated := c.resourceCache.updateSpaceInCache(owner, name, generation) if !isUpdated { - //add space to cache + // add space to cache space := &facade.Space{ Guid: guid, Name: name, @@ -111,11 +106,12 @@ func (c *organizationClient) UpdateSpace(ctx context.Context, guid string, name func (c *organizationClient) DeleteSpace(ctx context.Context, guid string, owner string) error { _, err := c.client.Spaces.Delete(ctx, guid) - // Delete space from cache - if c.resourceCache.checkResourceCacheEnabled() { + // delete space from cache + if err == nil && c.resourceCache.checkResourceCacheEnabled() { c.resourceCache.deleteSpaceFromCache(owner) c.resourceCache.deleteSpaceUserRoleFromCache(guid) } + return err } @@ -124,23 +120,20 @@ func (c *organizationClient) AddAuditor(ctx context.Context, guid string, userna } func (c *organizationClient) AddDeveloper(ctx context.Context, guid string, username string) error { - if c.resourceCache.checkResourceCacheEnabled() { - // Attempt to retrieve space user role from cache + // attempt to retrieve space user role from cache if c.resourceCache.isCacheExpired(spaceUserRoleType) { - // populateResourceCache[*organizationClient](c, spaceUserRoles) populateResourceCache[*organizationClient](c, spaceUserRoleType, username) } if len(c.resourceCache.getCachedSpaceUserRoles()) != 0 { - _, roleInCache := c.resourceCache.getSpaceUserRoleFromCache(guid) - if roleInCache { + _, inCache := c.resourceCache.getSpaceUserRoleFromCache(guid) + if inCache { return nil } } } - fmt.Printf("get space user role from CF only in case of cache is off or space user role not found in cache or creation case") - // Attempt to retrieve binding from Cloud Foundry + // attempt to retrieve space user role from Cloud Foundry userListOpts := cfclient.NewUserListOptions() userListOpts.UserNames.EqualTo(username) users, err := c.client.Users.ListAll(ctx, userListOpts) @@ -173,9 +166,8 @@ func (c *organizationClient) AddManager(ctx context.Context, guid string, userna return nil } -// Initspace wraps cfclient.Space as a facade.Space +// InitSpace wraps cfclient.Space as a facade.Space func InitSpace(space *cfresource.Space, owner string) (*facade.Space, error) { - guid := space.GUID name := space.Name generation, err := strconv.ParseInt(*space.Metadata.Annotations[annotationGeneration], 10, 64) @@ -192,5 +184,5 @@ func InitSpace(space *cfresource.Space, owner string) (*facade.Space, error) { Name: name, Owner: owner, Generation: generation, - }, err + }, nil } From 717817fec04b0f5e47afb1ab37405e7b973811f1 Mon Sep 17 00:00:00 2001 From: Santiago Ventura <160760719+santiago-ventura@users.noreply.github.com> Date: Wed, 18 Sep 2024 17:38:00 +0200 Subject: [PATCH 43/63] update official documentation with the new cache feature --- .../content/en/docs/configuration/operator.md | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/website/content/en/docs/configuration/operator.md b/website/content/en/docs/configuration/operator.md index f6f44f0..32f9e56 100644 --- a/website/content/en/docs/configuration/operator.md +++ b/website/content/en/docs/configuration/operator.md @@ -62,10 +62,56 @@ Notes: ## Environment variables +**Kubecibfig Configuration** cf-service-operator honors the following environment variables: - `$KUBECONFIG` the path to the kubeconfig used by the operator executable; note that this has lower precedence than the command line flag `-kubeconfig`. +**Cache Configuration** +To optimize the usage of CF resources and reduce the number of API calls, the CF service operator supports an optional caching mechanism. This feature allows resources to be stored in memory and refreshed based on a configurable timeout. +By storing the CF resources in memory, we aim to reduce the number of requests to the CF API and avoid reaching the rate limit. + +The cache feature is optional and can be enabled via the environment variable RESOURCE_CACHE_ENABLED, which can have values of true or false by default. Below are the environment variables that control the caching behavior: + +- **`RESOURCE_CACHE_ENABLED`** + - Description: Determines whether the caching mechanism is enabled or disabled. + - Type: Boolean + - Default: `true` + - Values: + - `true`: Enables caching of CF resources. + - `false`: Disables the cache, and the operator will fetch CF resources directly from the CF API on each request. + +- **`CACHE_TIMEOUT`** + - Description: Defines the duration after which the cache is refreshed. The cache is refreshed based on the last time the cache was refreshed. + - Type: String + - Default: `1m` (1 minute) + - Values: + - The timeout can be specified in seconds (`s`), minutes (`m`), or hours (`h`). For example: + - `30s` for 30 seconds + - `10m` for 10 minutes + - `1h` for 1 hour. + +These environment variables can be configured in your `deployment.yaml` file as follows: + +```yaml + env: + - name: CACHE_TIMEOUT + value: "{{ .Values.cache.timeout }}" + - name: RESOURCE_CACHE_ENABLED + value: "{{ .Values.cache.enabled }}" +``` + +Additionally, the corresponding values can be set in the `values.yaml` file, allowing the operator to be easily configured: + +```yaml +# -- Enable Resources Cache +cache: + # -- Whether to enable the cache + enabled: true # default: true + # -- Cache expiration time + timeout: 1m # default: 1m +``` + ## Logging cf-service-operator uses [logr](https://github.com/go-logr) with [zap](https://github.com/uber-go/zap) for logging. From 6a2214f2231a6ee6b0a79d28fe769b47da73b953 Mon Sep 17 00:00:00 2001 From: SantVent-Steam <160760719+santiago-ventura@users.noreply.github.com> Date: Thu, 19 Sep 2024 09:10:36 +0200 Subject: [PATCH 44/63] Update operator.md --- website/content/en/docs/configuration/operator.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/content/en/docs/configuration/operator.md b/website/content/en/docs/configuration/operator.md index 32f9e56..8c2809c 100644 --- a/website/content/en/docs/configuration/operator.md +++ b/website/content/en/docs/configuration/operator.md @@ -76,7 +76,7 @@ The cache feature is optional and can be enabled via the environment variable RE - **`RESOURCE_CACHE_ENABLED`** - Description: Determines whether the caching mechanism is enabled or disabled. - Type: Boolean - - Default: `true` + - Default: `false` - Values: - `true`: Enables caching of CF resources. - `false`: Disables the cache, and the operator will fetch CF resources directly from the CF API on each request. @@ -107,7 +107,7 @@ Additionally, the corresponding values can be set in the `values.yaml` file, all # -- Enable Resources Cache cache: # -- Whether to enable the cache - enabled: true # default: true + enabled: false # default: false # -- Cache expiration time timeout: 1m # default: 1m ``` From 4467f58d375c6ac717f59d4082704474bb07da09 Mon Sep 17 00:00:00 2001 From: SantVent-Steam <160760719+santiago-ventura@users.noreply.github.com> Date: Thu, 19 Sep 2024 09:36:51 +0200 Subject: [PATCH 45/63] Update based on review comments --- website/content/en/docs/configuration/operator.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/content/en/docs/configuration/operator.md b/website/content/en/docs/configuration/operator.md index 8c2809c..86b4d1a 100644 --- a/website/content/en/docs/configuration/operator.md +++ b/website/content/en/docs/configuration/operator.md @@ -82,7 +82,7 @@ The cache feature is optional and can be enabled via the environment variable RE - `false`: Disables the cache, and the operator will fetch CF resources directly from the CF API on each request. - **`CACHE_TIMEOUT`** - - Description: Defines the duration after which the cache is refreshed. The cache is refreshed based on the last time the cache was refreshed. + Description: This defines the duration after which the cache is refreshed. The cache is refreshed based on the last time it was refreshed. - Type: String - Default: `1m` (1 minute) - Values: @@ -101,7 +101,7 @@ These environment variables can be configured in your `deployment.yaml` file as value: "{{ .Values.cache.enabled }}" ``` -Additionally, the corresponding values can be set in the `values.yaml` file, allowing the operator to be easily configured: +Additionally, the corresponding values can be set in the `values.yaml` file of the [helm chart](https://github.com/SAP/cf-service-operator-helm/blob/main/chart/values.yaml)![image](https://github.com/user-attachments/assets/0426656d-fd55-4276-a7c3-e4483ba74218), allowing the operator to be easily configured: ```yaml # -- Enable Resources Cache From 6c99dc4ab666d8de073746f8116d91dac3b65745 Mon Sep 17 00:00:00 2001 From: SantVent-Steam <160760719+santiago-ventura@users.noreply.github.com> Date: Thu, 19 Sep 2024 09:38:54 +0200 Subject: [PATCH 46/63] remove error in markdown --- website/content/en/docs/configuration/operator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/en/docs/configuration/operator.md b/website/content/en/docs/configuration/operator.md index 86b4d1a..c044cb0 100644 --- a/website/content/en/docs/configuration/operator.md +++ b/website/content/en/docs/configuration/operator.md @@ -101,7 +101,7 @@ These environment variables can be configured in your `deployment.yaml` file as value: "{{ .Values.cache.enabled }}" ``` -Additionally, the corresponding values can be set in the `values.yaml` file of the [helm chart](https://github.com/SAP/cf-service-operator-helm/blob/main/chart/values.yaml)![image](https://github.com/user-attachments/assets/0426656d-fd55-4276-a7c3-e4483ba74218), allowing the operator to be easily configured: +Additionally, the corresponding values can be set in the `values.yaml` file of the [helm chart](https://github.com/SAP/cf-service-operator-helm/blob/main/chart/values.yaml), allowing the operator to be easily configured: ```yaml # -- Enable Resources Cache From 11a962bcc351723ee9ab9e5b13c470e97edd9b99 Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Thu, 19 Sep 2024 10:56:31 +0200 Subject: [PATCH 47/63] simplify/document populate functions --- internal/cf/client.go | 298 ++++++++++++++++++++++-------------------- 1 file changed, 153 insertions(+), 145 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index 7d3f95e..b14ae99 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -74,10 +74,8 @@ var ( func initAndConfigureResourceCache(config *config.Config) *resourceCache { cacheInstanceOnce.Do(func() { - //TODO: make this initialize cache for different testing purposes + // TODO: make this initialize cache for different testing purposes cacheInstance = initResourceCache() - //TODO:Remove later:print cache initialized - fmt.Printf("Resource cache initialized") cacheInstance.setResourceCacheEnabled(config.IsResourceCacheEnabled) cacheInstance.setCacheTimeOut(config.CacheTimeOut) }) @@ -313,76 +311,77 @@ func populateResourceCache[T manageResourceCache](c T, resourceType cacheResourc } } -func (c *spaceClient) populateServiceInstances(ctx context.Context) error { - refreshServiceInstanceResourceCacheMutex.Lock() - defer refreshServiceInstanceResourceCacheMutex.Unlock() +func (c *spaceClient) populateServiceBindings(ctx context.Context) error { + refreshServiceBindingResourceCacheMutex.Lock() + defer refreshServiceBindingResourceCacheMutex.Unlock() - if c.resourceCache.isCacheExpired(instanceType) { - instanceOptions := cfclient.NewServiceInstanceListOptions() - instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) - instanceOptions.Page = 1 - instanceOptions.PerPage = 5000 - - cfInstances, err := c.client.ServiceInstances.ListAll(ctx, instanceOptions) - //TODO:remove later after review - fmt.Println("populate instance cache called") - if err != nil { - return err - } + if !c.resourceCache.isCacheExpired(bindingType) { + return nil + } - var waitGroup sync.WaitGroup - for _, cfInstance := range cfInstances { - waitGroup.Add(1) - go func(cfInstance *cfresource.ServiceInstance) { - defer waitGroup.Done() - if instance, err := c.InitInstance(cfInstance, nil); err == nil { - c.resourceCache.addInstanceInCache(*cfInstance.Metadata.Labels[labelOwner], instance) - } else { - log.Printf("Error initializing instance: %s", err) - } - }(cfInstance) - } - waitGroup.Wait() - c.resourceCache.setLastCacheTime(instanceType) + // retrieve all service bindings with the specified owner + bindingOptions := cfclient.NewServiceCredentialBindingListOptions() + bindingOptions.ListOptions.LabelSelector.EqualTo(labelOwner) + bindingOptions.Page = 1 + bindingOptions.PerPage = 5000 + cfBindings, err := c.client.ServiceCredentialBindings.ListAll(ctx, bindingOptions) + if err != nil { + return err } - return nil + // wrap each service binding as a facade.Binding and add it to the cache (in parallel) + var waitGroup sync.WaitGroup + for _, cfBinding := range cfBindings { + waitGroup.Add(1) + go func(cfBinding *cfresource.ServiceCredentialBinding) { + defer waitGroup.Done() + if binding, err := c.InitBinding(ctx, cfBinding, nil); err == nil { + c.resourceCache.addBindingInCache(*cfBinding.Metadata.Labels[labelOwner], binding) + } else { + log.Printf("Error initializing binding: %s", err) + } + }(cfBinding) + } + waitGroup.Wait() + c.resourceCache.setLastCacheTime(bindingType) + return nil } -func (c *spaceClient) populateServiceBindings(ctx context.Context) error { - refreshServiceBindingResourceCacheMutex.Lock() - defer refreshServiceBindingResourceCacheMutex.Unlock() +func (c *spaceClient) populateServiceInstances(ctx context.Context) error { + refreshServiceInstanceResourceCacheMutex.Lock() + defer refreshServiceInstanceResourceCacheMutex.Unlock() - if c.resourceCache.isCacheExpired(bindingType) { - bindingOptions := cfclient.NewServiceCredentialBindingListOptions() - bindingOptions.ListOptions.LabelSelector.EqualTo(labelOwner) - bindingOptions.Page = 1 - bindingOptions.PerPage = 5000 - - cfBindings, err := c.client.ServiceCredentialBindings.ListAll(ctx, bindingOptions) - //TODO:remove later after review - fmt.Println("populate service binding cache called") - if err != nil { - return err - } + if !c.resourceCache.isCacheExpired(instanceType) { + return nil + } - var waitGroup sync.WaitGroup - for _, cfBinding := range cfBindings { - waitGroup.Add(1) - go func(cfBinding *cfresource.ServiceCredentialBinding) { - defer waitGroup.Done() - if binding, err := c.InitBinding(ctx, cfBinding, nil); err == nil { - c.resourceCache.addBindingInCache(*cfBinding.Metadata.Labels[labelOwner], binding) - } else { - log.Printf("Error initializing binding: %s", err) - } - }(cfBinding) - } - waitGroup.Wait() - c.resourceCache.setLastCacheTime(bindingType) + // retrieve all service instances with the specified owner + instanceOptions := cfclient.NewServiceInstanceListOptions() + instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) + instanceOptions.Page = 1 + instanceOptions.PerPage = 5000 + cfInstances, err := c.client.ServiceInstances.ListAll(ctx, instanceOptions) + if err != nil { + return err } + // wrap each service instance as a facade.Instance and add it to the cache (in parallel) + var waitGroup sync.WaitGroup + for _, cfInstance := range cfInstances { + waitGroup.Add(1) + go func(cfInstance *cfresource.ServiceInstance) { + defer waitGroup.Done() + if instance, err := c.InitInstance(cfInstance, nil); err == nil { + c.resourceCache.addInstanceInCache(*cfInstance.Metadata.Labels[labelOwner], instance) + } else { + log.Printf("Error initializing instance: %s", err) + } + }(cfInstance) + } + waitGroup.Wait() + c.resourceCache.setLastCacheTime(instanceType) + return nil } @@ -390,102 +389,111 @@ func (c *organizationClient) populateSpaces(ctx context.Context) error { refreshSpaceResourceCacheMutex.Lock() defer refreshSpaceResourceCacheMutex.Unlock() - if c.resourceCache.isCacheExpired(spaceType) { - spaceOptions := cfclient.NewSpaceListOptions() - //TODO: check for existing spaces as label owner annotation wont be present - spaceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) - spaceOptions.Page = 1 - spaceOptions.PerPage = 5000 - - //TODO:remove later after review - fmt.Println("populate Space cache called") - cfSpaces, err := c.client.Spaces.ListAll(ctx, spaceOptions) - if err != nil { - return err - } + if !c.resourceCache.isCacheExpired(spaceType) { + return nil + } - var waitGroup sync.WaitGroup - for _, cfSpace := range cfSpaces { - waitGroup.Add(1) - go func(cfSpace *cfresource.Space) { - defer waitGroup.Done() - if binding, err := InitSpace(cfSpace, ""); err == nil { - c.resourceCache.addSpaceInCache(*cfSpace.Metadata.Labels[labelOwner], binding) - } else { - log.Printf("Error initializing space: %s", err) - } - }(cfSpace) - } - waitGroup.Wait() - c.resourceCache.setLastCacheTime(spaceType) + // retrieve all spaces with the specified owner + // TODO: check for existing spaces as label owner annotation wont be present + spaceOptions := cfclient.NewSpaceListOptions() + spaceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) + spaceOptions.Page = 1 + spaceOptions.PerPage = 5000 + cfSpaces, err := c.client.Spaces.ListAll(ctx, spaceOptions) + if err != nil { + return err } + // wrap each space as a facade.Space and add it to the cache (in parallel) + var waitGroup sync.WaitGroup + for _, cfSpace := range cfSpaces { + waitGroup.Add(1) + go func(cfSpace *cfresource.Space) { + defer waitGroup.Done() + if binding, err := InitSpace(cfSpace, ""); err == nil { + c.resourceCache.addSpaceInCache(*cfSpace.Metadata.Labels[labelOwner], binding) + } else { + log.Printf("Error initializing space: %s", err) + } + }(cfSpace) + } + waitGroup.Wait() + c.resourceCache.setLastCacheTime(spaceType) + return nil } func (c *organizationClient) populateSpaceUserRoleCache(ctx context.Context, username string) error { refreshSpaceUserRoleCacheMutex.Lock() defer refreshSpaceUserRoleCacheMutex.Unlock() - fmt.Println("populate Space cache called") - if c.resourceCache.isCacheExpired(spaceUserRoleType) { - spaceOptions := cfclient.NewSpaceListOptions() - - spaceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) - spaceOptions.Page = 1 - spaceOptions.PerPage = 5000 - cfSpaces, err := c.client.Spaces.ListAll(ctx, spaceOptions) - if err != nil { - return err - } - if len(cfSpaces) == 0 { - return fmt.Errorf("no user spaces found") - } - var spaceGUIDs []string - for _, cfSpace := range cfSpaces { - spaceGUIDs = append(spaceGUIDs, cfSpace.GUID) - } - userOptions := cfclient.NewUserListOptions() - userOptions.UserNames.EqualTo(username) - userOptions.Page = 1 - userOptions.PerPage = 5000 + if !c.resourceCache.isCacheExpired(spaceUserRoleType) { + return nil + } - users, err := c.client.Users.ListAll(ctx, userOptions) - if err != nil { - return err - } - if len(users) == 0 { - return fmt.Errorf("found no user with name: %s", username) - } else if len(users) > 1 { - return fmt.Errorf("found multiple users with name: %s (this should not be possible, actually)", username) - } - user := users[0] - - roleListOpts := cfclient.NewRoleListOptions() - roleListOpts.SpaceGUIDs.EqualTo(strings.Join(spaceGUIDs, ",")) - roleListOpts.UserGUIDs.EqualTo(user.GUID) - roleListOpts.Types.EqualTo(cfresource.SpaceRoleDeveloper.String()) - roleListOpts.Page = 1 - roleListOpts.PerPage = 5000 - cfRoles, err := c.client.Roles.ListAll(ctx, roleListOpts) - if err != nil { - return err - } + // retrieve all spaces with the specified owner + spaceOptions := cfclient.NewSpaceListOptions() + spaceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) + spaceOptions.Page = 1 + spaceOptions.PerPage = 5000 + cfSpaces, err := c.client.Spaces.ListAll(ctx, spaceOptions) + if err != nil { + return err + } + if len(cfSpaces) == 0 { + return fmt.Errorf("no user spaces found") + } + var spaceGUIDs []string + for _, cfSpace := range cfSpaces { + spaceGUIDs = append(spaceGUIDs, cfSpace.GUID) + } - if len(cfRoles) == 0 { - return fmt.Errorf("no RoleSpaceUser relationship found") - } - var waitGroup sync.WaitGroup - for _, cfRole := range cfRoles { - waitGroup.Add(1) - go func(cfrole *cfresource.Role) { - defer waitGroup.Done() - c.resourceCache.addSpaceUserRoleInCache(cfrole.Relationships.Space.Data.GUID, cfrole.Relationships.User.Data.GUID, username, cfrole.Type) - }(cfRole) - } - waitGroup.Wait() - c.resourceCache.setLastCacheTime(spaceUserRoleType) + // retrieve user with the specified name + userOptions := cfclient.NewUserListOptions() + userOptions.UserNames.EqualTo(username) + userOptions.Page = 1 + userOptions.PerPage = 5000 + users, err := c.client.Users.ListAll(ctx, userOptions) + if err != nil { + return err + } + if len(users) == 0 { + return fmt.Errorf("found no user with name: %s", username) + } else if len(users) > 1 { + return fmt.Errorf("found multiple users with name: %s (this should not be possible, actually)", username) + } + user := users[0] + + // retrieve corresponding role + roleListOpts := cfclient.NewRoleListOptions() + roleListOpts.SpaceGUIDs.EqualTo(strings.Join(spaceGUIDs, ",")) + roleListOpts.UserGUIDs.EqualTo(user.GUID) + roleListOpts.Types.EqualTo(cfresource.SpaceRoleDeveloper.String()) + roleListOpts.Page = 1 + roleListOpts.PerPage = 5000 + cfRoles, err := c.client.Roles.ListAll(ctx, roleListOpts) + if err != nil { + return err + } + if len(cfRoles) == 0 { + return fmt.Errorf("no RoleSpaceUser relationship found") + } + + // add each role to the cache (in parallel) + var waitGroup sync.WaitGroup + for _, cfRole := range cfRoles { + waitGroup.Add(1) + go func(cfrole *cfresource.Role) { + defer waitGroup.Done() + c.resourceCache.addSpaceUserRoleInCache( + cfrole.Relationships.Space.Data.GUID, + cfrole.Relationships.User.Data.GUID, + username, + cfrole.Type) + }(cfRole) } + waitGroup.Wait() + c.resourceCache.setLastCacheTime(spaceUserRoleType) return nil } From 863e5182a35aec05978e8266d1e8a744a5efd935 Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Thu, 19 Sep 2024 14:58:02 +0200 Subject: [PATCH 48/63] remove not needed length check --- internal/cf/binding.go | 8 +++----- internal/cf/client.go | 10 ++-------- internal/cf/instance.go | 8 +++----- internal/cf/resourcecache.go | 5 +---- internal/cf/space.go | 11 ++++++----- 5 files changed, 15 insertions(+), 27 deletions(-) diff --git a/internal/cf/binding.go b/internal/cf/binding.go index 67dc94b..3784d53 100644 --- a/internal/cf/binding.go +++ b/internal/cf/binding.go @@ -53,11 +53,9 @@ func (c *spaceClient) GetBinding(ctx context.Context, bindingOpts map[string]str if c.resourceCache.isCacheExpired(bindingType) { populateResourceCache[*spaceClient](c, bindingType, "") } - if len(c.resourceCache.getCachedBindings()) != 0 { - binding, inCache := c.resourceCache.getBindingFromCache(bindingOpts["owner"]) - if inCache { - return binding, nil - } + binding, inCache := c.resourceCache.getBindingFromCache(bindingOpts["owner"]) + if inCache { + return binding, nil } } diff --git a/internal/cf/client.go b/internal/cf/client.go index b14ae99..18a9e1d 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -275,37 +275,31 @@ type manageResourceCache interface { func populateResourceCache[T manageResourceCache](c T, resourceType cacheResourceType, username string) { ctx := context.Background() + var err error switch resourceType { case bindingType: if client, ok := any(c).(ResourceServicesClient[T]); ok { err = client.populateServiceBindings(ctx) - //TODO:remove later - fmt.Println("populate service binding finished") } case instanceType: if client, ok := any(c).(ResourceServicesClient[T]); ok { err = client.populateServiceInstances(ctx) - //TODO:remove later - fmt.Println("populate service instance finished") } case spaceType: if client, ok := any(c).(ResourceSpaceClient[T]); ok { err = client.populateSpaces(ctx) - //TODO:remove later - fmt.Println("populate space finished") } case spaceUserRoleType: if client, ok := any(c).(ResourceSpaceClient[T]); ok { err = client.populateSpaceUserRoleCache(ctx, username) - fmt.Println("populate service spaceUserRoles finished") } } if err != nil { // reset the cache to nil in case of error - log.Printf("Error populating %s: %s", resourceType, err) + log.Printf("Error populating cache for type %s: %s", resourceType, err) c.resetCache(resourceType) return } diff --git a/internal/cf/instance.go b/internal/cf/instance.go index 61f5ca1..cb91905 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -52,11 +52,9 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s if c.resourceCache.isCacheExpired(instanceType) { populateResourceCache[*spaceClient](c, instanceType, "") } - if len(c.resourceCache.getCachedInstances()) != 0 { - instance, inCache := c.resourceCache.getInstanceFromCache(instanceOpts["owner"]) - if inCache { - return instance, nil - } + instance, inCache := c.resourceCache.getInstanceFromCache(instanceOpts["owner"]) + if inCache { + return instance, nil } } diff --git a/internal/cf/resourcecache.go b/internal/cf/resourcecache.go index ba7c0fe..0d0b4dc 100644 --- a/internal/cf/resourcecache.go +++ b/internal/cf/resourcecache.go @@ -105,8 +105,6 @@ func (c *resourceCache) setLastCacheTime(resourceType cacheResourceType) { case spaceUserRoleType: c.spaceUserRoleLastCacheTime = now } - //TODO:remove later - fmt.Printf("Last cache time for %s: %v\n", resourceType, now) } // isCacheExpired checks if the cache is expired for a specific resource type @@ -129,9 +127,8 @@ func (c *resourceCache) isCacheExpired(resourceType cacheResourceType) bool { } expirationTime := lastCacheTime.Add(c.cacheTimeOut) - //TODO:remove later - fmt.Printf("Expiration time for %s: %v and last cached time: %v and timenow :%v\n", resourceType, expirationTime, lastCacheTime, time.Now()) isExpired := time.Now().After(expirationTime) + return isExpired } diff --git a/internal/cf/space.go b/internal/cf/space.go index 1c4f331..7659e99 100644 --- a/internal/cf/space.go +++ b/internal/cf/space.go @@ -22,11 +22,9 @@ func (c *organizationClient) GetSpace(ctx context.Context, owner string) (*facad if c.resourceCache.isCacheExpired(spaceType) { populateResourceCache[*organizationClient](c, spaceType, "") } - if len(c.resourceCache.getCachedSpaces()) != 0 { - space, inCache := c.resourceCache.getSpaceFromCache(owner) - if inCache { - return space, nil - } + space, inCache := c.resourceCache.getSpaceFromCache(owner) + if inCache { + return space, nil } } @@ -45,6 +43,7 @@ func (c *organizationClient) GetSpace(ctx context.Context, owner string) (*facad } space := spaces[0] + // TODO: add directly to cache return InitSpace(space, owner) } @@ -69,6 +68,8 @@ func (c *organizationClient) CreateSpace(ctx context.Context, name string, owner WithAnnotation(annotationPrefix, annotationKeyGeneration, strconv.FormatInt(generation, 10)) _, err = c.client.Spaces.Create(ctx, req) + // do not add space to the cache here because we wait until the space GUID is known + return err } From 443f26c3fdfb1df4c67f3419d5b9993c6bd918e4 Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Thu, 19 Sep 2024 17:18:20 +0200 Subject: [PATCH 49/63] Add TODO for keys in shared resource cache --- internal/cf/resourcecache.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/cf/resourcecache.go b/internal/cf/resourcecache.go index 0d0b4dc..8ae4300 100644 --- a/internal/cf/resourcecache.go +++ b/internal/cf/resourcecache.go @@ -22,6 +22,14 @@ import ( type resourceCache struct { mutex sync.RWMutex + // TODO: document which keys are used for the maps + // TODO: check if the keys for resources like spaces are unique across all CF organziations + // Scenario: + // - we have two CF organizations and one org_client for each of them + // - both org_clients will use the same shared resource cache + // => verify that it is not possible that two spaces from different CF organizations + // are using the same key for below map + // cache for each resource type bindings map[string]*facade.Binding instances map[string]*facade.Instance From 109a2627b8ff6c0fd9cb5b33e72dd89923540053 Mon Sep 17 00:00:00 2001 From: santiago-ventura <160760719+santiago-ventura@users.noreply.github.com> Date: Fri, 20 Sep 2024 12:49:40 +0200 Subject: [PATCH 50/63] health.go uses the cache to retrieve the space --- internal/cf/client.go | 9 ++++++++- internal/cf/health.go | 10 ++++++++-- internal/controllers/space_controller.go | 4 ++-- internal/facade/health.go | 10 +++++++--- 4 files changed, 25 insertions(+), 8 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index 18a9e1d..7ce8867 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -225,7 +225,7 @@ func NewSpaceClient(spaceGuid string, url string, username string, password stri } -func NewSpaceHealthChecker(spaceGuid string, url string, username string, password string) (facade.SpaceHealthChecker, error) { +func NewSpaceHealthChecker(spaceGuid string, url string, username string, password string, config *config.Config) (facade.SpaceHealthChecker, error) { clientCacheMutex.Lock() defer clientCacheMutex.Unlock() @@ -254,6 +254,13 @@ func NewSpaceHealthChecker(spaceGuid string, url string, username string, passwo } } + if config.IsResourceCacheEnabled && client.resourceCache == nil { + if cfResourceCache != nil { + // It is expected cfResourceCache be already populated + client.resourceCache = cfResourceCache + } + } + return client, err } diff --git a/internal/cf/health.go b/internal/cf/health.go index 7e3fc0c..46789c8 100644 --- a/internal/cf/health.go +++ b/internal/cf/health.go @@ -7,8 +7,14 @@ package cf import "context" -func (c *spaceClient) Check(ctx context.Context) error { - // TODO: Need to check if caching needed here or code can be removed +// TODO: Ask why do we have the health check with a different client than the origanization unit? +func (c *spaceClient) Check(ctx context.Context, owner string) error { + if c.resourceCache.checkResourceCacheEnabled() { + _, inCache := c.resourceCache.getSpaceFromCache(owner) + if inCache { + return nil + } + } _, err := c.client.Spaces.Get(ctx, c.spaceGuid) if err != nil { return err diff --git a/internal/controllers/space_controller.go b/internal/controllers/space_controller.go index ae9cd6f..243f09e 100644 --- a/internal/controllers/space_controller.go +++ b/internal/controllers/space_controller.go @@ -235,13 +235,13 @@ func (r *SpaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (resu url := string(secret.Data["url"]) username := string(secret.Data["username"]) password := string(secret.Data["password"]) - checker, err := r.HealthCheckerBuilder(status.SpaceGuid, url, username, password) + checker, err := r.HealthCheckerBuilder(status.SpaceGuid, url, username, password, r.Config) if err != nil { return ctrl.Result{}, errors.Wrapf(err, "failed to build the healthchecker from secret %s", secretName) } log.V(1).Info("Checking space") - if err := checker.Check(ctx); err != nil { + if err := checker.Check(ctx, cfspace.Owner); err != nil { return ctrl.Result{}, errors.Wrap(err, "healthcheck failed") } diff --git a/internal/facade/health.go b/internal/facade/health.go index 394ba6d..b525838 100644 --- a/internal/facade/health.go +++ b/internal/facade/health.go @@ -5,11 +5,15 @@ SPDX-License-Identifier: Apache-2.0 package facade -import "context" +import ( + "context" + + "github.com/sap/cf-service-operator/internal/config" +) //counterfeiter:generate . SpaceHealthChecker type SpaceHealthChecker interface { - Check(ctx context.Context) error + Check(ctx context.Context, owner string) error } -type SpaceHealthCheckerBuilder func(string, string, string, string) (SpaceHealthChecker, error) +type SpaceHealthCheckerBuilder func(string, string, string, string, *config.Config) (SpaceHealthChecker, error) From 5fbc0ad32a52bf1213e9f19ebb603d139c4721dc Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Fri, 20 Sep 2024 18:42:55 +0530 Subject: [PATCH 51/63] small fix to mutex --- internal/cf/client.go | 8 +-- internal/cf/resourcecache.go | 65 ++++++++++--------- internal/controllers/suite_test.go | 2 +- .../facadefakes/fake_space_health_checker.go | 18 ++--- 4 files changed, 49 insertions(+), 44 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index 7ce8867..0c2d29b 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -61,10 +61,10 @@ var ( clientCacheMutex = &sync.Mutex{} clientCache = make(map[clientIdentifier]*clientCacheEntry) cfResourceCache *resourceCache - refreshServiceInstanceResourceCacheMutex = &sync.Mutex{} - refreshSpaceResourceCacheMutex = &sync.Mutex{} - refreshServiceBindingResourceCacheMutex = &sync.Mutex{} - refreshSpaceUserRoleCacheMutex = &sync.Mutex{} + refreshServiceInstanceResourceCacheMutex = sync.Mutex{} + refreshSpaceResourceCacheMutex = sync.Mutex{} + refreshServiceBindingResourceCacheMutex = sync.Mutex{} + refreshSpaceUserRoleCacheMutex = sync.Mutex{} ) var ( diff --git a/internal/cf/resourcecache.go b/internal/cf/resourcecache.go index 8ae4300..bb1616f 100644 --- a/internal/cf/resourcecache.go +++ b/internal/cf/resourcecache.go @@ -20,7 +20,10 @@ import ( // The map uses the owner of the instance (which is Kubernetes UID) as key and the service instance // as value. type resourceCache struct { - mutex sync.RWMutex + instanceMutex sync.RWMutex + bindingMutex sync.RWMutex + spaceMutex sync.RWMutex + spaceUserRoleMutex sync.RWMutex // TODO: document which keys are used for the maps // TODO: check if the keys for resources like spaces are unique across all CF organziations @@ -165,8 +168,8 @@ func (c *resourceCache) resetCache(resourceType cacheResourceType) { // addBindingInCache stores a binding to the cache func (c *resourceCache) addBindingInCache(key string, binding *facade.Binding) { - c.mutex.Lock() - defer c.mutex.Unlock() + c.bindingMutex.Lock() + defer c.bindingMutex.Unlock() c.bindings[key] = binding // TODO :remove After internal review fmt.Printf("Added the binding to Cache: %v \n", binding) @@ -174,8 +177,8 @@ func (c *resourceCache) addBindingInCache(key string, binding *facade.Binding) { // deleteBindingFromCache deletes a specific binding from the cache func (c *resourceCache) deleteBindingFromCache(key string) { - c.mutex.Lock() - defer c.mutex.Unlock() + c.bindingMutex.Lock() + defer c.bindingMutex.Unlock() delete(c.bindings, key) // TODO :remove After internal review fmt.Printf("Added the binding to Cache: %v \n", key) @@ -189,8 +192,8 @@ func (c *resourceCache) getCachedBindings() map[string]*facade.Binding { // getBindingFromCache retrieves a specific binding from the cache func (c *resourceCache) getBindingFromCache(key string) (*facade.Binding, bool) { - c.mutex.RLock() - defer c.mutex.RUnlock() + c.bindingMutex.RLock() + defer c.bindingMutex.RUnlock() binding, found := c.bindings[key] // TODO :remove After internal review fmt.Printf("Got the binding from Cache: %v \n", binding) @@ -199,8 +202,8 @@ func (c *resourceCache) getBindingFromCache(key string) (*facade.Binding, bool) // updateBindingInCache updates a specific binding in the cache func (c *resourceCache) updateBindingInCache(owner string, parameters map[string]interface{}, generation int64) (status bool) { - c.mutex.Lock() - defer c.mutex.Unlock() + c.bindingMutex.Lock() + defer c.bindingMutex.Unlock() //update if the instance is found in the cache //update all the struct variables if they are not nil or empty binding, found := c.bindings[owner] @@ -224,8 +227,8 @@ func (c *resourceCache) updateBindingInCache(owner string, parameters map[string // addInstanceInCache stores an instance to the cache func (c *resourceCache) addInstanceInCache(key string, instance *facade.Instance) { - c.mutex.Lock() - defer c.mutex.Unlock() + c.instanceMutex.Lock() + defer c.instanceMutex.Unlock() // TODO :remove After internal review fmt.Printf("Added the instance to Cache: %v \n", instance) c.instances[key] = instance @@ -233,8 +236,8 @@ func (c *resourceCache) addInstanceInCache(key string, instance *facade.Instance // deleteInstanceFromCache deletes a specific instance from the cache func (c *resourceCache) deleteInstanceFromCache(key string) { - c.mutex.Lock() - defer c.mutex.Unlock() + c.instanceMutex.Lock() + defer c.instanceMutex.Unlock() delete(c.instances, key) // TODO :remove After internal review fmt.Printf("deleted the instance from Cache: %v \n", key) @@ -248,8 +251,8 @@ func (c *resourceCache) getCachedInstances() map[string]*facade.Instance { // getInstanceFromCache retrieves a specific instance from the cache func (c *resourceCache) getInstanceFromCache(key string) (*facade.Instance, bool) { - c.mutex.RLock() - defer c.mutex.RUnlock() + c.instanceMutex.RLock() + defer c.instanceMutex.RUnlock() instance, found := c.instances[key] // TODO :remove After internal review fmt.Printf("Got the instance from Cache: %v \n", instance) @@ -258,8 +261,8 @@ func (c *resourceCache) getInstanceFromCache(key string) (*facade.Instance, bool // updateInstanceInCache updates a specific instance in the cache func (c *resourceCache) updateInstanceInCache(owner string, name string, servicePlanGuid string, parameters map[string]interface{}, generation int64) (status bool) { - c.mutex.Lock() - defer c.mutex.Unlock() + c.instanceMutex.Lock() + defer c.instanceMutex.Unlock() //update if the instance is found in the cache //update all the struct variables if they are not nil or empty instance, found := c.instances[owner] @@ -290,8 +293,8 @@ func (c *resourceCache) updateInstanceInCache(owner string, name string, service // addSpaceInCache stores a space to the cache func (c *resourceCache) addSpaceInCache(key string, space *facade.Space) { - c.mutex.Lock() - defer c.mutex.Unlock() + c.spaceMutex.Lock() + defer c.spaceMutex.Unlock() c.spaces[key] = space // TODO :remove After internal review fmt.Printf("Added the space to Cache: %v \n", space) @@ -299,8 +302,8 @@ func (c *resourceCache) addSpaceInCache(key string, space *facade.Space) { // deleteSpaceFromCache deletes a specific space from the cache func (c *resourceCache) deleteSpaceFromCache(key string) { - c.mutex.Lock() - defer c.mutex.Unlock() + c.spaceMutex.Lock() + defer c.spaceMutex.Unlock() delete(c.spaces, key) // TODO :remove After internal review fmt.Printf("Deleted the space from Cache: %v \n", key) @@ -314,8 +317,8 @@ func (c *resourceCache) getCachedSpaces() map[string]*facade.Space { // getSpaceFromCache retrieves a specific space from the cache func (c *resourceCache) getSpaceFromCache(key string) (*facade.Space, bool) { - c.mutex.RLock() - defer c.mutex.RUnlock() + c.spaceMutex.RLock() + defer c.spaceMutex.RUnlock() space, found := c.spaces[key] // TODO :remove After internal review fmt.Printf("Got the space from Cache: %v \n", space) @@ -324,8 +327,8 @@ func (c *resourceCache) getSpaceFromCache(key string) (*facade.Space, bool) { // updateSpaceInCache updates a specific space in the cache func (c *resourceCache) updateSpaceInCache(owner string, name string, generation int64) (status bool) { - c.mutex.Lock() - defer c.mutex.Unlock() + c.spaceMutex.Lock() + defer c.spaceMutex.Unlock() //update if the space is found in the cache //update all the struct variables if they are not nil or empty space, found := c.spaces[owner] @@ -348,8 +351,8 @@ func (c *resourceCache) updateSpaceInCache(owner string, name string, generation // addSpaceUserRoleInCache adds a specific spaceuserrole to the cache func (c *resourceCache) addSpaceUserRoleInCache(spaceGuid string, userGuid string, username string, roleType string) { - c.mutex.Lock() - defer c.mutex.Unlock() + c.spaceUserRoleMutex.Lock() + defer c.spaceUserRoleMutex.Unlock() role := &spaceUserRole{ user: username, spaceGuid: spaceGuid, @@ -363,8 +366,8 @@ func (c *resourceCache) addSpaceUserRoleInCache(spaceGuid string, userGuid strin // deleteSpaceUserRoleFromCache deletes a specifc spaceuserrole from the cache func (c *resourceCache) deleteSpaceUserRoleFromCache(spaceGuid string) { - c.mutex.Lock() - defer c.mutex.Unlock() + c.spaceUserRoleMutex.Lock() + defer c.spaceUserRoleMutex.Unlock() delete(c.spaceUserRoles, spaceGuid) // TODO :remove After internal review fmt.Printf("Deleted the space user role from Cache: %v \n", spaceGuid) @@ -377,8 +380,8 @@ func (c *resourceCache) getCachedSpaceUserRoles() map[string]*spaceUserRole { // getSpaceUserRoleFromCache gets a specific spaceuserrole from the cache func (c *resourceCache) getSpaceUserRoleFromCache(key string) (*spaceUserRole, bool) { - c.mutex.RLock() - defer c.mutex.RUnlock() + c.spaceUserRoleMutex.RLock() + defer c.spaceUserRoleMutex.RUnlock() spaceUserRole, found := c.spaceUserRoles[key] // TODO :remove After internal review fmt.Printf("Got the space user role from Cache: %v \n", spaceUserRole) diff --git a/internal/controllers/suite_test.go b/internal/controllers/suite_test.go index 7fa5681..055a23e 100644 --- a/internal/controllers/suite_test.go +++ b/internal/controllers/suite_test.go @@ -211,7 +211,7 @@ func addControllers(k8sManager ctrl.Manager) { ClientBuilder: func(organizationName string, url string, username string, password string, config *config.Config) (facade.OrganizationClient, error) { return fakeOrgClient, nil }, - HealthCheckerBuilder: func(spaceGuid string, url string, username string, password string) (facade.SpaceHealthChecker, error) { + HealthCheckerBuilder: func(spaceGuid string, url string, username string, password string, config *config.Config) (facade.SpaceHealthChecker, error) { return fakeSpaceHealthChecker, nil }, Config: cfg, diff --git a/internal/facade/facadefakes/fake_space_health_checker.go b/internal/facade/facadefakes/fake_space_health_checker.go index 0ac62e2..81f5bad 100644 --- a/internal/facade/facadefakes/fake_space_health_checker.go +++ b/internal/facade/facadefakes/fake_space_health_checker.go @@ -13,10 +13,11 @@ import ( ) type FakeSpaceHealthChecker struct { - CheckStub func(context.Context) error + CheckStub func(context.Context, string) error checkMutex sync.RWMutex checkArgsForCall []struct { arg1 context.Context + arg2 string } checkReturns struct { result1 error @@ -28,18 +29,19 @@ type FakeSpaceHealthChecker struct { invocationsMutex sync.RWMutex } -func (fake *FakeSpaceHealthChecker) Check(arg1 context.Context) error { +func (fake *FakeSpaceHealthChecker) Check(arg1 context.Context, arg2 string) error { fake.checkMutex.Lock() ret, specificReturn := fake.checkReturnsOnCall[len(fake.checkArgsForCall)] fake.checkArgsForCall = append(fake.checkArgsForCall, struct { arg1 context.Context - }{arg1}) + arg2 string + }{arg1, arg2}) stub := fake.CheckStub fakeReturns := fake.checkReturns - fake.recordInvocation("Check", []interface{}{arg1}) + fake.recordInvocation("Check", []interface{}{arg1, arg2}) fake.checkMutex.Unlock() if stub != nil { - return stub(arg1) + return stub(arg1, arg2) } if specificReturn { return ret.result1 @@ -53,17 +55,17 @@ func (fake *FakeSpaceHealthChecker) CheckCallCount() int { return len(fake.checkArgsForCall) } -func (fake *FakeSpaceHealthChecker) CheckCalls(stub func(context.Context) error) { +func (fake *FakeSpaceHealthChecker) CheckCalls(stub func(context.Context, string) error) { fake.checkMutex.Lock() defer fake.checkMutex.Unlock() fake.CheckStub = stub } -func (fake *FakeSpaceHealthChecker) CheckArgsForCall(i int) context.Context { +func (fake *FakeSpaceHealthChecker) CheckArgsForCall(i int) (context.Context, string) { fake.checkMutex.RLock() defer fake.checkMutex.RUnlock() argsForCall := fake.checkArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2 } func (fake *FakeSpaceHealthChecker) CheckReturns(result1 error) { From a092be58536522e85f6c7629d192772a1d5a2be8 Mon Sep 17 00:00:00 2001 From: Shilpa Ramasamyreddy Date: Thu, 26 Sep 2024 13:54:11 +0530 Subject: [PATCH 52/63] modified the global cfResourceCache logic --- internal/cf/client.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index 0c2d29b..6ca06e7 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -60,7 +60,6 @@ type clientCacheEntry struct { var ( clientCacheMutex = &sync.Mutex{} clientCache = make(map[clientIdentifier]*clientCacheEntry) - cfResourceCache *resourceCache refreshServiceInstanceResourceCacheMutex = sync.Mutex{} refreshSpaceResourceCacheMutex = sync.Mutex{} refreshServiceBindingResourceCacheMutex = sync.Mutex{} @@ -68,18 +67,18 @@ var ( ) var ( - cacheInstance *resourceCache + cfResourceCache *resourceCache cacheInstanceOnce sync.Once ) func initAndConfigureResourceCache(config *config.Config) *resourceCache { cacheInstanceOnce.Do(func() { // TODO: make this initialize cache for different testing purposes - cacheInstance = initResourceCache() - cacheInstance.setResourceCacheEnabled(config.IsResourceCacheEnabled) - cacheInstance.setCacheTimeOut(config.CacheTimeOut) + cfResourceCache = initResourceCache() + cfResourceCache.setResourceCacheEnabled(config.IsResourceCacheEnabled) + cfResourceCache.setCacheTimeOut(config.CacheTimeOut) }) - return cacheInstance + return cfResourceCache } func newOrganizationClient(organizationName string, url string, username string, password string) (*organizationClient, error) { @@ -179,7 +178,6 @@ func NewOrganizationClient(organizationName string, url string, username string, client.resourceCache = initAndConfigureResourceCache(config) populateResourceCache(client, spaceType, "") populateResourceCache(client, spaceUserRoleType, username) - cfResourceCache = client.resourceCache } return client, err @@ -218,7 +216,6 @@ func NewSpaceClient(spaceGuid string, url string, username string, password stri client.resourceCache = initAndConfigureResourceCache(config) populateResourceCache(client, instanceType, "") populateResourceCache(client, bindingType, "") - cfResourceCache = client.resourceCache } return client, err @@ -237,7 +234,7 @@ func NewSpaceHealthChecker(spaceGuid string, url string, username string, passwo var client *spaceClient = nil if isInCache { // re-use CF client from cache and wrap it as spaceClient - client = &spaceClient{spaceGuid: spaceGuid, client: cacheEntry.client} + client = &spaceClient{spaceGuid: spaceGuid, client: cacheEntry.client, resourceCache: cfResourceCache} if cacheEntry.password != password { // password was rotated => delete client from cache and create a new one below delete(clientCache, identifier) From cb8068aa5a5972d16caef2bd6aae3c573c9bc3ef Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Thu, 26 Sep 2024 14:42:01 +0200 Subject: [PATCH 53/63] remove temporary log messages --- internal/cf/resourcecache.go | 49 ++++-------------------------------- 1 file changed, 5 insertions(+), 44 deletions(-) diff --git a/internal/cf/resourcecache.go b/internal/cf/resourcecache.go index bb1616f..9283c67 100644 --- a/internal/cf/resourcecache.go +++ b/internal/cf/resourcecache.go @@ -6,7 +6,6 @@ SPDX-License-Identifier: Apache-2.0 package cf import ( - "fmt" "log" "sync" "time" @@ -17,23 +16,14 @@ import ( // The resource cache is a simple in-memory cache to store CF resources like spaces, instances and // bindings using a map protected by a mutex. // The resource cache is used to avoid making multiple calls to the CF API and avoid rate limits. -// The map uses the owner of the instance (which is Kubernetes UID) as key and the service instance -// as value. type resourceCache struct { instanceMutex sync.RWMutex bindingMutex sync.RWMutex spaceMutex sync.RWMutex spaceUserRoleMutex sync.RWMutex - // TODO: document which keys are used for the maps - // TODO: check if the keys for resources like spaces are unique across all CF organziations - // Scenario: - // - we have two CF organizations and one org_client for each of them - // - both org_clients will use the same shared resource cache - // => verify that it is not possible that two spaces from different CF organizations - // are using the same key for below map - // cache for each resource type + // (owner of the corresponding custom resource (i.e. Kubernetes UID) is used as key) bindings map[string]*facade.Binding instances map[string]*facade.Instance spaces map[string]*facade.Space @@ -145,7 +135,6 @@ func (c *resourceCache) isCacheExpired(resourceType cacheResourceType) bool { // reset cache of a specific resource type and last cache time func (c *resourceCache) resetCache(resourceType cacheResourceType) { - fmt.Printf("reset requested for %v \n", resourceType) switch resourceType { case bindingType: c.bindings = make(map[string]*facade.Binding) @@ -171,8 +160,6 @@ func (c *resourceCache) addBindingInCache(key string, binding *facade.Binding) { c.bindingMutex.Lock() defer c.bindingMutex.Unlock() c.bindings[key] = binding - // TODO :remove After internal review - fmt.Printf("Added the binding to Cache: %v \n", binding) } // deleteBindingFromCache deletes a specific binding from the cache @@ -180,9 +167,6 @@ func (c *resourceCache) deleteBindingFromCache(key string) { c.bindingMutex.Lock() defer c.bindingMutex.Unlock() delete(c.bindings, key) - // TODO :remove After internal review - fmt.Printf("Added the binding to Cache: %v \n", key) - } // getCachedBindings retrieves all bindings from the cache @@ -195,8 +179,6 @@ func (c *resourceCache) getBindingFromCache(key string) (*facade.Binding, bool) c.bindingMutex.RLock() defer c.bindingMutex.RUnlock() binding, found := c.bindings[key] - // TODO :remove After internal review - fmt.Printf("Got the binding from Cache: %v \n", binding) return binding, found } @@ -229,8 +211,6 @@ func (c *resourceCache) updateBindingInCache(owner string, parameters map[string func (c *resourceCache) addInstanceInCache(key string, instance *facade.Instance) { c.instanceMutex.Lock() defer c.instanceMutex.Unlock() - // TODO :remove After internal review - fmt.Printf("Added the instance to Cache: %v \n", instance) c.instances[key] = instance } @@ -239,9 +219,6 @@ func (c *resourceCache) deleteInstanceFromCache(key string) { c.instanceMutex.Lock() defer c.instanceMutex.Unlock() delete(c.instances, key) - // TODO :remove After internal review - fmt.Printf("deleted the instance from Cache: %v \n", key) - } // getCachedInstances retrieves all instances from the cache @@ -254,8 +231,6 @@ func (c *resourceCache) getInstanceFromCache(key string) (*facade.Instance, bool c.instanceMutex.RLock() defer c.instanceMutex.RUnlock() instance, found := c.instances[key] - // TODO :remove After internal review - fmt.Printf("Got the instance from Cache: %v \n", instance) return instance, found } @@ -263,8 +238,8 @@ func (c *resourceCache) getInstanceFromCache(key string) (*facade.Instance, bool func (c *resourceCache) updateInstanceInCache(owner string, name string, servicePlanGuid string, parameters map[string]interface{}, generation int64) (status bool) { c.instanceMutex.Lock() defer c.instanceMutex.Unlock() - //update if the instance is found in the cache - //update all the struct variables if they are not nil or empty + // update if the instance is found in the cache + // update all the struct variables if they are not nil or empty instance, found := c.instances[owner] if found { if name != "" { @@ -284,7 +259,6 @@ func (c *resourceCache) updateInstanceInCache(owner string, name string, service } return false - } // ----------------------------------------------------------------------------------------------- @@ -296,8 +270,6 @@ func (c *resourceCache) addSpaceInCache(key string, space *facade.Space) { c.spaceMutex.Lock() defer c.spaceMutex.Unlock() c.spaces[key] = space - // TODO :remove After internal review - fmt.Printf("Added the space to Cache: %v \n", space) } // deleteSpaceFromCache deletes a specific space from the cache @@ -305,9 +277,6 @@ func (c *resourceCache) deleteSpaceFromCache(key string) { c.spaceMutex.Lock() defer c.spaceMutex.Unlock() delete(c.spaces, key) - // TODO :remove After internal review - fmt.Printf("Deleted the space from Cache: %v \n", key) - } // getCachedSpaces retrieves all spaces from the cache @@ -320,8 +289,6 @@ func (c *resourceCache) getSpaceFromCache(key string) (*facade.Space, bool) { c.spaceMutex.RLock() defer c.spaceMutex.RUnlock() space, found := c.spaces[key] - // TODO :remove After internal review - fmt.Printf("Got the space from Cache: %v \n", space) return space, found } @@ -329,8 +296,8 @@ func (c *resourceCache) getSpaceFromCache(key string) (*facade.Space, bool) { func (c *resourceCache) updateSpaceInCache(owner string, name string, generation int64) (status bool) { c.spaceMutex.Lock() defer c.spaceMutex.Unlock() - //update if the space is found in the cache - //update all the struct variables if they are not nil or empty + // update if the space is found in the cache + // update all the struct variables if they are not nil or empty space, found := c.spaces[owner] if found { if name != "" { @@ -360,8 +327,6 @@ func (c *resourceCache) addSpaceUserRoleInCache(spaceGuid string, userGuid strin roleType: roleType, } c.spaceUserRoles[spaceGuid] = role - // TODO :remove After internal review - fmt.Printf("Added the space user role to Cache: %v \n", role) } // deleteSpaceUserRoleFromCache deletes a specifc spaceuserrole from the cache @@ -369,8 +334,6 @@ func (c *resourceCache) deleteSpaceUserRoleFromCache(spaceGuid string) { c.spaceUserRoleMutex.Lock() defer c.spaceUserRoleMutex.Unlock() delete(c.spaceUserRoles, spaceGuid) - // TODO :remove After internal review - fmt.Printf("Deleted the space user role from Cache: %v \n", spaceGuid) } // getCachedSpaceUserRoles lists all spaceuserroles from the cache @@ -383,7 +346,5 @@ func (c *resourceCache) getSpaceUserRoleFromCache(key string) (*spaceUserRole, b c.spaceUserRoleMutex.RLock() defer c.spaceUserRoleMutex.RUnlock() spaceUserRole, found := c.spaceUserRoles[key] - // TODO :remove After internal review - fmt.Printf("Got the space user role from Cache: %v \n", spaceUserRole) return spaceUserRole, found } From 781fb58d70e547488badf1ba211d2da668c500ab Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Thu, 26 Sep 2024 14:42:42 +0200 Subject: [PATCH 54/63] enable resource cache in launch.json --- .vscode/launch.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index 7210fae..32575cc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,6 +10,9 @@ "request": "launch", "mode": "auto", "program": "${workspaceFolder}", + "env": { + "RESOURCE_CACHE_ENABLED": "true" + }, "args": [ "--kubeconfig=${workspaceFolder}/.kubeconfig", "--webhook-bind-address=:2443", From f1d443c45e86aa47853ffb8e4906b0d4c875ad11 Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Fri, 27 Sep 2024 16:45:02 +0200 Subject: [PATCH 55/63] remove unneccessary type --- internal/cf/binding.go | 2 +- internal/cf/instance.go | 2 +- internal/cf/space.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/cf/binding.go b/internal/cf/binding.go index 3784d53..ceacc10 100644 --- a/internal/cf/binding.go +++ b/internal/cf/binding.go @@ -51,7 +51,7 @@ func (c *spaceClient) GetBinding(ctx context.Context, bindingOpts map[string]str if c.resourceCache.checkResourceCacheEnabled() { // attempt to retrieve binding from cache if c.resourceCache.isCacheExpired(bindingType) { - populateResourceCache[*spaceClient](c, bindingType, "") + populateResourceCache(c, bindingType, "") } binding, inCache := c.resourceCache.getBindingFromCache(bindingOpts["owner"]) if inCache { diff --git a/internal/cf/instance.go b/internal/cf/instance.go index cb91905..f83496a 100644 --- a/internal/cf/instance.go +++ b/internal/cf/instance.go @@ -50,7 +50,7 @@ func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]s if c.resourceCache.checkResourceCacheEnabled() { // attempt to retrieve instance from cache if c.resourceCache.isCacheExpired(instanceType) { - populateResourceCache[*spaceClient](c, instanceType, "") + populateResourceCache(c, instanceType, "") } instance, inCache := c.resourceCache.getInstanceFromCache(instanceOpts["owner"]) if inCache { diff --git a/internal/cf/space.go b/internal/cf/space.go index 7659e99..9859d0f 100644 --- a/internal/cf/space.go +++ b/internal/cf/space.go @@ -20,7 +20,7 @@ func (c *organizationClient) GetSpace(ctx context.Context, owner string) (*facad if c.resourceCache.checkResourceCacheEnabled() { // attempt to retrieve space from cache if c.resourceCache.isCacheExpired(spaceType) { - populateResourceCache[*organizationClient](c, spaceType, "") + populateResourceCache(c, spaceType, "") } space, inCache := c.resourceCache.getSpaceFromCache(owner) if inCache { @@ -124,7 +124,7 @@ func (c *organizationClient) AddDeveloper(ctx context.Context, guid string, user if c.resourceCache.checkResourceCacheEnabled() { // attempt to retrieve space user role from cache if c.resourceCache.isCacheExpired(spaceUserRoleType) { - populateResourceCache[*organizationClient](c, spaceUserRoleType, username) + populateResourceCache(c, spaceUserRoleType, username) } if len(c.resourceCache.getCachedSpaceUserRoles()) != 0 { _, inCache := c.resourceCache.getSpaceUserRoleFromCache(guid) From 233448ee93aebd7d416693daf9446d16e66872e2 Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Fri, 27 Sep 2024 16:47:58 +0200 Subject: [PATCH 56/63] fix typo --- internal/cf/client.go | 4 ++-- internal/cf/space.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index 6ca06e7..0cdf175 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -408,8 +408,8 @@ func (c *organizationClient) populateSpaces(ctx context.Context) error { waitGroup.Add(1) go func(cfSpace *cfresource.Space) { defer waitGroup.Done() - if binding, err := InitSpace(cfSpace, ""); err == nil { - c.resourceCache.addSpaceInCache(*cfSpace.Metadata.Labels[labelOwner], binding) + if space, err := c.InitSpace(cfSpace, ""); err == nil { + c.resourceCache.addSpaceInCache(*cfSpace.Metadata.Labels[labelOwner], space) } else { log.Printf("Error initializing space: %s", err) } diff --git a/internal/cf/space.go b/internal/cf/space.go index 9859d0f..f8147a3 100644 --- a/internal/cf/space.go +++ b/internal/cf/space.go @@ -44,7 +44,7 @@ func (c *organizationClient) GetSpace(ctx context.Context, owner string) (*facad space := spaces[0] // TODO: add directly to cache - return InitSpace(space, owner) + return c.InitSpace(space, owner) } // Required parameters (may not be initial): name, owner, generation @@ -168,7 +168,7 @@ func (c *organizationClient) AddManager(ctx context.Context, guid string, userna } // InitSpace wraps cfclient.Space as a facade.Space -func InitSpace(space *cfresource.Space, owner string) (*facade.Space, error) { +func (c *organizationClient) InitSpace(space *cfresource.Space, owner string) (*facade.Space, error) { guid := space.GUID name := space.Name generation, err := strconv.ParseInt(*space.Metadata.Annotations[annotationGeneration], 10, 64) From a770254544bb2284a7ada0510e98da89e6f86d6c Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:25:43 +0200 Subject: [PATCH 57/63] fix linting issue --- Dockerfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9566cbb..26a5588 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM --platform=$BUILDPLATFORM golang:1.23.0 as builder +FROM --platform=$BUILDPLATFORM golang:1.23.0 AS builder ARG TARGETOS ARG TARGETARCH @@ -20,9 +20,10 @@ COPY crds/ crds/ COPY Makefile Makefile # Run tests and build -RUN make envtest \ - && CGO_ENABLED=0 KUBEBUILDER_ASSETS="/workspace/bin/k8s/current" go test ./... \ - && CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager main.go +#RUN make envtest \ +# && CGO_ENABLED=0 KUBEBUILDER_ASSETS="/workspace/bin/k8s/current" go test ./... \ +# && CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager main.go +RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager main.go # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details From 6ce7e9890e6b0dd3dd59887e6b29e111f7c50620 Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:26:12 +0200 Subject: [PATCH 58/63] get details for credentials on demand --- internal/cf/binding.go | 21 ++--- internal/cf/client.go | 66 +++++++++++----- internal/cf/client_test.go | 47 +++++------- .../controllers/servicebinding_controller.go | 7 ++ internal/facade/client.go | 1 + .../facade/facadefakes/fake_space_client.go | 76 +++++++++++++++++++ 6 files changed, 161 insertions(+), 57 deletions(-) diff --git a/internal/cf/binding.go b/internal/cf/binding.go index ceacc10..f578609 100644 --- a/internal/cf/binding.go +++ b/internal/cf/binding.go @@ -185,15 +185,6 @@ func (c *spaceClient) InitBinding(ctx context.Context, serviceBinding *cfresourc guid := serviceBinding.GUID - var credentials map[string]interface{} - if state == facade.BindingStateReady { - details, err := c.client.ServiceCredentialBindings.GetDetails(ctx, guid) - if err != nil { - return nil, errors.Wrap(err, "error getting service binding details") - } - credentials = details.Credentials - } - return &facade.Binding{ Guid: guid, Name: serviceBinding.Name, @@ -202,6 +193,16 @@ func (c *spaceClient) InitBinding(ctx context.Context, serviceBinding *cfresourc ParameterHash: *serviceBinding.Metadata.Annotations[annotationParameterHash], State: state, StateDescription: serviceBinding.LastOperation.Description, - Credentials: credentials, + Credentials: nil, // filled later }, nil } + +func (c *spaceClient) FillBindingDetails(ctx context.Context, binding *facade.Binding) error { + details, err := c.client.ServiceCredentialBindings.GetDetails(ctx, binding.Guid) + if err != nil { + return errors.Wrap(err, "error getting service binding details") + } + binding.Credentials = details.Credentials + + return nil +} diff --git a/internal/cf/client.go b/internal/cf/client.go index 0cdf175..b73585a 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -8,13 +8,12 @@ package cf import ( "context" "fmt" - "log" - "strings" "sync" cfclient "github.com/cloudfoundry-community/go-cfclient/v3/client" cfconfig "github.com/cloudfoundry-community/go-cfclient/v3/config" cfresource "github.com/cloudfoundry-community/go-cfclient/v3/resource" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/metrics" "github.com/sap/cf-service-operator/internal/config" @@ -303,7 +302,8 @@ func populateResourceCache[T manageResourceCache](c T, resourceType cacheResourc if err != nil { // reset the cache to nil in case of error - log.Printf("Error populating cache for type %s: %s", resourceType, err) + logger := ctrl.LoggerFrom(ctx) + logger.Error(err, "Failed to populate cache", "type", resourceType) c.resetCache(resourceType) return } @@ -317,6 +317,8 @@ func (c *spaceClient) populateServiceBindings(ctx context.Context) error { return nil } + logger := ctrl.LoggerFrom(ctx) + // retrieve all service bindings with the specified owner bindingOptions := cfclient.NewServiceCredentialBindingListOptions() bindingOptions.ListOptions.LabelSelector.EqualTo(labelOwner) @@ -336,7 +338,7 @@ func (c *spaceClient) populateServiceBindings(ctx context.Context) error { if binding, err := c.InitBinding(ctx, cfBinding, nil); err == nil { c.resourceCache.addBindingInCache(*cfBinding.Metadata.Labels[labelOwner], binding) } else { - log.Printf("Error initializing binding: %s", err) + logger.Error(err, "Failed to wrap binding", "binding", cfBinding.GUID) } }(cfBinding) } @@ -354,6 +356,8 @@ func (c *spaceClient) populateServiceInstances(ctx context.Context) error { return nil } + logger := ctrl.LoggerFrom(ctx) + // retrieve all service instances with the specified owner instanceOptions := cfclient.NewServiceInstanceListOptions() instanceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) @@ -373,7 +377,7 @@ func (c *spaceClient) populateServiceInstances(ctx context.Context) error { if instance, err := c.InitInstance(cfInstance, nil); err == nil { c.resourceCache.addInstanceInCache(*cfInstance.Metadata.Labels[labelOwner], instance) } else { - log.Printf("Error initializing instance: %s", err) + logger.Error(err, "Failed to wrap instance", "instance", cfInstance.GUID) } }(cfInstance) } @@ -391,6 +395,8 @@ func (c *organizationClient) populateSpaces(ctx context.Context) error { return nil } + logger := ctrl.LoggerFrom(ctx) + // retrieve all spaces with the specified owner // TODO: check for existing spaces as label owner annotation wont be present spaceOptions := cfclient.NewSpaceListOptions() @@ -411,7 +417,7 @@ func (c *organizationClient) populateSpaces(ctx context.Context) error { if space, err := c.InitSpace(cfSpace, ""); err == nil { c.resourceCache.addSpaceInCache(*cfSpace.Metadata.Labels[labelOwner], space) } else { - log.Printf("Error initializing space: %s", err) + logger.Error(err, "Failed to wrap space", "space", cfSpace.GUID) } }(cfSpace) } @@ -429,7 +435,7 @@ func (c *organizationClient) populateSpaceUserRoleCache(ctx context.Context, use return nil } - // retrieve all spaces with the specified owner + // retrieve all spaces with specified label 'owner' spaceOptions := cfclient.NewSpaceListOptions() spaceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) spaceOptions.Page = 1 @@ -439,14 +445,14 @@ func (c *organizationClient) populateSpaceUserRoleCache(ctx context.Context, use return err } if len(cfSpaces) == 0 { - return fmt.Errorf("no user spaces found") + return fmt.Errorf("no spaces found") } var spaceGUIDs []string for _, cfSpace := range cfSpaces { spaceGUIDs = append(spaceGUIDs, cfSpace.GUID) } - // retrieve user with the specified name + // retrieve user with specified name (to get user GUID) userOptions := cfclient.NewUserListOptions() userOptions.UserNames.EqualTo(username) userOptions.Page = 1 @@ -456,30 +462,50 @@ func (c *organizationClient) populateSpaceUserRoleCache(ctx context.Context, use return err } if len(users) == 0 { - return fmt.Errorf("found no user with name: %s", username) + return fmt.Errorf("no user found with name '%s'", username) } else if len(users) > 1 { - return fmt.Errorf("found multiple users with name: %s (this should not be possible, actually)", username) + return fmt.Errorf("multiple users found with name '%s'", username) } user := users[0] - // retrieve corresponding role + // prepare common filter options for retrieving SpaceDeveloper role for above user roleListOpts := cfclient.NewRoleListOptions() - roleListOpts.SpaceGUIDs.EqualTo(strings.Join(spaceGUIDs, ",")) - roleListOpts.UserGUIDs.EqualTo(user.GUID) roleListOpts.Types.EqualTo(cfresource.SpaceRoleDeveloper.String()) + roleListOpts.UserGUIDs.EqualTo(user.GUID) roleListOpts.Page = 1 roleListOpts.PerPage = 5000 - cfRoles, err := c.client.Roles.ListAll(ctx, roleListOpts) - if err != nil { - return err + + // collect SpaceDeveloper role for above user in chunks (each N spaces) + // otherwise, the request will fail with 414 Request-URI Too Long + const chunkSize = 30 // 30 space GUIDs each 36 characters plus one comma each => 1110 characters + + var spaceGUID string + var collectedCfRoles []*cfresource.Role + for len(spaceGUIDs) > 0 { + var spaceFilter = "" + for i := 0; i < chunkSize && len(spaceGUIDs) > 0; i++ { + // pop first space GUID from the list + spaceGUID, spaceGUIDs = spaceGUIDs[0], spaceGUIDs[1:] + if i > 0 { + spaceFilter += "," + } + spaceFilter += spaceGUID + } + roleListOpts.SpaceGUIDs.EqualTo(spaceFilter) + roles, err := c.client.Roles.ListAll(ctx, roleListOpts) + if err != nil { + return err + } + collectedCfRoles = append(collectedCfRoles, roles...) } - if len(cfRoles) == 0 { - return fmt.Errorf("no RoleSpaceUser relationship found") + + if len(collectedCfRoles) == 0 { + return fmt.Errorf("no SpaceDeveloper role found for user '%s'", username) } // add each role to the cache (in parallel) var waitGroup sync.WaitGroup - for _, cfRole := range cfRoles { + for _, cfRole := range collectedCfRoles { waitGroup.Add(1) go func(cfrole *cfresource.Role) { defer waitGroup.Done() diff --git a/internal/cf/client_test.go b/internal/cf/client_test.go index 1d9652a..181af06 100644 --- a/internal/cf/client_test.go +++ b/internal/cf/client_test.go @@ -662,7 +662,8 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(spaceClient).ToNot(BeNil()) // Verify resource cache is populated during client creation - Expect(server.ReceivedRequests()).To(HaveLen(5)) + const numRequests = 4 + Expect(server.ReceivedRequests()).To(HaveLen(numRequests)) // - Discover UAA endpoint Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/")) @@ -675,60 +676,52 @@ var _ = Describe("CF Client tests", Ordered, func() { // - Populate cache with bindings Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring(serviceBindingURI)) - // -get the binding details - Expect(server.ReceivedRequests()[4].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[4].RequestURI).To(ContainSubstring(serviceBindingURI + "/" + fakeServiceBindings.Resources[0].GUID + "/details")) // Make a request and verify that cache is used and no additional requests expected spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - Expect(server.ReceivedRequests()).To(HaveLen(5)) // still same as above + Expect(server.ReceivedRequests()).To(HaveLen(numRequests)) // still same as above // Make another request after cache expired and verify that cache is repopulated time.Sleep(10 * time.Second) spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) spaceClient.GetBinding(ctx, map[string]string{"owner": Owner}) - Expect(server.ReceivedRequests()).To(HaveLen(8)) // one more request to repopulate cache + Expect(server.ReceivedRequests()).To(HaveLen(6)) // one more request to repopulate cache + Expect(server.ReceivedRequests()[4].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[4].RequestURI).To(ContainSubstring(serviceInstancesURI)) + Expect(server.ReceivedRequests()[4].RequestURI).NotTo(ContainSubstring(Owner)) Expect(server.ReceivedRequests()[5].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[5].RequestURI).To(ContainSubstring(serviceInstancesURI)) + Expect(server.ReceivedRequests()[5].RequestURI).To(ContainSubstring(serviceBindingURI)) Expect(server.ReceivedRequests()[5].RequestURI).NotTo(ContainSubstring(Owner)) - Expect(server.ReceivedRequests()[6].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[6].RequestURI).To(ContainSubstring(serviceBindingURI)) - Expect(server.ReceivedRequests()[6].RequestURI).NotTo(ContainSubstring(Owner)) - Expect(server.ReceivedRequests()[7].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[7].RequestURI).To(ContainSubstring(serviceBindingURI + "/" + fakeServiceBindings.Resources[0].GUID + "/details")) // Delete instance from cache err = spaceClient.DeleteInstance(ctx, "test-instance-guid-1", Owner) Expect(err).To(BeNil()) - Expect(server.ReceivedRequests()).To(HaveLen(9)) + Expect(server.ReceivedRequests()).To(HaveLen(7)) // - Delete instance from cache - Expect(server.ReceivedRequests()[8].Method).To(Equal("DELETE")) - Expect(server.ReceivedRequests()[8].RequestURI).To(ContainSubstring("test-instance-guid-1")) + Expect(server.ReceivedRequests()[6].Method).To(Equal("DELETE")) + Expect(server.ReceivedRequests()[6].RequestURI).To(ContainSubstring("test-instance-guid-1")) // Get instance from cache should return empty spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - Expect(server.ReceivedRequests()).To(HaveLen(10)) + Expect(server.ReceivedRequests()).To(HaveLen(8)) // - Get call to cf to get the instance - Expect(server.ReceivedRequests()[9].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[9].RequestURI).To(ContainSubstring(Owner)) + Expect(server.ReceivedRequests()[7].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[7].RequestURI).To(ContainSubstring(Owner)) // Delete binding from cache err = spaceClient.DeleteBinding(ctx, "test-binding-guid-1", Owner) Expect(err).To(BeNil()) - Expect(server.ReceivedRequests()).To(HaveLen(11)) + Expect(server.ReceivedRequests()).To(HaveLen(9)) // - Delete binding from cache - Expect(server.ReceivedRequests()[10].Method).To(Equal("DELETE")) - Expect(server.ReceivedRequests()[10].RequestURI).To(ContainSubstring("test-binding-guid-1")) + Expect(server.ReceivedRequests()[8].Method).To(Equal("DELETE")) + Expect(server.ReceivedRequests()[8].RequestURI).To(ContainSubstring("test-binding-guid-1")) // Get binding from cache should return empty spaceClient.GetBinding(ctx, map[string]string{"owner": Owner}) - Expect(server.ReceivedRequests()).To(HaveLen(13)) + Expect(server.ReceivedRequests()).To(HaveLen(10)) // - Get call to cf to get the binding - Expect(server.ReceivedRequests()[11].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[11].RequestURI).To(ContainSubstring(Owner)) - Expect(server.ReceivedRequests()[12].Method).To(Equal("GET")) - Expect(server.ReceivedRequests()[12].RequestURI).To(ContainSubstring(serviceBindingURI + "/" + fakeServiceBindings.Resources[0].GUID + "/details")) - + Expect(server.ReceivedRequests()[9].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[9].RequestURI).To(ContainSubstring(Owner)) }) }) diff --git a/internal/controllers/servicebinding_controller.go b/internal/controllers/servicebinding_controller.go index ee5630a..ffe35ec 100644 --- a/internal/controllers/servicebinding_controller.go +++ b/internal/controllers/servicebinding_controller.go @@ -360,6 +360,13 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque } else if serviceBinding.Annotations["service-operator.cf.cs.sap.com/with-sap-binding-metadata"] == "false" { withMetadata = false } + + err = client.FillBindingDetails(ctx, cfbinding) + if err != nil { + // TODO: implement error handling + return ctrl.Result{RequeueAfter: 10 * time.Minute}, nil + } + err = r.storeBindingSecret(ctx, serviceInstance, serviceBinding, cfbinding.Credentials, spec.SecretName, spec.SecretKey, withMetadata) if err != nil { // TODO: implement error handling diff --git a/internal/facade/client.go b/internal/facade/client.go index 4c65002..ff4183b 100644 --- a/internal/facade/client.go +++ b/internal/facade/client.go @@ -90,6 +90,7 @@ type SpaceClient interface { CreateBinding(ctx context.Context, name string, serviceInstanceGuid string, parameters map[string]interface{}, owner string, generation int64) error UpdateBinding(ctx context.Context, guid string, owner string, generation int64, parameters map[string]interface{}) error DeleteBinding(ctx context.Context, guid string, owner string) error + FillBindingDetails(ctx context.Context, binding *Binding) error FindServicePlan(ctx context.Context, serviceOfferingName string, servicePlanName string, spaceGuid string) (string, error) } diff --git a/internal/facade/facadefakes/fake_space_client.go b/internal/facade/facadefakes/fake_space_client.go index 57816e4..98686cc 100644 --- a/internal/facade/facadefakes/fake_space_client.go +++ b/internal/facade/facadefakes/fake_space_client.go @@ -72,6 +72,18 @@ type FakeSpaceClient struct { deleteInstanceReturnsOnCall map[int]struct { result1 error } + FillBindingDetailsStub func(context.Context, *facade.Binding) error + fillBindingDetailsMutex sync.RWMutex + fillBindingDetailsArgsForCall []struct { + arg1 context.Context + arg2 *facade.Binding + } + fillBindingDetailsReturns struct { + result1 error + } + fillBindingDetailsReturnsOnCall map[int]struct { + result1 error + } FindServicePlanStub func(context.Context, string, string, string) (string, error) findServicePlanMutex sync.RWMutex findServicePlanArgsForCall []struct { @@ -417,6 +429,68 @@ func (fake *FakeSpaceClient) DeleteInstanceReturnsOnCall(i int, result1 error) { }{result1} } +func (fake *FakeSpaceClient) FillBindingDetails(arg1 context.Context, arg2 *facade.Binding) error { + fake.fillBindingDetailsMutex.Lock() + ret, specificReturn := fake.fillBindingDetailsReturnsOnCall[len(fake.fillBindingDetailsArgsForCall)] + fake.fillBindingDetailsArgsForCall = append(fake.fillBindingDetailsArgsForCall, struct { + arg1 context.Context + arg2 *facade.Binding + }{arg1, arg2}) + stub := fake.FillBindingDetailsStub + fakeReturns := fake.fillBindingDetailsReturns + fake.recordInvocation("FillBindingDetails", []interface{}{arg1, arg2}) + fake.fillBindingDetailsMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeSpaceClient) FillBindingDetailsCallCount() int { + fake.fillBindingDetailsMutex.RLock() + defer fake.fillBindingDetailsMutex.RUnlock() + return len(fake.fillBindingDetailsArgsForCall) +} + +func (fake *FakeSpaceClient) FillBindingDetailsCalls(stub func(context.Context, *facade.Binding) error) { + fake.fillBindingDetailsMutex.Lock() + defer fake.fillBindingDetailsMutex.Unlock() + fake.FillBindingDetailsStub = stub +} + +func (fake *FakeSpaceClient) FillBindingDetailsArgsForCall(i int) (context.Context, *facade.Binding) { + fake.fillBindingDetailsMutex.RLock() + defer fake.fillBindingDetailsMutex.RUnlock() + argsForCall := fake.fillBindingDetailsArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeSpaceClient) FillBindingDetailsReturns(result1 error) { + fake.fillBindingDetailsMutex.Lock() + defer fake.fillBindingDetailsMutex.Unlock() + fake.FillBindingDetailsStub = nil + fake.fillBindingDetailsReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeSpaceClient) FillBindingDetailsReturnsOnCall(i int, result1 error) { + fake.fillBindingDetailsMutex.Lock() + defer fake.fillBindingDetailsMutex.Unlock() + fake.FillBindingDetailsStub = nil + if fake.fillBindingDetailsReturnsOnCall == nil { + fake.fillBindingDetailsReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.fillBindingDetailsReturnsOnCall[i] = struct { + result1 error + }{result1} +} + func (fake *FakeSpaceClient) FindServicePlan(arg1 context.Context, arg2 string, arg3 string, arg4 string) (string, error) { fake.findServicePlanMutex.Lock() ret, specificReturn := fake.findServicePlanReturnsOnCall[len(fake.findServicePlanArgsForCall)] @@ -763,6 +837,8 @@ func (fake *FakeSpaceClient) Invocations() map[string][][]interface{} { defer fake.deleteBindingMutex.RUnlock() fake.deleteInstanceMutex.RLock() defer fake.deleteInstanceMutex.RUnlock() + fake.fillBindingDetailsMutex.RLock() + defer fake.fillBindingDetailsMutex.RUnlock() fake.findServicePlanMutex.RLock() defer fake.findServicePlanMutex.RUnlock() fake.getBindingMutex.RLock() From 445aea1073ab9b696d7947b0f27c178442d23056 Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Wed, 2 Oct 2024 17:31:47 +0200 Subject: [PATCH 59/63] add logging --- internal/cf/client.go | 12 ++++++++++-- internal/cf/client_test.go | 26 +++++++++++++++++++------- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/internal/cf/client.go b/internal/cf/client.go index b73585a..77653e2 100644 --- a/internal/cf/client.go +++ b/internal/cf/client.go @@ -318,6 +318,7 @@ func (c *spaceClient) populateServiceBindings(ctx context.Context) error { } logger := ctrl.LoggerFrom(ctx) + logger.V(1).Info("Populating cache for service bindings") // retrieve all service bindings with the specified owner bindingOptions := cfclient.NewServiceCredentialBindingListOptions() @@ -357,6 +358,7 @@ func (c *spaceClient) populateServiceInstances(ctx context.Context) error { } logger := ctrl.LoggerFrom(ctx) + logger.V(1).Info("Populating cache for service instances") // retrieve all service instances with the specified owner instanceOptions := cfclient.NewServiceInstanceListOptions() @@ -396,6 +398,7 @@ func (c *organizationClient) populateSpaces(ctx context.Context) error { } logger := ctrl.LoggerFrom(ctx) + logger.V(1).Info("Populating cache for spaces") // retrieve all spaces with the specified owner // TODO: check for existing spaces as label owner annotation wont be present @@ -435,6 +438,9 @@ func (c *organizationClient) populateSpaceUserRoleCache(ctx context.Context, use return nil } + logger := ctrl.LoggerFrom(ctx) + logger.V(1).Info("Populating cache for roles") + // retrieve all spaces with specified label 'owner' spaceOptions := cfclient.NewSpaceListOptions() spaceOptions.ListOptions.LabelSelector.EqualTo(labelOwner) @@ -447,6 +453,8 @@ func (c *organizationClient) populateSpaceUserRoleCache(ctx context.Context, use if len(cfSpaces) == 0 { return fmt.Errorf("no spaces found") } + + // collect GUIDs of spaces var spaceGUIDs []string for _, cfSpace := range cfSpaces { spaceGUIDs = append(spaceGUIDs, cfSpace.GUID) @@ -484,11 +492,11 @@ func (c *organizationClient) populateSpaceUserRoleCache(ctx context.Context, use for len(spaceGUIDs) > 0 { var spaceFilter = "" for i := 0; i < chunkSize && len(spaceGUIDs) > 0; i++ { - // pop first space GUID from the list - spaceGUID, spaceGUIDs = spaceGUIDs[0], spaceGUIDs[1:] if i > 0 { spaceFilter += "," } + // pop first item from list + spaceGUID, spaceGUIDs = spaceGUIDs[0], spaceGUIDs[1:] spaceFilter += spaceGUID } roleListOpts.SpaceGUIDs.EqualTo(spaceFilter) diff --git a/internal/cf/client_test.go b/internal/cf/client_test.go index 181af06..a13c998 100644 --- a/internal/cf/client_test.go +++ b/internal/cf/client_test.go @@ -643,7 +643,7 @@ var _ = Describe("CF Client tests", Ordered, func() { clientConfig.IsResourceCacheEnabled = true clientConfig.CacheTimeOut = "5s" // short duration for fast test - //resource cache will be initialized only only once, so we need to wait till the cache expiry from previous test + // resource cache is initialized only once, so we need to wait till cache expiry from previous test time.Sleep(10 * time.Second) // Route to handler for DELETE request @@ -662,7 +662,7 @@ var _ = Describe("CF Client tests", Ordered, func() { Expect(spaceClient).ToNot(BeNil()) // Verify resource cache is populated during client creation - const numRequests = 4 + numRequests := 4 Expect(server.ReceivedRequests()).To(HaveLen(numRequests)) // - Discover UAA endpoint Expect(server.ReceivedRequests()[0].Method).To(Equal("GET")) @@ -673,54 +673,66 @@ var _ = Describe("CF Client tests", Ordered, func() { // - Populate cache with instances Expect(server.ReceivedRequests()[2].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[2].RequestURI).To(ContainSubstring(serviceInstancesURI)) + Expect(server.ReceivedRequests()[2].RequestURI).NotTo(ContainSubstring(Owner)) // - Populate cache with bindings Expect(server.ReceivedRequests()[3].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[3].RequestURI).To(ContainSubstring(serviceBindingURI)) + Expect(server.ReceivedRequests()[3].RequestURI).NotTo(ContainSubstring(Owner)) // Make a request and verify that cache is used and no additional requests expected spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) Expect(server.ReceivedRequests()).To(HaveLen(numRequests)) // still same as above // Make another request after cache expired and verify that cache is repopulated + numRequests += 2 // one more request for the repopulatation of the two caches time.Sleep(10 * time.Second) spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) spaceClient.GetBinding(ctx, map[string]string{"owner": Owner}) - Expect(server.ReceivedRequests()).To(HaveLen(6)) // one more request to repopulate cache + Expect(server.ReceivedRequests()).To(HaveLen(numRequests)) + // - Populate cache with instances Expect(server.ReceivedRequests()[4].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[4].RequestURI).To(ContainSubstring(serviceInstancesURI)) Expect(server.ReceivedRequests()[4].RequestURI).NotTo(ContainSubstring(Owner)) + // - Populate cache with bindings Expect(server.ReceivedRequests()[5].Method).To(Equal("GET")) Expect(server.ReceivedRequests()[5].RequestURI).To(ContainSubstring(serviceBindingURI)) Expect(server.ReceivedRequests()[5].RequestURI).NotTo(ContainSubstring(Owner)) // Delete instance from cache + numRequests += 1 err = spaceClient.DeleteInstance(ctx, "test-instance-guid-1", Owner) Expect(err).To(BeNil()) - Expect(server.ReceivedRequests()).To(HaveLen(7)) + Expect(server.ReceivedRequests()).To(HaveLen(numRequests)) // - Delete instance from cache Expect(server.ReceivedRequests()[6].Method).To(Equal("DELETE")) Expect(server.ReceivedRequests()[6].RequestURI).To(ContainSubstring("test-instance-guid-1")) // Get instance from cache should return empty + numRequests += 1 spaceClient.GetInstance(ctx, map[string]string{"owner": Owner}) - Expect(server.ReceivedRequests()).To(HaveLen(8)) + Expect(server.ReceivedRequests()).To(HaveLen(numRequests)) // - Get call to cf to get the instance Expect(server.ReceivedRequests()[7].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[7].RequestURI).To(ContainSubstring(serviceInstancesURI)) Expect(server.ReceivedRequests()[7].RequestURI).To(ContainSubstring(Owner)) // Delete binding from cache + numRequests += 1 err = spaceClient.DeleteBinding(ctx, "test-binding-guid-1", Owner) Expect(err).To(BeNil()) - Expect(server.ReceivedRequests()).To(HaveLen(9)) + Expect(server.ReceivedRequests()).To(HaveLen(numRequests)) // - Delete binding from cache Expect(server.ReceivedRequests()[8].Method).To(Equal("DELETE")) + Expect(server.ReceivedRequests()[8].RequestURI).To(ContainSubstring(serviceBindingURI)) Expect(server.ReceivedRequests()[8].RequestURI).To(ContainSubstring("test-binding-guid-1")) // Get binding from cache should return empty + numRequests += 1 spaceClient.GetBinding(ctx, map[string]string{"owner": Owner}) - Expect(server.ReceivedRequests()).To(HaveLen(10)) + Expect(server.ReceivedRequests()).To(HaveLen(numRequests)) // - Get call to cf to get the binding Expect(server.ReceivedRequests()[9].Method).To(Equal("GET")) + Expect(server.ReceivedRequests()[9].RequestURI).To(ContainSubstring(serviceBindingURI)) Expect(server.ReceivedRequests()[9].RequestURI).To(ContainSubstring(Owner)) }) From 1eea3f2f972f76beafe4d29533d9702104e205c9 Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:19:42 +0200 Subject: [PATCH 60/63] fix error message --- internal/controllers/servicebinding_controller.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/controllers/servicebinding_controller.go b/internal/controllers/servicebinding_controller.go index ffe35ec..7295961 100644 --- a/internal/controllers/servicebinding_controller.go +++ b/internal/controllers/servicebinding_controller.go @@ -222,9 +222,8 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque serviceBinding.SetReadyCondition(cfv1alpha1.ConditionUnknown, string(cfbinding.State), cfbinding.StateDescription) return ctrl.Result{Requeue: true}, nil } else if cfbinding != nil && cfbinding.State != facade.BindingStateReady { - - //return the reconcile function to not reconcile and error message - return ctrl.Result{}, fmt.Errorf("orphaned instance is not ready to be adopted") + // return the reconcile function to not reconcile and error message + return ctrl.Result{}, fmt.Errorf("orphaned binding is not ready to be adopted") } } } From bd782cf1c644024157de6957add017cd3dfe0fb9 Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:36:48 +0200 Subject: [PATCH 61/63] Improve documentation about caching --- .../content/en/docs/configuration/operator.md | 80 +++++++++++-------- 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/website/content/en/docs/configuration/operator.md b/website/content/en/docs/configuration/operator.md index c044cb0..376942a 100644 --- a/website/content/en/docs/configuration/operator.md +++ b/website/content/en/docs/configuration/operator.md @@ -9,9 +9,9 @@ description: > ## Command line parameters -cf-service-operator accepts the following command line flags: +The `cf-service-operator` accepts the following command line flags: -``` +```bash Usage of manager: -cluster-resource-namespace string The namespace for secrets in which cluster-scoped resources are found. @@ -47,7 +47,9 @@ Usage of manager: ``` Notes: -- When running in-cluster, then `-cluster-resource-namespace` defaults to the operator's namespace; otherwise this flag is mandatory. + +- When running in-cluster, then `-cluster-resource-namespace` defaults to the operator's namespace; + otherwise this flag is mandatory. - The logic for looking up the kubeconfig file is - path provided as `-kubeconfig` (if present) - value of environment variable `$KUBECONFIG`(if present) @@ -62,33 +64,42 @@ Notes: ## Environment variables -**Kubecibfig Configuration** -cf-service-operator honors the following environment variables: - -- `$KUBECONFIG` the path to the kubeconfig used by the operator executable; note that this has lower precedence than the command line flag `-kubeconfig`. - -**Cache Configuration** -To optimize the usage of CF resources and reduce the number of API calls, the CF service operator supports an optional caching mechanism. This feature allows resources to be stored in memory and refreshed based on a configurable timeout. -By storing the CF resources in memory, we aim to reduce the number of requests to the CF API and avoid reaching the rate limit. - -The cache feature is optional and can be enabled via the environment variable RESOURCE_CACHE_ENABLED, which can have values of true or false by default. Below are the environment variables that control the caching behavior: - -- **`RESOURCE_CACHE_ENABLED`** - - Description: Determines whether the caching mechanism is enabled or disabled. - - Type: Boolean - - Default: `false` - - Values: - - `true`: Enables caching of CF resources. - - `false`: Disables the cache, and the operator will fetch CF resources directly from the CF API on each request. - -- **`CACHE_TIMEOUT`** - Description: This defines the duration after which the cache is refreshed. The cache is refreshed based on the last time it was refreshed. - - Type: String - - Default: `1m` (1 minute) - - Values: - - The timeout can be specified in seconds (`s`), minutes (`m`), or hours (`h`). For example: - - `30s` for 30 seconds - - `10m` for 10 minutes +The `cf-service-operator` honors the following environment variables: + +### Kubernetes Configuration + +- `$KUBECONFIG` the path to the kubeconfig used by the operator executable. \ + Note, that this has lower precedence than the command line flag `-kubeconfig`. + +### Cache Configuration + +To optimize the usage of CF resources and reduce the number of API calls, the CF service operator +supports an optional caching mechanism. This feature allows resources to be stored in memory and +refreshed based on a configurable timeout. \ +By storing the CF resources in memory, we aim to reduce the number of requests to the CF API and +avoid reaching the rate limit. + +Below are the environment variables that control the caching behavior: + +- **`RESOURCE_CACHE_ENABLED`** + - Description: Determines whether the caching mechanism is enabled or disabled. + - Type: Boolean + - Default: `false` + - Values: + - `true`: Enables caching of CF resources. + - `false`: Disables the cache, and the operator will fetch CF resources directly from the + CF API on each request. + +- **`CACHE_TIMEOUT`** + - Description: This defines the duration after which the cache is refreshed. + The cache is refreshed based on the last time it was refreshed. + - Type: String + - Default: `1m` (1 minute) + - Values: + - The timeout can be specified in seconds (`s`), minutes (`m`), or hours (`h`). \ + For example: + - `30s` for 30 seconds + - `10m` for 10 minutes - `1h` for 1 hour. These environment variables can be configured in your `deployment.yaml` file as follows: @@ -98,7 +109,7 @@ These environment variables can be configured in your `deployment.yaml` file as - name: CACHE_TIMEOUT value: "{{ .Values.cache.timeout }}" - name: RESOURCE_CACHE_ENABLED - value: "{{ .Values.cache.enabled }}" + value: "{{ .Values.cache.enabled }}" ``` Additionally, the corresponding values can be set in the `values.yaml` file of the [helm chart](https://github.com/SAP/cf-service-operator-helm/blob/main/chart/values.yaml), allowing the operator to be easily configured: @@ -107,12 +118,13 @@ Additionally, the corresponding values can be set in the `values.yaml` file of t # -- Enable Resources Cache cache: # -- Whether to enable the cache - enabled: false # default: false + enabled: false # default: false # -- Cache expiration time - timeout: 1m # default: 1m + timeout: 1m # default: 1m ``` ## Logging -cf-service-operator uses [logr](https://github.com/go-logr) with [zap](https://github.com/uber-go/zap) for logging. +The `cf-service-operator` uses [logr](https://github.com/go-logr) with +[zap](https://github.com/uber-go/zap) for logging. Please check the according documentation for details about how to configure logging. From f8da71c5fa591b4b38560708d99b4c4c745c8f80 Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:41:29 +0200 Subject: [PATCH 62/63] update go version --- Dockerfile | 9 ++++----- Makefile | 2 +- go.mod | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 26a5588..a7cc2f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM --platform=$BUILDPLATFORM golang:1.23.0 AS builder +FROM --platform=$BUILDPLATFORM golang:1.23.1 AS builder ARG TARGETOS ARG TARGETARCH @@ -20,10 +20,9 @@ COPY crds/ crds/ COPY Makefile Makefile # Run tests and build -#RUN make envtest \ -# && CGO_ENABLED=0 KUBEBUILDER_ASSETS="/workspace/bin/k8s/current" go test ./... \ -# && CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager main.go -RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager main.go +RUN make envtest \ + && CGO_ENABLED=0 KUBEBUILDER_ASSETS="/workspace/bin/k8s/current" go test ./... \ + && CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager main.go # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details diff --git a/Makefile b/Makefile index b7e5620..fee787d 100644 --- a/Makefile +++ b/Makefile @@ -157,7 +157,7 @@ KUSTOMIZE_VERSION ?= v3.8.7 CONTROLLER_TOOLS_VERSION ?= v0.14.0 CODE_GENERATOR_VERSION ?= v0.23.4 COUNTERFEITER_VERSION ?= v6.8.1 -GOLINT_VERSION ?= v1.57.1 +GOLINT_VERSION ?= v1.61.0 KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" .PHONY: kustomize diff --git a/go.mod b/go.mod index 03f6968..3770961 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/sap/cf-service-operator -go 1.22.5 +go 1.23.1 require ( github.com/cloudfoundry-community/go-cfclient/v3 v3.0.0-alpha.5 From 0e37bb7d3db220a58ebf2e16ec72da0a615e96bc Mon Sep 17 00:00:00 2001 From: RalfHammer <119853077+RalfHammer@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:41:45 +0200 Subject: [PATCH 63/63] remove unused functions --- internal/cf/resourcecache.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/internal/cf/resourcecache.go b/internal/cf/resourcecache.go index 9283c67..b77ffff 100644 --- a/internal/cf/resourcecache.go +++ b/internal/cf/resourcecache.go @@ -169,11 +169,6 @@ func (c *resourceCache) deleteBindingFromCache(key string) { delete(c.bindings, key) } -// getCachedBindings retrieves all bindings from the cache -func (c *resourceCache) getCachedBindings() map[string]*facade.Binding { - return c.bindings -} - // getBindingFromCache retrieves a specific binding from the cache func (c *resourceCache) getBindingFromCache(key string) (*facade.Binding, bool) { c.bindingMutex.RLock() @@ -221,11 +216,6 @@ func (c *resourceCache) deleteInstanceFromCache(key string) { delete(c.instances, key) } -// getCachedInstances retrieves all instances from the cache -func (c *resourceCache) getCachedInstances() map[string]*facade.Instance { - return c.instances -} - // getInstanceFromCache retrieves a specific instance from the cache func (c *resourceCache) getInstanceFromCache(key string) (*facade.Instance, bool) { c.instanceMutex.RLock() @@ -279,11 +269,6 @@ func (c *resourceCache) deleteSpaceFromCache(key string) { delete(c.spaces, key) } -// getCachedSpaces retrieves all spaces from the cache -func (c *resourceCache) getCachedSpaces() map[string]*facade.Space { - return c.spaces -} - // getSpaceFromCache retrieves a specific space from the cache func (c *resourceCache) getSpaceFromCache(key string) (*facade.Space, bool) { c.spaceMutex.RLock()