Merge lp:~jtv/gwacl/service-endpoints into lp:gwacl

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Jeroen T. Vermeulen
Approved revision: 224
Merged at revision: 213
Proposed branch: lp:~jtv/gwacl/service-endpoints
Merge into: lp:gwacl
Diff against target: 912 lines (+306/-82)
12 files modified
endpoints.go (+63/-0)
endpoints_test.go (+115/-0)
example/management/run.go (+3/-1)
management_base.go (+2/-2)
management_base_test.go (+28/-28)
management_test.go (+1/-1)
poller_test.go (+3/-3)
storage_base.go (+22/-8)
storage_base_test.go (+28/-6)
x509dispatcher_test.go (+5/-5)
x509session.go (+10/-8)
x509session_test.go (+26/-20)
To merge this branch: bzr merge lp:~jtv/gwacl/service-endpoints
Reviewer Review Type Date Requested Status
Julian Edwards (community) Approve
Review via email: mp+178660@code.launchpad.net

Commit message

Support Chinese Azure endpoints, as well as the international ones.

Description of the change

This removes the hard-coding of a single pair of base URLs for Azure's Storage and Management APIs. I defined a new type, APIEndpoint, specifying the base URL that these other URLs should be derived from. It's a bit clearer than passing just URL strings around, because those can easily become ambiguous as to what components of the ultimate URL they already include. Methods on the new type will give you URLs for the storage API, the management API, and perhaps in the future, APIs for the other services as well.

You'll note that AZURE_URL is now defaultManagement, a private variable used for tests only. There no longer is a single URL that will service all cases, and actually this was never "the" Azure URL to begin with -- it was for the management API only.

Both the management and the storage API now need to know which location they will act on. Unfortunately the way in which these are added is entirely asymmetrical:
 * The management API uses a constructor, which adds a mandatory argument. This means incompatibility in the API.
 * The storage API uses a struct, so the field is optional. It chooses an arbitrary (but backwards-compatible) default.

I believe the right thing to do after this would be to remove the default value for the storage API, and require a value. (Not done here because the diff was quite large enough already!) Maybe even provide a constructor like we have for the management API, because if you're going to add a mandatory parameter in a place that just about every client is going to use, breaking client code at compile time is still better than breaking it at run time.

Jeroen

To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote :
Download full text (3.4 KiB)

Looks pretty good! I have some suggestions to make it better of course, but it
can land when you've made the changes.

