Skip to content

Commit bb8d497

Browse files
author
isaac
committed
Add key-value support
Full support for key-value endpoints for both http and stream contexts. Discard the reponse body in some cases so that http keepalive will work.
1 parent 1900f9f commit bb8d497

File tree

3 files changed

+300
-0
lines changed

3 files changed

+300
-0
lines changed

client/nginx.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,8 @@ func (client *NginxClient) post(path string, input interface{}) error {
496496
"expected %v response, got %v",
497497
http.StatusCreated, resp.StatusCode))
498498
}
499+
// empty response Body so keepalive will work
500+
io.Copy(ioutil.Discard, resp.Body)
499501

500502
return nil
501503
}
@@ -778,3 +780,187 @@ func (client *NginxClient) getStreamUpstreams() (*StreamUpstreams, error) {
778780
}
779781
return &upstreams, nil
780782
}
783+
784+
// GetKeyVals fetches all key/value pairs for a given zone. (http)
785+
func (client *NginxClient) GetKeyVals(zone string) (map[string]string, error) {
786+
return client.getKeyVals(zone, false)
787+
}
788+
789+
// GetStreamKeyVals fetches all key/value pairs for a given zone. (stream)
790+
func (client *NginxClient) GetStreamKeyVals(zone string) (map[string]string, error) {
791+
return client.getKeyVals(zone, true)
792+
}
793+
794+
func (client *NginxClient) getKeyVals(zone string, stream bool) (map[string]string, error) {
795+
keyvals := make(map[string]string)
796+
base := "http"
797+
if stream {
798+
base = "stream"
799+
}
800+
err := client.get(fmt.Sprintf("%v/keyvals/%v", base, zone), &keyvals)
801+
if err != nil {
802+
return nil, fmt.Errorf("failed to get keyvals for zone: %v/%v err: %v", base, zone, err)
803+
}
804+
return keyvals, nil
805+
}
806+
807+
// GetKeyValsAll fetches all key/value pairs for all zones. (http)
808+
func (client *NginxClient) GetKeyValsAll() (map[string]map[string]string, error) {
809+
return client.getKeyValsAll(false)
810+
}
811+
812+
// GetStreamKeyValsAll fetches all key/value pairs for all zones. (stream)
813+
func (client *NginxClient) GetStreamKeyValsAll() (map[string]map[string]string, error) {
814+
return client.getKeyValsAll(true)
815+
}
816+
817+
func (client *NginxClient) getKeyValsAll(stream bool) (map[string]map[string]string, error) {
818+
keyvals := make(map[string]map[string]string)
819+
base := "http"
820+
if stream {
821+
base = "stream"
822+
}
823+
err := client.get(fmt.Sprintf("%v/keyvals", base), &keyvals)
824+
if err != nil {
825+
return nil, fmt.Errorf("failed to get keyvals for all %v zones. err: %v", base, err)
826+
}
827+
return keyvals, nil
828+
}
829+
830+
// AddKeyVal adds a new key/value pair.
831+
func (client *NginxClient) AddKeyVal(zone string, keyval map[string]string) error {
832+
return client.addKeyVal(zone, keyval, false)
833+
}
834+
835+
// AddStreamKeyVal adds a new key/value pair. (stream)
836+
func (client *NginxClient) AddStreamKeyVal(zone string, keyval map[string]string) error {
837+
return client.addKeyVal(zone, keyval, true)
838+
}
839+
840+
func (client *NginxClient) addKeyVal(zone string, keyval map[string]string, stream bool) error {
841+
APIVersion := 3
842+
base := "http"
843+
if stream {
844+
base = "stream"
845+
}
846+
path := fmt.Sprintf("%v/keyvals", base)
847+
url := fmt.Sprintf("%v/%v/%v", client.apiEndpoint, APIVersion, path)
848+
if zone != "" {
849+
url = fmt.Sprintf("%v/%v", url, zone)
850+
}
851+
852+
jsonInput, err := json.Marshal(keyval)
853+
if err != nil {
854+
return fmt.Errorf("failed to marshall input: %v", err)
855+
}
856+
857+
resp, err := client.httpClient.Post(url, "application/json", bytes.NewBuffer(jsonInput))
858+
if err != nil {
859+
return fmt.Errorf("failed to create delete request: %v", err)
860+
}
861+
defer resp.Body.Close()
862+
863+
if resp.StatusCode != http.StatusCreated {
864+
return createResponseMismatchError(resp.Body).Wrap(fmt.Sprintf(
865+
"expected %v response, got %v",
866+
http.StatusCreated, resp.StatusCode))
867+
}
868+
io.Copy(ioutil.Discard, resp.Body)
869+
return nil
870+
}
871+
872+
// ModifyKeyVal modifies the value of an existing key. (http)
873+
func (client *NginxClient) ModifyKeyVal(zone string, keyval map[string]string) error {
874+
return client.modifyKeyVal(zone, keyval, false)
875+
}
876+
877+
// ModifyStreamKeyVal modifies the value of an existing key. (stream)
878+
func (client *NginxClient) ModifyStreamKeyVal(zone string, keyval map[string]string) error {
879+
return client.modifyKeyVal(zone, keyval, true)
880+
}
881+
882+
func (client *NginxClient) modifyKeyVal(zone string, keyval map[string]string, stream bool) error {
883+
APIVersion := 3
884+
base := "http"
885+
if stream {
886+
base = "stream"
887+
}
888+
path := fmt.Sprintf("%v/keyvals", base)
889+
url := fmt.Sprintf("%v/%v/%v", client.apiEndpoint, APIVersion, path)
890+
if zone != "" {
891+
url = fmt.Sprintf("%v/%v", url, zone)
892+
} else {
893+
return fmt.Errorf("zone required")
894+
}
895+
896+
jsonInput, err := json.Marshal(keyval)
897+
if err != nil {
898+
return fmt.Errorf("failed to marshall input: %v", err)
899+
}
900+
req, err := http.NewRequest(http.MethodPatch, url, bytes.NewBuffer(jsonInput))
901+
if err != nil {
902+
return fmt.Errorf("failed to create a patch request: %v", err)
903+
}
904+
req.Header.Set("Content-Type", "application/json")
905+
906+
resp, err := client.httpClient.Do(req)
907+
if err != nil {
908+
return fmt.Errorf("failed to do patch request: %v", err)
909+
}
910+
defer resp.Body.Close()
911+
// We will consider ONLY 204 as success
912+
if resp.StatusCode != http.StatusNoContent {
913+
return createResponseMismatchError(resp.Body).Wrap(fmt.Sprintf(
914+
"expected %v response, got %v",
915+
http.StatusNoContent, resp.StatusCode))
916+
}
917+
io.Copy(ioutil.Discard, resp.Body)
918+
return nil
919+
}
920+
921+
// DeleteKey deletes the key/value pair for a given key. (http)
922+
func (client *NginxClient) DeleteKey(zone string, key string) error {
923+
return client.deleteKey(zone, key, false)
924+
}
925+
926+
// DeleteStreamKey deletes the key/value pair for a given key. (stream)
927+
func (client *NginxClient) DeleteStreamKey(zone string, key string) error {
928+
return client.deleteKey(zone, key, true)
929+
}
930+
931+
func (client *NginxClient) deleteKey(zone string, key string, stream bool) error {
932+
APIVersion := 3
933+
base := "http"
934+
if stream {
935+
base = "stream"
936+
}
937+
path := fmt.Sprintf("%v/keyvals", base)
938+
url := fmt.Sprintf("%v/%v/%v", client.apiEndpoint, APIVersion, path)
939+
if zone != "" {
940+
url = fmt.Sprintf("%v/%v", url, zone)
941+
} else {
942+
return fmt.Errorf("zone required")
943+
}
944+
945+
req, err := http.NewRequest(http.MethodDelete, url, nil)
946+
if err != nil {
947+
return fmt.Errorf("failed to create a delete request: %v", err)
948+
}
949+
req.Header.Set("Content-Type", "application/json")
950+
951+
resp, err := client.httpClient.Do(req)
952+
if err != nil {
953+
return fmt.Errorf("failed to do delete request: %v", err)
954+
}
955+
defer resp.Body.Close()
956+
957+
// Expect status 204
958+
if resp.StatusCode != http.StatusNoContent {
959+
return createResponseMismatchError(resp.Body).Wrap(fmt.Sprintf(
960+
"expected %v response, got %v",
961+
http.StatusNoContent, resp.StatusCode))
962+
}
963+
io.Copy(ioutil.Discard, resp.Body)
964+
return nil
965+
966+
}

