Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(redis): reader endpoint #835

Merged
merged 5 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions acceptance-tests/apps/redisapp/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.20

require (
github.com/cloudfoundry-community/go-cfenv v1.18.0
github.com/go-chi/chi/v5 v5.0.8
github.com/go-redis/redis/v8 v8.11.5
github.com/mitchellh/mapstructure v1.5.0
)
Expand Down
2 changes: 2 additions & 0 deletions acceptance-tests/apps/redisapp/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand Down
27 changes: 10 additions & 17 deletions acceptance-tests/apps/redisapp/internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,20 @@ import (
"fmt"
"log"
"net/http"
"strings"
"redisapp/internal/credentials"

"github.com/go-redis/redis/v8"
"github.com/go-chi/chi/v5"
)

func App(options *redis.Options) http.HandlerFunc {
client := redis.NewClient(options)
func App(creds credentials.Credentials) http.Handler {
r := chi.NewRouter()

return func(w http.ResponseWriter, r *http.Request) {
key := strings.Trim(r.URL.Path, "/")
switch r.Method {
case http.MethodHead:
aliveness(w, r)
case http.MethodGet:
handleGet(w, r, key, client)
case http.MethodPut:
handleSet(w, r, key, client)
default:
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
}
}
r.Head("/", aliveness)
r.Put("/primary/{key}", handleSet(creds))
r.Get("/primary/{key}", handleGet(creds, primaryNode))
r.Get("/replica/{key}", handleGet(creds, replicaNode))

return r
}

func aliveness(w http.ResponseWriter, r *http.Request) {
Expand Down
58 changes: 44 additions & 14 deletions acceptance-tests/apps/redisapp/internal/app/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,57 @@ package app
import (
"log"
"net/http"
"redisapp/internal/credentials"

"github.com/go-chi/chi/v5"
"github.com/go-redis/redis/v8"
)

func handleGet(w http.ResponseWriter, r *http.Request, key string, client *redis.Client) {
log.Println("Handling get.")
const (
primaryNode = true
replicaNode = false
)

value, err := client.Get(r.Context(), key).Result()
if err != nil {
fail(w, http.StatusNotFound, "Error retrieving value: %s", err)
return
}
func handleGet(creds credentials.Credentials, primary bool) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("Handling get (primary=%t).", primary)

key := chi.URLParam(r, "key")
if key == "" {
fail(w, http.StatusBadRequest, "url parameter 'key' is required")
return
}

c, err := client(creds, primary)
if err != nil {
fail(w, http.StatusFailedDependency, "could not create client: %s", err)
return
}

w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "text/html")
_, err = w.Write([]byte(value))
value, err := c.Get(r.Context(), key).Result()
if err != nil {
fail(w, http.StatusNotFound, "error retrieving value from Redis: %s", err)
return
}

if err != nil {
log.Printf("Error writing value: %s", err)
return
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "text/html")
_, err = w.Write([]byte(value))

if err != nil {
log.Printf("Error writing value: %s", err)
return
}

log.Printf("Value %q retrived from key %q.", value, key)
blgm marked this conversation as resolved.
Show resolved Hide resolved
}
}

log.Printf("Value %q retrived from key %q.", value, key)
func client(creds credentials.Credentials, primary bool) (*redis.Client, error) {
switch primary {
case primaryNode:
return creds.Client(), nil
default:
return creds.ReaderClient()
}
}
39 changes: 24 additions & 15 deletions acceptance-tests/apps/redisapp/internal/app/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,34 @@ import (
"io"
"log"
"net/http"
"redisapp/internal/credentials"

"github.com/go-redis/redis/v8"
"github.com/go-chi/chi/v5"
)

func handleSet(w http.ResponseWriter, r *http.Request, key string, client *redis.Client) {
log.Println("Handling set.")
func handleSet(creds credentials.Credentials) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Println("Handling set.")

rawValue, err := io.ReadAll(r.Body)
if err != nil {
fail(w, http.StatusBadRequest, "Error parsing value: %s", err)
return
}
key := chi.URLParam(r, "key")
if key == "" {
fail(w, http.StatusBadRequest, "url parameter 'key' is required")
return
}

value := string(rawValue)
if err := client.Set(r.Context(), key, value, 0).Err(); err != nil {
fail(w, http.StatusFailedDependency, "Failed to set value: %s", err)
return
}
rawValue, err := io.ReadAll(r.Body)
if err != nil {
fail(w, http.StatusBadRequest, "error parsing value from body: %s", err)
return
}

w.WriteHeader(http.StatusCreated)
log.Printf("Key %q set to value %q.", key, value)
value := string(rawValue)
if err := creds.Client().Set(r.Context(), key, value, 0).Err(); err != nil {
fail(w, http.StatusFailedDependency, "failed to set value in Redis: %s", err)
return
}

w.WriteHeader(http.StatusCreated)
log.Printf("Key %q set to value %q.", key, value)
}
}
47 changes: 32 additions & 15 deletions acceptance-tests/apps/redisapp/internal/credentials/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,51 @@ import (
"github.com/mitchellh/mapstructure"
)

