Merge lp:~julian-edwards/gwacl/affinity-group into lp:gwacl
- affinity-group
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Julian Edwards |
Approved revision: | 180 |
Merged at revision: | 171 |
Proposed branch: | lp:~julian-edwards/gwacl/affinity-group |
Merge into: | lp:gwacl |
Diff against target: |
301 lines (+240/-0) 5 files modified
example/management/run.go (+16/-0) management_base.go (+56/-0) management_base_test.go (+59/-0) xmlobjects.go (+48/-0) xmlobjects_test.go (+61/-0) |
To merge this branch: | bzr merge lp:~julian-edwards/gwacl/affinity-group |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Raphaël Badin (community) | Approve | ||
Review via email: mp+173855@code.launchpad.net |
Commit message
Add CreateAffinityG
Description of the change
It turns out that this is needed by vnets, so add all the bumf for AffinityGroup creation, update and deletion.
Julian Edwards (julian-edwards) wrote : | # |
Thank you for the review.
On 10/07/13 18:16, Raphaël Badin wrote:
> Review: Approve
>
> Looks good!
>
> [0]
>
> 59 + var err error
> 60 + url := "affinitygroups/" + request.Name
>
> and
>
> 78 + var err error
> 79 + url := "affinitygroups/" + request.Name
>
> You need to use checkPathCompon
Gnargh, I forgot. Again.
There has to be a better way of dealing with this.
>
> [1]
>
> 279 +}
> 280 func (suite *xmlSuite) TestDeployment(c *C) {
>
> Missing an empty line here I think… did you run 'make format'?
I did - maybe it's a merge error...
>
> [2]
>
> You might want to do this in another branch or in here since it's really tiny but anyway, I'm mentioning it here so that we don't forget: NewCreateHosted
Separate branch, yes :)
>
> [3]
>
> 36 +type CreateAffinityG
> 37 + CreateAffinityGroup *CreateAffinity
> 38 +}
>
> Not sure if it's really useful but I guess it's more coherent with the other calls to have this intermediate CreateAffinityG
Consistency always wins. Besides, we may want to add more parameters
later as previously discussed.
>
> [4]
>
> 9 + affinityGroupName := gwacl.MakeRando
>
> Why did you use MakeRandomHostn
Because it does exactly what I need. We can rename it as necessary I
suppose.
>
> [5]
>
> 165 +//
> 166 +type CreateAffinityGroup struct {
>
> Maybe include the link to the API documentation in the "docstring"…? Here and in a couple of other places.
>
Yes - despite me telling others to do the same thing, and mostly
remembering to do so myself, I always miss one!
Cheers.
Gavin Panella (allenap) wrote : | # |
> [2]
>
> You might want to do this in another branch or in here since it's
> really tiny but anyway, I'm mentioning it here so that we don't
> forget: NewCreateHosted
> take an affinity group name (AffinityGroup is already a field in the
> CreateHostedService struct). Then we need to update the call to
> NewCreateHosted
> take the name of the newly created affinity group. In this branch
> as it is now, you're creating the affinity group in
> example/
> linked to it.
Perhaps we ought to have a NewCreateHosted
struct so that we can pass optional arguments into the constructor?
</black-humour>
I'm coming to the opinion that the NewBlah() pattern for constructors
is actually an antipattern. Calling conventions for Go's functions
have an impedance mismatch with constructing a struct directly, so it
always feels like hammering a square peg into the proverbial.
I'm wondering if something like the following might be better:
type Thing struct {
...
}
func (t *Thing) Init() *Thing {
// Initialise, e.g. set default fields if they're nil.
...
return t
}
var thing *Thing = &Thing{...}.Init()
This still supports multiple constructors, any of which /could/ also
take args. The struct itself has to be exported for use by other
packages, which may be an undesirable side-effect in some cases.
Julian Edwards (julian-edwards) wrote : | # |
Attempt to merge into lp:gwacl failed due to conflicts:
text conflict in management_base.go
text conflict in management_
text conflict in xmlobjects_test.go
Gavin Panella (allenap) wrote : | # |
> [2]
>
> You might want to do this in another branch or in here since it's
> really tiny but anyway, I'm mentioning it here so that we don't
> forget: NewCreateHosted
> take an affinity group name (AffinityGroup is already a field in the
> CreateHostedService struct). Then we need to update the call to
> NewCreateHosted
> take the name of the newly created affinity group. In this branch
> as it is now, you're creating the affinity group in
> example/
> linked to it.
Perhaps we ought to have a NewCreateHosted
struct so that we can pass optional arguments into the constructor?
</black-humour>
I'm coming to the opinion that the NewBlah() pattern for constructors
is actually an antipattern. Calling conventions for Go's functions
have an impedance mismatch with constructing a struct directly, so it
always feels like hammering a square peg into the proverbial.
I'm wondering if something like the following might be better:
type Thing struct {
...
}
func (t *Thing) Init() *Thing {
// Initialise, e.g. set default fields if they're nil.
...
return t
}
var thing *Thing = &Thing{...}.Init()
This still supports multiple constructors, any of which /could/ also
take args. The struct itself has to be exported for use by other
packages, which may be an undesirable side-effect in some cases.
Julian Edwards (julian-edwards) wrote : | # |
I too have felt uneasy about some of these "constructors" as it's just
as easy to set the fields directly.
The only time it's particularly useful is when you have the need for a
non-Go default value (like our XLMNS).
Preview Diff
1 | === modified file 'example/management/run.go' | |||
2 | --- example/management/run.go 2013-07-09 20:17:57 +0000 | |||
3 | +++ example/management/run.go 2013-07-10 09:03:28 +0000 | |||
4 | @@ -87,6 +87,7 @@ | |||
5 | 87 | } | 87 | } |
6 | 88 | 88 | ||
7 | 89 | func ExerciseHostedServicesAPI(api *gwacl.ManagementAPI) { | 89 | func ExerciseHostedServicesAPI(api *gwacl.ManagementAPI) { |
8 | 90 | var err error | ||
9 | 90 | networkConfig, err := api.GetNetworkConfiguration() | 91 | networkConfig, err := api.GetNetworkConfiguration() |
10 | 91 | checkError(err) | 92 | checkError(err) |
11 | 92 | if networkConfig == nil { | 93 | if networkConfig == nil { |
12 | @@ -100,6 +101,21 @@ | |||
13 | 100 | location := "West US" | 101 | location := "West US" |
14 | 101 | release := "13.04" | 102 | release := "13.04" |
15 | 102 | 103 | ||
16 | 104 | affinityGroupName := gwacl.MakeRandomHostname("affinitygroup") | ||
17 | 105 | fmt.Printf("Creating an affinity group...") | ||
18 | 106 | cag := gwacl.NewCreateAffinityGroup(affinityGroupName, "affinity-label", "affinity-description", location) | ||
19 | 107 | err = api.CreateAffinityGroup(&gwacl.CreateAffinityGroupRequest{ | ||
20 | 108 | CreateAffinityGroup: cag}) | ||
21 | 109 | checkError(err) | ||
22 | 110 | fmt.Println("created %s", affinityGroupName) | ||
23 | 111 | |||
24 | 112 | defer func() { | ||
25 | 113 | fmt.Println("Deleting affinity group %s", affinityGroupName) | ||
26 | 114 | api.DeleteAffinityGroup(&gwacl.DeleteAffinityGroupRequest{ | ||
27 | 115 | Name: affinityGroupName}) | ||
28 | 116 | fmt.Println("Done deleting affinity group %s", affinityGroupName) | ||
29 | 117 | }() | ||
30 | 118 | |||
31 | 103 | fmt.Printf("Getting OS Image for release '%s' and location '%s'...\n", release, location) | 119 | fmt.Printf("Getting OS Image for release '%s' and location '%s'...\n", release, location) |
32 | 104 | images, err := api.ListOSImages() | 120 | images, err := api.ListOSImages() |
33 | 105 | checkError(err) | 121 | checkError(err) |
34 | 106 | 122 | ||
35 | === modified file 'management_base.go' | |||
36 | --- management_base.go 2013-07-09 20:11:22 +0000 | |||
37 | +++ management_base.go 2013-07-10 09:03:28 +0000 | |||
38 | @@ -358,6 +358,62 @@ | |||
39 | 358 | return &role, nil | 358 | return &role, nil |
40 | 359 | } | 359 | } |
41 | 360 | 360 | ||
42 | 361 | type CreateAffinityGroupRequest struct { | ||
43 | 362 | CreateAffinityGroup *CreateAffinityGroup | ||
44 | 363 | } | ||
45 | 364 | |||
46 | 365 | // CreateAffinityGroup sends a request to make a new affinity group. | ||
47 | 366 | func (api *ManagementAPI) CreateAffinityGroup(request *CreateAffinityGroupRequest) error { | ||
48 | 367 | var err error | ||
49 | 368 | url := "affinitygroups" | ||
50 | 369 | body, err := request.CreateAffinityGroup.Serialize() | ||
51 | 370 | if err != nil { | ||
52 | 371 | return err | ||
53 | 372 | } | ||
54 | 373 | response, err := api.session.post(url, []byte(body), "application/xml") | ||
55 | 374 | return api.blockUntilCompleted(response) | ||
56 | 375 | } | ||
57 | 376 | |||
58 | 377 | type UpdateAffinityGroupRequest struct { | ||
59 | 378 | Name string | ||
60 | 379 | UpdateAffinityGroup *UpdateAffinityGroup | ||
61 | 380 | } | ||
62 | 381 | |||
63 | 382 | // UpdateAffinityGroup sends a request to update the named affinity group. | ||
64 | 383 | func (api *ManagementAPI) UpdateAffinityGroup(request *UpdateAffinityGroupRequest) error { | ||
65 | 384 | var err error | ||
66 | 385 | checkPathComponents(request.Name) | ||
67 | 386 | url := "affinitygroups/" + request.Name | ||
68 | 387 | body, err := request.UpdateAffinityGroup.Serialize() | ||
69 | 388 | if err != nil { | ||
70 | 389 | return err | ||
71 | 390 | } | ||
72 | 391 | response, err := api.session.put(url, []byte(body)) | ||
73 | 392 | if err != nil { | ||
74 | 393 | return err | ||
75 | 394 | } | ||
76 | 395 | return api.blockUntilCompleted(response) | ||
77 | 396 | } | ||
78 | 397 | |||
79 | 398 | type DeleteAffinityGroupRequest struct { | ||
80 | 399 | Name string | ||
81 | 400 | } | ||
82 | 401 | |||
83 | 402 | // DeleteAffinityGroup requests a deletion of the named affinity group. | ||
84 | 403 | func (api *ManagementAPI) DeleteAffinityGroup(request *DeleteAffinityGroupRequest) error { | ||
85 | 404 | var err error | ||
86 | 405 | checkPathComponents(request.Name) | ||
87 | 406 | url := "affinitygroups/" + request.Name | ||
88 | 407 | if err != nil { | ||
89 | 408 | return err | ||
90 | 409 | } | ||
91 | 410 | response, err := api.session.delete(url) | ||
92 | 411 | if err != nil { | ||
93 | 412 | return err | ||
94 | 413 | } | ||
95 | 414 | return api.blockUntilCompleted(response) | ||
96 | 415 | } | ||
97 | 416 | |||
98 | 361 | // GetNetworkConfiguration gets the network configuration for this | 417 | // GetNetworkConfiguration gets the network configuration for this |
99 | 362 | // subscription. If there is no network configuration the configuration will | 418 | // subscription. If there is no network configuration the configuration will |
100 | 363 | // be nil. | 419 | // be nil. |
101 | 364 | 420 | ||
102 | === modified file 'management_base_test.go' | |||
103 | --- management_base_test.go 2013-07-09 20:11:22 +0000 | |||
104 | +++ management_base_test.go 2013-07-10 09:03:28 +0000 | |||
105 | @@ -848,6 +848,65 @@ | |||
106 | 848 | c.Check(role.RoleName, Equals, "rolename") | 848 | c.Check(role.RoleName, Equals, "rolename") |
107 | 849 | } | 849 | } |
108 | 850 | 850 | ||
109 | 851 | func (suite *managementBaseAPISuite) TestCreateAffinityGroup(c *C) { | ||
110 | 852 | api := makeAPI(c) | ||
111 | 853 | cag := NewCreateAffinityGroup( | ||
112 | 854 | "name", "label", "description", "location") | ||
113 | 855 | request := CreateAffinityGroupRequest{ | ||
114 | 856 | CreateAffinityGroup: cag} | ||
115 | 857 | fixedResponse := x509Response{ | ||
116 | 858 | StatusCode: http.StatusCreated} | ||
117 | 859 | rigFixedResponseDispatcher(&fixedResponse) | ||
118 | 860 | recordedRequests := make([]*X509Request, 0) | ||
119 | 861 | rigRecordingDispatcher(&recordedRequests) | ||
120 | 862 | |||
121 | 863 | err := api.CreateAffinityGroup(&request) | ||
122 | 864 | c.Assert(err, IsNil) | ||
123 | 865 | |||
124 | 866 | expectedURL := AZURE_URL + api.session.subscriptionId + "/affinitygroups" | ||
125 | 867 | expectedBody, _ := cag.Serialize() | ||
126 | 868 | checkOneRequest(c, &recordedRequests, expectedURL, []byte(expectedBody), "POST") | ||
127 | 869 | } | ||
128 | 870 | |||
129 | 871 | func (suite *managementBaseAPISuite) TestUpdateAffinityGroup(c *C) { | ||
130 | 872 | api := makeAPI(c) | ||
131 | 873 | uag := NewUpdateAffinityGroup("label", "description") | ||
132 | 874 | request := UpdateAffinityGroupRequest{ | ||
133 | 875 | Name: "groupname", | ||
134 | 876 | UpdateAffinityGroup: uag} | ||
135 | 877 | fixedResponse := x509Response{ | ||
136 | 878 | StatusCode: http.StatusCreated} | ||
137 | 879 | rigFixedResponseDispatcher(&fixedResponse) | ||
138 | 880 | recordedRequests := make([]*X509Request, 0) | ||
139 | 881 | rigRecordingDispatcher(&recordedRequests) | ||
140 | 882 | |||
141 | 883 | err := api.UpdateAffinityGroup(&request) | ||
142 | 884 | c.Assert(err, IsNil) | ||
143 | 885 | |||
144 | 886 | expectedURL := (AZURE_URL + api.session.subscriptionId + | ||
145 | 887 | "/affinitygroups/" + request.Name) | ||
146 | 888 | expectedBody, _ := uag.Serialize() | ||
147 | 889 | checkOneRequest(c, &recordedRequests, expectedURL, []byte(expectedBody), "PUT") | ||
148 | 890 | } | ||
149 | 891 | |||
150 | 892 | func (suite *managementBaseAPISuite) TestDeleteAffinityGroup(c *C) { | ||
151 | 893 | api := makeAPI(c) | ||
152 | 894 | request := DeleteAffinityGroupRequest{ | ||
153 | 895 | Name: "groupname"} | ||
154 | 896 | fixedResponse := x509Response{ | ||
155 | 897 | StatusCode: http.StatusCreated} | ||
156 | 898 | rigFixedResponseDispatcher(&fixedResponse) | ||
157 | 899 | recordedRequests := make([]*X509Request, 0) | ||
158 | 900 | rigRecordingDispatcher(&recordedRequests) | ||
159 | 901 | |||
160 | 902 | err := api.DeleteAffinityGroup(&request) | ||
161 | 903 | c.Assert(err, IsNil) | ||
162 | 904 | |||
163 | 905 | expectedURL := (AZURE_URL + api.session.subscriptionId + | ||
164 | 906 | "/affinitygroups/" + request.Name) | ||
165 | 907 | checkOneRequest(c, &recordedRequests, expectedURL, nil, "DELETE") | ||
166 | 908 | } | ||
167 | 909 | |||
168 | 851 | func makeNetworkConfiguration() *NetworkConfiguration { | 910 | func makeNetworkConfiguration() *NetworkConfiguration { |
169 | 852 | return &NetworkConfiguration{ | 911 | return &NetworkConfiguration{ |
170 | 853 | XMLNS: XMLNS_NC, | 912 | XMLNS: XMLNS_NC, |
171 | 854 | 913 | ||
172 | === modified file 'xmlobjects.go' | |||
173 | --- xmlobjects.go 2013-07-10 08:33:49 +0000 | |||
174 | +++ xmlobjects.go 2013-07-10 09:03:28 +0000 | |||
175 | @@ -634,6 +634,54 @@ | |||
176 | 634 | } | 634 | } |
177 | 635 | 635 | ||
178 | 636 | // | 636 | // |
179 | 637 | // Affinity Group | ||
180 | 638 | // | ||
181 | 639 | |||
182 | 640 | // See http://msdn.microsoft.com/en-us/library/windowsazure/gg715317.aspx | ||
183 | 641 | type CreateAffinityGroup struct { | ||
184 | 642 | XMLNS string `xml:"xmlns,attr"` | ||
185 | 643 | Name string `xml:"Name"` | ||
186 | 644 | Label string `xml:"Label"` // Must be base64 encoded. | ||
187 | 645 | Description string `xml:"Description",omitempty` | ||
188 | 646 | Location string `xml:"Location"` // Value comes from ListLocations. | ||
189 | 647 | } | ||
190 | 648 | |||
191 | 649 | func (c *CreateAffinityGroup) Serialize() (string, error) { | ||
192 | 650 | return toxml(c) | ||
193 | 651 | } | ||
194 | 652 | |||
195 | 653 | func NewCreateAffinityGroup(name, label, description, location string) *CreateAffinityGroup { | ||
196 | 654 | base64label := base64.StdEncoding.EncodeToString([]byte(label)) | ||
197 | 655 | return &CreateAffinityGroup{ | ||
198 | 656 | XMLNS: XMLNS, | ||
199 | 657 | Name: name, | ||
200 | 658 | Label: base64label, | ||
201 | 659 | Description: description, | ||
202 | 660 | Location: location, | ||
203 | 661 | } | ||
204 | 662 | } | ||
205 | 663 | |||
206 | 664 | // See http://msdn.microsoft.com/en-us/library/windowsazure/gg715316.aspx | ||
207 | 665 | type UpdateAffinityGroup struct { | ||
208 | 666 | XMLNS string `xml:"xmlns,attr"` | ||
209 | 667 | Label string `xml:"Label"` // Must be base64 encoded. | ||
210 | 668 | Description string `xml:"Description",omitempty` | ||
211 | 669 | } | ||
212 | 670 | |||
213 | 671 | func (u *UpdateAffinityGroup) Serialize() (string, error) { | ||
214 | 672 | return toxml(u) | ||
215 | 673 | } | ||
216 | 674 | |||
217 | 675 | func NewUpdateAffinityGroup(label, description string) *UpdateAffinityGroup { | ||
218 | 676 | base64label := base64.StdEncoding.EncodeToString([]byte(label)) | ||
219 | 677 | return &UpdateAffinityGroup{ | ||
220 | 678 | XMLNS: XMLNS, | ||
221 | 679 | Label: base64label, | ||
222 | 680 | Description: description, | ||
223 | 681 | } | ||
224 | 682 | } | ||
225 | 683 | |||
226 | 684 | // | ||
227 | 637 | // Storage Services bits | 685 | // Storage Services bits |
228 | 638 | // | 686 | // |
229 | 639 | 687 | ||
230 | 640 | 688 | ||
231 | === modified file 'xmlobjects_test.go' | |||
232 | --- xmlobjects_test.go 2013-07-10 08:41:26 +0000 | |||
233 | +++ xmlobjects_test.go 2013-07-10 09:03:28 +0000 | |||
234 | @@ -363,6 +363,67 @@ | |||
235 | 363 | c.Assert(observed, Equals, expected) | 363 | c.Assert(observed, Equals, expected) |
236 | 364 | } | 364 | } |
237 | 365 | 365 | ||
238 | 366 | func (suite *xmlSuite) TestCreateAffinityGroup(c *C) { | ||
239 | 367 | expected := dedent.Dedent(` | ||
240 | 368 | <CreateAffinityGroup xmlns="http://schemas.microsoft.com/windowsazure"> | ||
241 | 369 | <Name>affinity-group-name</Name> | ||
242 | 370 | <Label>base64-encoded-affinity-group-label</Label> | ||
243 | 371 | <Description>affinity-group-description</Description> | ||
244 | 372 | <Location>location</Location> | ||
245 | 373 | </CreateAffinityGroup>`) | ||
246 | 374 | |||
247 | 375 | input := CreateAffinityGroup{ | ||
248 | 376 | XMLNS: XMLNS, | ||
249 | 377 | Name: "affinity-group-name", | ||
250 | 378 | Label: "base64-encoded-affinity-group-label", | ||
251 | 379 | Description: "affinity-group-description", | ||
252 | 380 | Location: "location"} | ||
253 | 381 | |||
254 | 382 | observed, err := input.Serialize() | ||
255 | 383 | c.Assert(err, IsNil) | ||
256 | 384 | c.Assert(observed, Equals, expected) | ||
257 | 385 | } | ||
258 | 386 | |||
259 | 387 | func (suite *xmlSuite) TestNewCreateAffinityGroup(c *C) { | ||
260 | 388 | name := "name" | ||
261 | 389 | label := "label" | ||
262 | 390 | description := "description" | ||
263 | 391 | location := "location" | ||
264 | 392 | ag := NewCreateAffinityGroup(name, label, description, location) | ||
265 | 393 | base64label := base64.StdEncoding.EncodeToString([]byte(label)) | ||
266 | 394 | c.Check(ag.XMLNS, Equals, XMLNS) | ||
267 | 395 | c.Check(ag.Name, Equals, name) | ||
268 | 396 | c.Check(ag.Label, Equals, base64label) | ||
269 | 397 | c.Check(ag.Description, Equals, description) | ||
270 | 398 | c.Check(ag.Location, Equals, location) | ||
271 | 399 | } | ||
272 | 400 | |||
273 | 401 | func (suite *xmlSuite) TestUpdateAffinityGroup(c *C) { | ||
274 | 402 | expected := dedent.Dedent(` | ||
275 | 403 | <UpdateAffinityGroup xmlns="http://schemas.microsoft.com/windowsazure"> | ||
276 | 404 | <Label>base64-encoded-affinity-group-label</Label> | ||
277 | 405 | <Description>affinity-group-description</Description> | ||
278 | 406 | </UpdateAffinityGroup>`) | ||
279 | 407 | input := UpdateAffinityGroup{ | ||
280 | 408 | XMLNS: XMLNS, | ||
281 | 409 | Label: "base64-encoded-affinity-group-label", | ||
282 | 410 | Description: "affinity-group-description"} | ||
283 | 411 | |||
284 | 412 | observed, err := input.Serialize() | ||
285 | 413 | c.Assert(err, IsNil) | ||
286 | 414 | c.Assert(observed, Equals, expected) | ||
287 | 415 | } | ||
288 | 416 | |||
289 | 417 | func (suite *xmlSuite) TestNewUpdateAffinityGroup(c *C) { | ||
290 | 418 | label := "label" | ||
291 | 419 | description := "description" | ||
292 | 420 | ag := NewUpdateAffinityGroup(label, description) | ||
293 | 421 | base64label := base64.StdEncoding.EncodeToString([]byte(label)) | ||
294 | 422 | c.Check(ag.XMLNS, Equals, XMLNS) | ||
295 | 423 | c.Check(ag.Label, Equals, base64label) | ||
296 | 424 | c.Check(ag.Description, Equals, description) | ||
297 | 425 | } | ||
298 | 426 | |||
299 | 366 | func (suite *xmlSuite) TestNetworkConfigurationDeserialize(c *C) { | 427 | func (suite *xmlSuite) TestNetworkConfigurationDeserialize(c *C) { |
300 | 367 | // Template from | 428 | // Template from |
301 | 368 | // http://msdn.microsoft.com/en-us/library/windowsazure/jj157196.aspx | 429 | // http://msdn.microsoft.com/en-us/library/windowsazure/jj157196.aspx |
Looks good!
[0]
59 + var err error
60 + url := "affinitygroups/" + request.Name
and
78 + var err error
79 + url := "affinitygroups/" + request.Name
You need to use checkPathCompon ents() to make sure request.Name can be safely used in the url.
[1]
279 +}
280 func (suite *xmlSuite) TestDeployment(c *C) {
Missing an empty line here I think… did you run 'make format'?
[2]
You might want to do this in another branch or in here since it's really tiny but anyway, I'm mentioning it here so that we don't forget: NewCreateHosted ServiceWithLoca tion() needs to be updated to take an affinity group name (AffinityGroup is already a field in the CreateHostedService struct). Then we need to update the call to NewCreateHosted ServiceWithLoca tion() in example/ management/ run.go to take the name of the newly created affinity group. In this branch as it is now, you're creating the affinity group in example/ management/ run.go but the created hosted service is not linked to it.
[3]
36 +type CreateAffinityG roupRequest struct { Group
37 + CreateAffinityGroup *CreateAffinity
38 +}
Not sure if it's really useful but I guess it's more coherent with the other calls to have this intermediate CreateAffinityG roupRequest structure…?
[4]
9 + affinityGroupName := gwacl.MakeRando mHostname( "affinitygroup" )
Why did you use MakeRandomHostn ame() here? AFAIK this is not a hostname is it? Hum, looking at names.go we might want to expose makeRandomIdent ifier() and use that directly.
[5]
165 +//
166 +type CreateAffinityGroup struct {
Maybe include the link to the API documentation in the "docstring"…? Here and in a couple of other places.