docker/nginx.conf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,16 @@ http {
2828

2929
#gzip on;
3030

31+
keyval_zone zone=zone_one:32k;
32+
keyval $arg_text $text zone=zone_one;
33+
3134
include /etc/nginx/conf.d/*.conf;
3235
}
3336

3437
stream {
38+
keyval_zone zone=zone_one_stream:32k;
39+
keyval $hostname $text zone=zone_one_stream;
40+
3541
upstream stream_test {
3642
zone stream_test 64k;
3743
}

tests/client_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,114 @@ func TestStreamStats(t *testing.T) {
524524
}
525525
}
526526

527+
func TestKeyValue(t *testing.T) {
528+
testKeyValue(t, false)
529+
testKeyValue(t, true)
530+
}
531+
532+
func testKeyValue(t *testing.T, stream bool) {
533+
zoneName := "zone_one"
534+
if stream {
535+
zoneName = "zone_one_stream"
536+
}
537+
httpClient := &http.Client{}
538+
c, err := client.NewNginxClient(httpClient, "http://127.0.0.1:8080/api")
539+
if err != nil {
540+
t.Fatalf("Error connecting to nginx: %v", err)
541+
}
542+
543+
keyvalMap := make(map[string]string)
544+
keyvalMap["key1"] = "val1"
545+
allMap := make(map[string]map[string]string)
546+
allMap[zoneName] = keyvalMap
547+
548+
if stream {
549+
err = c.AddStreamKeyVal(zoneName, keyvalMap)
550+
551+
} else {
552+
err = c.AddKeyVal(zoneName, keyvalMap)
553+
}
554+
if err != nil {
555+
t.Errorf("Couldn't set keyvals: %v", err)
556+
}
557+
var keyval map[string]string
558+
if stream {
559+
keyval, err = c.GetStreamKeyVals(zoneName)
560+
} else {
561+
keyval, err = c.GetKeyVals(zoneName)
562+
}
563+
if err != nil {
564+
t.Errorf("Couldn't get keyvals for zone: %v, err: %v", zoneName, err)
565+
}
566+
if !reflect.DeepEqual(keyvalMap, keyval) {
567+
t.Errorf("maps are not equal. expected: %+v, got: %+v", keyvalMap, keyval)
568+
}
569+
570+
var keyvals map[string]map[string]string
571+
if stream {
572+
keyvals, err = c.GetStreamKeyValsAll()
573+
} else {
574+
keyvals, err = c.GetKeyValsAll()
575+
}
576+
if err != nil {
577+
t.Errorf("Couldn't get keyvals, %v", err)
578+
}
579+
if !reflect.DeepEqual(allMap, keyvals) {
580+
t.Errorf("maps are not equal. expected: %+v, got: %+v", allMap, keyvals)
581+
}
582+
583+
// modify keyval
584+
keyvalMap["key1"] = "valModified1"
585+
if stream {
586+
err = c.ModifyStreamKeyVal(zoneName, keyvalMap)
587+
} else {
588+
err = c.ModifyKeyVal(zoneName, keyvalMap)
589+
}
590+
if err != nil {
591+
t.Errorf("couldn't set keyval: %v", err)
592+
}
593+
if stream {
594+
keyval, err = c.GetStreamKeyVals(zoneName)
595+
} else {
596+
keyval, err = c.GetKeyVals(zoneName)
597+
}
598+
if err != nil {
599+
t.Errorf("couldn't get keyval: %v", err)
600+
}
601+
if !reflect.DeepEqual(keyvalMap, keyval) {
602+
t.Errorf("maps are not equal. expected: %+v, got: %+v", keyvalMap, keyval)
603+
}
604+
605+
// error expected
606+
if stream {
607+
err = c.AddStreamKeyVal(zoneName, keyvalMap)
608+
} else {
609+
err = c.AddKeyVal(zoneName, keyvalMap)
610+
}
611+
if err == nil {
612+
t.Errorf("adding same key/val should result in error")
613+
}
614+
615+
if stream {
616+
err = c.DeleteStreamKey(zoneName, "key1")
617+
} else {
618+
err = c.DeleteKey(zoneName, "key1")
619+
}
620+
if err != nil {
621+
t.Errorf("error deleting key")
622+
}
623+
624+
// error expected
625+
if stream {
626+
err = c.ModifyStreamKeyVal(zoneName, keyvalMap)
627+
} else {
628+
err = c.ModifyKeyVal(zoneName, keyvalMap)
629+
}
630+
if err == nil {
631+
t.Errorf("modifying nonexistent key/val should result in error")
632+
}
633+
}
634+
527635
func compareUpstreamServers(x []client.UpstreamServer, y []client.UpstreamServer) bool {
528636
var xServers []string
529637
for _, us := range x {

0 commit comments

Comments
 (0)