func Read() (*redis.Options, error) {
type Credentials struct {
Host string `mapstructure:"host"`
ReaderEndpoint string `mapstructure:"reader_endpoint"`
Password string `mapstructure:"password"`
TLSPort int `mapstructure:"tls_port"`
}

func Read() (Credentials, error) {
app, err := cfenv.Current()
if err != nil {
return nil, fmt.Errorf("error reading app env: %w", err)
return Credentials{}, fmt.Errorf("error reading app env: %w", err)
}
svs, err := app.Services.WithTag("redis")
if err != nil {
return nil, fmt.Errorf("error reading Redis service details")
}

var r struct {
Host string `mapstructure:"host"`
Password string `mapstructure:"password"`
TLSPort int `mapstructure:"tls_port"`
return Credentials{}, fmt.Errorf("error reading Redis service details")
}

var r Credentials
if err := mapstructure.Decode(svs[0].Credentials, &r); err != nil {
return nil, fmt.Errorf("failed to decode credentials: %w", err)
return Credentials{}, fmt.Errorf("failed to decode credentials: %w", err)
}

if r.Host == "" || r.Password == "" || r.TLSPort == 0 {
return nil, fmt.Errorf("parsed credentials are not valid")
return Credentials{}, fmt.Errorf("parsed credentials are not valid")
}

return &redis.Options{
Addr: fmt.Sprintf("%s:%d", r.Host, r.TLSPort),
Password: r.Password,
return r, nil
}

func (c Credentials) Client() *redis.Client {
return c.client(c.Host)
}

func (c Credentials) ReaderClient() (*redis.Client, error) {
if c.ReaderEndpoint == "" {
return nil, fmt.Errorf("no reader endpoint in the credentials")
}
return c.client(c.ReaderEndpoint), nil
}

func (c Credentials) client(endpoint string) *redis.Client {
return redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", endpoint, c.TLSPort),
Password: c.Password,
DB: 0,
TLSConfig: &tls.Config{},
}, nil
})
}
8 changes: 6 additions & 2 deletions acceptance-tests/redis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ var _ = Describe("Redis", Label("redis"), func() {
By("setting a key-value using the first app")
key := random.Hexadecimal()
value := random.Hexadecimal()
appOne.PUT(value, key)
appOne.PUT(value, "/primary/%s", key)

By("getting the value using the second app")
got := appTwo.GET(key)
got := appTwo.GET("/primary/%s", key)
Expect(got).To(Equal(value))

By("getting the value using the reader endpoint")
got2 := appTwo.GET("/replica/%s", key)
Expect(got2).To(Equal(value))
})
})
14 changes: 7 additions & 7 deletions acceptance-tests/upgrade/update_and_upgrade_redis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ var _ = Describe("Redis", Label("redis"), func() {
By("setting a key-value using the first app")
key := random.Hexadecimal()
value := random.Hexadecimal()
appOne.PUT(value, key)
appOne.PUT(value, "/primary/%s", key)

By("getting the value using the second app")
Expect(appTwo.GET(key)).To(Equal(value))
Expect(appTwo.GET("/primary/%s", key)).To(Equal(value))

By("pushing the development version of the broker")
serviceBroker.UpdateBroker(developmentBuildDir)
Expand All @@ -62,13 +62,13 @@ var _ = Describe("Redis", Label("redis"), func() {
serviceInstance.Upgrade()

By("getting the value using the second app")
Expect(appTwo.GET(key)).To(Equal(value))
Expect(appTwo.GET("/primary/%s", key)).To(Equal(value))

By("updating the instance plan")
serviceInstance.Update(services.WithPlan("default"))

By("getting the value using the second app")
Expect(appTwo.GET(key)).To(Equal(value))
Expect(appTwo.GET("/primary/%s", key)).To(Equal(value))

By("deleting bindings created before the upgrade")
bindingOne.Unbind()
Expand All @@ -80,13 +80,13 @@ var _ = Describe("Redis", Label("redis"), func() {
apps.Restage(appOne, appTwo)

By("getting the value using the second app")
Expect(appTwo.GET(key)).To(Equal(value))
Expect(appTwo.GET("/primary/%s", key)).To(Equal(value))

By("checking data can still be written and read")
keyTwo := random.Hexadecimal()
valueTwo := random.Hexadecimal()
appOne.PUT(valueTwo, keyTwo)
Expect(appTwo.GET(keyTwo)).To(Equal(valueTwo))
appOne.PUT(valueTwo, "/primary/%s", keyTwo)
Expect(appTwo.GET("/primary/%s", keyTwo)).To(Equal(valueTwo))
})
})

Expand Down