27 + if strings.Contains(location, "China") {

It's "China East" and "China North" AFAICS, it might be worth being specific
instead of using Contains in case a new one pops up that's not actually in the
same domain. Although sod's law dictates whatever we do it'll be wrong ... but
I think explicit is always better as you end up with fewer surprises.

60 +// StorageAPI returns the base URL for the endpoint's storage API.
61 +func (endpoint APIEndpoint) StorageAPI() string {

Why not a pointer receiver? (same for ManagementAPI)

62 + return prefixHost("blob", string(endpoint))
63 +}

Given there's three types of storage, this should to be named BlobStorageAPI().
I'm not even sure the "API" part is accurate, but I'm not going to bikeshed over it.

Also, the storage URL returned here is still not complete, it would need the account prefixed.
It might be worth encapsulating that functionality in this object so that all the URL
manipulation is done in the same place. I'd have StorageAPI take an account name parameter.

114 +func (*endpointsSuite) TestGetEndpointMakesGoodGuessesForUknownRegions(c *C) {
115 + c.Check(
116 + GetEndpoint("South San Marino Highlands"),
117 + Equals,
118 + GetEndpoint("West US"))
119 + c.Check(
120 + GetEndpoint("Central China West"),
121 + Equals,
122 + GetEndpoint("China East"))
123 +}

I'm not sure I like this test at all. Its expected output is a function of the method being tested
rather than something either constant or independent of it, and that's kinda nasty. It also feels
fragile.

I'd much rather you were explicit in testing expected output to be the required endpoint URLs.

535 + // AzureEndpoint specifies a base service endpoint URL for the Azure APIs.
536 + // If this is not set, it will default to the international endpoint which
537 + // will not work in mainland China.
538 + //
539 + // Try to set this if at all possible. Use GetEndpoint() to obtain the
540 + // endpoint associated with a given service location, e.g. "West US" or
541 + // "North Europe" or "East China".
542 + AzureEndpoint APIEndpoint

I don't like the "try to set this" stuff at all here. Let's just panic if it's not set.
Out of interest, I wonder if Go works like C++ here and lets you supply a value that works with
the type's constructor as well as supplying the type itself.

558 + if endpoint == "" {
559 + // No API endpoint specified. Default to the international one.
560 + // This will not work for mainland China.
561 + endpoint = GetEndpoint("West US")

Remove this and panic() if it's not set.

563 + return prefixHost(context.Account, endpoint.StorageAPI())

And as I mentioned before, let's encapsulate this in APIEndpoint.

732 +// defaultManagement is the international management API for Azure.
733 +// (Mainland China gets a different URL).
734 +const defaultManagement = "https://management.core.windows.net/"

I hate Go for breaking the convention that consts are in CAPS. Grrr.

Anyway this should be defa...

Read more...

review: Approve
lp:~jtv/gwacl/service-endpoints updated
223. By Jeroen T. Vermeulen

Review change: rename StorageAPI to BlobStorageAPI.

224. By Jeroen T. Vermeulen

Document usage of the blob-storage API URL.

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :
Download full text (6.3 KiB)

> 27 + if strings.Contains(location, "China") {
>
> It's "China East" and "China North" AFAICS, it might be worth being specific
> instead of using Contains in case a new one pops up that's not actually in the
> same domain. Although sod's law dictates whatever we do it'll be wrong ...
> but
> I think explicit is always better as you end up with fewer surprises.

I disagree. When a new region appears with "China" in its name, there's a good chance that it'll be in mainland China. Unless they called it "South China Sea" but it seems unlikely politically -- and "Indo-China" has gone right out of fashion. Conversely, there is a region in China that isn't in mainland China and isn't using the Chinese endpoints, and they took care not to name it after China.

So the name "China" seems to be a good indicator, and "locations with China in the names use the mainland-Chinese endpoints, the rest use the international ones" is a rule that accommodates both the known cases and expected future cases. It's explicit, just not as fragile and verbose as specifying known endpoints. It gives us a chance of supporting a new location without code changes.

> 60 +// StorageAPI returns the base URL for the endpoint's storage API.
> 61 +func (endpoint APIEndpoint) StorageAPI() string {
>
> Why not a pointer receiver? (same for ManagementAPI)

Because it's the language's way of saying that you're not going to get your object modified by calling this. I don't care much about it either way.

> 62 + return prefixHost("blob", string(endpoint))
> 63 +}
>
> Given there's three types of storage, this should to be named
> BlobStorageAPI().

Fixed.

> Also, the storage URL returned here is still not complete, it would need the
> account prefixed.
> It might be worth encapsulating that functionality in this object so that all
> the URL
> manipulation is done in the same place. I'd have StorageAPI take an account
> name parameter.

We do need that, but I felt that was in a different layer of responsibility. We already had a method for it, which I kept in place (although I made it re-use prefixHost so it's very simple).

> 114 +func (*endpointsSuite)
> TestGetEndpointMakesGoodGuessesForUknownRegions(c *C) {
> 115 + c.Check(
> 116 + GetEndpoint("South San Marino Highlands"),
> 117 + Equals,
> 118 + GetEndpoint("West US"))
> 119 + c.Check(
> 120 + GetEndpoint("Central China West"),
> 121 + Equals,
> 122 + GetEndpoint("China East"))
> 123 +}
>
> I'm not sure I like this test at all. Its expected output is a function of the
> method being tested
> rather than something either constant or independent of it, and that's kinda
> nasty. It also feels
> fragile.
>
> I'd much rather you were explicit in testing expected output to be the
> required endpoint URLs.

This test verifies a specific relationship between the function's responses to different inputs. Not the actual responses themselves; those are covered by tests at the appropriate level and they're not relevant here. This is just a higher level of abstraction and I don't think that's a bad thi...

Read more...

Revision history for this message
Julian Edwards (julian-edwards) wrote :
Download full text (4.0 KiB)

On Tuesday 06 Aug 2013 08:02:26 you wrote:
> I disagree. When a new region appears with "China" in its name, there's a

[snip]

Ok I just thought I'd bring it up. I think we'll end up getting bitten one way or
another but hey ... SEP.

> > 60 +// StorageAPI returns the base URL for the endpoint's storage
> > API.
> > 61 +func (endpoint APIEndpoint) StorageAPI() string {
> >
> > Why not a pointer receiver? (same for ManagementAPI)
>
> Because it's the language's way of saying that you're not going to get your
> object modified by calling this. I don't care much about it either way.

Ok - it's just that we know that non-pointer receivers don't meet the interface etc
etc. Most of the rest of the code uses pointer receivers.

> > Also, the storage URL returned here is still not complete, it would need
> > the account prefixed.
> > It might be worth encapsulating that functionality in this object so that
> > all the URL
> > manipulation is done in the same place. I'd have StorageAPI take an
> > account name parameter.
>
> We do need that, but I felt that was in a different layer of responsibility.
> We already had a method for it, which I kept in place (although I made it
> re-use prefixHost so it's very simple).

I genuinely don't think it's a different layer of responsibility.

My reasoning is that each storage account is a different API endpoint. This code
says it returns an API endpoint, but it doesn't, it's not usable yet.

> This test verifies a specific relationship between the function's responses
> to different inputs. Not the actual responses themselves; those are
> covered by tests at the appropriate level and they're not relevant here.
> This is just a higher level of abstraction and I don't think that's a bad
> thing.
>
> Making the change you ask for would replace an explicit statement of the
> relationship with one that is implicit and easily lost in the unnecessary
> detail. But it also increases fragility. For example, we might at some
> point remove the trailing slashes after the URLs' hostnames. Completely
> arbitrary change, but simplestreams might force us to make it someday.
> That is completely outside the scope of this test: the test shouldn't care.
> But the way you're asking me to write it, it'd break this test for no
> reason. An engineer with a whole bunch of these failures on their hands
> would have to fix up the URLs in the tests on auto-pilot.
>
> On the other hand, imagine that the *relationship* that this test verifies
> breaks during development. The test would fail, because that's what it's
> for. But if I wrote it your way, the required change would be
> indistinguishable from the change required for the inconsequential change
> from the first example. What is the engineer's reasonable response? Fix
> up the URLs in the tests, on auto-pilot. At that point the test becomes
> meaningless -- because the relationship that's being tested was left
> implicit in the data instead of explicit, and because of the unnecessary
> fragility.

Ok we'll have to agree to disagree.

> > I don't like the "try to set this" stuff at all here. Let's just panic if
> > it's not set.
>
> Neither ...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'endpoints.go'
--- endpoints.go 1970-01-01 00:00:00 +0000
+++ endpoints.go 2013-08-06 08:00:44 +0000
@@ -0,0 +1,63 @@
1// Copyright 2013 Canonical Ltd. This software is licensed under the
2// GNU Lesser General Public License version 3 (see the file COPYING).
3
4package gwacl
5
6import (
7 "fmt"
8 "net/url"
9 "strings"
10)
11
12// APIEndpoint describes the base URL for accesing Windows Azure's APIs.
13//
14// Azure will have subdomains on this URL's domain, such as blob.<domain> for
15// storage, with further sub-domains for storage accounts; management.<domain>
16// for the management API; and possibly more such as queue.<domain>,
17// table.<domain>. APIEndpoint defines methods to obtain these URLs.
18type APIEndpoint string
19
20// GetEndpoint returns the API endpoint for the given location. This is
21// hard-coded, so some guesswork may be involved.
22func GetEndpoint(location string) APIEndpoint {
23 if strings.Contains(location, "China") {
24 // Mainland China is a special case. It has its own endpoint.
25 return "https://core.chinacloudapi.cn/"
26 }
27
28 // The rest of the world shares a single endpoint.
29 return "https://core.windows.net/"
30}
31
32// prefixHost prefixes the hostname part of a URL with a subdomain. For
33// example, prefixHost("foo", "http://example.com") becomes
34// "http://foo.example.com".
35//
36// The URL must be well-formed, and contain a hostname.
37func prefixHost(host, originalURL string) string {
38 parsedURL, err := url.Parse(originalURL)
39 if err != nil {
40 panic(fmt.Errorf("failed to parse URL %s - %v", originalURL, err))
41 }
42 if parsedURL.Host == "" {
43 panic(fmt.Errorf("no hostname in URL '%s'", originalURL))
44 }
45 // Escape manually. Strangely, turning a url.URL into a string does not
46 // do this for you.
47 parsedURL.Host = url.QueryEscape(host) + "." + parsedURL.Host
48 return parsedURL.String()
49}
50
51// ManagementAPI returns the URL for the endpoint's management API.
52func (endpoint APIEndpoint) ManagementAPI() string {
53 return prefixHost("management", string(endpoint))
54}
55
56// BlobStorageAPI returns the base URL for the endpoint's blob storage API.
57//
58// Actual storage API requests are made to subdomains of this URL. To address
59// a particular storage account, prefix it as a subdomain to the hostname
60// portion of this URL.
61func (endpoint APIEndpoint) BlobStorageAPI() string {
62 return prefixHost("blob", string(endpoint))
63}
064
=== added file 'endpoints_test.go'
--- endpoints_test.go 1970-01-01 00:00:00 +0000
+++ endpoints_test.go 2013-08-06 08:00:44 +0000
@@ -0,0 +1,115 @@
1// Copyright 2013 Canonical Ltd. This software is licensed under the
2// GNU Lesser General Public License version 3 (see the file COPYING).
3
4package gwacl
5
6import (
7 "fmt"
8 . "launchpad.net/gocheck"
9 "net/url"
10)
11
12type endpointsSuite struct{}
13
14var _ = Suite(&endpointsSuite{})
15
16func (*endpointsSuite) TestGetEndpointReturnsEndpointsForKnownRegions(c *C) {
17 internationalLocations := []string{
18 "West Europe",
19 "East Asia",
20 "East US 2",
21 "Southeast Asia",
22 "East US",
23 "Central US",
24 "West US",
25 "North Europe",
26 }
27 internationalEndpoint := APIEndpoint("https://core.windows.net/")
28
29 for _, location := range internationalLocations {
30 c.Check(GetEndpoint(location), Equals, internationalEndpoint)
31 }
32
33 // The mainland-China locations have a different endpoint.
34 // (Actually the East Asia data centre is said to be in Hong Kong, but it
35 // acts as international).
36 mainlandChinaLocations := []string{
37 "China East",
38 "China North",
39 }
40 mainlandChinaEndpoint := APIEndpoint("https://core.chinacloudapi.cn/")
41 for _, location := range mainlandChinaLocations {
42 c.Check(GetEndpoint(location), Equals, mainlandChinaEndpoint)
43 }
44}
45
46func (*endpointsSuite) TestGetEndpointMakesGoodGuessesForUknownRegions(c *C) {
47 c.Check(
48 GetEndpoint("South San Marino Highlands"),
49 Equals,
50 GetEndpoint("West US"))
51 c.Check(
52 GetEndpoint("Central China West"),
53 Equals,
54 GetEndpoint("China East"))
55}
56
57func (*endpointsSuite) TestPrefixHostPrefixesSubdomain(c *C) {
58 c.Check(
59 prefixHost("foo", "http://example.com"),
60 Equals,
61 "http://foo.example.com")
62}
63
64func (*endpointsSuite) TestPrefixHostPreservesOtherURLComponents(c *C) {
65 c.Check(
66 prefixHost("foo", "http://example.com/"),
67 Equals,
68 "http://foo.example.com/")
69 c.Check(
70 prefixHost("foo", "nntp://example.com"),
71 Equals,
72 "nntp://foo.example.com")
73 c.Check(
74 prefixHost("foo", "http://user@example.com"),
75 Equals,
76 "http://user@foo.example.com")
77 c.Check(
78 prefixHost("foo", "http://example.com:999"),
79 Equals,
80 "http://foo.example.com:999")
81 c.Check(
82 prefixHost("foo", "http://example.com/path"),
83 Equals,
84 "http://foo.example.com/path")
85}
86
87func (*endpointsSuite) TestPrefixHostEscapes(c *C) {
88 host := "5%=1/20?"
89 c.Check(
90 prefixHost(host, "http://example.com"),
91 Equals,
92 fmt.Sprintf("http://%s.example.com", url.QueryEscape(host)))
93}
94
95func (*endpointsSuite) TestManagementAPICombinesWithGetEndpoint(c *C) {
96 c.Check(
97 GetEndpoint("West US").ManagementAPI(),
98 Equals,
99 "https://management.core.windows.net/")
100 c.Check(
101 GetEndpoint("China East").ManagementAPI(),
102 Equals,
103 "https://management.core.chinacloudapi.cn/")
104}
105
106func (*endpointsSuite) TestBlobStorageAPICombinesWithGetEndpoint(c *C) {
107 c.Check(
108 GetEndpoint("West US").BlobStorageAPI(),
109 Equals,
110 "https://blob.core.windows.net/")
111 c.Check(
112 GetEndpoint("China East").BlobStorageAPI(),
113 Equals,
114 "https://blob.core.chinacloudapi.cn/")
115}
0116
=== modified file 'example/management/run.go'
--- example/management/run.go 2013-07-25 22:23:31 +0000
+++ example/management/run.go 2013-08-06 08:00:44 +0000
@@ -22,11 +22,13 @@
22var certFile string22var certFile string
23var subscriptionID string23var subscriptionID string
24var pause bool24var pause bool
25var location string
2526
26func getParams() error {27func getParams() error {
27 flag.StringVar(&certFile, "cert", "", "Name of your management certificate file (in PEM format).")28 flag.StringVar(&certFile, "cert", "", "Name of your management certificate file (in PEM format).")
28 flag.StringVar(&subscriptionID, "subscriptionid", "", "Your Azure subscription ID.")29 flag.StringVar(&subscriptionID, "subscriptionid", "", "Your Azure subscription ID.")
29 flag.BoolVar(&pause, "pause", false, "Wait for user input after the VM is brought up (useful for further testing)")30 flag.BoolVar(&pause, "pause", false, "Wait for user input after the VM is brought up (useful for further testing)")
31 flag.StringVar(&location, "location", "North Europe", "Azure cloud location, e.g. 'West US' or 'China East'")
3032
31 flag.Parse()33 flag.Parse()
3234
@@ -73,7 +75,7 @@
73 os.Exit(1)75 os.Exit(1)
74 }76 }
7577
76 api, err := gwacl.NewManagementAPI(subscriptionID, certFile)78 api, err := gwacl.NewManagementAPI(subscriptionID, certFile, location)
77 checkError(err)79 checkError(err)
7880
79 ExerciseHostedServicesAPI(api)81 ExerciseHostedServicesAPI(api)
8082
=== modified file 'management_base.go'
--- management_base.go 2013-07-25 22:02:41 +0000
+++ management_base.go 2013-08-06 08:00:44 +0000
@@ -34,8 +34,8 @@
3434
35// NewManagementAPI creates an object used to interact with Windows Azure's API.35// NewManagementAPI creates an object used to interact with Windows Azure's API.
36// http://msdn.microsoft.com/en-us/library/windowsazure/ff800682.aspx36// http://msdn.microsoft.com/en-us/library/windowsazure/ff800682.aspx
37func NewManagementAPI(subscriptionId string, certFile string) (*ManagementAPI, error) {37func NewManagementAPI(subscriptionId, certFile, location string) (*ManagementAPI, error) {
38 session, err := newX509Session(subscriptionId, certFile)38 session, err := newX509Session(subscriptionId, certFile, location)
39 if err != nil {39 if err != nil {
40 return nil, err40 return nil, err
41 }41 }
4242
=== modified file 'management_base_test.go'
--- management_base_test.go 2013-07-25 22:02:41 +0000
+++ management_base_test.go 2013-08-06 08:00:44 +0000
@@ -36,7 +36,7 @@
3636
37func makeAPI(c *C) *ManagementAPI {37func makeAPI(c *C) *ManagementAPI {
38 subscriptionId := "subscriptionId"38 subscriptionId := "subscriptionId"
39 api, err := NewManagementAPI(subscriptionId, "")39 api, err := NewManagementAPI(subscriptionId, "", "West US")
40 c.Assert(err, IsNil)40 c.Assert(err, IsNil)
41 // Polling is disabled by default.41 // Polling is disabled by default.
42 api.PollerInterval = 042 api.PollerInterval = 0
@@ -233,23 +233,23 @@
233 err := ioutil.WriteFile(certFile, []byte(testCert), 0600)233 err := ioutil.WriteFile(certFile, []byte(testCert), 0600)
234 c.Assert(err, IsNil)234 c.Assert(err, IsNil)
235235
236 api, err := NewManagementAPI(subscriptionId, certFile)236 api, err := NewManagementAPI(subscriptionId, certFile, "West US")
237237
238 c.Assert(err, IsNil)238 c.Assert(err, IsNil)
239 session, err := newX509Session(subscriptionId, certFile)239 session, err := newX509Session(subscriptionId, certFile, "West US")
240 c.Assert(api.session.subscriptionId, DeepEquals, session.subscriptionId)240 c.Assert(api.session.subscriptionId, DeepEquals, session.subscriptionId)
241 c.Assert(api.session.certFile, DeepEquals, session.certFile)241 c.Assert(api.session.certFile, DeepEquals, session.certFile)
242}242}
243243
244func (suite *managementBaseAPISuite) TestNewManagementAPISetsDefaultPollerInterval(c *C) {244func (suite *managementBaseAPISuite) TestNewManagementAPISetsDefaultPollerInterval(c *C) {
245 api, err := NewManagementAPI("subscriptionId", "")245 api, err := NewManagementAPI("subscriptionId", "", "West US")
246 c.Assert(err, IsNil)246 c.Assert(err, IsNil)
247247
248 c.Assert(api.PollerInterval, Equals, DefaultPollerInterval)248 c.Assert(api.PollerInterval, Equals, DefaultPollerInterval)
249}249}
250250
251func (suite *managementBaseAPISuite) TestNewManagementAPISetsDefaultPollerTimeout(c *C) {251func (suite *managementBaseAPISuite) TestNewManagementAPISetsDefaultPollerTimeout(c *C) {
252 api, err := NewManagementAPI("subscriptionId", "")252 api, err := NewManagementAPI("subscriptionId", "", "West US")
253 c.Assert(err, IsNil)253 c.Assert(err, IsNil)
254254
255 c.Assert(api.PollerTimeout, Equals, DefaultPollerTimeout)255 c.Assert(api.PollerTimeout, Equals, DefaultPollerTimeout)
@@ -394,7 +394,7 @@
394 descriptors, err := api.ListHostedServices()394 descriptors, err := api.ListHostedServices()
395395
396 c.Assert(err, IsNil)396 c.Assert(err, IsNil)
397 expectedURL := AZURE_URL + api.session.subscriptionId + "/services/hostedservices"397 expectedURL := defaultManagement + api.session.subscriptionId + "/services/hostedservices"
398 checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", nil, "GET")398 checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", nil, "GET")
399 c.Assert(descriptors[0].URL, Equals, url)399 c.Assert(descriptors[0].URL, Equals, url)
400}400}
@@ -424,7 +424,7 @@
424 err := api.UpdateHostedService(serviceName, update)424 err := api.UpdateHostedService(serviceName, update)
425425
426 c.Assert(err, IsNil)426 c.Assert(err, IsNil)
427 expectedURL := AZURE_URL + api.session.subscriptionId + "/services/hostedservices/" + serviceName427 expectedURL := defaultManagement + api.session.subscriptionId + "/services/hostedservices/" + serviceName
428 checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", requestPayload, "PUT")428 checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", requestPayload, "PUT")
429}429}
430430
@@ -435,7 +435,7 @@
435 } else {435 } else {
436 query = "embed-detail=false"436 query = "embed-detail=false"
437 }437 }
438 expectedURL := fmt.Sprintf("%s%s/services/hostedservices/%s?%s", AZURE_URL,438 expectedURL := fmt.Sprintf("%s%s/services/hostedservices/%s?%s", defaultManagement,
439 api.session.subscriptionId, serviceName, query)439 api.session.subscriptionId, serviceName, query)
440 checkRequest(c, httpRequest, expectedURL, "2012-03-01", nil, "GET")440 checkRequest(c, httpRequest, expectedURL, "2012-03-01", nil, "GET")
441}441}
@@ -543,7 +543,7 @@
543 createHostedService := NewCreateHostedServiceWithLocation("testName", "testLabel", "East US")543 createHostedService := NewCreateHostedServiceWithLocation("testName", "testLabel", "East US")
544 err := api.AddHostedService(createHostedService)544 err := api.AddHostedService(createHostedService)
545 c.Assert(err, IsNil)545 c.Assert(err, IsNil)
546 expectedURL := AZURE_URL + api.session.subscriptionId + "/services/hostedservices"546 expectedURL := defaultManagement + api.session.subscriptionId + "/services/hostedservices"
547 expectedPayload, err := marshalXML(createHostedService)547 expectedPayload, err := marshalXML(createHostedService)
548 c.Assert(err, IsNil)548 c.Assert(err, IsNil)
549 checkOneRequest(c, recordedRequests, expectedURL, "2012-03-01", expectedPayload, "POST")549 checkOneRequest(c, recordedRequests, expectedURL, "2012-03-01", expectedPayload, "POST")
@@ -573,7 +573,7 @@
573 err := api.CheckHostedServiceNameAvailability(serviceName)573 err := api.CheckHostedServiceNameAvailability(serviceName)
574574
575 c.Assert(err, IsNil)575 c.Assert(err, IsNil)
576 expectedURL := (AZURE_URL + api.session.subscriptionId +576 expectedURL := (defaultManagement + api.session.subscriptionId +
577 "/services/hostedservices/operations/isavailable/" + serviceName)577 "/services/hostedservices/operations/isavailable/" + serviceName)
578 checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", nil, "GET")578 checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", nil, "GET")
579}579}
@@ -595,7 +595,7 @@
595595
596 c.Assert(err, ErrorMatches, reason)596 c.Assert(err, ErrorMatches, reason)
597 c.Check(recordedRequests, HasLen, 1)597 c.Check(recordedRequests, HasLen, 1)
598 expectedURL := (AZURE_URL + api.session.subscriptionId +598 expectedURL := (defaultManagement + api.session.subscriptionId +
599 "/services/hostedservices/operations/isavailable/" + serviceName)599 "/services/hostedservices/operations/isavailable/" + serviceName)
600 checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", nil, "GET")600 checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", nil, "GET")
601}601}
@@ -637,7 +637,7 @@
637}637}
638638
639func assertDeleteHostedServiceRequest(c *C, api *ManagementAPI, serviceName string, httpRequest *X509Request) {639func assertDeleteHostedServiceRequest(c *C, api *ManagementAPI, serviceName string, httpRequest *X509Request) {
640 expectedURL := fmt.Sprintf("%s%s/services/hostedservices/%s", AZURE_URL,640 expectedURL := fmt.Sprintf("%s%s/services/hostedservices/%s", defaultManagement,
641 api.session.subscriptionId, serviceName)641 api.session.subscriptionId, serviceName)
642 checkRequest(c, httpRequest, expectedURL, "2010-10-28", nil, "DELETE")642 checkRequest(c, httpRequest, expectedURL, "2010-10-28", nil, "DELETE")
643}643}
@@ -669,7 +669,7 @@
669 err := api.AddDeployment(deployment, serviceName)669 err := api.AddDeployment(deployment, serviceName)
670670
671 c.Assert(err, IsNil)671 c.Assert(err, IsNil)
672 expectedURL := AZURE_URL + api.session.subscriptionId + "/services/hostedservices/" + serviceName + "/deployments"672 expectedURL := defaultManagement + api.session.subscriptionId + "/services/hostedservices/" + serviceName + "/deployments"
673 expectedPayload, err := marshalXML(deployment)673 expectedPayload, err := marshalXML(deployment)
674 c.Assert(err, IsNil)674 c.Assert(err, IsNil)
675 checkOneRequest(c, recordedRequests, expectedURL, "2012-03-01", expectedPayload, "POST")675 checkOneRequest(c, recordedRequests, expectedURL, "2012-03-01", expectedPayload, "POST")
@@ -677,7 +677,7 @@
677677
678func assertDeleteDeploymentRequest(c *C, api *ManagementAPI, hostedServiceName, deploymentName string, httpRequest *X509Request) {678func assertDeleteDeploymentRequest(c *C, api *ManagementAPI, hostedServiceName, deploymentName string, httpRequest *X509Request) {
679 expectedURL := fmt.Sprintf(679 expectedURL := fmt.Sprintf(
680 "%s%s/services/hostedservices/%s/deployments/%s", AZURE_URL,680 "%s%s/services/hostedservices/%s/deployments/%s", defaultManagement,
681 api.session.subscriptionId, hostedServiceName, deploymentName)681 api.session.subscriptionId, hostedServiceName, deploymentName)
682 checkRequest(c, httpRequest, expectedURL, "2012-03-01", nil, "DELETE")682 checkRequest(c, httpRequest, expectedURL, "2012-03-01", nil, "DELETE")
683}683}
@@ -776,7 +776,7 @@
776776
777func assertGetDeploymentRequest(c *C, api *ManagementAPI, request *GetDeploymentRequest, httpRequest *X509Request) {777func assertGetDeploymentRequest(c *C, api *ManagementAPI, request *GetDeploymentRequest, httpRequest *X509Request) {
778 expectedURL := fmt.Sprintf(778 expectedURL := fmt.Sprintf(
779 "%s%s/services/hostedservices/%s/deployments/%s", AZURE_URL,779 "%s%s/services/hostedservices/%s/deployments/%s", defaultManagement,
780 api.session.subscriptionId, request.ServiceName, request.DeploymentName)780 api.session.subscriptionId, request.ServiceName, request.DeploymentName)
781 checkRequest(c, httpRequest, expectedURL, "2012-03-01", nil, "GET")781 checkRequest(c, httpRequest, expectedURL, "2012-03-01", nil, "GET")
782}782}
@@ -818,7 +818,7 @@
818 err := api.AddStorageAccount(cssi)818 err := api.AddStorageAccount(cssi)
819 c.Assert(err, IsNil)819 c.Assert(err, IsNil)
820820
821 expectedURL := AZURE_URL + api.session.subscriptionId + "/services/storageservices"821 expectedURL := defaultManagement + api.session.subscriptionId + "/services/storageservices"
822 expectedPayload, err := marshalXML(cssi)822 expectedPayload, err := marshalXML(cssi)
823 c.Assert(err, IsNil)823 c.Assert(err, IsNil)
824 checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", expectedPayload, "POST")824 checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", expectedPayload, "POST")
@@ -873,7 +873,7 @@
873}873}
874874
875func assertDeleteDiskRequest(c *C, api *ManagementAPI, diskName string, httpRequest *X509Request) {875func assertDeleteDiskRequest(c *C, api *ManagementAPI, diskName string, httpRequest *X509Request) {
876 expectedURL := fmt.Sprintf("%s%s/services/disks/%s", AZURE_URL,876 expectedURL := fmt.Sprintf("%s%s/services/disks/%s", defaultManagement,
877 api.session.subscriptionId, diskName)877 api.session.subscriptionId, diskName)
878 checkRequest(c, httpRequest, expectedURL, "2012-08-01", nil, "DELETE")878 checkRequest(c, httpRequest, expectedURL, "2012-08-01", nil, "DELETE")
879}879}
@@ -930,7 +930,7 @@
930 err := api.performRoleOperation(serviceName, deploymentName, roleName, version, operation)930 err := api.performRoleOperation(serviceName, deploymentName, roleName, version, operation)
931931
932 c.Assert(err, IsNil)932 c.Assert(err, IsNil)
933 expectedURL := AZURE_URL + api.session.subscriptionId + "/services/hostedservices/" + serviceName + "/deployments/" + deploymentName + "/roleinstances/" + roleName + "/Operations"933 expectedURL := defaultManagement + api.session.subscriptionId + "/services/hostedservices/" + serviceName + "/deployments/" + deploymentName + "/roleinstances/" + roleName + "/Operations"
934 expectedPayload, err := marshalXML(operation)934 expectedPayload, err := marshalXML(operation)
935 c.Assert(err, IsNil)935 c.Assert(err, IsNil)
936 checkOneRequest(c, recordedRequests, expectedURL, version, expectedPayload, "POST")936 checkOneRequest(c, recordedRequests, expectedURL, version, expectedPayload, "POST")
@@ -942,7 +942,7 @@
942 request := &StartRoleRequest{"serviceName", "deploymentName", "roleName"}942 request := &StartRoleRequest{"serviceName", "deploymentName", "roleName"}
943 err := api.StartRole(request)943 err := api.StartRole(request)
944 c.Assert(err, IsNil)944 c.Assert(err, IsNil)
945 expectedURL := (AZURE_URL + api.session.subscriptionId + "/services/hostedservices/" +945 expectedURL := (defaultManagement + api.session.subscriptionId + "/services/hostedservices/" +
946 request.ServiceName + "/deployments/" + request.DeploymentName + "/roleinstances/" +946 request.ServiceName + "/deployments/" + request.DeploymentName + "/roleinstances/" +
947 request.RoleName + "/Operations")947 request.RoleName + "/Operations")
948 expectedPayload, err := marshalXML(startRoleOperation)948 expectedPayload, err := marshalXML(startRoleOperation)
@@ -956,7 +956,7 @@
956 request := &RestartRoleRequest{"serviceName", "deploymentName", "roleName"}956 request := &RestartRoleRequest{"serviceName", "deploymentName", "roleName"}
957 err := api.RestartRole(request)957 err := api.RestartRole(request)
958 c.Assert(err, IsNil)958 c.Assert(err, IsNil)
959 expectedURL := (AZURE_URL + api.session.subscriptionId + "/services/hostedservices/" +959 expectedURL := (defaultManagement + api.session.subscriptionId + "/services/hostedservices/" +
960 request.ServiceName + "/deployments/" + request.DeploymentName + "/roleinstances/" +960 request.ServiceName + "/deployments/" + request.DeploymentName + "/roleinstances/" +
961 request.RoleName + "/Operations")961 request.RoleName + "/Operations")
962 expectedPayload, err := marshalXML(restartRoleOperation)962 expectedPayload, err := marshalXML(restartRoleOperation)
@@ -967,7 +967,7 @@
967func assertShutdownRoleRequest(c *C, api *ManagementAPI, request *ShutdownRoleRequest, httpRequest *X509Request) {967func assertShutdownRoleRequest(c *C, api *ManagementAPI, request *ShutdownRoleRequest, httpRequest *X509Request) {
968 expectedURL := fmt.Sprintf(968 expectedURL := fmt.Sprintf(
969 "%s%s/services/hostedservices/%s/deployments/%s/roleinstances/%s/Operations",969 "%s%s/services/hostedservices/%s/deployments/%s/roleinstances/%s/Operations",
970 AZURE_URL, api.session.subscriptionId, request.ServiceName,970 defaultManagement, api.session.subscriptionId, request.ServiceName,
971 request.DeploymentName, request.RoleName)971 request.DeploymentName, request.RoleName)
972 expectedPayload, err := marshalXML(shutdownRoleOperation)972 expectedPayload, err := marshalXML(shutdownRoleOperation)
973 c.Assert(err, IsNil)973 c.Assert(err, IsNil)
@@ -985,7 +985,7 @@
985}985}
986986
987func assertGetRoleRequest(c *C, api *ManagementAPI, httpRequest *X509Request, serviceName, deploymentName, roleName string) {987func assertGetRoleRequest(c *C, api *ManagementAPI, httpRequest *X509Request, serviceName, deploymentName, roleName string) {
988 expectedURL := (AZURE_URL + api.session.subscriptionId +988 expectedURL := (defaultManagement + api.session.subscriptionId +
989 "/services/hostedservices/" +989 "/services/hostedservices/" +
990 serviceName + "/deployments/" + deploymentName + "/roles/" + roleName)990 serviceName + "/deployments/" + deploymentName + "/roles/" + roleName)
991 checkRequest(c, httpRequest, expectedURL, "2012-03-01", nil, "GET")991 checkRequest(c, httpRequest, expectedURL, "2012-03-01", nil, "GET")
@@ -1013,7 +1013,7 @@
1013}1013}
10141014
1015func assertUpdateRoleRequest(c *C, api *ManagementAPI, httpRequest *X509Request, serviceName, deploymentName, roleName, expectedXML string) {1015func assertUpdateRoleRequest(c *C, api *ManagementAPI, httpRequest *X509Request, serviceName, deploymentName, roleName, expectedXML string) {
1016 expectedURL := (AZURE_URL + api.session.subscriptionId +1016 expectedURL := (defaultManagement + api.session.subscriptionId +
1017 "/services/hostedservices/" +1017 "/services/hostedservices/" +
1018 serviceName + "/deployments/" + deploymentName + "/roles/" + roleName)1018 serviceName + "/deployments/" + deploymentName + "/roles/" + roleName)
1019 checkRequest(1019 checkRequest(
@@ -1100,7 +1100,7 @@
1100 err := api.CreateAffinityGroup(&request)1100 err := api.CreateAffinityGroup(&request)
1101 c.Assert(err, IsNil)1101 c.Assert(err, IsNil)
11021102
1103 expectedURL := AZURE_URL + api.session.subscriptionId + "/affinitygroups"1103 expectedURL := defaultManagement + api.session.subscriptionId + "/affinitygroups"
1104 expectedBody, _ := cag.Serialize()1104 expectedBody, _ := cag.Serialize()
1105 checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", []byte(expectedBody), "POST")1105 checkOneRequest(c, &recordedRequests, expectedURL, "2012-03-01", []byte(expectedBody), "POST")
1106}1106}
@@ -1120,7 +1120,7 @@
1120 err := api.UpdateAffinityGroup(&request)1120 err := api.UpdateAffinityGroup(&request)
1121 c.Assert(err, IsNil)1121 c.Assert(err, IsNil)
11221122
1123 expectedURL := (AZURE_URL + api.session.subscriptionId +1123 expectedURL := (defaultManagement + api.session.subscriptionId +
1124 "/affinitygroups/" + request.Name)1124 "/affinitygroups/" + request.Name)
1125 expectedBody, _ := uag.Serialize()1125 expectedBody, _ := uag.Serialize()
1126 checkOneRequest(c, &recordedRequests, expectedURL, "2011-02-25", []byte(expectedBody), "PUT")1126 checkOneRequest(c, &recordedRequests, expectedURL, "2011-02-25", []byte(expectedBody), "PUT")
@@ -1139,7 +1139,7 @@
1139 err := api.DeleteAffinityGroup(&request)1139 err := api.DeleteAffinityGroup(&request)
1140 c.Assert(err, IsNil)1140 c.Assert(err, IsNil)
11411141
1142 expectedURL := (AZURE_URL + api.session.subscriptionId +1142 expectedURL := (defaultManagement + api.session.subscriptionId +
1143 "/affinitygroups/" + request.Name)1143 "/affinitygroups/" + request.Name)
1144 checkOneRequest(c, &recordedRequests, expectedURL, "2011-02-25", nil, "DELETE")1144 checkOneRequest(c, &recordedRequests, expectedURL, "2011-02-25", nil, "DELETE")
1145}1145}
@@ -1206,7 +1206,7 @@
12061206
1207func assertGetNetworkConfigurationRequest(c *C, api *ManagementAPI, httpRequest *X509Request) {1207func assertGetNetworkConfigurationRequest(c *C, api *ManagementAPI, httpRequest *X509Request) {
1208 expectedURL := fmt.Sprintf(1208 expectedURL := fmt.Sprintf(
1209 "%s%s/services/networking/media", AZURE_URL,1209 "%s%s/services/networking/media", defaultManagement,
1210 api.session.subscriptionId)1210 api.session.subscriptionId)
1211 checkRequest(c, httpRequest, expectedURL, "2012-03-01", nil, "GET")1211 checkRequest(c, httpRequest, expectedURL, "2012-03-01", nil, "GET")
1212}1212}
@@ -1245,7 +1245,7 @@
12451245
1246func assertSetNetworkConfigurationRequest(c *C, api *ManagementAPI, body []byte, httpRequest *X509Request) {1246func assertSetNetworkConfigurationRequest(c *C, api *ManagementAPI, body []byte, httpRequest *X509Request) {
1247 expectedURL := fmt.Sprintf(1247 expectedURL := fmt.Sprintf(
1248 "%s%s/services/networking/media", AZURE_URL,1248 "%s%s/services/networking/media", defaultManagement,
1249 api.session.subscriptionId)1249 api.session.subscriptionId)
1250 checkRequest(c, httpRequest, expectedURL, "2012-03-01", body, "PUT")1250 checkRequest(c, httpRequest, expectedURL, "2012-03-01", body, "PUT")
1251 // Azure chokes when the content type is text/xml or similar.1251 // Azure chokes when the content type is text/xml or similar.
12521252
=== modified file 'management_test.go'
--- management_test.go 2013-08-05 12:50:48 +0000
+++ management_test.go 2013-08-06 08:00:44 +0000
@@ -87,7 +87,7 @@
87 c.Assert(record, Not(HasLen), 0)87 c.Assert(record, Not(HasLen), 0)
88 expectedURL := fmt.Sprintf(88 expectedURL := fmt.Sprintf(
89 "%ssubscriptionId/services/hostedservices/%s?embed-detail=true",89 "%ssubscriptionId/services/hostedservices/%s?embed-detail=true",
90 AZURE_URL, service.ServiceName)90 defaultManagement, service.ServiceName)
91 c.Check(record[0].URL, Equals, expectedURL)91 c.Check(record[0].URL, Equals, expectedURL)
92 c.Check(record[0].Method, Equals, "GET")92 c.Check(record[0].Method, Equals, "GET")
93}93}
9494
=== modified file 'poller_test.go'
--- poller_test.go 2013-07-19 16:11:18 +0000
+++ poller_test.go 2013-08-06 08:00:44 +0000
@@ -19,7 +19,7 @@
19func (suite *pollerSuite) makeAPI(c *C) *ManagementAPI {19func (suite *pollerSuite) makeAPI(c *C) *ManagementAPI {
20 subscriptionId := "subscriptionId"20 subscriptionId := "subscriptionId"
21 subscriptionId = subscriptionId21 subscriptionId = subscriptionId
22 api, err := NewManagementAPI(subscriptionId, "")22 api, err := NewManagementAPI(subscriptionId, "", "West US")
23 c.Assert(err, IsNil)23 c.Assert(err, IsNil)
24 return api24 return api
25}25}
@@ -119,7 +119,7 @@
119 _, err := poller.poll()119 _, err := poller.poll()
120120
121 c.Assert(err, IsNil)121 c.Assert(err, IsNil)
122 expectedURL := AZURE_URL + api.session.subscriptionId + "/operations/" + operationID122 expectedURL := defaultManagement + api.session.subscriptionId + "/operations/" + operationID
123 checkOneRequest(c, recordedRequests, expectedURL, "2009-10-01", nil, "GET")123 checkOneRequest(c, recordedRequests, expectedURL, "2009-10-01", nil, "GET")
124}124}
125125
@@ -210,7 +210,7 @@
210 c.Assert(err, IsNil)210 c.Assert(err, IsNil)
211 c.Assert(response, DeepEquals, secondResponse.response)211 c.Assert(response, DeepEquals, secondResponse.response)
212 operationPollerInstance := poller.(operationPoller)212 operationPollerInstance := poller.(operationPoller)
213 expectedURL := AZURE_URL + operationPollerInstance.api.session.subscriptionId + "/operations/" + operationID213 expectedURL := defaultManagement + operationPollerInstance.api.session.subscriptionId + "/operations/" + operationID
214 c.Assert(len(recordedRequests), Equals, 2)214 c.Assert(len(recordedRequests), Equals, 2)
215 checkRequest(c, recordedRequests[0], expectedURL, "2009-10-01", nil, "GET")215 checkRequest(c, recordedRequests[0], expectedURL, "2009-10-01", nil, "GET")
216 checkRequest(c, recordedRequests[1], expectedURL, "2009-10-01", nil, "GET")216 checkRequest(c, recordedRequests[1], expectedURL, "2009-10-01", nil, "GET")
217217
=== modified file 'storage_base.go'
--- storage_base.go 2013-08-05 10:53:20 +0000
+++ storage_base.go 2013-08-06 08:00:44 +0000
@@ -202,9 +202,22 @@
202// request to the storage services API. It also has an HTTP Client to allow202// request to the storage services API. It also has an HTTP Client to allow
203// overriding for custom behaviour, during testing for example.203// overriding for custom behaviour, during testing for example.
204type StorageContext struct {204type StorageContext struct {
205 // Account is a storage account name.
205 Account string206 Account string
206 // Access key: access will be anonymous if the key is the empty string.207
207 Key string208 // Key authenticates the storage account. Access will be anonymous if this
209 // is left empty.
210 Key string
211
212 // AzureEndpoint specifies a base service endpoint URL for the Azure APIs.
213 // If this is not set, it will default to the international endpoint which
214 // will not work in mainland China.
215 //
216 // Try to set this if at all possible. Use GetEndpoint() to obtain the
217 // endpoint associated with a given service location, e.g. "West US" or
218 // "North Europe" or "East China".
219 AzureEndpoint APIEndpoint
220
208 client *http.Client221 client *http.Client
209}222}
210223
@@ -321,12 +334,13 @@
321// getAccountURL returns the base URL for the context's storage account.334// getAccountURL returns the base URL for the context's storage account.
322// (The result ends in a slash.)335// (The result ends in a slash.)
323func (context *StorageContext) getAccountURL() string {336func (context *StorageContext) getAccountURL() string {
324 escapedAccount := url.QueryEscape(context.Account)337 endpoint := context.AzureEndpoint
325 // Use https. This does not suffer from the "no renegotiation" bug in338 if endpoint == "" {
326 // Go's implementation. It's optional, but it gets around spurious339 // No API endpoint specified. Default to the international one.
327 // authentication failures when working through a proxy that messes with340 // This will not work for mainland China.
328 // our requests instead of passing them on verbatim.341 endpoint = GetEndpoint("West US")
329 return fmt.Sprintf("https://%s.blob.core.windows.net/", escapedAccount)342 }
343 return prefixHost(context.Account, endpoint.BlobStorageAPI())
330}344}
331345
332// getContainerURL returns the URL for a given storage container.346// getContainerURL returns the URL for a given storage container.
333347
=== modified file 'storage_base_test.go'
--- storage_base_test.go 2013-08-05 10:53:20 +0000
+++ storage_base_test.go 2013-08-06 08:00:44 +0000
@@ -286,13 +286,35 @@
286 return MakeRandomString(3) + "?&" + MakeRandomString(3) + "$%"286 return MakeRandomString(3) + "?&" + MakeRandomString(3) + "$%"
287}287}
288288
289func (suite *TestStorageContext) TestGetAccountURL(c *C) {289func (suite *TestStorageContext) TestGetAccountURLCombinesAccountAndEndpoint(c *C) {
290 context := StorageContext{
291 Account: "myaccount",
292 AzureEndpoint: "http://example.com",
293 }
294 c.Check(
295 context.getAccountURL(),
296 Equals,
297 "http://myaccount.blob.example.com")
298}
299
300func (suite *TestStorageContext) TestGetAccountURLEscapesHostname(c *C) {
290 account := makeNastyURLUnfriendlyString()301 account := makeNastyURLUnfriendlyString()
291 context := StorageContext{Account: account}302 context := StorageContext{
292 c.Check(303 Account: account,
293 context.getAccountURL(),304 AzureEndpoint: "http://example.com",
294 Equals,305 }
295 "https://"+url.QueryEscape(account)+".blob.core.windows.net/")306 c.Check(
307 context.getAccountURL(),
308 Equals,
309 "http://"+url.QueryEscape(account)+".blob.example.com")
310}
311
312func (*TestStorageContext) TestGetAccountURLDefaultsToInternationalEndpoint(c *C) {
313 context := StorageContext{Account: "myaccount"}
314 c.Check(
315 context.getAccountURL(),
316 Equals,
317 "https://myaccount.blob.core.windows.net/")
296}318}
297319
298func (suite *TestStorageContext) TestGetContainerURL(c *C) {320func (suite *TestStorageContext) TestGetContainerURL(c *C) {
299321
=== modified file 'x509dispatcher_test.go'
--- x509dispatcher_test.go 2013-07-30 05:24:42 +0000
+++ x509dispatcher_test.go 2013-08-06 08:00:44 +0000
@@ -49,7 +49,7 @@
49 server := makeRecordingHTTPServer(httpRequests, http.StatusOK, nil, nil)49 server := makeRecordingHTTPServer(httpRequests, http.StatusOK, nil, nil)
50 defer server.Close()50 defer server.Close()
51 // No real certificate needed since we're testing on http, not https.51 // No real certificate needed since we're testing on http, not https.
52 session, err := newX509Session("subscriptionid", "")52 session, err := newX509Session("subscriptionid", "", "West US")
53 c.Assert(err, IsNil)53 c.Assert(err, IsNil)
54 path := "/foo/bar"54 path := "/foo/bar"
55 version := "test-version"55 version := "test-version"
@@ -74,7 +74,7 @@
74 server := makeRecordingHTTPServer(httpRequests, http.StatusOK, responseBody, nil)74 server := makeRecordingHTTPServer(httpRequests, http.StatusOK, responseBody, nil)
75 defer server.Close()75 defer server.Close()
76 // No real certificate needed since we're testing on http, not https.76 // No real certificate needed since we're testing on http, not https.
77 session, err := newX509Session("subscriptionid", "")77 session, err := newX509Session("subscriptionid", "", "West US")
78 c.Assert(err, IsNil)78 c.Assert(err, IsNil)
79 path := "/foo/bar"79 path := "/foo/bar"
80 version := "test-version"80 version := "test-version"
@@ -98,7 +98,7 @@
98 server := makeRecordingHTTPServer(httpRequests, http.StatusOK, nil, nil)98 server := makeRecordingHTTPServer(httpRequests, http.StatusOK, nil, nil)
99 defer server.Close()99 defer server.Close()
100 // No real certificate needed since we're testing on http, not https.100 // No real certificate needed since we're testing on http, not https.
101 session, err := newX509Session("subscriptionid", "")101 session, err := newX509Session("subscriptionid", "", "West US")
102 c.Assert(err, IsNil)102 c.Assert(err, IsNil)
103 path := "/foo/bar"103 path := "/foo/bar"
104 version := "test-version"104 version := "test-version"
@@ -122,7 +122,7 @@
122 server := makeRecordingHTTPServer(httpRequests, http.StatusOK, responseBody, nil)122 server := makeRecordingHTTPServer(httpRequests, http.StatusOK, responseBody, nil)
123 defer server.Close()123 defer server.Close()
124 // No real certificate needed since we're testing on http, not https.124 // No real certificate needed since we're testing on http, not https.
125 session, err := newX509Session("subscriptionid", "")125 session, err := newX509Session("subscriptionid", "", "West US")
126 c.Assert(err, IsNil)126 c.Assert(err, IsNil)
127 path := "/foo/bar"127 path := "/foo/bar"
128 version := "test-version"128 version := "test-version"
@@ -151,7 +151,7 @@
151 serveMux.HandleFunc("/", returnRequest)151 serveMux.HandleFunc("/", returnRequest)
152 server := httptest.NewServer(serveMux)152 server := httptest.NewServer(serveMux)
153 defer server.Close()153 defer server.Close()
154 session, err := newX509Session("subscriptionid", "")154 session, err := newX509Session("subscriptionid", "", "West US")
155 c.Assert(err, IsNil)155 c.Assert(err, IsNil)
156 path := "/foo/bar"156 path := "/foo/bar"
157 request := newX509RequestGET(server.URL+path, "testversion")157 request := newX509RequestGET(server.URL+path, "testversion")
158158
=== modified file 'x509session.go'
--- x509session.go 2013-07-22 12:53:27 +0000
+++ x509session.go 2013-08-06 08:00:44 +0000
@@ -15,13 +15,14 @@
15 subscriptionId string15 subscriptionId string
16 certFile string16 certFile string
17 client *http.Client17 client *http.Client
18 baseURL *url.URL
18}19}
1920
20// newX509Session creates and returns a new x509Session based on credentials21// newX509Session creates and returns a new x509Session based on credentials
21// and X509 certificate files.22// and X509 certificate files.
22// For testing purposes, certFile can be passed as the empty string and it23// For testing purposes, certFile can be passed as the empty string and it
23// will be ignored.24// will be ignored.
24func newX509Session(subscriptionId string, certFile string) (*x509Session, error) {25func newX509Session(subscriptionId, certFile, location string) (*x509Session, error) {
25 certs := []tls.Certificate{}26 certs := []tls.Certificate{}
26 if certFile != "" {27 if certFile != "" {
27 //28 //
@@ -39,16 +40,21 @@
39 },40 },
40 }41 }
4142
43 endpointURL := GetEndpoint(location).ManagementAPI()
44 baseURL, err := url.Parse(endpointURL)
45 if err != nil {
46 panic(fmt.Errorf("cannot parse Azure endpoint URL '%s' - %v", endpointURL, err))
47 }
48
42 session := x509Session{49 session := x509Session{
43 subscriptionId: subscriptionId,50 subscriptionId: subscriptionId,
44 certFile: certFile,51 certFile: certFile,
45 client: &client,52 client: &client,
53 baseURL: baseURL,
46 }54 }
47 return &session, nil55 return &session, nil
48}56}
4957
50var AZURE_URL = "https://management.core.windows.net/"
51
52// composeURL puts together a URL for an item on the Azure API based on58// composeURL puts together a URL for an item on the Azure API based on
53// the starting point used by the session, and a given relative path from59// the starting point used by the session, and a given relative path from
54// there.60// there.
@@ -56,16 +62,12 @@
56 if strings.HasPrefix(path, "/") {62 if strings.HasPrefix(path, "/") {
57 panic(fmt.Errorf("got absolute API path '%s' instead of relative one", path))63 panic(fmt.Errorf("got absolute API path '%s' instead of relative one", path))
58 }64 }
59 azureURL, err := url.Parse(AZURE_URL)
60 if err != nil {
61 panic(err)
62 }
63 escapedID := url.QueryEscape(session.subscriptionId)65 escapedID := url.QueryEscape(session.subscriptionId)
64 pathURL, err := url.Parse(escapedID + "/" + path)66 pathURL, err := url.Parse(escapedID + "/" + path)
65 if err != nil {67 if err != nil {
66 panic(err)68 panic(err)
67 }69 }
68 return azureURL.ResolveReference(pathURL).String()70 return session.baseURL.ResolveReference(pathURL).String()
69}71}
7072
71// _X509Dispatcher is the function used to dispatch requests. We call the73// _X509Dispatcher is the function used to dispatch requests. We call the
7274
=== modified file 'x509session_test.go'
--- x509session_test.go 2013-07-19 16:11:18 +0000
+++ x509session_test.go 2013-08-06 08:00:44 +0000
@@ -17,6 +17,10 @@
17 "time"17 "time"
18)18)
1919
20// defaultManagement is the international management API for Azure.
21// (Mainland China gets a different URL).
22const defaultManagement = "https://management.core.windows.net/"
23
20// x509DispatcherFixture records the current x509 dispatcher before a test,24// x509DispatcherFixture records the current x509 dispatcher before a test,
21// and restores it after. This gives your test the freedom to replace the25// and restores it after. This gives your test the freedom to replace the
22// dispatcher with test doubles, using any of the rig*Dispatcher functions.26// dispatcher with test doubles, using any of the rig*Dispatcher functions.
@@ -104,20 +108,22 @@
104 return certFile, keyFile108 return certFile, keyFile
105}109}
106110
107func (suite *x509SessionSuite) TestNewX509SessionCreation(c *C) {111func (suite *x509SessionSuite) TestNewX509Session(c *C) {
108 _, err := newX509Session("subscriptionid", "")112 session, err := newX509Session("subscriptionid", "", "China East")
109 c.Assert(err, IsNil)113 c.Assert(err, IsNil)
114 c.Assert(session.baseURL, NotNil)
115 c.Check(session.baseURL.String(), Equals, GetEndpoint("China East").ManagementAPI())
110}116}
111117
112func (suite *x509SessionSuite) TestComposeURLComposesURLWithRelativePath(c *C) {118func (suite *x509SessionSuite) TestComposeURLComposesURLWithRelativePath(c *C) {
113 const subscriptionID = "subscriptionid"119 const subscriptionID = "subscriptionid"
114 const path = "foo/bar"120 const path = "foo/bar"
115 session, err := newX509Session(subscriptionID, "")121 session, err := newX509Session(subscriptionID, "", "West US")
116 c.Assert(err, IsNil)122 c.Assert(err, IsNil)
117123
118 url := session.composeURL(path)124 url := session.composeURL(path)
119125
120 c.Check(url, Matches, AZURE_URL+subscriptionID+"/"+path)126 c.Check(url, Matches, defaultManagement+subscriptionID+"/"+path)
121}127}
122128
123func (suite *x509SessionSuite) TestComposeURLRejectsAbsolutePath(c *C) {129func (suite *x509SessionSuite) TestComposeURLRejectsAbsolutePath(c *C) {
@@ -126,7 +132,7 @@
126 c.Assert(err, NotNil)132 c.Assert(err, NotNil)
127 c.Check(err, ErrorMatches, ".*absolute.*path.*")133 c.Check(err, ErrorMatches, ".*absolute.*path.*")
128 }()134 }()
129 session, err := newX509Session("subscriptionid", "")135 session, err := newX509Session("subscriptionid", "", "West US")
130 c.Assert(err, IsNil)136 c.Assert(err, IsNil)
131137
132 // This panics because we're passing an absolute path.138 // This panics because we're passing an absolute path.
@@ -136,7 +142,7 @@
136func (suite *x509SessionSuite) TestGetServerErrorProducesServerError(c *C) {142func (suite *x509SessionSuite) TestGetServerErrorProducesServerError(c *C) {
137 msg := "huhwhat"143 msg := "huhwhat"
138 status := http.StatusNotFound144 status := http.StatusNotFound
139 session, err := newX509Session("subscriptionid", "")145 session, err := newX509Session("subscriptionid", "", "West US")
140 c.Assert(err, IsNil)146 c.Assert(err, IsNil)
141147
142 err = session.getServerError(status, []byte{}, msg)148 err = session.getServerError(status, []byte{}, msg)
@@ -152,7 +158,7 @@
152 http.StatusOK,158 http.StatusOK,
153 http.StatusNoContent,159 http.StatusNoContent,
154 }160 }
155 session, err := newX509Session("subscriptionid", "")161 session, err := newX509Session("subscriptionid", "", "West US")
156 c.Assert(err, IsNil)162 c.Assert(err, IsNil)
157163
158 for _, status := range goodCodes {164 for _, status := range goodCodes {
@@ -170,7 +176,7 @@
170 http.StatusInternalServerError,176 http.StatusInternalServerError,
171 http.StatusNotImplemented,177 http.StatusNotImplemented,
172 }178 }
173 session, err := newX509Session("subscriptionid", "")179 session, err := newX509Session("subscriptionid", "", "West US")
174 c.Assert(err, IsNil)180 c.Assert(err, IsNil)
175181
176 for _, status := range badCodes {182 for _, status := range badCodes {
@@ -181,7 +187,7 @@
181func (suite *x509SessionSuite) TestGetIssuesRequest(c *C) {187func (suite *x509SessionSuite) TestGetIssuesRequest(c *C) {
182 subscriptionID := "subscriptionID"188 subscriptionID := "subscriptionID"
183 uri := "resource"189 uri := "resource"
184 session, err := newX509Session(subscriptionID, "")190 session, err := newX509Session(subscriptionID, "", "West US")
185 c.Assert(err, IsNil)191 c.Assert(err, IsNil)
186 // Record incoming requests, and have them return a given reply.192 // Record incoming requests, and have them return a given reply.
187 fixedResponse := x509Response{193 fixedResponse := x509Response{
@@ -198,14 +204,14 @@
198204
199 c.Assert(len(recordedRequests), Equals, 1)205 c.Assert(len(recordedRequests), Equals, 1)
200 request := recordedRequests[0]206 request := recordedRequests[0]
201 c.Check(request.URL, Equals, AZURE_URL+subscriptionID+"/"+uri)207 c.Check(request.URL, Equals, defaultManagement+subscriptionID+"/"+uri)
202 c.Check(request.Method, Equals, "GET")208 c.Check(request.Method, Equals, "GET")
203 c.Check(request.APIVersion, Equals, version)209 c.Check(request.APIVersion, Equals, version)
204 c.Check(*receivedResponse, DeepEquals, fixedResponse)210 c.Check(*receivedResponse, DeepEquals, fixedResponse)
205}211}
206212
207func (suite *x509SessionSuite) TestGetReportsClientSideError(c *C) {213func (suite *x509SessionSuite) TestGetReportsClientSideError(c *C) {
208 session, err := newX509Session("subscriptionid", "")214 session, err := newX509Session("subscriptionid", "", "West US")
209 msg := "could not dispatch request"215 msg := "could not dispatch request"
210 rigFailingDispatcher(fmt.Errorf(msg))216 rigFailingDispatcher(fmt.Errorf(msg))
211217
@@ -217,7 +223,7 @@
217}223}
218224
219func (suite *x509SessionSuite) TestGetReportsServerSideError(c *C) {225func (suite *x509SessionSuite) TestGetReportsServerSideError(c *C) {
220 session, err := newX509Session("subscriptionid", "")226 session, err := newX509Session("subscriptionid", "", "West US")
221 fixedResponse := x509Response{227 fixedResponse := x509Response{
222 StatusCode: http.StatusForbidden,228 StatusCode: http.StatusForbidden,
223 Body: []byte("Body"),229 Body: []byte("Body"),
@@ -238,7 +244,7 @@
238 version := "test-version"244 version := "test-version"
239 requestBody := []byte("Request body")245 requestBody := []byte("Request body")
240 requestContentType := "bogusContentType"246 requestContentType := "bogusContentType"
241 session, err := newX509Session(subscriptionID, "")247 session, err := newX509Session(subscriptionID, "", "West US")
242 c.Assert(err, IsNil)248 c.Assert(err, IsNil)
243 // Record incoming requests, and have them return a given reply.249 // Record incoming requests, and have them return a given reply.
244 fixedResponse := x509Response{250 fixedResponse := x509Response{
@@ -254,7 +260,7 @@
254260
255 c.Assert(len(recordedRequests), Equals, 1)261 c.Assert(len(recordedRequests), Equals, 1)
256 request := recordedRequests[0]262 request := recordedRequests[0]
257 c.Check(request.URL, Equals, AZURE_URL+subscriptionID+"/"+uri)263 c.Check(request.URL, Equals, defaultManagement+subscriptionID+"/"+uri)
258 c.Check(request.Method, Equals, "POST")264 c.Check(request.Method, Equals, "POST")
259 c.Check(request.APIVersion, Equals, version)265 c.Check(request.APIVersion, Equals, version)
260 c.Check(request.ContentType, Equals, requestContentType)266 c.Check(request.ContentType, Equals, requestContentType)
@@ -263,7 +269,7 @@
263}269}
264270
265func (suite *x509SessionSuite) TestPostReportsClientSideError(c *C) {271func (suite *x509SessionSuite) TestPostReportsClientSideError(c *C) {
266 session, err := newX509Session("subscriptionid", "")272 session, err := newX509Session("subscriptionid", "", "West US")
267 msg := "could not dispatch request"273 msg := "could not dispatch request"
268 rigFailingDispatcher(fmt.Errorf(msg))274 rigFailingDispatcher(fmt.Errorf(msg))
269275
@@ -275,7 +281,7 @@
275}281}
276282
277func (suite *x509SessionSuite) TestPostReportsServerSideError(c *C) {283func (suite *x509SessionSuite) TestPostReportsServerSideError(c *C) {
278 session, err := newX509Session("subscriptionid", "")284 session, err := newX509Session("subscriptionid", "", "West US")
279 fixedResponse := x509Response{285 fixedResponse := x509Response{
280 StatusCode: http.StatusForbidden,286 StatusCode: http.StatusForbidden,
281 Body: []byte("Body"),287 Body: []byte("Body"),
@@ -294,7 +300,7 @@
294 subscriptionID := "subscriptionID"300 subscriptionID := "subscriptionID"
295 uri := "resource"301 uri := "resource"
296 version := "test-version"302 version := "test-version"
297 session, err := newX509Session(subscriptionID, "")303 session, err := newX509Session(subscriptionID, "", "West US")
298 c.Assert(err, IsNil)304 c.Assert(err, IsNil)
299 // Record incoming requests, and have them return a given reply.305 // Record incoming requests, and have them return a given reply.
300 fixedResponse := x509Response{StatusCode: http.StatusOK}306 fixedResponse := x509Response{StatusCode: http.StatusOK}
@@ -308,7 +314,7 @@
308 c.Check(*response, DeepEquals, fixedResponse)314 c.Check(*response, DeepEquals, fixedResponse)
309 c.Assert(len(recordedRequests), Equals, 1)315 c.Assert(len(recordedRequests), Equals, 1)
310 request := recordedRequests[0]316 request := recordedRequests[0]
311 c.Check(request.URL, Equals, AZURE_URL+subscriptionID+"/"+uri)317 c.Check(request.URL, Equals, defaultManagement+subscriptionID+"/"+uri)
312 c.Check(request.Method, Equals, "DELETE")318 c.Check(request.Method, Equals, "DELETE")
313 c.Check(request.APIVersion, Equals, version)319 c.Check(request.APIVersion, Equals, version)
314}320}
@@ -318,7 +324,7 @@
318 uri := "resource"324 uri := "resource"
319 version := "test-version"325 version := "test-version"
320 requestBody := []byte("Request body")326 requestBody := []byte("Request body")
321 session, err := newX509Session(subscriptionID, "")327 session, err := newX509Session(subscriptionID, "", "West US")
322 c.Assert(err, IsNil)328 c.Assert(err, IsNil)
323 // Record incoming requests, and have them return a given reply.329 // Record incoming requests, and have them return a given reply.
324 fixedResponse := x509Response{330 fixedResponse := x509Response{
@@ -333,7 +339,7 @@
333339
334 c.Assert(len(recordedRequests), Equals, 1)340 c.Assert(len(recordedRequests), Equals, 1)
335 request := recordedRequests[0]341 request := recordedRequests[0]
336 c.Check(request.URL, Equals, AZURE_URL+subscriptionID+"/"+uri)342 c.Check(request.URL, Equals, defaultManagement+subscriptionID+"/"+uri)
337 c.Check(request.Method, Equals, "PUT")343 c.Check(request.Method, Equals, "PUT")
338 c.Check(request.APIVersion, Equals, version)344 c.Check(request.APIVersion, Equals, version)
339 c.Check(request.Payload, DeepEquals, requestBody)345 c.Check(request.Payload, DeepEquals, requestBody)

Subscribers

People subscribed via source and target branches