Skip to content

Commit

Permalink
feat: adding go cac and experimentation client wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
namitgoel committed Aug 25, 2024
1 parent a455f93 commit 72d613f
Show file tree
Hide file tree
Showing 5 changed files with 347 additions and 0 deletions.
40 changes: 40 additions & 0 deletions clients/go/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Set directory path that contains superposition object files in <span style="color: red" > SUPERPOSITION_LIB_PATH </span> env variable;

Set directory path that contains C header file <span style="color: red" > SUPERPOSITION_INCLUDE_PATH </span> env variable;

<span style="color: red" > export CGO_CFLAGS="-I${SUPERPOSITION_INCLUDE_PATH}" </span> (Set this flag in order to know go where to pick C header file from).

## [<u> CAC Client </u>](./cacclient/main.go)

* #### <span style="color: red" > export CGO_LDFLAGS="-L${SUPERPOSITION_LIB_PATH} -lcac_client" </span> (Set this flag in order to know go where to pick superposition object files from).
1. This exports a class that exposes functions that internally call rust functions.
2. For Different platform it read different superposition object files.
* <span style="color: #808080" >For Mac </span> -> libcac_client.dylib
* <span style="color: #357EC7" >For Windows </span> -> libcac_client.so
* <span style="color: orange" >For Linux </span> -> libcac_client.dll
3. This run CAC CLient in two thread one is main thread another is worker thread.
4. Polling updates for config are done on different thread. ([ref](./cacclient/main.go#50)).


## [<u> Experimentation Client </u>](./expclient/main.go)

* #### <span style="color: red" > export CGO_LDFLAGS="-L${SUPERPOSITION_LIB_PATH} -lexperimentation_client" </span> (Set this flag in order to know go where to pick superposition object files from).
1. This exports a class that exposes functions that internally call rust functions.
2. For Different platform it read different superposition object files.
* <span style="color: #808080" >For Mac </span> -> libexperimentation_client.dylib
* <span style="color: #357EC7" >For Windows </span> -> libexperimentation_client.so
* <span style="color: orange" >For Linux </span> -> libexperimentation_client.dll
3. This run Experimentation CLient in two thread one is main thread another is worker thread.
4. Polling updates for experiments are done on different thread. ([ref](./expclient/main.go#55)).


## [<u> Test </u>](./main.go)

* #### <span style="color: red" > export CGO_LDFLAGS="-L${SUPERPOSITION_LIB_PATH} -lcac_client -lexperimentation_client" </span> (Set this to run sample project)
1. To test this sample project follow below steps.
* Run superposition client.
* Run <u> **go run main.go** </u>.
2. By Default this sample code uses [dev](./main.go#L11) tenant.
3. By Default this sample code assumes superposition is running on [8080](./main.go#L13) port.
3. By Default this sample code polls superposition every [1 second](./main.go#L12) port.
4. This sample code creates both [CAC CLient](./main.go#15) and [Experimentation Client](./main.go#20) with above default values.
158 changes: 158 additions & 0 deletions clients/go/cacclient/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package cacclient

/*
#include "libcac_client.h"
*/
import "C"
import (
"encoding/json"
"fmt"
"strings"
"unsafe"
)

// CacClient struct
type CacClient struct {
tenant string
pollingFrequency int
cacHostName string
delimiter string
}

// NewCacClient creates a new CacClient
func NewCacClient(tenantName string, pollingFrequency int, cacHostName string) *CacClient {
return &CacClient{tenant: tenantName, pollingFrequency: pollingFrequency, cacHostName: cacHostName, delimiter: ","}
}

// GetCacLastErrorMessage gets the last error message from the C library
func (c *CacClient) GetCacLastErrorMessage() string {
return C.GoString(C.cac_last_error_message())
}

// GetCacLastErrorLength gets the length of the last error message
func (c *CacClient) GetCacLastErrorLength() int {
return int(C.cac_last_error_length())
}

// GetCacClient gets the client pointer from the C library
func (c *CacClient) GetCacClient() *C.Arc_Client {
return C.cac_get_client(C.CString(c.tenant))
}

// CreateNewCacClient creates a new client in the C library
func (c *CacClient) CreateNewCacClient() int {
resp := C.cac_new_client(C.CString(c.tenant), C.ulong(c.pollingFrequency), C.CString(c.cacHostName))
if resp == 1 {
errorMessage := c.GetCacLastErrorMessage()
fmt.Printf("Some Error Occur while creating new client: %s\n", errorMessage)
}
return int(resp)
}

// StartCacPollingUpdate starts polling updates in a separate goroutine
func (c *CacClient) StartCacPollingUpdate() {
tenant := C.CString(c.tenant)
go C.cac_start_polling_update(tenant)
}

// GetCacConfig gets the configuration from the C library
func (c *CacClient) GetCacConfig(filterQuery *map[string]string, filterPrefix *[]string) string {
var strFilterPrefix, strFilterQuery *string

if filterPrefix != nil {
val := strings.Join(*filterPrefix, c.delimiter)
strFilterPrefix = &val
}

if filterQuery != nil {
byteVal, err := json.Marshal(filterQuery)
if err != nil {
fmt.Println("Failed to covert json to string")
return ""
}
strVal := string(byteVal)
strFilterQuery = &strVal
}
clientPtr := c.GetCacClient()
var fp, fq *C.char

if strFilterPrefix != nil {
fp = C.CString(*strFilterPrefix)
} else {
fp = nil
}

if strFilterQuery != nil {
fq = C.CString(*strFilterQuery)
} else {
fq = nil
}
return C.GoString(C.cac_get_config(clientPtr, fq, fp))
}

// FreeCacClient frees the client in the C library
func (c *CacClient) FreeCacClient(clientPtr string) {
ptr := c.GetCacClient()
C.cac_free_client(ptr)
}

// FreeCacString frees a string in the C library
func (c *CacClient) FreeCacString(s string) {
cs := C.CString(s)
defer C.free(unsafe.Pointer(cs))
C.cac_free_string(cs)
}

// GetLastModified gets the last modified timestamp from the C library
func (c *CacClient) GetLastModified() string {
clientPtr := c.GetCacClient()
return C.GoString(C.cac_get_last_modified(clientPtr))
}

// GetResolvedConfig gets the resolved configuration from the C library
func (c *CacClient) GetResolvedConfig(query map[string]string, filterKeys *[]string, mergeStrategy string) string {

var strfilterKeys *string

if filterKeys != nil {
val := strings.Join(*filterKeys, c.delimiter)
strfilterKeys = &val
}

var fk *C.char
if strfilterKeys != nil {
fk = C.CString(*strfilterKeys)
} else {
fk = nil
}
clientPtr := c.GetCacClient()
strQuery, err := json.Marshal(query)
if err != nil {
fmt.Println("Failed to covert json to string")
return ""
}
q := C.CString(string(strQuery))
ms := C.CString(mergeStrategy)

return C.GoString(C.cac_get_resolved_config(clientPtr, q, fk, ms))
}

// GetDefaultConfig gets the default configuration from the C library
func (c *CacClient) GetDefaultConfig(filterKeys *[]string) string {
clientPtr := c.GetCacClient()
var strfilterKeys *string

if filterKeys != nil {
val := strings.Join(*filterKeys, c.delimiter)
strfilterKeys = &val
}

var fk *C.char
if strfilterKeys != nil {
fk = C.CString(*strfilterKeys)
} else {
fk = nil
}

return C.GoString(C.cac_get_default_config(clientPtr, fk))
}
124 changes: 124 additions & 0 deletions clients/go/expclient/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package expclient

/*
#include "libexperimentation_client.h"
*/
import "C"
import (
"encoding/json"
"fmt"
"strings"
)

// Define ExperimentationClient structure
type ExperimentationClient struct {
tenant string
pollingFrequency int
cacHostName string
delimiter string
}

// Constructor
func NewExperimentationClient(tenant string, pollingFrequency int, hostName string) *ExperimentationClient {
return &ExperimentationClient{
tenant: tenant,
pollingFrequency: pollingFrequency,
cacHostName: hostName,
delimiter: ",",
}
}

// Methods
func (ec *ExperimentationClient) CreateNewExperimentationClient() int {
respCode := C.expt_new_client(C.CString(ec.tenant), C.ulong(ec.pollingFrequency), C.CString(ec.cacHostName))
if respCode == 1 {
errorMessage := C.GoString(C.expt_last_error_message())
fmt.Printf("Error occurred while creating new experimentation client: %s\n", errorMessage)
return int(respCode)
}
return int(respCode)
}

func (ec *ExperimentationClient) GetExperimentationClient() *C.Arc_Client {
return C.expt_get_client(C.CString(ec.tenant))
}

func (ec *ExperimentationClient) GetRunningExperiments() string {
clientPtr := ec.GetExperimentationClient()
return C.GoString(C.expt_get_running_experiments(clientPtr))
}

func (ec *ExperimentationClient) FreeString(str string) {
C.expt_free_string(C.CString(str))
}

func (ec *ExperimentationClient) StartExperimentationPollingUpdate() {
go C.expt_start_polling_update(C.CString(ec.tenant))
}

func (ec *ExperimentationClient) GetExperimentationLastErrorLength() int {
return int(C.expt_last_error_length())
}

func (ec *ExperimentationClient) FreeExperimentationClient() {
clientPtr := ec.GetExperimentationClient()
C.expt_free_client(clientPtr)
}

func (ec *ExperimentationClient) GetFilteredSatisfiedExperiments(context map[string]string, filterPrefix *[]string) string {
clientPtr := ec.GetExperimentationClient()
strContext, err := json.Marshal(context)
if err != nil {
fmt.Println("Failed to covert json to string")
return ""
}
var strFilterPrefix *string

if filterPrefix != nil {
val := strings.Join(*filterPrefix, ec.delimiter)
strFilterPrefix = &val
}

var fk *C.char
if strFilterPrefix != nil {
fk = C.CString(*strFilterPrefix)
} else {
fk = nil
}

return C.GoString(C.expt_get_filtered_satisfied_experiments(clientPtr, C.CString(string(strContext)), fk))
}

func (ec *ExperimentationClient) GetApplicableVariant(context map[string]string, toss int) string {
clientPtr := ec.GetExperimentationClient()
strContext, err := json.Marshal(context)
if err != nil {
fmt.Println("Failed to covert json to string")
return ""
}
return C.GoString(C.expt_get_applicable_variant(clientPtr, C.CString(string(strContext)), C.short(toss)))
}

func (ec *ExperimentationClient) GetSatisfiedExperiments(context map[string]string, filterPrefix *[]string) string {
clientPtr := ec.GetExperimentationClient()
strContext, err := json.Marshal(context)
var strFilterPrefix *string

if filterPrefix != nil {
val := strings.Join(*filterPrefix, ec.delimiter)
strFilterPrefix = &val
}

var fk *C.char
if strFilterPrefix != nil {
fk = C.CString(*strFilterPrefix)
} else {
fk = nil
}

if err != nil {
fmt.Println("Failed to covert json to string")
return ""
}
return C.GoString(C.expt_get_satisfied_experiments(clientPtr, C.CString(string(strContext)), fk))
}
3 changes: 3 additions & 0 deletions clients/go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module goclients

go 1.22.5
22 changes: 22 additions & 0 deletions clients/go/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package main

import (
"fmt"
"goclients/cacclient"
"goclients/expclient"
)

func main() {
// Example usage
tenant := "dev"
pollingFrequency := 1
cachostName := "http://localhost:8080"

client := cacclient.NewCacClient(tenant, pollingFrequency, cachostName)
client.CreateNewCacClient()
fmt.Println("Default Configs", client.GetCacConfig(nil, nil))

expClient := expclient.NewExperimentationClient(tenant, pollingFrequency, cachostName)
expClient.CreateNewExperimentationClient()
fmt.Println("Running experiments:", expClient.GetRunningExperiments())
}

0 comments on commit 72d613f

Please sign in to comment.