-
Notifications
You must be signed in to change notification settings - Fork 60
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
New Backend: Apache Ignite #146
Conversation
backends/config/config_test.go
Outdated
@@ -155,6 +155,13 @@ func TestNewBaseBackend(t *testing.T) { | |||
{msg: "Error creating Redis backend: ", lvl: logrus.FatalLevel}, | |||
}, | |||
}, | |||
//{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this commented out code be deleted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It probably needs to be uncommented, and perhaps worked on if it isn't in working condition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corrected
backends/ignite.go
Outdated
return "", err | ||
} | ||
|
||
// Unmarshall response |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very Minor Nitpick: Spelling Unmarshall
--> Unmarshal
backends/ignite.go
Outdated
return "", utils.NewPBCError(utils.GET_INTERNAL_SERVER, igniteResponse.Error) | ||
} else if igniteResponse.Status > 0 { | ||
return "", utils.NewPBCError(utils.GET_INTERNAL_SERVER, "Ignite response.Status not zero") | ||
} else if len(igniteResponse.Response) == 0 { // both igniteResponse.Status == 0 && len(igniteResponse.Error) == 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can delete the comment here
backends/ignite.go
Outdated
return false, err | ||
} | ||
|
||
// Unmarshall response |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same spelling nitpick
backends/ignite.go
Outdated
} | ||
|
||
// Validate response | ||
if igniteResponse.Status > 0 || len(igniteResponse.Error) > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be combined into igniteResponse.Response
like you did in getResponse validation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This validation changed
|
||
// NewIgniteBackend expects a valid config.IgniteBackend object | ||
func NewIgniteBackend(cfg config.Ignite) *IgniteBackend { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: Can you remove this blank line
// Get creates an Get http.Request with the URL query values needed to perform a "get" operation | ||
// on an Ignite storage instance using the REST API | ||
func (back *IgniteBackend) Get(ctx context.Context, key string) (string, error) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: Can you remove this blank line
backends/ignite.go
Outdated
|
||
// Get creates an Get http.Request with the URL query values needed to perform a "get" operation | ||
// on an Ignite storage instance using the REST API | ||
func (back *IgniteBackend) Get(ctx context.Context, key string) (string, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think back
can be renamed to ig
, what do you think?
utils/errors.go
Outdated
@@ -18,6 +18,7 @@ const ( | |||
KEY_NOT_FOUND // GET http.StatusNotFound 404 | |||
KEY_LENGTH // GET http.StatusNotFound 404 | |||
UNKNOWN_STORED_DATA_TYPE // GET http.StatusInternalServerError 500 | |||
GET_INTERNAL_SERVER // PUT http.StatusInternalServerError 500 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the comment here say // GET http.StatusInternalServerError 500
?
backends/ignite.go
Outdated
|
||
// Get implements the IgniteDB interface Get method and makes the Ignite storage client retrieve | ||
// the value that has been previously stored under 'key' if its TTL is still current. We can tell | ||
// when a key is not faound when Ignite doesn't return an error, nor a 'Status' different than zero, but |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
faound -> found
efb169e
to
6fb3224
Compare
|
||
// putResponse is used to unmarshal the Ignite server's response to a PUT request with | ||
// the "cmd" URL query field set to "putifabs" | ||
type putResponse struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason for defining both a getResponse
and a putResponse
when they both seem to be the same structure?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The difference is that in putResponse.Response
is a bool
and getResponse.Response
is a string
. Do you want me to try to consolidate them into a single struct
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good observation by @hhhjort but I think keeping them as separate structs due to the type difference on Response
makes sense.
config/backends.go
Outdated
Scheme string `mapstructure:"scheme"` | ||
Host string `mapstructure:"host"` | ||
Port int `mapstructure:"port"` | ||
Secure bool `mapstructure:"secure"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be renamed VerifyCert
or some documentation added to explain that secure in this context means verifying the SSL certificate on the ignite server. On the face of it Secure
sounds like it may be redundant with Scheme
, or suggest that there is a third possibility for Scheme
that may or may not use a secure connection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modified and added a comment
host: "127.0.0.1" | ||
port: 8080 | ||
secure: false | ||
headers: !!omap |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is !!omap
needed here? It looks like this is just to ensure order is preserved when loading the file but there is only one element.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we need it to read the headers as map[String]string
config/backends.go
Outdated
log.Infof("config.backend.ignite.host: %s", cfg.Host) | ||
log.Infof("config.backend.ignite.port: %d", cfg.Port) | ||
log.Infof("config.backend.ignite.cache.name: %s", cfg.Cache.Name) | ||
log.Infof("config.backend.ignite.cache.create_on_start: %v", cfg.Cache.CreateOnStart) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this supposed to be %t
instead of %v
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corrected
VerifyCert bool `mapstructure:"secure"` | ||
Headers map[string]string `mapstructure:"headers"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These don't have log.Infof
statements in validateAndLog
. I'm not sure if that's intentional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
} | ||
|
||
if httpResp.StatusCode != http.StatusOK { | ||
httpErr = fmt.Errorf("Ignite error. Unexpected status code: %d", httpResp.StatusCode) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we able to just return here when this error is detected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we return here, we'd get an entry in the logs with the status code alone, which is not that helpful. The logic behind not exiting here is to append more information to httpErr
such as the error message found in httpResp.Body
or lack thereof.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. I can get on board with this approach, however, I'm curious if/when the additional details are needed. For example, typically if the status code is 204 that means that the request was fulfilled without a body so if that is a status code that Ignite might send back, I don't see why we might need to append "Received empty httpResp.Body" to the error message. Shouldn't we be able to just report back that status code 204 was received from Ignite and rely on that status code's meaning to tell the story?
I guess I'm asking if you've observed a need to provide this additional information when the status code is not 200 when working with a live Ignite server. In most cases the semantics of a HTTP code provide the information we need. Furthermore, I'm curious if you've observed any instances where Ignite provides an error message in the body along with the non-200 status code. If so, would that suffice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed offline. The Ignite REST API docs imply that all responses are 200s and we have yet to encounter a non-200 response when working with a live Ignite server. Any error details are expected to be in the response body. As such, I'm good with the current design which is focused on receiving and unpacking a response while catching transport and response body errors but also accounting for the possibility of non-200 error codes. A non-200 error code is returned but only after we've attempted to read the response body since there might be more information there.
if httpResp.Body == nil { | ||
errMsg := "Received empty httpResp.Body" | ||
if httpErr == nil { | ||
return nil, fmt.Errorf("Ignite error. %s", errMsg) | ||
} | ||
return nil, fmt.Errorf("%s; %s", httpErr.Error(), errMsg) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we decide it makes sense to return on line 59 above then I think this logic can be simplified to:
if httpResp.Body == nil {
return nil, errors.New("Ignite error. Received empty body")
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about keeping the logic above in order to potentially have more helpful error logs. Do you agree?
igniteResponse := getResponse{} | ||
|
||
if unmarshalErr := json.Unmarshal(responseBytes, &igniteResponse); unmarshalErr != nil { | ||
return fmt.Errorf("Unmarshal response error: %s; Response body: %s", unmarshalErr.Error(), string(responseBytes)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just curious, is this error message style consistent with the rest of PBC? This error would ultimately be logged as "Error creating Ignite backend: Unmarshal response error: [some error] Response body: [body bytes]"
by the calling code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's kind of long but, this is the only Unmarshal error that PBC handles. We are currently using Ignite error
for server side errors, validation errors look like: Error creating Ignite backend
, so Unmarshal response error
seemed reasonable for me. Should we keep messages shorter?
backends/ignite.go
Outdated
igniteResponse := getResponse{} | ||
|
||
if unmarshalErr := json.Unmarshal(responseBytes, &igniteResponse); unmarshalErr != nil { | ||
return "", fmt.Errorf("Unmarshal response error: %s; Response body: %s", unmarshalErr.Error(), string(responseBytes)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should an unmarshal error be considered to be of type utils.GET_INTERNAL_SERVER
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. GET_INTERNAL_SERVER
and PUT_INTERNAL_SERVER
are being used to label errors that happen on the backend side. Given that this particular error happens on PBC, I decided to keep things simple and not label this error as INTERNAL_SERVER
but, I realize that a malformed JSON blob could be consequence of a server side error. Maybe a JSON validation error should be considered a GET_INTERNAL_SERVER
error. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm I was thinking an unmarshal failure here can be attributed to a malformed response from the Ignite server, in which case I would consider this a GET_INTERNAL_SERVER
error. I'm curious what @hhhjort thinks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you are right. Modified
|
||
// putResponse is used to unmarshal the Ignite server's response to a PUT request with | ||
// the "cmd" URL query field set to "putifabs" | ||
type putResponse struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good observation by @hhhjort but I think keeping them as separate structs due to the type difference on Response
makes sense.
return utils.NewPBCError(utils.PUT_INTERNAL_SERVER, "Ignite responded with non-zero successStatus code") | ||
} | ||
|
||
if !igniteResponse.Response { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the !
supposed to be here? If igniteResponse.Response
is true a record already exists right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that we are not rewritting values for existing keys, we are using Apache Ignite's putifabsent
command. According to the put-if-absent docs: "The response field contains true
if the entry was put, false
otherwise."
desc: "config validation error", | ||
testCases: []testCase{ | ||
{ | ||
desc: "empty scheme", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: for the "empty scheme", "empty host" and "empty port" tests, I think it would be best if all of the values were valid except for a single field so we can be sure the field under test is what is causing the error. The error message is the same for all of these empty fields so it is not possible to discern what is causing the failure as the test is currently constructed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
No description provided.