Skip to content

Commit

Permalink
Add ReleaseMachines method to the Controller.
Browse files Browse the repository at this point in the history
  • Loading branch information
howbazaar committed Apr 2, 2016
1 parent d86ea39 commit 294d068
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 0 deletions.
38 changes: 38 additions & 0 deletions controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,44 @@ func (c *controller) AllocateMachine(args AllocateMachineArgs) (Machine, error)
return machine, nil
}

// ReleaseMachinesArgs is an argument struct for passing the machine system IDs
// and an optional comment into the ReleaseMachines method.
type ReleaseMachinesArgs struct {
SystemIDs []string
Comment string
}

// ReleaseMachines implements Controller.
//
// Release multiple machines at once. Returns
// - BadRequestError if any of the machines cannot be found
// - PermissionError if the user does not have permission to release any of the machines
// - CannotCompleteError if any of the machines could not be released due to their current state
func (c *controller) ReleaseMachines(args ReleaseMachinesArgs) error {
params := NewURLParams()
params.MaybeAddMany("machines", args.SystemIDs)
params.MaybeAdd("comment", args.Comment)
_, err := c.post("machines", "release", params.Values)
if err != nil {
// A 409 Status code is "No Matching Machines"
if svrErr, ok := errors.Cause(err).(ServerError); ok {
if svrErr.StatusCode == http.StatusBadRequest {
return errors.Wrap(err, NewBadRequestError(svrErr.BodyMessage))
}
if svrErr.StatusCode == http.StatusForbidden {
return errors.Wrap(err, NewPermissionError(svrErr.BodyMessage))
}
if svrErr.StatusCode == http.StatusConflict {
return errors.Wrap(err, NewCannotCompleteError(svrErr.BodyMessage))
}
}
// Translate http errors.
return NewUnexpectedError(err)
}

return nil
}

