Merge lp:~julian-edwards/gwacl/availability-response into lp:gwacl

Proposed by Julian Edwards
Status: Merged
Approved by: Julian Edwards
Approved revision: 207
Merged at revision: 200
Proposed branch: lp:~julian-edwards/gwacl/availability-response
Merge into: lp:gwacl
Diff against target: 192 lines (+142/-0)
4 files modified
management_base.go (+25/-0)
management_base_test.go (+87/-0)
xmlobjects.go (+12/-0)
xmlobjects_test.go (+18/-0)
To merge this branch: bzr merge lp:~julian-edwards/gwacl/availability-response
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+176855@code.launchpad.net

Commit message

Add CheckHostedServiceNameAvailability management call.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Well done, well tested. Testing against bad XML in the response, and against bugs in your test helper, is particularly thorough. It's a shame that test setup is always such a pain in Go.

.

The interface for CheckHostedServiceNameAvailability makes perfect sense to me, but I suspect part of its documentation was written before it became clear to what extent the Azure API's version deserved to be hidden:

16 +// CheckHostedServiceNameAvailability returns true if the supplied name is
17 +// available to use as a cloud service name. It returns nil if it is available
18 +// or an error containing the reason if it is not.

The function now returns nil if the name can be used, not true as that first comment line says.

By the way, it would help to hide a bit of unjustified specificity in Azure's API here. The focus on availability is misleading. A user might well decide that the only sensible way to check for name availability is to allocate it, and ignore this function — or worse, remove a call to this function from client code. But we also need this function to detect failures from name-based blocking.

Maybe you could say "acceptable" instead of "available," and add something like "a name might be unacceptable because it is already in use, or because it contains a pattern that is rejected by Azure policy, such as a trademarked or a controversial word."

I half wonder whether AddHostedService should use the new function to help diagnose Azure errors with status codes 400 and 409, just so we can return decently specific error objects. But programmatically identifiable error objects are just not part of Go idiom — or at least, combining them with helpful error strings isn't.

.

In TestAddHostedServiceWithBadName, you do:

96 + c.Check(recordedRequests, HasLen, 1)

No need for that. It's built into checkOneRequest.

review: Approve
207. By Julian Edwards

review comments applied

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On 25/07/13 15:21, Jeroen T. Vermeulen wrote:
> Review: Approve
>
> Well done, well tested. Testing against bad XML in the response, and against bugs in your test helper, is particularly thorough. It's a shame that test setup is always such a pain in Go.
>
> .
>
> The interface for CheckHostedServiceNameAvailability makes perfect sense to me, but I suspect part of its documentation was written before it became clear to what extent the Azure API's version deserved to be hidden:
>
> 16 +// CheckHostedServiceNameAvailability returns true if the supplied name is
> 17 +// available to use as a cloud service name. It returns nil if it is available
> 18 +// or an error containing the reason if it is not.

Yes caught in my own headlights here. Fixed, thanks.

>
> The function now returns nil if the name can be used, not true as that first comment line says.
>
> By the way, it would help to hide a bit of unjustified specificity in Azure's API here. The focus on availability is misleading. A user might well decide that the only sensible way to check for name availability is to allocate it, and ignore this function — or worse, remove a call to this function from client code. But we also need this function to detect failures from name-based blocking.
>
> Maybe you could say "acceptable" instead of "available," and add something like "a name might be unacceptable because it is already in use, or because it contains a pattern that is rejected by Azure policy, such as a trademarked or a controversial word."
>
> I half wonder whether AddHostedService should use the new function to help diagnose Azure errors with status codes 400 and 409, just so we can return decently specific error objects. But programmatically identifiable error objects are just not part of Go idiom — or at least, combining them with helpful error strings isn't.

Possibly, but I would much rather make random IDs UUIDs instead.

>
> .
>
> In TestAddHostedServiceWithBadName, you do:
>
> 96 + c.Check(recordedRequests, HasLen, 1)
>
> No need for that. It's built into checkOneRequest.
>

