-
Notifications
You must be signed in to change notification settings - Fork 70
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
Reworking the datasource format #84
Conversation
} | ||
logrus.Tracef("'%s' is a request to a datasource", requestPath) | ||
matchingGroups := proxyMatcher.FindAllStringSubmatch(requestPath, -1) | ||
func catchDatasourceAndPath(c echo.Context, dts datasource.DAO, globalDTS globaldatasource.DAO) (v1.DatasourceSpec, 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.
catch
sounds a bit unusual for this kind of operation, how about extract
?
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.
extract is definitely better :). Thanks !
logrus.Errorf(proxyErr.Error()) | ||
proxyErr = err | ||
} | ||
// use a dedicated HTTP transport to avoid any TSL encrypt issue |
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.
// use a dedicated HTTP transport to avoid any TSL encrypt issue | |
// use a dedicated HTTP transport to avoid any TLS encryption issues |
} | ||
|
||
func (h *httpProxy) prepareRequest(c echo.Context) error { | ||
req := c.Request() | ||
// We have to modify the HOST of the request in order to match the host of the targetURL | ||
// So far I'm not sure to understand exactly why, but if you are going to remove it, be sure of what you are doing. | ||
// It has been done to fix an error returned by Openshift itself saying the target doesn't exist. | ||
// Since we are using HTTP/1, setting the HOST is setting also an header so if the host and the header are different |
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.
Aside: In HTTP/1.1 the host is only sent in the Host:
header, so that's why it definitely needs to be set so that the backend router / proxy knows who you want to talk to.
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.
ah that's where it comes from. Perhaps I should change the comment then ?
// Fix header | ||
if len(req.Header.Get(echo.HeaderXRealIP)) == 0 { | ||
req.Header.Set(echo.HeaderXRealIP, c.RealIP()) | ||
} | ||
if len(req.Header.Get(echo.HeaderXForwardedProto)) == 0 { | ||
req.Header.Set(echo.HeaderXForwardedProto, c.Scheme()) | ||
} | ||
// set header according to the configuration | ||
if len(h.config.Headers) > 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.
Do we want to allow overriding even special headers like the user agent or the host header here? Maybe fine for now, but I'd add a TODO to think about this later.
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.
indeed we should have a list of header that cannot be overridden
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.
will put a todo
if authConfig.BasicAuth != nil { | ||
password, err := authConfig.BasicAuth.GetPassword() | ||
if err != nil { | ||
logrus.WithError(err).Error("unable to get access to the password for the basic auth") |
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.
"unable to get access" sounds like a permissions / auth problem... looking at the code of GetPassword()
the most likely reason would be if there's a file configured and probably the file doesn't exist or has a wrong name. "unable to read" / "unable to retrieve"?
@@ -24,17 +24,20 @@ type Query struct { | |||
// Name is a prefix of the Datasource.metadata.name that is used to filter the list of the Datasource. | |||
// Name can be empty in case you want to return the full list of Datasource available. | |||
Name string `query:"name"` | |||
// Project is the exact name of the project. | |||
// The value can come or from the path of the URL or from the query parameter |
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 value can come or from the path of the URL or from the query parameter | |
// The value can come from the path of the URL or from the query parameter |
|
||
type Query struct { | ||
etcd.Query | ||
// Name is a prefix of the GlobalDatasource.metadata.name that is used to filter the list of the GlobalDatasource. |
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.
NamePrefix
could be a less confusing name for this field then?
f317de7
to
25a2e5d
Compare
alright I think I tackled all comments @juliusv. I also renamed the |
It's a lot of code, so I didn't manage to analyze everything in detail, but generally it looks good 👍 |
desc := h.config.URL.String() | ||
proxyErr = fmt.Errorf("error proxying, remote unreachable: target=%s, err=%w", desc, err) | ||
logrus.Errorf(proxyErr.Error()) | ||
proxyErr = err |
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 this seems to clobber the definition proxyErr assignment on line 158?
Perhaps use a different variable if you want to retain the error, i.e.
logrus.WithError(err).Errorf("error proxying, remote unreachable: target=%s, err=%w", desc, err)
proxyErr = err
or just
proxyErr = fmt.Errorf("error proxying, remote unreachable: target=%s, err=%w", desc, err)
logrus.Errorf(proxyErr.Error())
if err != nil { | ||
if etcd.IsKeyNotFound(err) { | ||
logrus.Debugf("unable to find the Datasource '%s' in project '%s'", datasourceName, projectName) | ||
return nil, "", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("unable to forward the request to the datasource '%s', datasource doesn't exist", datasourceName)) |
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.
nit: Should this be http.StatusNotFound?
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.
that's a good question. Looking at the definition of the error BadGateway
probably it's indeed better to return the status not found
} | ||
datasourceName := matchingGroups[0][1] | ||
// getting the datasource object | ||
dts, err := dao.Get(datasourceName) | ||
if err != nil { | ||
if etcd.IsKeyNotFound(err) { | ||
logrus.Debugf("unable to find the Datasource '%s'", datasourceName) | ||
return nil, echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("unable to forward the request to the datasource '%s', datasource doesn't exist", datasourceName)) | ||
return nil, "", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("unable to forward the request to the datasource '%s', datasource doesn't exist", datasourceName)) |
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.
nit: Should this be http.StatusNotFound?
@@ -41,13 +41,13 @@ func (d *dao) Update(entity *v1.Datasource) error { | |||
return d.client.Upsert(key, entity) | |||
} | |||
|
|||
func (d *dao) Delete(name string) error { | |||
key := v1.GenerateDatasourceID(name) | |||
func (d *dao) Delete(project string, name 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.
nit: Can be project, name string
here (since types are same for the two args)
return d.client.Delete(key) | ||
} | ||
|
||
func (d *dao) Get(name string) (*v1.Datasource, error) { | ||
key := v1.GenerateDatasourceID(name) | ||
func (d *dao) Get(project string, name string) (*v1.Datasource, 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.
nit: Can be project, name string
here (since types are same for the two args)
} | ||
|
||
type DAO interface { | ||
Create(entity *v1.Datasource) error | ||
Update(entity *v1.Datasource) error | ||
Delete(name string) error | ||
Get(name string) (*v1.Datasource, error) | ||
Delete(project string, name 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.
nit: Can be project, name string
here (since types are same for the two args)
Delete(name string) error | ||
Get(name string) (*v1.Datasource, error) | ||
Delete(project string, name string) error | ||
Get(project string, name string) (*v1.Datasource, 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.
nit: Can be project, name string
here (since types are same for the two args)
return nil | ||
} | ||
|
||
func (h *HTTPConfig) validate(conf tmpHTTPConfig) 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.
nit: Hm this method also seems to be setting variables on the HTTPConfig
itself. Perhaps this would be better named validateAndParse(...)
or something?
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.
mmm I don't know, this 'convention' is used on every object. Well it's fine to change it everywhere, perhaps I'm a bit too familiar that in a validate
method you have code that is changing and setting data. It's a sort of way to validate the data, because at the end, well your data is valid and contained the proper value.
That's the way I see it. WDYT ?
var defaultPrometheusAllowedEndpoints = []HTTPAllowedEndpoint{ | ||
{ | ||
Endpoint: "/api/v1/labels", | ||
Method: http.MethodPost, | ||
}, | ||
{ | ||
Endpoint: "/api/v1/series", | ||
Method: http.MethodPost, | ||
}, | ||
{ | ||
Endpoint: "/api/v1/metadata", | ||
Method: http.MethodGet, | ||
}, | ||
{ | ||
Endpoint: "/api/v1/query", | ||
Method: http.MethodPost, | ||
}, | ||
{ | ||
Endpoint: "/api/v1/query_range", | ||
Method: http.MethodPost, | ||
}, | ||
} |
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.
Probably want to add the /api/v1/label/FOO/values
endpoint too?
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.
yeah that's a TODO (I forgot to add it), we should be able to managed endpoint with variable inside.
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.
and this is not yet used by the Proxy
var defaultPrometheusAllowedEndpoints = []HTTPAllowedEndpoint{ | ||
{ | ||
Endpoint: "/api/v1/labels", | ||
Method: http.MethodPost, |
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, is this not GET?
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.
We support both methods:
...I think someone in the past once ran into problems with GET
and the URL length limit for match parameters, so for newer Prometheus versions, we support POST
as well for some of these endpoints.
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.
actually both are supported. It's just POST is avoiding the size limit of the URL, because the parameters are passed in the 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.
LGTM other than minor comments
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
981188e
to
b326e93
Compare
Signed-off-by: Augustin Husson <husson.augustin@gmail.com>
Codecov Report
@@ Coverage Diff @@
## master #84 +/- ##
==========================================
- Coverage 59.20% 55.39% -3.81%
==========================================
Files 47 54 +7
Lines 1934 2381 +447
==========================================
+ Hits 1145 1319 +174
- Misses 651 880 +229
- Partials 138 182 +44
Continue to review full report at Codecov.
|
This PR is changing deeply the datamodel of the datasource. The idea is to introduce the concept of multi different type of datasource.
Kind
will give you the type of the datasource.Default
will say if it's the default datasource for the same type. Meaning that you will be able to define one default datasource per type of datasource. One default datasource for Prometheus, one for MySQL ...etc.As you can see, it introduced:
The Prometheus Datasource define a default
whiteList
configuration. That's the only thing really specialize for Prometheus itself.It also introduced the notion of the local datasource. You can define a datasource in your project. The datasource definition is exactly the same as the one you can define globaly.
Only dashboard in the same project will be able to use it. Dashboards from other project won't be able to use it. Only the global datasources are shared across the different dashboards / projects.
A consequency of changing the spec of the datasource and introducing the local datasource is the recode of the Proxy.
For the moment, we only support Prometheus which exposed an HTTP API, but at some point we likely have not HTTP datasource.
The proxy is taking care of that, by introducing a proxy factory. Depending of the datasource type, an http proxy will be created or something else.
I will add more unit test, but in the meantine I will be interesting by your opinion about these changes @juliusv and @robskillington :).