func (c *controller) post(path, op string, params url.Values) (interface{}, error) {
path = EnsureTrailingSlash(path)
requestID := nextrequestID()
Expand Down
55 changes: 55 additions & 0 deletions controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,59 @@ func (s *controllerSuite) TestAllocateMachineUnexpected(c *gc.C) {
c.Assert(err, jc.Satisfies, IsUnexpectedError)
}

func (s *controllerSuite) TestReleaseMachines(c *gc.C) {
s.server.AddPostResponse("/api/2.0/machines/?op=release", http.StatusOK, "[]")
controller := s.getController(c)
err := controller.ReleaseMachines(ReleaseMachinesArgs{
SystemIDs: []string{"this", "that"},
Comment: "all good",
})
c.Assert(err, jc.ErrorIsNil)

request := s.server.LastRequest()
// There should be one entry in the form values for each of the args.
c.Assert(request.PostForm["machines"], jc.SameContents, []string{"this", "that"})
c.Assert(request.PostForm.Get("comment"), gc.Equals, "all good")
}

func (s *controllerSuite) TestReleaseMachinesBadRequest(c *gc.C) {
s.server.AddPostResponse("/api/2.0/machines/?op=release", http.StatusBadRequest, "unknown machines")
controller := s.getController(c)
err := controller.ReleaseMachines(ReleaseMachinesArgs{
SystemIDs: []string{"this", "that"},
})
c.Assert(err, jc.Satisfies, IsBadRequestError)
c.Assert(err.Error(), gc.Equals, "unknown machines")
}

func (s *controllerSuite) TestReleaseMachinesForbidden(c *gc.C) {
s.server.AddPostResponse("/api/2.0/machines/?op=release", http.StatusForbidden, "bzzt denied")
controller := s.getController(c)
err := controller.ReleaseMachines(ReleaseMachinesArgs{
SystemIDs: []string{"this", "that"},
})
c.Assert(err, jc.Satisfies, IsPermissionError)
c.Assert(err.Error(), gc.Equals, "bzzt denied")
}

func (s *controllerSuite) TestReleaseMachinesConflict(c *gc.C) {
s.server.AddPostResponse("/api/2.0/machines/?op=release", http.StatusConflict, "machine busy")
controller := s.getController(c)
err := controller.ReleaseMachines(ReleaseMachinesArgs{
SystemIDs: []string{"this", "that"},
})
c.Assert(err, jc.Satisfies, IsCannotCompleteError)
c.Assert(err.Error(), gc.Equals, "machine busy")
}

func (s *controllerSuite) TestReleaseMachinesUnexpected(c *gc.C) {
s.server.AddPostResponse("/api/2.0/machines/?op=release", http.StatusBadGateway, "wat")
controller := s.getController(c)
err := controller.ReleaseMachines(ReleaseMachinesArgs{
SystemIDs: []string{"this", "that"},
})
c.Assert(err, jc.Satisfies, IsUnexpectedError)
c.Assert(err.Error(), gc.Equals, "unexpected: ServerError: 502 Bad Gateway (wat)")
}

var versionResponse = `{"version": "unknown", "subversion": "", "capabilities": ["networks-management", "static-ipaddresses", "ipv6-deployment-ubuntu", "devices-management", "storage-deployment-ubuntu", "network-deployment-ubuntu"]}`
57 changes: 57 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,60 @@ func IsDeserializationError(err error) bool {
_, ok := errors.Cause(err).(*DeserializationError)
return ok
}

// BadRequestError is returned when the requested action cannot be performed
// due to bad or incorrect parameters passed to the server.
type BadRequestError struct {
errors.Err
}

// NewBadRequestError constructs a new BadRequestError and sets the location.
func NewBadRequestError(message string) error {
err := &BadRequestError{Err: errors.NewErr(message)}
err.SetLocation(1)
return err
}

// IsBadRequestError returns true if err is a NoMatchError.
func IsBadRequestError(err error) bool {
_, ok := errors.Cause(err).(*BadRequestError)
return ok
}

// PermissionError is returned when the user does not have permission to do the
// requested action.
type PermissionError struct {
errors.Err
}

// NewPermissionError constructs a new PermissionError and sets the location.
func NewPermissionError(message string) error {
err := &PermissionError{Err: errors.NewErr(message)}
err.SetLocation(1)
return err
}

// IsPermissionError returns true if err is a NoMatchError.
func IsPermissionError(err error) bool {
_, ok := errors.Cause(err).(*PermissionError)
return ok
}

// CannotCompleteError is returned when the requested action is unable to
// complete for some server side reason.
type CannotCompleteError struct {
errors.Err
}

// NewCannotCompleteError constructs a new CannotCompleteError and sets the location.
func NewCannotCompleteError(message string) error {
err := &CannotCompleteError{Err: errors.NewErr(message)}
err.SetLocation(1)
return err
}

// IsCannotCompleteError returns true if err is a NoMatchError.
func IsCannotCompleteError(err error) bool {
_, ok := errors.Cause(err).(*CannotCompleteError)
return ok
}
21 changes: 21 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,24 @@ func (*errorTypesSuite) TestWrapWithDeserializationError(c *gc.C) {
stack := errors.ErrorStack(err)
c.Assert(strings.Split(stack, "\n"), gc.HasLen, 2)
}

func (*errorTypesSuite) TestBadRequestError(c *gc.C) {
err := NewBadRequestError("omg")
c.Assert(err, gc.NotNil)
c.Assert(err, jc.Satisfies, IsBadRequestError)
c.Assert(err.Error(), gc.Equals, "omg")
}

func (*errorTypesSuite) TestPermissionError(c *gc.C) {
err := NewPermissionError("naughty")
c.Assert(err, gc.NotNil)
c.Assert(err, jc.Satisfies, IsPermissionError)
c.Assert(err.Error(), gc.Equals, "naughty")
}

func (*errorTypesSuite) TestCannotCompleteError(c *gc.C) {
err := NewCannotCompleteError("server says no")
c.Assert(err, gc.NotNil)
c.Assert(err, jc.Satisfies, IsCannotCompleteError)
c.Assert(err.Error(), gc.Equals, "server says no")
}
1 change: 1 addition & 0 deletions interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Controller interface {
Machines(MachinesArgs) ([]Machine, error)

AllocateMachine(AllocateMachineArgs) (Machine, error)
ReleaseMachines(ReleaseMachinesArgs) error
}

// Fabric represents a set of interconnected VLANs that are capable of mutual
Expand Down

0 comments on commit 294d068

Please sign in to comment.