From cbc9a4251202f01d74dbe5885438b996d78a4336 Mon Sep 17 00:00:00 2001 From: Joel Unzain Date: Fri, 22 Sep 2017 11:30:19 -0700 Subject: [PATCH 1/7] Initialization first attempt. --- src/tr1d1um/conversion_utils.go | 2 +- src/tr1d1um/http.go | 6 +++- src/tr1d1um/http_test.go | 6 ++-- src/tr1d1um/tr1d1um.go | 64 ++++++++++++++++++++++----------- 4 files changed, 53 insertions(+), 25 deletions(-) diff --git a/src/tr1d1um/conversion_utils.go b/src/tr1d1um/conversion_utils.go index d58f2179..f8aa7443 100644 --- a/src/tr1d1um/conversion_utils.go +++ b/src/tr1d1um/conversion_utils.go @@ -192,7 +192,7 @@ func (helper *EncodingHelper) EncodeJSON(v interface{}) (data []byte, err error) //ExtractPayload decodes an encoded wrp message and returns its payload func (helper *EncodingHelper) ExtractPayload(input io.Reader, format wrp.Format) (payload []byte, err error) { - wrpResponse := &wrp.Message{} + wrpResponse := &wrp.Message{Type:wrp.SimpleRequestResponseMessageType} if err = wrp.NewDecoder(input, format).Decode(wrpResponse); err == nil { payload = wrpResponse.Payload diff --git a/src/tr1d1um/http.go b/src/tr1d1um/http.go index 0fedf6a8..f5af3f74 100644 --- a/src/tr1d1um/http.go +++ b/src/tr1d1um/http.go @@ -7,6 +7,10 @@ import ( "github.com/Comcast/webpa-common/logging" "github.com/go-kit/kit/log" "github.com/gorilla/mux" +// "github.com/Comcast/webpa-common/wrp" +// "encoding/json" +// "bytes" +// "fmt" ) //ConversionHandler wraps the main WDMP -> WRP conversion method @@ -21,7 +25,7 @@ type ConversionHandler struct { } //ConversionHandler handles the different incoming tr1 requests -func (ch *ConversionHandler) ConversionHandler(origin http.ResponseWriter, req *http.Request) { +func (ch *ConversionHandler) ServeHTTP(origin http.ResponseWriter, req *http.Request) { var err error var wdmp interface{} var urlVars Vars diff --git a/src/tr1d1um/http_test.go b/src/tr1d1um/http_test.go index 90ce3d4e..3b99c1de 100644 --- a/src/tr1d1um/http_test.go +++ b/src/tr1d1um/http_test.go @@ -36,7 +36,7 @@ func TestConversionHandler(t *testing.T) { Return(&GetWDMP{}, errors.New(errMsg)).Once() recorder := httptest.NewRecorder() - ch.ConversionHandler(recorder, commonRequest) + ch.ServeHTTP(recorder, commonRequest) assert.EqualValues(http.StatusInternalServerError, recorder.Code) mockConversion.AssertExpectations(t) @@ -48,7 +48,7 @@ func TestConversionHandler(t *testing.T) { Return(wdmpGet, nil).Once() recorder := httptest.NewRecorder() - ch.ConversionHandler(recorder, commonRequest) + ch.ServeHTTP(recorder, commonRequest) mockEncoding.AssertExpectations(t) mockConversion.AssertExpectations(t) @@ -110,7 +110,7 @@ func SetUpTest(encodeArg interface{}, req *http.Request) { mockSender.On("Send", ch, recorder, payload, req).Return(resp, nil).Once() mockSender.On("HandleResponse", ch, nil, resp, recorder).Once() - ch.ConversionHandler(recorder, req) + ch.ServeHTTP(recorder, req) } func AssertCommonCalls(t *testing.T) { diff --git a/src/tr1d1um/tr1d1um.go b/src/tr1d1um/tr1d1um.go index 66694fcc..3bacbfca 100644 --- a/src/tr1d1um/tr1d1um.go +++ b/src/tr1d1um/tr1d1um.go @@ -4,6 +4,8 @@ import ( "net/http" "os" "time" + "fmt" + "os/signal" "github.com/Comcast/webpa-common/logging" "github.com/Comcast/webpa-common/secure" @@ -16,22 +18,26 @@ import ( "github.com/justinas/alice" "github.com/spf13/pflag" "github.com/spf13/viper" + "github.com/Comcast/webpa-common/concurrent" ) const ( applicationName = "tr1d1um" DefaultKeyId = "current" + baseURI = "/api" + version = "v2"// TODO: Should these values change? ) func tr1d1um(arguments []string) int { var ( f = pflag.NewFlagSet(applicationName, pflag.ContinueOnError) v = viper.New() - logger, _, err = server.Initialize(applicationName, arguments, f, v) + logger, webPA , err = server.Initialize(applicationName, arguments, f, v) ) if err != nil { - logging.Error(logger).Log(logging.MessageKey(), "Unable to initialize Viper environment: %s\n", + logging.Error(logger).Log(logging.MessageKey(), "Unable to initialize Viper environment", logging.ErrorKey(), err) + fmt.Fprint(os.Stderr, "Unable to initialize viper" + err.Error()) return 1 } @@ -48,6 +54,8 @@ func tr1d1um(arguments []string) int { err = v.Unmarshal(tConfig) //todo: decide best way to get current unexported fields from viper if err != nil { errorLogger.Log(messageKey, "Unable to unmarshal configuration data into struct", errorKey, err) + + fmt.Fprint(os.Stderr, "Unable to unmarshall config") return 1 } @@ -55,50 +63,66 @@ func tr1d1um(arguments []string) int { if err != nil { infoLogger.Log(messageKey, "Error setting up pre handler", errorKey, err) + + fmt.Fprint(os.Stderr, "error setting up prehandler") return 1 } - conversionHandler := SetUpHandler(tConfig, errorLogger, infoLogger) + conversionHandler := SetUpHandler(tConfig, logger) - r := mux.NewRouter() + AddRoutes(preHandler, conversionHandler) + + _, tr1d1umServer := webPA.Prepare(logger, nil, conversionHandler) + + waitGroup, shutdown, err := concurrent.Execute(tr1d1umServer) - r = AddRoutes(r, preHandler, conversionHandler) + signals := make(chan os.Signal, 1) - //todo: finish this initialization method + signal.Notify(signals) + <-signals + close(shutdown) + + waitGroup.Wait() return 0 } -func AddRoutes(r *mux.Router, preHandler *alice.Chain, conversionHandler *ConversionHandler) *mux.Router { +func AddRoutes(preHandler *alice.Chain, conversionHandler *ConversionHandler) { var BodyNonNil = func(request *http.Request, match *mux.RouteMatch) bool { return request.Body != nil } - //todo: inquire about API version - r.Handle("/device/{deviceid}/{service}", preHandler.ThenFunc(conversionHandler.ConversionHandler)). + r := mux.NewRouter() + apiHandler := r.PathPrefix(fmt.Sprintf("%s/%s", baseURI, version)).Subrouter() + + apiHandler.Handle("/device/{deviceid}/{service}", preHandler.Then(conversionHandler)). Methods(http.MethodGet) - r.Handle("/device/{deviceid}/{service}", preHandler.ThenFunc(conversionHandler.ConversionHandler)). + apiHandler.Handle("/device/{deviceid}/{service}", preHandler.Then(conversionHandler)). Methods(http.MethodPatch).MatcherFunc(BodyNonNil) - r.Handle("/device/{deviceid}/{service}/{parameter}", preHandler.ThenFunc(conversionHandler. - ConversionHandler)).Methods(http.MethodDelete) + apiHandler.Handle("/device/{deviceid}/{service}/{parameter}", preHandler.Then(conversionHandler)). + Methods(http.MethodDelete) - r.Handle("/device/{deviceid}/{service}/{parameter}", preHandler.ThenFunc(conversionHandler. - ConversionHandler)).Methods(http.MethodPut, http.MethodPost).MatcherFunc(BodyNonNil) - - return r + apiHandler.Handle("/device/{deviceid}/{service}/{parameter}", preHandler.Then(conversionHandler)). + Methods(http.MethodPut, http.MethodPost).MatcherFunc(BodyNonNil) } -func SetUpHandler(tConfig *Tr1d1umConfig, errorLogger log.Logger, infoLogger log.Logger) (cHandler *ConversionHandler) { +func SetUpHandler(tConfig *Tr1d1umConfig, logger log.Logger) (cHandler *ConversionHandler) { timeOut, err := time.ParseDuration(tConfig.HttpTimeout) if err != nil { timeOut = time.Second * 60 //default val } - cHandler = &ConversionHandler{timeOut: timeOut, targetURL: tConfig.targetURL} + cHandler = &ConversionHandler{ + timeOut: timeOut, + targetURL: tConfig.targetURL, + wdmpConvert: &ConversionWDMP{&EncodingHelper{}}, + sender: &Tr1SendAndHandle{log:logger, timedClient:&http.Client{Timeout:time.Second*5}, NewHTTPRequest:http.NewRequest}, + encodingHelper:&EncodingHelper{}, + } //pass loggers - cHandler.errorLogger = errorLogger - cHandler.infoLogger = infoLogger + cHandler.errorLogger = logging.Error(logger) + cHandler.infoLogger = logging.Info(logger) cHandler.targetURL = "https://xmidt.comcast.net" //todo: should we get this from the configs instead? return } From 21814b0f0cc982565bfd757cea4ab5099001bb9a Mon Sep 17 00:00:00 2001 From: Joel Unzain Date: Fri, 22 Sep 2017 18:44:58 -0700 Subject: [PATCH 2/7] follow golint advice on all files --- src/tr1d1um/conversion_utils.go | 15 +++++---- src/tr1d1um/conversion_utils_test.go | 24 ++++++------- src/tr1d1um/http.go | 8 ++--- src/tr1d1um/tr1d1um.go | 50 +++++++++++++++------------- src/tr1d1um/tr1d1um_type.go | 8 +++-- src/tr1d1um/wdmp_type.go | 36 ++++++++++---------- 6 files changed, 74 insertions(+), 67 deletions(-) diff --git a/src/tr1d1um/conversion_utils.go b/src/tr1d1um/conversion_utils.go index f8aa7443..c4f9e1b8 100644 --- a/src/tr1d1um/conversion_utils.go +++ b/src/tr1d1um/conversion_utils.go @@ -45,11 +45,10 @@ type ConversionWDMP struct { encodingHelper EncodingTool } -/* The following functions break down the different cases for requests (https://swagger.webpa.comcast.net/) -containing WDMP content. Their main functionality is to attempt at reading such content, validating it -and subsequently returning a json type encoding of it. Most often this resulting []byte is used as payload for -wrp messages -*/ +//The following functions with names of the form {command}FlavorFormat serve as the low level builders of WDMP objects based on +//the commands they serve in TR181 devices + +//GetFlavorFormat constructs a WDMP object out of the contents of a given request. Supports the GET command func (cw *ConversionWDMP) GetFlavorFormat(req *http.Request, attr, namesKey, sep string) (wdmp *GetWDMP, err error) { wdmp = new(GetWDMP) @@ -69,6 +68,7 @@ func (cw *ConversionWDMP) GetFlavorFormat(req *http.Request, attr, namesKey, sep return } +//SetFlavorFormat has analogous functionality to GetFlavorformat but instead supports the various SET commands func (cw *ConversionWDMP) SetFlavorFormat(req *http.Request) (wdmp *SetWDMP, err error) { wdmp = new(SetWDMP) @@ -78,6 +78,7 @@ func (cw *ConversionWDMP) SetFlavorFormat(req *http.Request) (wdmp *SetWDMP, err return } +//DeleteFlavorFormat again has analogous functionality to GetFlavormat but for the DELETE_ROW command func (cw *ConversionWDMP) DeleteFlavorFormat(urlVars Vars, rowKey string) (wdmp *DeleteRowWDMP, err error) { wdmp = &DeleteRowWDMP{Command: CommandDeleteRow} @@ -90,6 +91,7 @@ func (cw *ConversionWDMP) DeleteFlavorFormat(urlVars Vars, rowKey string) (wdmp return } +//AddFlavorFormat supports the ADD_ROW command func (cw *ConversionWDMP) AddFlavorFormat(input io.Reader, urlVars Vars, tableName string) (wdmp *AddRowWDMP, err error) { wdmp = &AddRowWDMP{Command: CommandAddRow} @@ -107,6 +109,7 @@ func (cw *ConversionWDMP) AddFlavorFormat(input io.Reader, urlVars Vars, tableNa return } +//ReplaceFlavorFormat supports the REPLACE_ROWS command func (cw *ConversionWDMP) ReplaceFlavorFormat(input io.Reader, urlVars Vars, tableName string) (wdmp *ReplaceRowsWDMP, err error) { wdmp = &ReplaceRowsWDMP{Command: CommandReplaceRows} @@ -192,7 +195,7 @@ func (helper *EncodingHelper) EncodeJSON(v interface{}) (data []byte, err error) //ExtractPayload decodes an encoded wrp message and returns its payload func (helper *EncodingHelper) ExtractPayload(input io.Reader, format wrp.Format) (payload []byte, err error) { - wrpResponse := &wrp.Message{Type:wrp.SimpleRequestResponseMessageType} + wrpResponse := &wrp.Message{Type: wrp.SimpleRequestResponseMessageType} if err = wrp.NewDecoder(input, format).Decode(wrpResponse); err == nil { payload = wrpResponse.Payload diff --git a/src/tr1d1um/conversion_utils_test.go b/src/tr1d1um/conversion_utils_test.go index 439433ee..a86f0b60 100644 --- a/src/tr1d1um/conversion_utils_test.go +++ b/src/tr1d1um/conversion_utils_test.go @@ -12,11 +12,11 @@ import ( ) var ( - sampleNames = []string{"p1", "p2"} - dataType int8 = 3 - value string = "someVal" - name string = "someName" - valid = SetParam{Name: &name, Attributes: Attr{"notify": 0}} + sampleNames = []string{"p1", "p2"} + dataType int8 = 3 + value = "someVal" + name = "someName" + valid = SetParam{Name: &name, Attributes: Attr{"notify": 0}} emptyInputBuffer bytes.Buffer commonVars = Vars{"uThere?": "yes!"} replaceRows = IndexRow{"0": {"uno": "one", "dos": "two"}} @@ -67,19 +67,19 @@ func TestGetFlavorFormat(t *testing.T) { func TestSetFlavorFormat(t *testing.T) { assert := assert.New(t) c := ConversionWDMP{&EncodingHelper{}} - commonUrl := "http://device/config?k=v" + commonURL := "http://device/config?k=v" var req *http.Request t.Run("DecodeErr", func(t *testing.T) { invalidBody := bytes.NewBufferString("{") - req = httptest.NewRequest(http.MethodPatch, commonUrl, invalidBody) + req = httptest.NewRequest(http.MethodPatch, commonURL, invalidBody) _, err := c.SetFlavorFormat(req) assert.NotNil(err) }) t.Run("InvalidData", func(t *testing.T) { emptyBody := bytes.NewBufferString(`{}`) - req = httptest.NewRequest(http.MethodPatch, commonUrl, emptyBody) + req = httptest.NewRequest(http.MethodPatch, commonURL, emptyBody) _, err := c.SetFlavorFormat(req) assert.NotNil(err) @@ -239,22 +239,22 @@ func TestAddFlavorFormat(t *testing.T) { func TestGetFromURLPath(t *testing.T) { assert := assert.New(t) - fakeUrlVar := map[string]string{"k1": "k1v1,k1v2", "k2": "k2v1"} + fakeURLVar := map[string]string{"k1": "k1v1,k1v2", "k2": "k2v1"} c := ConversionWDMP{} t.Run("NormalCases", func(t *testing.T) { - k1ValGroup, exists := c.GetFromURLPath("k1", fakeUrlVar) + k1ValGroup, exists := c.GetFromURLPath("k1", fakeURLVar) assert.True(exists) assert.EqualValues("k1v1,k1v2", k1ValGroup) - k2ValGroup, exists := c.GetFromURLPath("k2", fakeUrlVar) + k2ValGroup, exists := c.GetFromURLPath("k2", fakeURLVar) assert.True(exists) assert.EqualValues("k2v1", k2ValGroup) }) t.Run("NonNilNonExistent", func(t *testing.T) { - _, exists := c.GetFromURLPath("k3", fakeUrlVar) + _, exists := c.GetFromURLPath("k3", fakeURLVar) assert.False(exists) }) diff --git a/src/tr1d1um/http.go b/src/tr1d1um/http.go index f5af3f74..40659527 100644 --- a/src/tr1d1um/http.go +++ b/src/tr1d1um/http.go @@ -7,10 +7,10 @@ import ( "github.com/Comcast/webpa-common/logging" "github.com/go-kit/kit/log" "github.com/gorilla/mux" -// "github.com/Comcast/webpa-common/wrp" -// "encoding/json" -// "bytes" -// "fmt" + // "github.com/Comcast/webpa-common/wrp" + // "encoding/json" + // "bytes" + // "fmt" ) //ConversionHandler wraps the main WDMP -> WRP conversion method diff --git a/src/tr1d1um/tr1d1um.go b/src/tr1d1um/tr1d1um.go index 3bacbfca..b23a7420 100644 --- a/src/tr1d1um/tr1d1um.go +++ b/src/tr1d1um/tr1d1um.go @@ -1,12 +1,13 @@ package main import ( + "fmt" "net/http" "os" - "time" - "fmt" "os/signal" + "time" + "github.com/Comcast/webpa-common/concurrent" "github.com/Comcast/webpa-common/logging" "github.com/Comcast/webpa-common/secure" "github.com/Comcast/webpa-common/secure/handler" @@ -18,26 +19,26 @@ import ( "github.com/justinas/alice" "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/Comcast/webpa-common/concurrent" ) +//convenient global values const ( applicationName = "tr1d1um" - DefaultKeyId = "current" - baseURI = "/api" - version = "v2"// TODO: Should these values change? + DefaultKeyID = "current" + baseURI = "/api" + version = "v2" // TODO: Should these values change? ) func tr1d1um(arguments []string) int { var ( - f = pflag.NewFlagSet(applicationName, pflag.ContinueOnError) - v = viper.New() - logger, webPA , err = server.Initialize(applicationName, arguments, f, v) + f = pflag.NewFlagSet(applicationName, pflag.ContinueOnError) + v = viper.New() + logger, webPA, err = server.Initialize(applicationName, arguments, f, v) ) if err != nil { logging.Error(logger).Log(logging.MessageKey(), "Unable to initialize Viper environment", logging.ErrorKey(), err) - fmt.Fprint(os.Stderr, "Unable to initialize viper" + err.Error()) + fmt.Fprint(os.Stderr, "Unable to initialize viper"+err.Error()) return 1 } @@ -76,7 +77,7 @@ func tr1d1um(arguments []string) int { waitGroup, shutdown, err := concurrent.Execute(tr1d1umServer) - signals := make(chan os.Signal, 1) + signals := make(chan os.Signal, 1) signal.Notify(signals) <-signals @@ -87,6 +88,7 @@ func tr1d1um(arguments []string) int { return 0 } +//AddRoutes configures the paths and connection rules to TR1D1UM func AddRoutes(preHandler *alice.Chain, conversionHandler *ConversionHandler) { var BodyNonNil = func(request *http.Request, match *mux.RouteMatch) bool { return request.Body != nil @@ -102,24 +104,25 @@ func AddRoutes(preHandler *alice.Chain, conversionHandler *ConversionHandler) { Methods(http.MethodPatch).MatcherFunc(BodyNonNil) apiHandler.Handle("/device/{deviceid}/{service}/{parameter}", preHandler.Then(conversionHandler)). - Methods(http.MethodDelete) + Methods(http.MethodDelete) apiHandler.Handle("/device/{deviceid}/{service}/{parameter}", preHandler.Then(conversionHandler)). - Methods(http.MethodPut, http.MethodPost).MatcherFunc(BodyNonNil) + Methods(http.MethodPut, http.MethodPost).MatcherFunc(BodyNonNil) } +//SetUpHandler prepares the main handler under TR1D1UM which is the ConversionHandler func SetUpHandler(tConfig *Tr1d1umConfig, logger log.Logger) (cHandler *ConversionHandler) { - timeOut, err := time.ParseDuration(tConfig.HttpTimeout) + timeOut, err := time.ParseDuration(tConfig.HTTPTimeout) if err != nil { timeOut = time.Second * 60 //default val } cHandler = &ConversionHandler{ - timeOut: timeOut, - targetURL: tConfig.targetURL, - wdmpConvert: &ConversionWDMP{&EncodingHelper{}}, - sender: &Tr1SendAndHandle{log:logger, timedClient:&http.Client{Timeout:time.Second*5}, NewHTTPRequest:http.NewRequest}, - encodingHelper:&EncodingHelper{}, - } + timeOut: timeOut, + targetURL: tConfig.targetURL, + wdmpConvert: &ConversionWDMP{&EncodingHelper{}}, + sender: &Tr1SendAndHandle{log: logger, timedClient: &http.Client{Timeout: time.Second * 5}, NewHTTPRequest: http.NewRequest}, + encodingHelper: &EncodingHelper{}, + } //pass loggers cHandler.errorLogger = logging.Error(logger) cHandler.infoLogger = logging.Info(logger) @@ -127,6 +130,7 @@ func SetUpHandler(tConfig *Tr1d1umConfig, logger log.Logger) (cHandler *Conversi return } +//SetUpPreHandler configures the authorization requirements for requests to reach the main handler func SetUpPreHandler(v *viper.Viper, logger log.Logger) (preHandler *alice.Chain, err error) { validator, err := GetValidator(v) if err != nil { @@ -147,14 +151,14 @@ func SetUpPreHandler(v *viper.Viper, logger log.Logger) (preHandler *alice.Chain //GetValidator returns a validator for JWT tokens func GetValidator(v *viper.Viper) (validator secure.Validator, err error) { - default_validators := make(secure.Validators, 0, 0) + defaultValidators := make(secure.Validators, 0, 0) var jwtVals []JWTValidator v.UnmarshalKey("jwtValidators", &jwtVals) // make sure there is at least one jwtValidator supplied if len(jwtVals) < 1 { - validator = default_validators + validator = defaultValidators return } @@ -173,7 +177,7 @@ func GetValidator(v *viper.Viper) (validator secure.Validator, err error) { validators = append( validators, secure.JWSValidator{ - DefaultKeyId: DefaultKeyId, + DefaultKeyId: DefaultKeyID, Resolver: keyResolver, JWTValidators: []*jwt.Validator{validatorDescriptor.Custom.New()}, }, diff --git a/src/tr1d1um/tr1d1um_type.go b/src/tr1d1um/tr1d1um_type.go index a63b8c51..249bcf00 100644 --- a/src/tr1d1um/tr1d1um_type.go +++ b/src/tr1d1um/tr1d1um_type.go @@ -5,6 +5,7 @@ import ( "github.com/Comcast/webpa-common/secure/key" ) +//Tr1d1umConfig wraps all the config type Tr1d1umConfig struct { Addr string `json:"addr"` PprofAddr string `json:"pprofaddr"` @@ -12,11 +13,11 @@ type Tr1d1umConfig struct { Key string `json:"key"` AuthKey []string `json:"authKey"` HandlerTimeout string `json:"handlerTimeout"` - HttpTimeout string `json:"httpTimeout"` + HTTPTimeout string `json:"httpTimeout"` HealthInterval string `json:"healthInterval"` Version string `json:"version"` - MaxApiTcpConns int64 `json:"maxApiTcpConnections"` - MaxHealthTcpConns int64 `json:"maxHealthTcpConnections"` + MaxTCPConns int64 `json:"maxApiTcpConnections"` + MaxHealthTCPConns int64 `json:"maxHealthTcpConnections"` ServiceList []string `json:"serviceList"` WrpSource string `json:"wrpSource"` targetURL string `json:"targetUrl"` @@ -31,6 +32,7 @@ type Tr1d1umConfig struct { } `json:"jwtValidators"` } +//JWTValidator provides a convenient way to define jwt validator through config files type JWTValidator struct { // JWTKeys is used to create the key.Resolver for JWT verification keys Keys key.ResolverFactory diff --git a/src/tr1d1um/wdmp_type.go b/src/tr1d1um/wdmp_type.go index c48a68a5..34975491 100644 --- a/src/tr1d1um/wdmp_type.go +++ b/src/tr1d1um/wdmp_type.go @@ -6,6 +6,7 @@ import ( "github.com/go-ozzo/ozzo-validation" ) +//All the supported commands, WebPA Headers and misc const ( CommandGet = "GET" CommandGetAttrs = "GET_ATTRIBUTES" @@ -26,23 +27,14 @@ const ( WRPSource = "dns:tr1d1um.webpa.comcast.net" ) -/* - GET-Flavored structs -*/ - +//GetWDMP serves as container for data used for the GET-flavored commands type GetWDMP struct { Command string `json:"command"` Names []string `json:"names,omitempty"` Attribute string `json:"attributes,omitempty"` } -/* - SET-Flavored structs -*/ - -type Attr map[string]interface{} -type IndexRow map[string]map[string]string - +//SetParam defines the structure for Parameters read from json data. Applicable to the SET-flavored commands type SetParam struct { Name *string `json:"name"` DataType *int8 `json:"dataType,omitempty"` @@ -50,6 +42,10 @@ type SetParam struct { Attributes Attr `json:"attributes,omitempty"` } +//Attr facilitates reading in json data containing attributes applicable to the GET command +type Attr map[string]interface{} + +//SetWDMP serves as container for data used for the SET-flavored commands type SetWDMP struct { Command string `json:"command"` OldCid string `json:"old-cid,omitempty"` @@ -58,30 +54,30 @@ type SetWDMP struct { Parameters []SetParam `json:"parameters,omitempty"` } -/* - Row-related command structs -*/ - +//AddRowWDMP serves as container for data used for the ADD_ROW command type AddRowWDMP struct { Command string `json:"command"` Table string `json:"table"` Row map[string]string `json:"row"` } +//ReplaceRowsWDMP serves as container for data used for the REPLACE_ROWS command type ReplaceRowsWDMP struct { Command string `json:"command"` Table string `json:"table"` Rows IndexRow `json:"rows"` } +//IndexRow facilitates data transfer from json data of the form {index1: {key:val}, index2: {key:val}, ... } +type IndexRow map[string]map[string]string + +//DeleteRowWDMP contains data used in the DELETE_ROW command type DeleteRowWDMP struct { Command string `json:"command"` Row string `json:"row"` } -/* Validation functions */ - -//Applicable for the SET and TEST_SET +//Validate defines the validation rules applicable to SetParam in the context of the SET and TEST_SET commands func (sp SetParam) Validate() error { return validation.ValidateStruct(&sp, validation.Field(&sp.Name, validation.NotNil), @@ -89,9 +85,11 @@ func (sp SetParam) Validate() error { validation.Field(&sp.Value, validation.Required)) } +//ValidateSETAttrParams validates an entire list of parameters. Applicable to SET commands func ValidateSETAttrParams(params []SetParam) (err error) { if params == nil || len(params) == 0 { - return errors.New("invalid list of params") + err = errors.New("invalid list of params") + return } for _, param := range params { if err = validation.Validate(param.Attributes, validation.Required.Error("invalid attr")); err != nil { From f1edeedd77b4a9829f1c1033aca9cbb8c6ac7cbb Mon Sep 17 00:00:00 2001 From: Joel Unzain Date: Mon, 25 Sep 2017 17:51:29 -0700 Subject: [PATCH 3/7] Adding webhooks --- src/tr1d1um/communication.go | 3 +- src/tr1d1um/http.go | 6 --- src/tr1d1um/tr1d1um.go | 98 ++++++++++++++++++++++++------------ 3 files changed, 69 insertions(+), 38 deletions(-) diff --git a/src/tr1d1um/communication.go b/src/tr1d1um/communication.go index bc75c469..2d8f2e35 100644 --- a/src/tr1d1um/communication.go +++ b/src/tr1d1um/communication.go @@ -38,7 +38,8 @@ func (tr1 *Tr1SendAndHandle) Send(ch *ConversionHandler, resp http.ResponseWrite return } - requestToServer, err := tr1.NewHTTPRequest(http.MethodGet, ch.targetURL, bytes.NewBuffer(wrpPayload)) + fullPath := ch.targetURL + baseURI + "/" + version + "/" + wrpMsg.Destination + requestToServer, err := tr1.NewHTTPRequest(http.MethodPost, fullPath, bytes.NewBuffer(wrpPayload)) if err != nil { resp.WriteHeader(http.StatusInternalServerError) diff --git a/src/tr1d1um/http.go b/src/tr1d1um/http.go index 40659527..a5e7878b 100644 --- a/src/tr1d1um/http.go +++ b/src/tr1d1um/http.go @@ -2,22 +2,16 @@ package main import ( "net/http" - "time" "github.com/Comcast/webpa-common/logging" "github.com/go-kit/kit/log" "github.com/gorilla/mux" - // "github.com/Comcast/webpa-common/wrp" - // "encoding/json" - // "bytes" - // "fmt" ) //ConversionHandler wraps the main WDMP -> WRP conversion method type ConversionHandler struct { infoLogger log.Logger errorLogger log.Logger - timeOut time.Duration targetURL string wdmpConvert ConversionTool sender SendAndHandle diff --git a/src/tr1d1um/tr1d1um.go b/src/tr1d1um/tr1d1um.go index b23a7420..7c78edb0 100644 --- a/src/tr1d1um/tr1d1um.go +++ b/src/tr1d1um/tr1d1um.go @@ -4,10 +4,9 @@ import ( "fmt" "net/http" "os" - "os/signal" "time" + "net/url" - "github.com/Comcast/webpa-common/concurrent" "github.com/Comcast/webpa-common/logging" "github.com/Comcast/webpa-common/secure" "github.com/Comcast/webpa-common/secure/handler" @@ -19,6 +18,8 @@ import ( "github.com/justinas/alice" "github.com/spf13/pflag" "github.com/spf13/viper" + "github.com/Comcast/webpa-common/concurrent" + "github.com/Comcast/webpa-common/webhook" ) //convenient global values @@ -29,72 +30,105 @@ const ( version = "v2" // TODO: Should these values change? ) -func tr1d1um(arguments []string) int { +func tr1d1um(arguments []string) (exitCode int) { + var ( f = pflag.NewFlagSet(applicationName, pflag.ContinueOnError) v = viper.New() logger, webPA, err = server.Initialize(applicationName, arguments, f, v) ) + if err != nil { - logging.Error(logger).Log(logging.MessageKey(), "Unable to initialize Viper environment", - logging.ErrorKey(), err) - fmt.Fprint(os.Stderr, "Unable to initialize viper"+err.Error()) + fmt.Fprintf(os.Stderr, "Unable to initialize viper: %s\n", err.Error()) return 1 } - + var ( - messageKey = logging.MessageKey() - errorKey = logging.ErrorKey() - infoLogger = logging.Info(logger) - errorLogger = logging.Error(logger) + infoLogger = logging.Info(logger) ) infoLogger.Log("configurationFile", v.ConfigFileUsed()) tConfig := new(Tr1d1umConfig) err = v.Unmarshal(tConfig) //todo: decide best way to get current unexported fields from viper - if err != nil { - errorLogger.Log(messageKey, "Unable to unmarshal configuration data into struct", errorKey, err) - fmt.Fprint(os.Stderr, "Unable to unmarshall config") + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to unmarshall config data into struct: %s\n", err.Error()) return 1 } preHandler, err := SetUpPreHandler(v, logger) if err != nil { - infoLogger.Log(messageKey, "Error setting up pre handler", errorKey, err) - - fmt.Fprint(os.Stderr, "error setting up prehandler") + fmt.Fprintf(os.Stderr, "error setting up prehandler: %s\n", err.Error()) return 1 } conversionHandler := SetUpHandler(tConfig, logger) + + r := mux.NewRouter() - AddRoutes(preHandler, conversionHandler) + AddRoutes(r, preHandler, conversionHandler) - _, tr1d1umServer := webPA.Prepare(logger, nil, conversionHandler) + if exitCode = ConfigureWebHooks(r,preHandler,v,logger); exitCode != 0 { + return + } - waitGroup, shutdown, err := concurrent.Execute(tr1d1umServer) + var ( + _, tr1d1umServer = webPA.Prepare(logger, nil, conversionHandler) + signals = make(chan os.Signal, 1) + ) - signals := make(chan os.Signal, 1) + if err := concurrent.Await(tr1d1umServer, signals); err != nil { + fmt.Fprintf(os.Stderr, "Error when starting %s: %s", applicationName, err) + return 4 + } - signal.Notify(signals) - <-signals - close(shutdown) + return 0 +} - waitGroup.Wait() +//ConfigureWebHooks sets route paths, initializes and synchronizes hook registries for this tr1d1um instance +func ConfigureWebHooks(r *mux.Router, preHandler *alice.Chain, v *viper.Viper, logger log.Logger) int { + webHookFactory, err := webhook.NewFactory(v) + + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating new webHook factory: %s\n", err) + return 1 + } + + webHookRegistry, webHookHandler := webHookFactory.NewRegistryAndHandler() + + // register webHook end points for api + r.Handle("/hook", preHandler.ThenFunc(webHookRegistry.UpdateRegistry)) + r.Handle("/hooks", preHandler.ThenFunc(webHookRegistry.GetRegistry)) + + selfURL := &url.URL{ + Scheme: "https", + Host: v.GetString("fqdn") + v.GetString("primary.address"), + } + + webHookFactory.Initialize(r, selfURL, webHookHandler, logger, nil) + webHookFactory.PrepareAndStart() + + startChan := make(chan webhook.Result, 1) + webHookFactory.Start.GetCurrentSystemsHooks(startChan) + + if webHookStartResults := <-startChan; webHookStartResults.Error == nil { + webHookFactory.SetList(webhook.NewList(webHookStartResults.Hooks)) + } else { + logging.Error(logger).Log(logging.ErrorKey(),webHookStartResults.Error) + } return 0 } + //AddRoutes configures the paths and connection rules to TR1D1UM -func AddRoutes(preHandler *alice.Chain, conversionHandler *ConversionHandler) { +func AddRoutes(r *mux.Router, preHandler *alice.Chain, conversionHandler *ConversionHandler) *mux.Router { var BodyNonNil = func(request *http.Request, match *mux.RouteMatch) bool { return request.Body != nil } - r := mux.NewRouter() apiHandler := r.PathPrefix(fmt.Sprintf("%s/%s", baseURI, version)).Subrouter() apiHandler.Handle("/device/{deviceid}/{service}", preHandler.Then(conversionHandler)). @@ -108,6 +142,8 @@ func AddRoutes(preHandler *alice.Chain, conversionHandler *ConversionHandler) { apiHandler.Handle("/device/{deviceid}/{service}/{parameter}", preHandler.Then(conversionHandler)). Methods(http.MethodPut, http.MethodPost).MatcherFunc(BodyNonNil) + + return r } //SetUpHandler prepares the main handler under TR1D1UM which is the ConversionHandler @@ -116,17 +152,17 @@ func SetUpHandler(tConfig *Tr1d1umConfig, logger log.Logger) (cHandler *Conversi if err != nil { timeOut = time.Second * 60 //default val } + cHandler = &ConversionHandler{ - timeOut: timeOut, - targetURL: tConfig.targetURL, wdmpConvert: &ConversionWDMP{&EncodingHelper{}}, - sender: &Tr1SendAndHandle{log: logger, timedClient: &http.Client{Timeout: time.Second * 5}, NewHTTPRequest: http.NewRequest}, + sender: &Tr1SendAndHandle{log: logger, timedClient: &http.Client{Timeout: timeOut}, + NewHTTPRequest: http.NewRequest}, encodingHelper: &EncodingHelper{}, } //pass loggers cHandler.errorLogger = logging.Error(logger) cHandler.infoLogger = logging.Info(logger) - cHandler.targetURL = "https://xmidt.comcast.net" //todo: should we get this from the configs instead? + cHandler.targetURL = "https://api-cd.xmidt.comcast.net:8090" return } From 6de94b68fefb5f1c53cb4d6d08424a6c9ff98632 Mon Sep 17 00:00:00 2001 From: Joel Unzain Date: Mon, 25 Sep 2017 18:16:33 -0700 Subject: [PATCH 4/7] Update glide.lock file --- src/glide.lock | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/glide.lock b/src/glide.lock index 8a3b753c..2bca84cf 100644 --- a/src/glide.lock +++ b/src/glide.lock @@ -1,6 +1,34 @@ hash: b2c19c5eeb3cd43768a435dc971e642e34d9f530a7ea7f7c2dc6c4a4bd293c34 -updated: 2017-09-21T10:26:20.231225578-07:00 +updated: 2017-09-25T18:15:11.399748551-07:00 imports: +- name: github.com/aws/aws-sdk-go + version: 7be45195c3af1b54a609812f90c05a7e492e2491 + subpackages: + - aws + - aws/awserr + - aws/awsutil + - aws/client + - aws/client/metadata + - aws/corehandlers + - aws/credentials + - aws/credentials/ec2rolecreds + - aws/credentials/endpointcreds + - aws/credentials/stscreds + - aws/defaults + - aws/ec2metadata + - aws/endpoints + - aws/request + - aws/session + - aws/signer/v4 + - private/protocol + - private/protocol/query + - private/protocol/query/queryutil + - private/protocol/rest + - private/protocol/xml/xmlutil + - service + - service/sns + - service/sns/snsiface + - service/sts - name: github.com/c9s/goprocinfo version: 19cb9f127a9c8d2034cf59ccb683cdb94b9deb6c subpackages: @@ -10,6 +38,7 @@ imports: subpackages: - concurrent - health + - httperror - logging - resource - secure @@ -17,6 +46,8 @@ imports: - secure/key - server - types + - webhook + - webhook/aws - wrp - name: github.com/davecgh/go-spew version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9 @@ -24,6 +55,8 @@ imports: - spew - name: github.com/fsnotify/fsnotify version: 4da3e2cfbabc9f751898f250b49f2439785783a1 +- name: github.com/go-ini/ini + version: c787282c39ac1fc618827141a1f762240def08a3 - name: github.com/go-kit/kit version: a9ca6725cbbea455e61c6bc8a1ed28e81eb3493b subpackages: @@ -50,6 +83,8 @@ imports: - json/parser - json/scanner - json/token +- name: github.com/jmespath/go-jmespath + version: bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d - name: github.com/jtacoma/uritemplates version: 307ae868f90f4ee1b73ebe4596e0394237dacce8 - name: github.com/justinas/alice @@ -98,7 +133,7 @@ imports: subpackages: - codec - name: golang.org/x/sys - version: 7ddbeae9ae08c6a06a59597f0c9edbc5ff2444ce + version: e312636bdaa2fac4f0acde9d17ab9fbad2b4ad10 subpackages: - unix - name: golang.org/x/text From 45307c7f9fe6664d9d66d6982e8bb40f0d9048c8 Mon Sep 17 00:00:00 2001 From: Joel Unzain Date: Mon, 25 Sep 2017 19:15:21 -0700 Subject: [PATCH 5/7] Stat endpoint --- src/tr1d1um/conversion_utils.go | 6 ++++++ src/tr1d1um/http.go | 1 + 2 files changed, 7 insertions(+) diff --git a/src/tr1d1um/conversion_utils.go b/src/tr1d1um/conversion_utils.go index c4f9e1b8..ac259ec7 100644 --- a/src/tr1d1um/conversion_utils.go +++ b/src/tr1d1um/conversion_utils.go @@ -11,6 +11,7 @@ import ( "github.com/Comcast/webpa-common/wrp" "github.com/go-ozzo/ozzo-validation" + "github.com/gorilla/mux" ) //Vars shortens frequently used type returned by mux.Vars() @@ -52,6 +53,11 @@ type ConversionWDMP struct { func (cw *ConversionWDMP) GetFlavorFormat(req *http.Request, attr, namesKey, sep string) (wdmp *GetWDMP, err error) { wdmp = new(GetWDMP) + if service, _ := cw.GetFromURLPath("service", mux.Vars(req)); service == "stat"{ + return + //todo: maybe we need more validation here + } + if nameGroup := req.FormValue(namesKey); nameGroup != "" { wdmp.Command = CommandGet wdmp.Names = strings.Split(nameGroup, sep) diff --git a/src/tr1d1um/http.go b/src/tr1d1um/http.go index a5e7878b..d1aacd1c 100644 --- a/src/tr1d1um/http.go +++ b/src/tr1d1um/http.go @@ -66,3 +66,4 @@ func (ch *ConversionHandler) ServeHTTP(origin http.ResponseWriter, req *http.Req response, err := ch.sender.Send(ch, origin, wdmpPayload, req) ch.sender.HandleResponse(ch, err, response, origin) } + From 9d1372d339bd5770da22b9b70b7b5f2530072c3b Mon Sep 17 00:00:00 2001 From: Joel Unzain Date: Tue, 26 Sep 2017 17:45:08 -0700 Subject: [PATCH 6/7] Clean up, pass authorization header, test GET stat --- src/tr1d1um/communication.go | 1 + src/tr1d1um/communication_test.go | 6 ++-- src/tr1d1um/conversion_utils.go | 11 ++++---- src/tr1d1um/conversion_utils_test.go | 16 +++++++++-- src/tr1d1um/http.go | 8 ++---- src/tr1d1um/http_test.go | 41 +++++++--------------------- src/tr1d1um/mocks_test.go | 4 +-- 7 files changed, 36 insertions(+), 51 deletions(-) diff --git a/src/tr1d1um/communication.go b/src/tr1d1um/communication.go index 2d8f2e35..aad04a64 100644 --- a/src/tr1d1um/communication.go +++ b/src/tr1d1um/communication.go @@ -49,6 +49,7 @@ func (tr1 *Tr1SendAndHandle) Send(ch *ConversionHandler, resp http.ResponseWrite //todo: any more headers to be added here requestToServer.Header.Set("Content-Type", wrp.JSON.ContentType()) + requestToServer.Header.Set("Authorization", req.Header.Get("Authorization")) respFromServer, err = tr1.timedClient.Do(requestToServer) return } diff --git a/src/tr1d1um/communication_test.go b/src/tr1d1um/communication_test.go index 6257ef40..1862094f 100644 --- a/src/tr1d1um/communication_test.go +++ b/src/tr1d1um/communication_test.go @@ -22,7 +22,7 @@ func TestSend(t *testing.T) { WRPPayload := []byte("payload") validURL := "http://someValidURL" - tr1 := &Tr1SendAndHandle{log: &logTracker{}, timedClient: &http.Client{Timeout: time.Second}} + tr1 := &Tr1SendAndHandle{log: &LightFakeLogger{}, timedClient: &http.Client{Timeout: time.Second}} tr1.NewHTTPRequest = http.NewRequest ch := &ConversionHandler{encodingHelper: mockEncoding, wdmpConvert: mockConversion, targetURL: validURL} @@ -89,13 +89,11 @@ func TestSend(t *testing.T) { func TestHandleResponse(t *testing.T) { assert := assert.New(t) - tr1 := &Tr1SendAndHandle{log: &logTracker{}, timedClient: &http.Client{Timeout: time.Second}} + tr1 := &Tr1SendAndHandle{log: &LightFakeLogger{}, timedClient: &http.Client{Timeout: time.Second}} tr1.NewHTTPRequest = http.NewRequest ch := &ConversionHandler{encodingHelper: mockEncoding, wdmpConvert: mockConversion} - //Cases - //incoming err t.Run("IncomingErr", func(t *testing.T) { recorder := httptest.NewRecorder() tr1.HandleResponse(nil, errors.New(errMsg), nil, recorder) diff --git a/src/tr1d1um/conversion_utils.go b/src/tr1d1um/conversion_utils.go index ac259ec7..6e23e338 100644 --- a/src/tr1d1um/conversion_utils.go +++ b/src/tr1d1um/conversion_utils.go @@ -11,7 +11,6 @@ import ( "github.com/Comcast/webpa-common/wrp" "github.com/go-ozzo/ozzo-validation" - "github.com/gorilla/mux" ) //Vars shortens frequently used type returned by mux.Vars() @@ -19,7 +18,7 @@ type Vars map[string]string //ConversionTool lays out the definition of methods to build WDMP from content in an http request type ConversionTool interface { - GetFlavorFormat(*http.Request, string, string, string) (*GetWDMP, error) + GetFlavorFormat(*http.Request, Vars, string, string, string) (*GetWDMP, error) SetFlavorFormat(*http.Request) (*SetWDMP, error) DeleteFlavorFormat(Vars, string) (*DeleteRowWDMP, error) AddFlavorFormat(io.Reader, Vars, string) (*AddRowWDMP, error) @@ -46,14 +45,14 @@ type ConversionWDMP struct { encodingHelper EncodingTool } -//The following functions with names of the form {command}FlavorFormat serve as the low level builders of WDMP objects based on -//the commands they serve in TR181 devices +//The following functions with names of the form {command}FlavorFormat serve as the low level builders of WDMP objects +// based on the commands they serve for Cloud <-> TR181 devices communication //GetFlavorFormat constructs a WDMP object out of the contents of a given request. Supports the GET command -func (cw *ConversionWDMP) GetFlavorFormat(req *http.Request, attr, namesKey, sep string) (wdmp *GetWDMP, err error) { +func (cw *ConversionWDMP) GetFlavorFormat(req *http.Request, pathVars Vars, attr, namesKey, sep string) (wdmp *GetWDMP, err error) { wdmp = new(GetWDMP) - if service, _ := cw.GetFromURLPath("service", mux.Vars(req)); service == "stat"{ + if service, _ := cw.GetFromURLPath("service", pathVars); service == "stat"{ return //todo: maybe we need more validation here } diff --git a/src/tr1d1um/conversion_utils_test.go b/src/tr1d1um/conversion_utils_test.go index a86f0b60..47b85e87 100644 --- a/src/tr1d1um/conversion_utils_test.go +++ b/src/tr1d1um/conversion_utils_test.go @@ -37,17 +37,27 @@ func TestGetFlavorFormat(t *testing.T) { t.Run("IdealGet", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "http://api/device/config?names=p1,p2", nil) - wdmp, err := c.GetFlavorFormat(req, "attributes", "names", ",") + wdmp, err := c.GetFlavorFormat(req, nil,"attributes", "names", ",") assert.Nil(err) assert.EqualValues(wdmpGet, wdmp) }) + t.Run("IdealGetStat", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "http://api/device/mac:112233445566/stat", nil) + + wdmp, err := c.GetFlavorFormat(req, map[string]string{"service":"stat"},"attributes", "names", ",") + + assert.Nil(err) + assert.EqualValues(new(GetWDMP), wdmp) + }) + t.Run("IdealGetAttr", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "http://api/device/config?names=p1,p2&attributes=attr1", nil) - wdmp, err := c.GetFlavorFormat(req, "attributes", "names", ",") + wdmp, err := c.GetFlavorFormat(req, nil,"attributes", "names", ",") assert.Nil(err) assert.EqualValues(wdmpGetAttrs, wdmp) @@ -57,7 +67,7 @@ func TestGetFlavorFormat(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "http://api/device/config?names=", nil) - _, err := c.GetFlavorFormat(req, "attributes", "names", ",") + _, err := c.GetFlavorFormat(req, nil,"attributes", "names", ",") assert.NotNil(err) assert.True(strings.HasPrefix(err.Error(), "names is a required")) diff --git a/src/tr1d1um/http.go b/src/tr1d1um/http.go index d1aacd1c..3a46b87f 100644 --- a/src/tr1d1um/http.go +++ b/src/tr1d1um/http.go @@ -22,11 +22,11 @@ type ConversionHandler struct { func (ch *ConversionHandler) ServeHTTP(origin http.ResponseWriter, req *http.Request) { var err error var wdmp interface{} - var urlVars Vars + var urlVars = mux.Vars(req) switch req.Method { case http.MethodGet: - wdmp, err = ch.wdmpConvert.GetFlavorFormat(req, "attributes", "names", ",") + wdmp, err = ch.wdmpConvert.GetFlavorFormat(req, urlVars, "attributes", "names", ",") break case http.MethodPatch: @@ -34,19 +34,17 @@ func (ch *ConversionHandler) ServeHTTP(origin http.ResponseWriter, req *http.Req break case http.MethodDelete: - urlVars = mux.Vars(req) wdmp, err = ch.wdmpConvert.DeleteFlavorFormat(urlVars, "parameter") break case http.MethodPut: - urlVars = mux.Vars(req) wdmp, err = ch.wdmpConvert.ReplaceFlavorFormat(req.Body, urlVars, "parameter") break case http.MethodPost: - urlVars = mux.Vars(req) wdmp, err = ch.wdmpConvert.AddFlavorFormat(req.Body, urlVars, "parameter") break + } if err != nil { diff --git a/src/tr1d1um/http_test.go b/src/tr1d1um/http_test.go index 3b99c1de..992bcf68 100644 --- a/src/tr1d1um/http_test.go +++ b/src/tr1d1um/http_test.go @@ -7,7 +7,6 @@ import ( "net/http/httptest" "testing" - "github.com/Comcast/webpa-common/wrp" "github.com/gorilla/mux" "github.com/stretchr/testify/assert" ) @@ -18,11 +17,12 @@ var ( payload, body = []byte("SomePayload"), bytes.NewBufferString("body") resp = &http.Response{} mockConversion, mockEncoding, mockSender = &MockConversionTool{}, &MockEncodingTool{}, &MockSendAndHandle{} + fakeLogger = &LightFakeLogger{} ch = &ConversionHandler{ wdmpConvert: mockConversion, sender: mockSender, encodingHelper: mockEncoding, - errorLogger: &logTracker{}, //todo: temparary as webpacommon mock for logging is hard to follow + errorLogger: fakeLogger, } ) @@ -30,9 +30,10 @@ func TestConversionHandler(t *testing.T) { assert := assert.New(t) commonURL := "http://device/config?" commonRequest := httptest.NewRequest(http.MethodGet, commonURL, nil) + var vars Vars = mux.Vars(commonRequest) t.Run("ErrDataParse", func(testing *testing.T) { - mockConversion.On("GetFlavorFormat", commonRequest, "attributes", "names", ","). + mockConversion.On("GetFlavorFormat", commonRequest, vars, "attributes", "names", ","). Return(&GetWDMP{}, errors.New(errMsg)).Once() recorder := httptest.NewRecorder() @@ -44,7 +45,7 @@ func TestConversionHandler(t *testing.T) { t.Run("ErrEncode", func(testing *testing.T) { mockEncoding.On("EncodeJSON", wdmpGet).Return([]byte(""), errors.New(errMsg)).Once() - mockConversion.On("GetFlavorFormat", commonRequest, "attributes", "names", ","). + mockConversion.On("GetFlavorFormat", commonRequest, vars, "attributes", "names", ","). Return(wdmpGet, nil).Once() recorder := httptest.NewRecorder() @@ -55,7 +56,8 @@ func TestConversionHandler(t *testing.T) { }) t.Run("IdealGet", func(t *testing.T) { - mockConversion.On("GetFlavorFormat", commonRequest, "attributes", "names", ",").Return(wdmpGet, nil).Once() + mockConversion.On("GetFlavorFormat", commonRequest, vars, "attributes", "names", ","). + Return(wdmpGet, nil).Once() SetUpTest(wdmpGet, commonRequest) AssertCommonCalls(t) @@ -119,31 +121,8 @@ func AssertCommonCalls(t *testing.T) { mockSender.AssertExpectations(t) } -type Catcher struct { - LasResult interface{} - SendRequestCalled bool -} - -func (catcher *Catcher) CatchResult(v interface{}) ([]byte, error) { - catcher.LasResult = v - return nil, nil -} -func (catcher *Catcher) InterceptRequest(_ *ConversionHandler, _ http.ResponseWriter, _ *wrp.Message) { - catcher.SendRequestCalled = true -} +type LightFakeLogger struct {} -type logTracker struct { - keys []interface{} - vals []interface{} -} - -func (fake *logTracker) Log(keyVals ...interface{}) (err error) { - for i, keyVal := range keyVals { - if i%2 == 0 { - fake.keys = append(fake.keys, keyVal) - } else { - fake.vals = append(fake.vals, keyVal) - } - } - return +func (fake *LightFakeLogger) Log(_ ...interface{}) error { + return nil } diff --git a/src/tr1d1um/mocks_test.go b/src/tr1d1um/mocks_test.go index 7bb8332f..bc78378e 100644 --- a/src/tr1d1um/mocks_test.go +++ b/src/tr1d1um/mocks_test.go @@ -14,8 +14,8 @@ type MockConversionTool struct { mock.Mock } -func (m *MockConversionTool) GetFlavorFormat(req *http.Request, i string, i2 string, i3 string) (*GetWDMP, error) { - args := m.Called(req, i, i2, i3) +func (m *MockConversionTool) GetFlavorFormat(req *http.Request, vars Vars,i string, i2 string, i3 string) (*GetWDMP, error) { + args := m.Called(req, vars, i, i2, i3) return args.Get(0).(*GetWDMP), args.Error(1) } From bd934a019124be4d56560def48e8361f60fe0856 Mon Sep 17 00:00:00 2001 From: Joel Unzain Date: Wed, 27 Sep 2017 16:55:04 -0700 Subject: [PATCH 7/7] More unit tests and gofmt-ed code --- src/tr1d1um/conversion_utils.go | 2 +- src/tr1d1um/conversion_utils_test.go | 8 +- src/tr1d1um/http.go | 1 - src/tr1d1um/http_test.go | 6 +- src/tr1d1um/mocks_test.go | 2 +- src/tr1d1um/tr1d1um.go | 40 +++++----- src/tr1d1um/tr1d1um_test.go | 115 +++++++++++++++++++++++++++ src/tr1d1um/tr1d1um_type.go | 2 +- 8 files changed, 147 insertions(+), 29 deletions(-) create mode 100644 src/tr1d1um/tr1d1um_test.go diff --git a/src/tr1d1um/conversion_utils.go b/src/tr1d1um/conversion_utils.go index 6e23e338..c3831451 100644 --- a/src/tr1d1um/conversion_utils.go +++ b/src/tr1d1um/conversion_utils.go @@ -52,7 +52,7 @@ type ConversionWDMP struct { func (cw *ConversionWDMP) GetFlavorFormat(req *http.Request, pathVars Vars, attr, namesKey, sep string) (wdmp *GetWDMP, err error) { wdmp = new(GetWDMP) - if service, _ := cw.GetFromURLPath("service", pathVars); service == "stat"{ + if service, _ := cw.GetFromURLPath("service", pathVars); service == "stat" { return //todo: maybe we need more validation here } diff --git a/src/tr1d1um/conversion_utils_test.go b/src/tr1d1um/conversion_utils_test.go index 47b85e87..49a257a7 100644 --- a/src/tr1d1um/conversion_utils_test.go +++ b/src/tr1d1um/conversion_utils_test.go @@ -37,7 +37,7 @@ func TestGetFlavorFormat(t *testing.T) { t.Run("IdealGet", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "http://api/device/config?names=p1,p2", nil) - wdmp, err := c.GetFlavorFormat(req, nil,"attributes", "names", ",") + wdmp, err := c.GetFlavorFormat(req, nil, "attributes", "names", ",") assert.Nil(err) assert.EqualValues(wdmpGet, wdmp) @@ -46,7 +46,7 @@ func TestGetFlavorFormat(t *testing.T) { t.Run("IdealGetStat", func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "http://api/device/mac:112233445566/stat", nil) - wdmp, err := c.GetFlavorFormat(req, map[string]string{"service":"stat"},"attributes", "names", ",") + wdmp, err := c.GetFlavorFormat(req, map[string]string{"service": "stat"}, "attributes", "names", ",") assert.Nil(err) assert.EqualValues(new(GetWDMP), wdmp) @@ -57,7 +57,7 @@ func TestGetFlavorFormat(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "http://api/device/config?names=p1,p2&attributes=attr1", nil) - wdmp, err := c.GetFlavorFormat(req, nil,"attributes", "names", ",") + wdmp, err := c.GetFlavorFormat(req, nil, "attributes", "names", ",") assert.Nil(err) assert.EqualValues(wdmpGetAttrs, wdmp) @@ -67,7 +67,7 @@ func TestGetFlavorFormat(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "http://api/device/config?names=", nil) - _, err := c.GetFlavorFormat(req, nil,"attributes", "names", ",") + _, err := c.GetFlavorFormat(req, nil, "attributes", "names", ",") assert.NotNil(err) assert.True(strings.HasPrefix(err.Error(), "names is a required")) diff --git a/src/tr1d1um/http.go b/src/tr1d1um/http.go index 3a46b87f..6ba1901c 100644 --- a/src/tr1d1um/http.go +++ b/src/tr1d1um/http.go @@ -64,4 +64,3 @@ func (ch *ConversionHandler) ServeHTTP(origin http.ResponseWriter, req *http.Req response, err := ch.sender.Send(ch, origin, wdmpPayload, req) ch.sender.HandleResponse(ch, err, response, origin) } - diff --git a/src/tr1d1um/http_test.go b/src/tr1d1um/http_test.go index 992bcf68..d7049802 100644 --- a/src/tr1d1um/http_test.go +++ b/src/tr1d1um/http_test.go @@ -17,7 +17,7 @@ var ( payload, body = []byte("SomePayload"), bytes.NewBufferString("body") resp = &http.Response{} mockConversion, mockEncoding, mockSender = &MockConversionTool{}, &MockEncodingTool{}, &MockSendAndHandle{} - fakeLogger = &LightFakeLogger{} + fakeLogger = &LightFakeLogger{} ch = &ConversionHandler{ wdmpConvert: mockConversion, sender: mockSender, @@ -57,7 +57,7 @@ func TestConversionHandler(t *testing.T) { t.Run("IdealGet", func(t *testing.T) { mockConversion.On("GetFlavorFormat", commonRequest, vars, "attributes", "names", ","). - Return(wdmpGet, nil).Once() + Return(wdmpGet, nil).Once() SetUpTest(wdmpGet, commonRequest) AssertCommonCalls(t) @@ -121,7 +121,7 @@ func AssertCommonCalls(t *testing.T) { mockSender.AssertExpectations(t) } -type LightFakeLogger struct {} +type LightFakeLogger struct{} func (fake *LightFakeLogger) Log(_ ...interface{}) error { return nil diff --git a/src/tr1d1um/mocks_test.go b/src/tr1d1um/mocks_test.go index bc78378e..ddefa079 100644 --- a/src/tr1d1um/mocks_test.go +++ b/src/tr1d1um/mocks_test.go @@ -14,7 +14,7 @@ type MockConversionTool struct { mock.Mock } -func (m *MockConversionTool) GetFlavorFormat(req *http.Request, vars Vars,i string, i2 string, i3 string) (*GetWDMP, error) { +func (m *MockConversionTool) GetFlavorFormat(req *http.Request, vars Vars, i string, i2 string, i3 string) (*GetWDMP, error) { args := m.Called(req, vars, i, i2, i3) return args.Get(0).(*GetWDMP), args.Error(1) } diff --git a/src/tr1d1um/tr1d1um.go b/src/tr1d1um/tr1d1um.go index 7c78edb0..de30262a 100644 --- a/src/tr1d1um/tr1d1um.go +++ b/src/tr1d1um/tr1d1um.go @@ -2,24 +2,25 @@ package main import ( "fmt" + "io/ioutil" "net/http" + "net/url" "os" "time" - "net/url" + "github.com/Comcast/webpa-common/concurrent" "github.com/Comcast/webpa-common/logging" "github.com/Comcast/webpa-common/secure" "github.com/Comcast/webpa-common/secure/handler" "github.com/Comcast/webpa-common/secure/key" "github.com/Comcast/webpa-common/server" + "github.com/Comcast/webpa-common/webhook" "github.com/SermoDigital/jose/jwt" "github.com/go-kit/kit/log" "github.com/gorilla/mux" "github.com/justinas/alice" "github.com/spf13/pflag" "github.com/spf13/viper" - "github.com/Comcast/webpa-common/concurrent" - "github.com/Comcast/webpa-common/webhook" ) //convenient global values @@ -42,7 +43,7 @@ func tr1d1um(arguments []string) (exitCode int) { fmt.Fprintf(os.Stderr, "Unable to initialize viper: %s\n", err.Error()) return 1 } - + var ( infoLogger = logging.Info(logger) ) @@ -65,18 +66,18 @@ func tr1d1um(arguments []string) (exitCode int) { } conversionHandler := SetUpHandler(tConfig, logger) - + r := mux.NewRouter() AddRoutes(r, preHandler, conversionHandler) - if exitCode = ConfigureWebHooks(r,preHandler,v,logger); exitCode != 0 { + if exitCode = ConfigureWebHooks(r, preHandler, v, logger); exitCode != 0 { return } var ( _, tr1d1umServer = webPA.Prepare(logger, nil, conversionHandler) - signals = make(chan os.Signal, 1) + signals = make(chan os.Signal, 1) ) if err := concurrent.Await(tr1d1umServer, signals); err != nil { @@ -116,17 +117,20 @@ func ConfigureWebHooks(r *mux.Router, preHandler *alice.Chain, v *viper.Viper, l if webHookStartResults := <-startChan; webHookStartResults.Error == nil { webHookFactory.SetList(webhook.NewList(webHookStartResults.Hooks)) } else { - logging.Error(logger).Log(logging.ErrorKey(),webHookStartResults.Error) + logging.Error(logger).Log(logging.ErrorKey(), webHookStartResults.Error) } return 0 } - //AddRoutes configures the paths and connection rules to TR1D1UM func AddRoutes(r *mux.Router, preHandler *alice.Chain, conversionHandler *ConversionHandler) *mux.Router { - var BodyNonNil = func(request *http.Request, match *mux.RouteMatch) bool { - return request.Body != nil + var BodyNonEmpty = func(request *http.Request, match *mux.RouteMatch) (accept bool) { + if request.Body != nil { + p, err := ioutil.ReadAll(request.Body) + accept = err == nil && len(p) > 0 + } + return } apiHandler := r.PathPrefix(fmt.Sprintf("%s/%s", baseURI, version)).Subrouter() @@ -135,14 +139,14 @@ func AddRoutes(r *mux.Router, preHandler *alice.Chain, conversionHandler *Conver Methods(http.MethodGet) apiHandler.Handle("/device/{deviceid}/{service}", preHandler.Then(conversionHandler)). - Methods(http.MethodPatch).MatcherFunc(BodyNonNil) + Methods(http.MethodPatch).MatcherFunc(BodyNonEmpty) apiHandler.Handle("/device/{deviceid}/{service}/{parameter}", preHandler.Then(conversionHandler)). Methods(http.MethodDelete) apiHandler.Handle("/device/{deviceid}/{service}/{parameter}", preHandler.Then(conversionHandler)). - Methods(http.MethodPut, http.MethodPost).MatcherFunc(BodyNonNil) - + Methods(http.MethodPut, http.MethodPost).MatcherFunc(BodyNonEmpty) + return r } @@ -154,15 +158,15 @@ func SetUpHandler(tConfig *Tr1d1umConfig, logger log.Logger) (cHandler *Conversi } cHandler = &ConversionHandler{ - wdmpConvert: &ConversionWDMP{&EncodingHelper{}}, - sender: &Tr1SendAndHandle{log: logger, timedClient: &http.Client{Timeout: timeOut}, - NewHTTPRequest: http.NewRequest}, + wdmpConvert: &ConversionWDMP{&EncodingHelper{}}, + sender: &Tr1SendAndHandle{log: logger, timedClient: &http.Client{Timeout: timeOut}, + NewHTTPRequest: http.NewRequest}, encodingHelper: &EncodingHelper{}, } //pass loggers cHandler.errorLogger = logging.Error(logger) cHandler.infoLogger = logging.Info(logger) - cHandler.targetURL = "https://api-cd.xmidt.comcast.net:8090" + cHandler.targetURL = tConfig.TargetURL return } diff --git a/src/tr1d1um/tr1d1um_test.go b/src/tr1d1um/tr1d1um_test.go new file mode 100644 index 00000000..93e8d4ad --- /dev/null +++ b/src/tr1d1um/tr1d1um_test.go @@ -0,0 +1,115 @@ +package main + +import ( + "bytes" + "fmt" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/Comcast/webpa-common/logging" + "github.com/gorilla/mux" + "github.com/justinas/alice" + "github.com/stretchr/testify/assert" +) + +func TestSetUpHandler(t *testing.T) { + assert := assert.New(t) + tConfig := &Tr1d1umConfig{HTTPTimeout: "10s", TargetURL: "https://someCoolURL.com"} + logger := logging.DefaultLogger() + + t.Run("NormalSetUp", func(t *testing.T) { + actualHandler := SetUpHandler(tConfig, logger) + AssertCommon(actualHandler, tConfig, assert) + }) + + t.Run("IncompleteConfig", func(t *testing.T) { + tConfig.HTTPTimeout = "" //make config struct incomplete + + actualHandler := SetUpHandler(tConfig, logger) + + realSender := actualHandler.sender.(*Tr1SendAndHandle) + assert.EqualValues(time.Second*60, realSender.timedClient.Timeout) //turn to default value + AssertCommon(actualHandler, tConfig, assert) + }) +} + +func TestRouteConfigurations(t *testing.T) { + r := mux.NewRouter() + fakePreHandler := new(alice.Chain) + fakeHandler := &ConversionHandler{} + + r = AddRoutes(r, fakePreHandler, fakeHandler) + + var nonEmpty bytes.Buffer + nonEmpty.WriteString(`{empty: false}`) + + requests := []*http.Request{ + //0: case no base uri + httptest.NewRequest(http.MethodGet, "http://someurl.com/", nil), + + //1: case method get but no service + httptest.NewRequest(http.MethodGet, "http://server.com/api/v2/device/mac:11223344/", nil), + + //2: case method get normal + httptest.NewRequest(http.MethodGet, "http://server.com/api/v2/device/mac:11223344/serv1", nil), + + //3: case method patch body is nil + httptest.NewRequest(http.MethodPatch, "http://server.com/api/v2/device/mac:11223344/serv1", nil), + + //4: case method (put | post) body is nil + httptest.NewRequest(http.MethodPost, "http://server.com/api/v2/device/mac:11223344/serv1/param1", nil), + + //5: No parameter. Applicable to methods delete, put and post + httptest.NewRequest(http.MethodPost, "http://server.com/api/v2/device/mac:11223344/serv1", &nonEmpty), + + //6: Normal Case. Applicable to methods delete, put and post + httptest.NewRequest(http.MethodPost, "http://server.com/api/v2/device/mac:11223344/serv1/param", + &nonEmpty), + } + + expectedResults := map[int]bool{ //a map for reading ease with respect to ^ + 0: false, 1: false, 2: true, 3: false, 4: false, 5: false, 6: true, + } + + testsCases := make([]RouteTestBundle, len(requests)) + + for i, request := range requests { + testsCases[i] = RouteTestBundle{request, expectedResults[i]} + } + AssertConfiguredRoutes(r, t, testsCases) +} + +//AssertConfiguredRoutes checks that all given tests cases pass with regards to requests that should be +//allowed to hit our handler +func AssertConfiguredRoutes(r *mux.Router, t *testing.T, testCases []RouteTestBundle) { + for _, bundle := range testCases { + var match mux.RouteMatch + if bundle.passing != r.Match(bundle.req, &match) { + fmt.Printf("Expecting request with URL='%s' and method='%s' to have a route?: %v but got %v", + bundle.req.URL, bundle.req.Method, bundle.passing, !bundle.passing) + t.FailNow() + } + } +} + +func AssertCommon(actualHandler *ConversionHandler, tConfig *Tr1d1umConfig, assert *assert.Assertions) { + assert.NotNil(actualHandler.wdmpConvert) + assert.NotNil(actualHandler.encodingHelper) + assert.NotNil(actualHandler.errorLogger) + assert.NotNil(actualHandler.infoLogger) + assert.EqualValues(tConfig.TargetURL, actualHandler.targetURL) + assert.NotNil(actualHandler.sender) + + realizedSender := actualHandler.sender.(*Tr1SendAndHandle) + + //assert necessary inner methods are set in the method under test + assert.NotNil(realizedSender.timedClient) + assert.NotNil(realizedSender.NewHTTPRequest) +} + +type RouteTestBundle struct { + req *http.Request + passing bool +} diff --git a/src/tr1d1um/tr1d1um_type.go b/src/tr1d1um/tr1d1um_type.go index 249bcf00..561cbdfd 100644 --- a/src/tr1d1um/tr1d1um_type.go +++ b/src/tr1d1um/tr1d1um_type.go @@ -20,7 +20,7 @@ type Tr1d1umConfig struct { MaxHealthTCPConns int64 `json:"maxHealthTcpConnections"` ServiceList []string `json:"serviceList"` WrpSource string `json:"wrpSource"` - targetURL string `json:"targetUrl"` + TargetURL string `json:"targetURL"` JWTValidators []struct { // JWTKeys is used to create the key.Resolver for JWT verification keys