Skip to content
This repository has been archived by the owner on May 6, 2022. It is now read-only.

Commit

Permalink
Cleanup of ups broker example + making controller follow the OSB API (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
nilebox authored Jul 25, 2017
1 parent 45a11ed commit ee57bfb
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 61 deletions.
38 changes: 36 additions & 2 deletions contrib/cmd/user-broker/user-broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,16 @@ limitations under the License.
package main

import (
"context"
"flag"
"fmt"
"os"
"os/signal"
"path"
"strconv"
"syscall"

"github.com/golang/glog"
"github.com/kubernetes-incubator/service-catalog/contrib/pkg/broker/server"
"github.com/kubernetes-incubator/service-catalog/contrib/pkg/broker/user_provided/controller"
"github.com/kubernetes-incubator/service-catalog/pkg"
Expand All @@ -37,10 +42,39 @@ func init() {
}

func main() {
if err := run(); err != nil && err != context.Canceled && err != context.DeadlineExceeded {
glog.Fatalln(err)
}
}

func run() error {
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
cancelOnInterrupt(ctx, cancelFunc)

return runWithContext(ctx)
}

func runWithContext(ctx context.Context) error {
if flag.Arg(0) == "version" {
fmt.Printf("%s/%s\n", path.Base(os.Args[0]), pkg.VERSION)
return
return nil
}

server.Start(options.Port, controller.CreateController())
addr := ":" + strconv.Itoa(options.Port)
return server.Run(ctx, addr, controller.CreateController())
}

// cancelOnInterrupt calls f when os.Interrupt or SIGTERM is received.
// It ignores subsequent interrupts on purpose - program should exit correctly after the first signal.
func cancelOnInterrupt(ctx context.Context, f context.CancelFunc) {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
select {
case <-ctx.Done():
case <-c:
f()
}
}()
}
10 changes: 5 additions & 5 deletions contrib/pkg/broker/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ import (
type Controller interface {
Catalog() (*brokerapi.Catalog, error)

GetServiceInstance(id string) (string, error)
CreateServiceInstance(id string, req *brokerapi.CreateServiceInstanceRequest) (*brokerapi.CreateServiceInstanceResponse, error)
RemoveServiceInstance(id string) (*brokerapi.DeleteServiceInstanceResponse, error)
GetServiceInstanceLastOperation(instanceID, serviceID, planID, operation string) (*brokerapi.LastOperationResponse, error)
CreateServiceInstance(instanceID string, req *brokerapi.CreateServiceInstanceRequest) (*brokerapi.CreateServiceInstanceResponse, error)
RemoveServiceInstance(instanceID, serviceID, planID string, acceptsIncomplete bool) (*brokerapi.DeleteServiceInstanceResponse, error)

Bind(instanceID string, bindingID string, req *brokerapi.BindingRequest) (*brokerapi.CreateServiceBindingResponse, error)
UnBind(instanceID string, bindingID string) error
Bind(instanceID, bindingID string, req *brokerapi.BindingRequest) (*brokerapi.CreateServiceBindingResponse, error)
UnBind(instanceID, bindingID, serviceID, planID string) error
}
73 changes: 47 additions & 26 deletions contrib/pkg/broker/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ limitations under the License.
package server

import (
"context"
"fmt"
"net/http"
"strconv"
"time"

"github.com/golang/glog"
"github.com/kubernetes-incubator/service-catalog/contrib/pkg/broker/controller"
Expand All @@ -35,15 +36,15 @@ type server struct {

// CreateHandler creates Broker HTTP handler based on an implementation
// of a controller.Controller interface.
func CreateHandler(c controller.Controller) http.Handler {
func createHandler(c controller.Controller) http.Handler {
s := server{
controller: c,
}

var router = mux.NewRouter()

router.HandleFunc("/v2/catalog", s.catalog).Methods("GET")
router.HandleFunc("/v2/service_instances/{instance_id}", s.getServiceInstance).Methods("GET")
router.HandleFunc("/v2/service_instances/{instance_id}/last_operation", s.getServiceInstanceLastOperation).Methods("GET")
router.HandleFunc("/v2/service_instances/{instance_id}", s.createServiceInstance).Methods("PUT")
router.HandleFunc("/v2/service_instances/{instance_id}", s.removeServiceInstance).Methods("DELETE")
router.HandleFunc("/v2/service_instances/{instance_id}/service_bindings/{binding_id}", s.bind).Methods("PUT")
Expand All @@ -52,14 +53,23 @@ func CreateHandler(c controller.Controller) http.Handler {
return router
}

// Start creates the HTTP handler based on an implementation of a
// controller.Controller interface, and begins to listen on the specified port.
func Start(serverPort int, c controller.Controller) {
glog.Infof("Starting server on %d\n", serverPort)
http.Handle("/", CreateHandler(c))
if err := http.ListenAndServe(":"+strconv.Itoa(serverPort), nil); err != nil {
panic(err)
// Run creates the HTTP handler based on an implementation of a
// controller.Controller interface, and begins to listen on the specified address.
func Run(ctx context.Context, addr string, c controller.Controller) error {
glog.Infof("Starting server on %d\n", addr)
srv := http.Server{
Addr: addr,
Handler: createHandler(c),
}
go func() {
<-ctx.Done()
c, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if srv.Shutdown(c) != nil {
srv.Close()
}
}()
return srv.ListenAndServe()
}

func (s *server) catalog(w http.ResponseWriter, r *http.Request) {
Expand All @@ -68,18 +78,22 @@ func (s *server) catalog(w http.ResponseWriter, r *http.Request) {
if result, err := s.controller.Catalog(); err == nil {
util.WriteResponse(w, http.StatusOK, result)
} else {
util.WriteResponse(w, http.StatusBadRequest, err)
util.WriteErrorResponse(w, http.StatusBadRequest, err)
}
}

func (s *server) getServiceInstance(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["instance_id"]
glog.Infof("GetServiceInstance ... %s\n", id)
func (s *server) getServiceInstanceLastOperation(w http.ResponseWriter, r *http.Request) {
instanceID := mux.Vars(r)["instance_id"]
q := r.URL.Query()
serviceID := q.Get("service_id")
planID := q.Get("plan_id")
operation := q.Get("operation")
glog.Infof("GetServiceInstance ... %s\n", instanceID)

if result, err := s.controller.GetServiceInstance(id); err == nil {
if result, err := s.controller.GetServiceInstanceLastOperation(instanceID, serviceID, planID, operation); err == nil {
util.WriteResponse(w, http.StatusOK, result)
} else {
util.WriteResponse(w, http.StatusBadRequest, err)
util.WriteErrorResponse(w, http.StatusBadRequest, err)
}
}

Expand All @@ -90,7 +104,7 @@ func (s *server) createServiceInstance(w http.ResponseWriter, r *http.Request) {
var req brokerapi.CreateServiceInstanceRequest
if err := util.BodyToObject(r, &req); err != nil {
glog.Errorf("error unmarshalling: %v", err)
util.WriteResponse(w, http.StatusBadRequest, err)
util.WriteErrorResponse(w, http.StatusBadRequest, err)
return
}

Expand All @@ -104,18 +118,22 @@ func (s *server) createServiceInstance(w http.ResponseWriter, r *http.Request) {
if result, err := s.controller.CreateServiceInstance(id, &req); err == nil {
util.WriteResponse(w, http.StatusCreated, result)
} else {
util.WriteResponse(w, http.StatusBadRequest, err)
util.WriteErrorResponse(w, http.StatusBadRequest, err)
}
}

func (s *server) removeServiceInstance(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["instance_id"]
glog.Infof("RemoveServiceInstance %s...\n", id)
instanceID := mux.Vars(r)["instance_id"]
q := r.URL.Query()
serviceID := q.Get("service_id")
planID := q.Get("plan_id")
acceptsIncomplete := q.Get("accepts_incomplete") == "true"
glog.Infof("RemoveServiceInstance %s...\n", instanceID)

if result, err := s.controller.RemoveServiceInstance(id); err == nil {
if result, err := s.controller.RemoveServiceInstance(instanceID, serviceID, planID, acceptsIncomplete); err == nil {
util.WriteResponse(w, http.StatusOK, result)
} else {
util.WriteResponse(w, http.StatusBadRequest, err)
util.WriteErrorResponse(w, http.StatusBadRequest, err)
}
}

Expand All @@ -129,7 +147,7 @@ func (s *server) bind(w http.ResponseWriter, r *http.Request) {

if err := util.BodyToObject(r, &req); err != nil {
glog.Errorf("Failed to unmarshall request: %v", err)
util.WriteResponse(w, http.StatusBadRequest, err)
util.WriteErrorResponse(w, http.StatusBadRequest, err)
return
}

Expand All @@ -146,20 +164,23 @@ func (s *server) bind(w http.ResponseWriter, r *http.Request) {
if result, err := s.controller.Bind(instanceID, bindingID, &req); err == nil {
util.WriteResponse(w, http.StatusOK, result)
} else {
util.WriteResponse(w, http.StatusBadRequest, err)
util.WriteErrorResponse(w, http.StatusBadRequest, err)
}
}

func (s *server) unBind(w http.ResponseWriter, r *http.Request) {
instanceID := mux.Vars(r)["instance_id"]
bindingID := mux.Vars(r)["binding_id"]
q := r.URL.Query()
serviceID := q.Get("service_id")
planID := q.Get("plan_id")
glog.Infof("UnBind: Service instance guid: %s:%s", bindingID, instanceID)

if err := s.controller.UnBind(instanceID, bindingID); err == nil {
if err := s.controller.UnBind(instanceID, bindingID, serviceID, planID); err == nil {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, "{}") //id)
} else {
util.WriteResponse(w, http.StatusBadRequest, err)
util.WriteErrorResponse(w, http.StatusBadRequest, err)
}
}
42 changes: 22 additions & 20 deletions contrib/pkg/broker/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package server_test
package server

import (
"encoding/json"
Expand All @@ -23,17 +23,20 @@ import (
"net/http/httptest"
"testing"

. "github.com/kubernetes-incubator/service-catalog/contrib/pkg/broker/server"
"github.com/kubernetes-incubator/service-catalog/contrib/pkg/broker/controller"
"github.com/kubernetes-incubator/service-catalog/pkg/brokerapi"
)

//
// Test of server /v2/catalog endpoint.
//

// Make sure that Controller stub implements controller.Controller interface
var _ controller.Controller = &Controller{}

// /v2/catalog returns HTTP error on error.
func TestCatalogReturnsHTTPErrorOnError(t *testing.T) {
handler := CreateHandler(&Controller{
handler := createHandler(&Controller{
t: t,
catalog: func() (*brokerapi.Catalog, error) {
return nil, errors.New("Catalog retrieval error")
Expand All @@ -51,15 +54,14 @@ func TestCatalogReturnsHTTPErrorOnError(t *testing.T) {
t.Errorf("Expected response content-type 'application/json', got '%s'", contentType)
}

// TODO: This is a bug. We should be returning an error string.
if body := rr.Body.String(); body != "{}" {
t.Errorf("Expected (albeit incorrectly) an empty JSON object as a response; got '%s'", body)
if body := rr.Body.String(); body != `{"Error":"Catalog retrieval error"}` {
t.Errorf("Expected structured error response; got '%s'", body)
}
}

// /v2/catalog returns compliant JSON
func TestCatalogReturnsCompliantJSON(t *testing.T) {
handler := CreateHandler(&Controller{
handler := createHandler(&Controller{
t: t,
catalog: func() (*brokerapi.Catalog, error) {
return &brokerapi.Catalog{Services: []*brokerapi.Service{
Expand Down Expand Up @@ -117,12 +119,12 @@ func readJSON(rr *httptest.ResponseRecorder) (map[string]interface{}, error) {
type Controller struct {
t *testing.T

catalog func() (*brokerapi.Catalog, error)
getServiceInstance func(id string) (string, error)
createServiceInstance func(id string, req *brokerapi.CreateServiceInstanceRequest) (*brokerapi.CreateServiceInstanceResponse, error)
removeServiceInstance func(id string) (*brokerapi.DeleteServiceInstanceResponse, error)
bind func(instanceID string, bindingID string, req *brokerapi.BindingRequest) (*brokerapi.CreateServiceBindingResponse, error)
unBind func(instanceID string, bindingID string) error
catalog func() (*brokerapi.Catalog, error)
getServiceInstanceLastOperation func(id string) (*brokerapi.LastOperationResponse, error)
createServiceInstance func(id string, req *brokerapi.CreateServiceInstanceRequest) (*brokerapi.CreateServiceInstanceResponse, error)
removeServiceInstance func(id string) (*brokerapi.DeleteServiceInstanceResponse, error)
bind func(instanceID string, bindingID string, req *brokerapi.BindingRequest) (*brokerapi.CreateServiceBindingResponse, error)
unBind func(instanceID string, bindingID string) error
}

func (controller *Controller) Catalog() (*brokerapi.Catalog, error) {
Expand All @@ -133,12 +135,12 @@ func (controller *Controller) Catalog() (*brokerapi.Catalog, error) {
return controller.catalog()
}

func (controller *Controller) GetServiceInstance(id string) (string, error) {
if controller.getServiceInstance == nil {
controller.t.Error("Test failed to provide 'getServiceInstance' handler")
func (controller *Controller) GetServiceInstanceLastOperation(instanceID, serviceID, planID, operation string) (*brokerapi.LastOperationResponse, error) {
if controller.getServiceInstanceLastOperation == nil {
controller.t.Error("Test failed to provide 'getServiceInstanceLastOperation' handler")
}

return controller.getServiceInstance(id)
return controller.getServiceInstanceLastOperation(instanceID)
}

func (controller *Controller) CreateServiceInstance(id string, req *brokerapi.CreateServiceInstanceRequest) (*brokerapi.CreateServiceInstanceResponse, error) {
Expand All @@ -149,12 +151,12 @@ func (controller *Controller) CreateServiceInstance(id string, req *brokerapi.Cr
return controller.createServiceInstance(id, req)
}

func (controller *Controller) RemoveServiceInstance(id string) (*brokerapi.DeleteServiceInstanceResponse, error) {
func (controller *Controller) RemoveServiceInstance(instanceID, serviceID, planID string, acceptsIncomplete bool) (*brokerapi.DeleteServiceInstanceResponse, error) {
if controller.removeServiceInstance == nil {
controller.t.Error("Test failed to provide 'removeServiceInstance' handler")
}

return controller.removeServiceInstance(id)
return controller.removeServiceInstance(instanceID)
}

func (controller *Controller) Bind(instanceID string, bindingID string, req *brokerapi.BindingRequest) (*brokerapi.CreateServiceBindingResponse, error) {
Expand All @@ -165,7 +167,7 @@ func (controller *Controller) Bind(instanceID string, bindingID string, req *bro
return controller.bind(instanceID, bindingID, req)
}

func (controller *Controller) UnBind(instanceID string, bindingID string) error {
func (controller *Controller) UnBind(instanceID, bindingID, serviceID, planID string) error {
if controller.unBind == nil {
controller.t.Error("Test failed to provide 'unBind' handler")
}
Expand Down
Loading

0 comments on commit ee57bfb

Please sign in to comment.