Ta.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'management_base.go'
--- management_base.go 2013-07-22 12:53:27 +0000
+++ management_base.go 2013-07-25 05:34:30 +0000
@@ -7,6 +7,7 @@
7 "encoding/xml"7 "encoding/xml"
8 "fmt"8 "fmt"
9 "net/http"9 "net/http"
10 "strings"
10 "time"11 "time"
11)12)
1213
@@ -179,6 +180,30 @@
179 return api.blockUntilCompleted(response)180 return api.blockUntilCompleted(response)
180}181}
181182
183// CheckHostedServiceNameAvailability looks to see if the supplied name is
184// acceptable to use as a cloud service name. It returns nil if it is available
185// or an error containing the reason if it is not. Names may not be acceptable
186// based on reserved words, trademarks and profanity.
187// See http://msdn.microsoft.com/en-us/library/windowsazure/jj154116.aspx
188func (api *ManagementAPI) CheckHostedServiceNameAvailability(name string) error {
189 var err error
190 response, err := api.session.get(
191 "services/hostedservices/operations/isavailable/"+name, "2012-03-01")
192 if err != nil {
193 return err
194 }
195
196 availability := &AvailabilityResponse{}
197 err = availability.Deserialize(response.Body)
198 if err != nil {
199 return err
200 }
201 if strings.ToLower(availability.Result) == "true" {
202 return nil
203 }
204 return fmt.Errorf(availability.Reason)
205}
206
182// DeleteHostedService deletes the named hosted service.207// DeleteHostedService deletes the named hosted service.
183// See http://msdn.microsoft.com/en-us/library/windowsazure/gg441305.aspx208// See http://msdn.microsoft.com/en-us/library/windowsazure/gg441305.aspx
184func (api *ManagementAPI) DeleteHostedService(serviceName string) error {209func (api *ManagementAPI) DeleteHostedService(serviceName string) error {
185210
=== modified file 'management_base_test.go'
--- management_base_test.go 2013-07-22 12:53:27 +0000
+++ management_base_test.go 2013-07-25 05:34:30 +0000
@@ -549,6 +549,93 @@
549 checkOneRequest(c, recordedRequests, expectedURL, "2012-03-01", expectedPayload, "POST")549 checkOneRequest(c, recordedRequests, expectedURL, "2012-03-01", expectedPayload, "POST")
550}550}
551551
552func makeAvailabilityResponse(result, reason string) string {
553 return fmt.Sprintf(`
554 <?xml version="1.0" encoding="utf-8"?>
555 <AvailabilityResponse xmlns="http://schemas.microsoft.com/windowsazure">
556 <Result>%s</Result>
557 <Reason>%s</Reason>
558 </AvailabilityResponse>`, result, reason)
559}
560
561func (*managementBaseAPISuite) TestAddHostedServiceWithOKName(c *C) {
562 api := makeAPI(c)
563 body := makeAvailabilityResponse("true", "")
564 fixedResponse := x509Response{
565 StatusCode: http.StatusOK,
566 Body: []byte(body),
567 }
568 rigFixedResponseDispatcher(&fixedResponse)
569 recordedRequests := make([]*X509Request, 0)
570 rigRecordingDispatcher(&recordedRequests)
571
572 serviceName := "service-name"
573 err := api.CheckHostedServiceNameAvailability(serviceName)
574
575 c.Assert(err, IsNil)
576 expectedURL := (AZURE_URL + api.session.subscriptionId +
577 "/services/hostedservices/operations/isavailable/" + serviceName)
578 checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", nil, "GET")
579}
580
581func (*managementBaseAPISuite) TestAddHostedServiceWithBadName(c *C) {
582 api := makeAPI(c)
583 reason := "This is a false test response"
584 body := makeAvailabilityResponse("false", reason)
585 fixedResponse := x509Response{
586 StatusCode: http.StatusOK,
587 Body: []byte(body),
588 }
589 rigFixedResponseDispatcher(&fixedResponse)
590 recordedRequests := make([]*X509Request, 0)
591 rigRecordingDispatcher(&recordedRequests)
592
593 serviceName := "service-name"
594 err := api.CheckHostedServiceNameAvailability(serviceName)
595
596 c.Assert(err, ErrorMatches, reason)
597 c.Check(recordedRequests, HasLen, 1)
598 expectedURL := (AZURE_URL + api.session.subscriptionId +
599 "/services/hostedservices/operations/isavailable/" + serviceName)
600 checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", nil, "GET")
601}
602
603func (*managementBaseAPISuite) TestAddHostedServiceWithServerError(c *C) {
604 api := makeAPI(c)
605 fixedResponse := x509Response{
606 StatusCode: http.StatusBadRequest,
607 }
608 rigFixedResponseDispatcher(&fixedResponse)
609 recordedRequests := make([]*X509Request, 0)
610 rigRecordingDispatcher(&recordedRequests)
611
612 serviceName := "service-name"
613 err := api.CheckHostedServiceNameAvailability(serviceName)
614
615 c.Assert(err, ErrorMatches, ".*Bad Request.*")
616}
617
618func (*managementBaseAPISuite) TestAddHostedServiceWithBadXML(c *C) {
619 api := makeAPI(c)
620 body := `
621 <AvailabilityResponse>
622 <Result>foo</Result>
623 <Reason>unclosed tag
624 </AvailabilityResponse>`
625 fixedResponse := x509Response{
626 StatusCode: http.StatusOK,
627 Body: []byte(body),
628 }
629 rigFixedResponseDispatcher(&fixedResponse)
630 recordedRequests := make([]*X509Request, 0)
631 rigRecordingDispatcher(&recordedRequests)
632
633 serviceName := "service-name"
634 err := api.CheckHostedServiceNameAvailability(serviceName)
635
636 c.Assert(err, ErrorMatches, ".*XML syntax error.*")
637}
638
552func assertDeleteHostedServiceRequest(c *C, api *ManagementAPI, serviceName string, httpRequest *X509Request) {639func assertDeleteHostedServiceRequest(c *C, api *ManagementAPI, serviceName string, httpRequest *X509Request) {
553 expectedURL := fmt.Sprintf("%s%s/services/hostedservices/%s", AZURE_URL,640 expectedURL := fmt.Sprintf("%s%s/services/hostedservices/%s", AZURE_URL,
554 api.session.subscriptionId, serviceName)641 api.session.subscriptionId, serviceName)
555642
=== modified file 'xmlobjects.go'
--- xmlobjects.go 2013-07-18 10:51:19 +0000
+++ xmlobjects.go 2013-07-25 05:34:30 +0000
@@ -430,6 +430,18 @@
430 return xml.Unmarshal(data, s)430 return xml.Unmarshal(data, s)
431}431}
432432
433// AvailabilityResponse is the reply from a Check Hosted Service Name
434// Availability operation.
435type AvailabilityResponse struct {
436 XMLNS string `xml:"xmlns,attr"`
437 Result string `xml:"Result"`
438 Reason string `xml:"Reason"`
439}
440
441func (a *AvailabilityResponse) Deserialize(data []byte) error {
442 return xml.Unmarshal(data, a)
443}
444
433// UpdateHostedService contains the details necessary to call the445// UpdateHostedService contains the details necessary to call the
434// UpdateHostedService management API call.446// UpdateHostedService management API call.
435// See http://msdn.microsoft.com/en-us/library/windowsazure/gg441303.aspx447// See http://msdn.microsoft.com/en-us/library/windowsazure/gg441303.aspx
436448
=== modified file 'xmlobjects_test.go'
--- xmlobjects_test.go 2013-07-18 01:45:02 +0000
+++ xmlobjects_test.go 2013-07-25 05:34:30 +0000
@@ -1717,6 +1717,24 @@
1717 c.Check(cssi.GeoReplicationEnabled, Equals, "false")1717 c.Check(cssi.GeoReplicationEnabled, Equals, "false")
1718}1718}
17191719
1720func (*xmlSuite) TestAvailabilityResponse(c *C) {
1721 input := `
1722 <?xml version="1.0" encoding="utf-8"?>
1723 <AvailabilityResponse xmlns="http://schemas.microsoft.com/windowsazure">
1724 <Result>name-availability</Result>
1725 <Reason>reason</Reason>
1726 </AvailabilityResponse>`
1727 expected := &AvailabilityResponse{
1728 XMLNS: XMLNS,
1729 Result: "name-availability",
1730 Reason: "reason",
1731 }
1732 observed := &AvailabilityResponse{}
1733 err := observed.Deserialize([]byte(input))
1734 c.Assert(err, IsNil)
1735 c.Assert(observed, DeepEquals, expected)
1736}
1737
1720func makeUpdateHostedService(label, description string, property ExtendedProperty) string {1738func makeUpdateHostedService(label, description string, property ExtendedProperty) string {
1721 template := dedent.Dedent(`1739 template := dedent.Dedent(`
1722 <UpdateHostedService xmlns="http://schemas.microsoft.com/windowsazure">1740 <UpdateHostedService xmlns="http://schemas.microsoft.com/windowsazure">

Subscribers

People subscribed via source and target branches

to all changes: