Skip to content
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

Merged
merged 22 commits into from Apr 24, 2018

Conversation

eriknelson
Copy link
Contributor

@eriknelson eriknelson commented Apr 19, 2018

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_url location at the conclusion of its provision run.

A few notes:

  • This feature requires an APB to be built on top of an apb-base that carries the dashboard redirector ansible module.
  • The broker author must specify in an alpha section of their APB spec that they would like to enable this feature. Example APB.
  • The deployment of the broker must have the route to the dashboard redirector service specified in the broker's configmap. We have a sensible default configured in the template for this.
  • This is entirely opt in; there should be no change to existing behavior unless the feature is explicitly opted into by an APB author.

Changes proposed in this pull request

  • Update bundle-lib and broker-client-go dependencies to bring in DashboardURL and Alpha feature gate support.
  • Update broker to check if conditions are right to use the feature.
  • Returns a DashboardURL on initial async provision response that points to the dashboard redirector service if the feature is enabled.
  • Adds dashboard-redirector binary and modifies the build files to bake it into the broker image.
  • Updates template to deploy the dashboard-redirector alongside the broker.

@openshift-ci-robot openshift-ci-robot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label Apr 19, 2018
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)
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

@mhrivnak mhrivnak left a 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 {
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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
Copy link
Member

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")
Copy link
Member

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?

Copy link
Contributor Author

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.

@dymurray dymurray changed the title Add dashboard redirector feature Bug 1569220 - Add dashboard redirector feature Apr 19, 2018
* Use url pkg to check scheme and URL structure
* Use correct return codes
@openshift-ci-robot openshift-ci-robot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Apr 19, 2018
@eriknelson
Copy link
Contributor Author

@mhrivnak Think I addressed your requests for change, let me know if I missed anything!

return false
}
if a.brokerConfig.DashboardRedirector == "" {
warnMsg := fmt.Sprintf("Attempting to provision %v", spec.FQName) +
Copy link
Contributor

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.

Copy link
Contributor Author

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 :)

Copy link
Contributor

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)


var response *ProvisionResponse

dashboardRedirectEnabled := func() bool {
Copy link
Contributor

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

Copy link
Contributor Author

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.

Copy link
Contributor

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
Copy link
Contributor

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.

Copy link
Contributor

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
Copy link
Contributor

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

Copy link
Member

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?

Copy link
Contributor Author

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).


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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/its/it's/

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,
Copy link
Member

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 {
Copy link
Member

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.

@dymurray
Copy link
Member

Still trying to get the DR container in the same pod as ASB.... routes are not cooperating but will push here when its done.

@openshift-ci-robot openshift-ci-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Apr 20, 2018
Copy link
Contributor

@jmrodri jmrodri left a 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.

return false
}
if a.brokerConfig.DashboardRedirector == "" {
warnMsg := fmt.Sprintf("Attempting to provision %v", spec.FQName) +
Copy link
Contributor

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)
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

@jmrodri jmrodri Apr 20, 2018

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.

@eriknelson
Copy link
Contributor Author

eriknelson commented Apr 20, 2018

@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).

@eriknelson
Copy link
Contributor Author

eriknelson commented Apr 20, 2018

@fabianvf think these template additions probably need to make their way into openshift-ansible.

@fabianvf
Copy link
Member

@eriknelson could you break the template changes out to a 3.10 aligned Trello card?

@dymurray
Copy link
Member

@fabianvf @eriknelson https://trello.com/c/b9eEU5Lk

I am working on a PR that I will need some help testing.

@dymurray
Copy link
Member

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())
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated!

Copy link
Member

@djzager djzager left a 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:
Copy link
Member

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-review size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants