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
Bug 1569220 - Add dashboard redirector feature #897
Conversation
* New crd fields * Parameterize dashboard_redirector config value * Use correct service account with DR
* Add dashboard_redirector config value * Logic to return dashboard_url if settings are present
* broker-client-go && bundle-lib
* Comply with dep updates
cmd/dashboard-redirector/main.go
Outdated
| logrus.Infof("DashboardURL found: %s, 301 redirecting", si.DashboardURL) | ||
| var redirectURL string | ||
| if !strings.HasPrefix("http", si.DashboardURL) { | ||
| redirectURL = fmt.Sprintf("http://%s", si.DashboardURL) |
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.
What's the use case behind this? Is it possible to just force whatever external entity is supplying the URL to supply a URL with a valid scheme?
Aside from the risk of assuming non-tls http, it's possible a service would have a management dashboard available through other non-http means, such as ssh.
If there is a use case that requires making this assumption, it would be helpful to add an in-line comment about it.
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.
What's the use case behind this? Is it possible to just force whatever external entity is supplying the URL to supply a URL with a valid scheme?
When an APB passes back something without a scheme; this is more friendly and allows that case to pass and assume http rather than explicitly fail.
Aside from the risk of assuming non-tls http
It seems to me the worst case scenario here is that http is assumed when not supported by the service. Many will force a redirect to https.
it's possible a service would have a management dashboard available through other non-http means, such as ssh.
Interesting point.
I'm anticipating there will be APB authors that unintentionally do not provide a scheme and the intention is to help them with a reasonable assumption. Alternatively, we can lock this down, warn, and default to not providing the dashboard url if the scheme is missing. As I'm thinking about it, the latter might not be a bad idea since the majority of authors will probably get that service URL dynamically from some API and store it in a var to later provide to our module, greater increasing the likelyhood of having the scheme handled by the remote system.
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.
Could we get a tiny README or similar for the redirector that explains what it does and how to use it? Even just two or three sentences explaining how to make a request to it (GET request with "id" as a query parameter containing...) and where it gets its data would be a great start.
| logrus.Infof("Got request for service instance %s, looking up dashboard_url", id) | ||
|
|
||
| si, err := crdDao.GetServiceInstance(id) | ||
| if err != nil { |
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.
What happens if the service instance doesn't exist? Would that result in err having a value here? Or would si just have the default zero value?
If it's the former, I think the StatusInternalServerError below is incorrect in that case and should be a 404, since the instance would have literally been "not found".
If it's the latter, then the log message "Successfully loaded" would be misleading.
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.
Updated to check if it's actually an is not found error and will return the correct code in that case, otherwise will return an internal 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'd change the value for errMsg too just to make it clear that all the software worked correctly, and your instance just doesn't exist. But that's a minor suggestion.
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.
updated
| - apiVersion: v1 | ||
| kind: Service | ||
| metadata: | ||
| name: dr |
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.
For someone looking at this Service and wondering what it is, could this be a touch more descriptive? It'll save us from answering the question "What is the 'dr' service?" a hundred times. ;)
| var errMsg string | ||
| logrus.Info("Checking for form") | ||
|
|
||
| id := r.FormValue("id") |
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 any security risk in handing out the dashboard URL to anyone who knows the instance ID? Or is there some implied authorization happening before the request makes it this far?
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 in person, decided to file an issue here to address if/when the feature graduates from alpha.
* Use url pkg to check scheme and URL structure * Use correct return codes
|
@mhrivnak Think I addressed your requests for change, let me know if I missed anything! |
pkg/broker/broker.go
Outdated
| return false | ||
| } | ||
| if a.brokerConfig.DashboardRedirector == "" { | ||
| warnMsg := fmt.Sprintf("Attempting to provision %v", spec.FQName) + |
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 personally prefer log.Warningf and then you can remove the string concatenation.
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 you have a preferred alternative to adding the strings? I really can't stand 130 char error messages, and that's all I care about :)
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.
You could do one of the following, use tick marks:
warnMsg := `Attempting to provision %v, which has dashboard redirect enabled,
but no dashboard_redirector route was found in the broker's configmap.
Deploying without a dashboard_url.`
log.Warningf(warnMsg, spec.FQName)You could keep the concatenation but remove the sprintf using the Warningf version:
warnMsg1 := "Attempting to provision %v, which has dashboard redirect enabled" +
"but no dashboard_redirector route was found in the broker's " +
"configmap. Deploying without a dashboard_url."
log.Warningf(warnMsg1, "FQName")Or you could reword the message to be shorter:
// short message, no fqname
log.Warning("Deploying without a dashboard_url, no dashboard_redirector route found")
// short message with fqname
log.Warningf("Deploying without a dashboard_url, no dashboard_redirector route found for %v", spec.FQName)
pkg/broker/broker.go
Outdated
|
|
||
| var response *ProvisionResponse | ||
|
|
||
| dashboardRedirectEnabled := func() bool { |
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 we make this it's own helper function below and not a function defined in a function?
Also do we need the dashboard url for GetServiceInstance
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.
Also do we need the dashboard url for GetServiceInstance
Good catch. We have a GET service instance endpoint, but I'm not sure that there is actually anything calling it? Ironically in the future, it will probably be the spec support that actually implements the workaround this PR is trying to address.
I think I'm going to add a helper that encapsulates getProvisionResponse given a service instance, and we can hide the details there. I prefer inline function defs like this because I'm trying to define what it means to be enabled at a higher level when the conditionals start to get more complex than a simple boolean expression in the conditional. I prefer inline because it's close to the call site.
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.
but I'm not sure that there is actually anything calling it?
👍
I prefer inline function defs like this because I'm trying to define what it means to be enabled at a higher level when the conditionals start to get more complex than a simple boolean expression in the conditional. I prefer inline because it's close to the call site.
I personally worry about to many inline functions. I believe that at a certain point they make it harder to follow the function. With that said I this one makes sense if it is all encapsulated in another helper function 👍 from me
| @@ -44,6 +44,22 @@ func (jss *JobStateSubscriber) Notify(msg JobMsg) { | |||
| return | |||
| } | |||
| } | |||
| //TODO: Need to get the service instance | |||
| //TODO: NOTE: THIS NEEDS TO BREAK OUT TO OWN SUBSCRIBER | |||
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 if we have the time, this might be something we should do.
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 don't think it needs a different subscriber, but it could definitely use a helper function. It feels weird where it is now.
func (jss *JobStateSubscriber) handleDashboardURL(msg JobMsg) {
// Update with dashboard URL.
if msg.DashboardURL != "" {
instance, err := jss.dao.GetServiceInstance(msg.InstanceUUID)
if err != nil {
log.Errorf("Error after job succeeded : %v", err)
return
}
instance.DashboardURL = msg.DashboardURL
err = jss.dao.SetServiceInstance(msg.InstanceUUID, instance)
if err != nil {
log.Errorf("Error after job succeeded : %v", err)
return
}
}The Notify would then look cleaner:
// Notify external API to notify this subscriber of a change in the Job
func (jss *JobStateSubscriber) Notify(msg JobMsg) {
log.Debugf("JobStateSubscriber Notify : msg state %v ", msg.State)
id := msg.InstanceUUID
if isBinding(msg) {
id = msg.BindingUUID
}
if _, err := jss.dao.SetState(id, msg.State); err != nil {
log.Errorf("Error JobStateSubscriber failed to set state after action %v completed with state %s err: %v", msg.State.Method, msg.State.State, err)
return
}
if msg.State.State == apb.StateSucceeded {
if err := jss.handleSucceeded(msg); err != nil {
log.Errorf("Error after job succeeded : %v", err)
return
}
}
handleDashboardURL(msg)
return
}| @@ -167,6 +184,36 @@ objects: | |||
| secret: | |||
| secretName: asb-auth-secret | |||
|
|
|||
| - apiVersion: v1 | |||
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 before we merge we should get this working as a container inside the asb pod IMO
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.
My belief is that this is so we don't have to expose a public facing route in order for the broker to be able to talk to it. Is that 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.
@djzager that's is true, but the broker in this case never talks to the redirector. It's for external clients (OpenShift UI).
cmd/dashboard-redirector/README.md
Outdated
|
|
||
| Currently, the OSB spec does not allow for the brokers to return a `dashboard_url` | ||
| at the conclusion of an async provision. This is problematic because brokers | ||
| (and bundles, in our case) may not actually know their `dashboard_url` if its |
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.
s/its/it's/
cmd/dashboard-redirector/README.md
Outdated
| As a workaround, our broker runs this "dashboard-redirector" as a simple | ||
| service that accepts a service instance ID, and looks up the service instance | ||
| in our DAO. If the `DashboardURL` is present on the service instance, the | ||
| dashboard-redirector will return a 302 redirect to that location. Otherwise, |
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.
s/302/301/
| logrus.Infof("Got request for service instance %s, looking up dashboard_url", id) | ||
|
|
||
| si, err := crdDao.GetServiceInstance(id) | ||
| if err != nil { |
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'd change the value for errMsg too just to make it clear that all the software worked correctly, and your instance just doesn't exist. But that's a minor suggestion.
|
Still trying to get the DR container in the same pod as ASB.... routes are not cooperating but will push here when its done. |
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.
+1 to all the others comments on this.
pkg/broker/broker.go
Outdated
| return false | ||
| } | ||
| if a.brokerConfig.DashboardRedirector == "" { | ||
| warnMsg := fmt.Sprintf("Attempting to provision %v", spec.FQName) + |
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.
You could do one of the following, use tick marks:
warnMsg := `Attempting to provision %v, which has dashboard redirect enabled,
but no dashboard_redirector route was found in the broker's configmap.
Deploying without a dashboard_url.`
log.Warningf(warnMsg, spec.FQName)You could keep the concatenation but remove the sprintf using the Warningf version:
warnMsg1 := "Attempting to provision %v, which has dashboard redirect enabled" +
"but no dashboard_redirector route was found in the broker's " +
"configmap. Deploying without a dashboard_url."
log.Warningf(warnMsg1, "FQName")Or you could reword the message to be shorter:
// short message, no fqname
log.Warning("Deploying without a dashboard_url, no dashboard_redirector route found")
// short message with fqname
log.Warningf("Deploying without a dashboard_url, no dashboard_redirector route found for %v", spec.FQName)| if err != nil { | ||
| panic(fmt.Sprintf("Sanity check failed! -> %v", err)) | ||
| } else { | ||
| logrus.Info("Sanity check passed! Loaded specs: %v", specs) |
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.
What does this print out?
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.
@jmrodri don't the ticks include the newlines? If so, gotta be honest I don't think any of those options really help :P Guess I'll throw this in my pile of go complaints.
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.
@eriknelson yeah the tick marks will include the new lines. Kind of like the python doc strings.
|
@mhrivnak Think I hit got your second round of comments. @shawn-hurley I'm not sure I understand why we need another subscriber? Could you elaborate on that? I just tested @dymurray's changes to the template and can confirm it's working in a single pod. Turns out there should only be a single service and multiple ports declared. Makes sense since the containers share the same port space of the virtual ip (svc). |
|
@fabianvf think these template additions probably need to make their way into openshift-ansible. |
|
@eriknelson could you break the template changes out to a 3.10 aligned Trello card? |
|
@fabianvf @eriknelson https://trello.com/c/b9eEU5Lk I am working on a PR that I will need some help testing. |
cmd/dashboard-redirector/main.go
Outdated
| errMsg = fmt.Sprintf("Something went wrong trying to load service instance [%s] -> %s", id, err) | ||
| http.Error(w, errMsg, http.StatusInternalServerError) | ||
| } | ||
| logrus.Errorf(errMsg, id, err.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.
One last nit to pick: here a 404 gets logged as at "Error", but below it gets logged at "Info". It would be better to have them at the same level, and I'd suggest Info. It's not a problematic or exceptional case by any means for the service instance to not be found.
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.
Updated!
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. One comment.
| @@ -517,6 +546,8 @@ objects: | |||
| properties: | |||
| specId: | |||
| type: string | |||
| dashboardUrl: | |||
| type: string | |||
| context: | |||
| type: object | |||
| properties: | |||
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.
Adding here, since I can't add it where it belongs. On line 554.
properties:
plateform:
type: string
namespace:
type: string
Should we fix this?
Describe what this PR does and why we need it:
This PR adds support for async provisioned dashboard urls. This is currently not possible with the OSB spec, so our solution is to add a redirect service that runs alongside the broker. The broker immediately returns a URL to the redirector service as a sort of "future" url, that will ultimately redirect to an APB's specified
dashboard_urllocation at the conclusion of its provision run.A few notes:
apb-basethat carries the dashboard redirector ansible module.alphasection of their APB spec that they would like to enable this feature. Example APB.Changes proposed in this pull request
DashboardURLandAlphafeature gate support.DashboardURLon initial async provision response that points to the dashboard redirector service if the feature is enabled.