Merge lp:~julian-edwards/gwacl/affinity-group into lp:gwacl

Proposed by Julian Edwards
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
Reviewer Review Type Date Requested Status
Raphaël Badin (community) Approve
Review via email: mp+173855@code.launchpad.net

Commit message

Add CreateAffinityGroup, UpdateAffinityGroup and DeleteAffinityGroup management api calls.

Description of the change

It turns out that this is needed by vnets, so add all the bumf for AffinityGroup creation, update and deletion.

To post a comment you must log in.
Revision history for this message
Raphaël Badin (rvb) wrote :

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 checkPathComponents() 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: NewCreateHostedServiceWithLocation() 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 NewCreateHostedServiceWithLocation() 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 CreateAffinityGroupRequest struct {
37 + CreateAffinityGroup *CreateAffinityGroup
38 +}

Not sure if it's really useful but I guess it's more coherent with the other calls to have this intermediate CreateAffinityGroupRequest structure…?

[4]

9 + affinityGroupName := gwacl.MakeRandomHostname("affinitygroup")

Why did you use MakeRandomHostname() here? AFAIK this is not a hostname is it? Hum, looking at names.go we might want to expose makeRandomIdentifier() 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.

review: Approve
Revision history for this message
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 checkPathComponents() to make sure request.Name can be safely used in the url.

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: NewCreateHostedServiceWithLocation() 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 NewCreateHostedServiceWithLocation() 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.

Separate branch, yes :)

>
> [3]
>
> 36 +type CreateAffinityGroupRequest struct {
> 37 + CreateAffinityGroup *CreateAffinityGroup
> 38 +}
>
> Not sure if it's really useful but I guess it's more coherent with the other calls to have this intermediate CreateAffinityGroupRequest structure…?

Consistency always wins. Besides, we may want to add more parameters
later as previously discussed.

>
> [4]
>
> 9 + affinityGroupName := gwacl.MakeRandomHostname("affinitygroup")
>
> Why did you use MakeRandomHostname() here? AFAIK this is not a hostname is it? Hum, looking at names.go we might want to expose makeRandomIdentifier() and use that directly.

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.

Revision history for this message
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: NewCreateHostedServiceWithLocation() 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
> NewCreateHostedServiceWithLocation() 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.

Perhaps we ought to have a NewCreateHostedServiceWithLocationParams
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.

Revision history for this message
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_base_test.go
text conflict in xmlobjects_test.go

Revision history for this message
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: NewCreateHostedServiceWithLocation() 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
> NewCreateHostedServiceWithLocation() 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.

Perhaps we ought to have a NewCreateHostedServiceWithLocationParams
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.

Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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 }
6
7 func ExerciseHostedServicesAPI(api *gwacl.ManagementAPI) {
8+ var err error
9 networkConfig, err := api.GetNetworkConfiguration()
10 checkError(err)
11 if networkConfig == nil {
12@@ -100,6 +101,21 @@
13 location := "West US"
14 release := "13.04"
15
16+ affinityGroupName := gwacl.MakeRandomHostname("affinitygroup")
17+ fmt.Printf("Creating an affinity group...")
18+ cag := gwacl.NewCreateAffinityGroup(affinityGroupName, "affinity-label", "affinity-description", location)
19+ err = api.CreateAffinityGroup(&gwacl.CreateAffinityGroupRequest{
20+ CreateAffinityGroup: cag})
21+ checkError(err)
22+ fmt.Println("created %s", affinityGroupName)
23+
24+ defer func() {
25+ fmt.Println("Deleting affinity group %s", affinityGroupName)
26+ api.DeleteAffinityGroup(&gwacl.DeleteAffinityGroupRequest{
27+ Name: affinityGroupName})
28+ fmt.Println("Done deleting affinity group %s", affinityGroupName)
29+ }()
30+
31 fmt.Printf("Getting OS Image for release '%s' and location '%s'...\n", release, location)
32 images, err := api.ListOSImages()
33 checkError(err)
34
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 return &role, nil
40 }
41
42+type CreateAffinityGroupRequest struct {
43+ CreateAffinityGroup *CreateAffinityGroup
44+}
45+
46+// CreateAffinityGroup sends a request to make a new affinity group.
47+func (api *ManagementAPI) CreateAffinityGroup(request *CreateAffinityGroupRequest) error {
48+ var err error
49+ url := "affinitygroups"
50+ body, err := request.CreateAffinityGroup.Serialize()
51+ if err != nil {
52+ return err
53+ }
54+ response, err := api.session.post(url, []byte(body), "application/xml")
55+ return api.blockUntilCompleted(response)
56+}
57+
58+type UpdateAffinityGroupRequest struct {
59+ Name string
60+ UpdateAffinityGroup *UpdateAffinityGroup
61+}
62+
63+// UpdateAffinityGroup sends a request to update the named affinity group.
64+func (api *ManagementAPI) UpdateAffinityGroup(request *UpdateAffinityGroupRequest) error {
65+ var err error
66+ checkPathComponents(request.Name)
67+ url := "affinitygroups/" + request.Name
68+ body, err := request.UpdateAffinityGroup.Serialize()
69+ if err != nil {
70+ return err
71+ }
72+ response, err := api.session.put(url, []byte(body))
73+ if err != nil {
74+ return err
75+ }
76+ return api.blockUntilCompleted(response)
77+}
78+
79+type DeleteAffinityGroupRequest struct {
80+ Name string
81+}
82+
83+// DeleteAffinityGroup requests a deletion of the named affinity group.
84+func (api *ManagementAPI) DeleteAffinityGroup(request *DeleteAffinityGroupRequest) error {
85+ var err error
86+ checkPathComponents(request.Name)
87+ url := "affinitygroups/" + request.Name
88+ if err != nil {
89+ return err
90+ }
91+ response, err := api.session.delete(url)
92+ if err != nil {
93+ return err
94+ }
95+ return api.blockUntilCompleted(response)
96+}
97+
98 // GetNetworkConfiguration gets the network configuration for this
99 // subscription. If there is no network configuration the configuration will
100 // be nil.
101
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 c.Check(role.RoleName, Equals, "rolename")
107 }
108
109+func (suite *managementBaseAPISuite) TestCreateAffinityGroup(c *C) {
110+ api := makeAPI(c)
111+ cag := NewCreateAffinityGroup(
112+ "name", "label", "description", "location")
113+ request := CreateAffinityGroupRequest{
114+ CreateAffinityGroup: cag}
115+ fixedResponse := x509Response{
116+ StatusCode: http.StatusCreated}
117+ rigFixedResponseDispatcher(&fixedResponse)
118+ recordedRequests := make([]*X509Request, 0)
119+ rigRecordingDispatcher(&recordedRequests)
120+
121+ err := api.CreateAffinityGroup(&request)
122+ c.Assert(err, IsNil)
123+
124+ expectedURL := AZURE_URL + api.session.subscriptionId + "/affinitygroups"
125+ expectedBody, _ := cag.Serialize()
126+ checkOneRequest(c, &recordedRequests, expectedURL, []byte(expectedBody), "POST")
127+}
128+
129+func (suite *managementBaseAPISuite) TestUpdateAffinityGroup(c *C) {
130+ api := makeAPI(c)
131+ uag := NewUpdateAffinityGroup("label", "description")
132+ request := UpdateAffinityGroupRequest{
133+ Name: "groupname",
134+ UpdateAffinityGroup: uag}
135+ fixedResponse := x509Response{
136+ StatusCode: http.StatusCreated}
137+ rigFixedResponseDispatcher(&fixedResponse)
138+ recordedRequests := make([]*X509Request, 0)
139+ rigRecordingDispatcher(&recordedRequests)
140+
141+ err := api.UpdateAffinityGroup(&request)
142+ c.Assert(err, IsNil)
143+
144+ expectedURL := (AZURE_URL + api.session.subscriptionId +
145+ "/affinitygroups/" + request.Name)
146+ expectedBody, _ := uag.Serialize()
147+ checkOneRequest(c, &recordedRequests, expectedURL, []byte(expectedBody), "PUT")
148+}
149+
150+func (suite *managementBaseAPISuite) TestDeleteAffinityGroup(c *C) {
151+ api := makeAPI(c)
152+ request := DeleteAffinityGroupRequest{
153+ Name: "groupname"}
154+ fixedResponse := x509Response{
155+ StatusCode: http.StatusCreated}
156+ rigFixedResponseDispatcher(&fixedResponse)
157+ recordedRequests := make([]*X509Request, 0)
158+ rigRecordingDispatcher(&recordedRequests)
159+
160+ err := api.DeleteAffinityGroup(&request)
161+ c.Assert(err, IsNil)
162+
163+ expectedURL := (AZURE_URL + api.session.subscriptionId +
164+ "/affinitygroups/" + request.Name)
165+ checkOneRequest(c, &recordedRequests, expectedURL, nil, "DELETE")
166+}
167+
168 func makeNetworkConfiguration() *NetworkConfiguration {
169 return &NetworkConfiguration{
170 XMLNS: XMLNS_NC,
171
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 }
177
178 //
179+// Affinity Group
180+//
181+
182+// See http://msdn.microsoft.com/en-us/library/windowsazure/gg715317.aspx
183+type CreateAffinityGroup struct {
184+ XMLNS string `xml:"xmlns,attr"`
185+ Name string `xml:"Name"`
186+ Label string `xml:"Label"` // Must be base64 encoded.
187+ Description string `xml:"Description",omitempty`
188+ Location string `xml:"Location"` // Value comes from ListLocations.
189+}
190+
191+func (c *CreateAffinityGroup) Serialize() (string, error) {
192+ return toxml(c)
193+}
194+
195+func NewCreateAffinityGroup(name, label, description, location string) *CreateAffinityGroup {
196+ base64label := base64.StdEncoding.EncodeToString([]byte(label))
197+ return &CreateAffinityGroup{
198+ XMLNS: XMLNS,
199+ Name: name,
200+ Label: base64label,
201+ Description: description,
202+ Location: location,
203+ }
204+}
205+
206+// See http://msdn.microsoft.com/en-us/library/windowsazure/gg715316.aspx
207+type UpdateAffinityGroup struct {
208+ XMLNS string `xml:"xmlns,attr"`
209+ Label string `xml:"Label"` // Must be base64 encoded.
210+ Description string `xml:"Description",omitempty`
211+}
212+
213+func (u *UpdateAffinityGroup) Serialize() (string, error) {
214+ return toxml(u)
215+}
216+
217+func NewUpdateAffinityGroup(label, description string) *UpdateAffinityGroup {
218+ base64label := base64.StdEncoding.EncodeToString([]byte(label))
219+ return &UpdateAffinityGroup{
220+ XMLNS: XMLNS,
221+ Label: base64label,
222+ Description: description,
223+ }
224+}
225+
226+//
227 // Storage Services bits
228 //
229
230
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 c.Assert(observed, Equals, expected)
236 }
237
238+func (suite *xmlSuite) TestCreateAffinityGroup(c *C) {
239+ expected := dedent.Dedent(`
240+ <CreateAffinityGroup xmlns="http://schemas.microsoft.com/windowsazure">
241+ <Name>affinity-group-name</Name>
242+ <Label>base64-encoded-affinity-group-label</Label>
243+ <Description>affinity-group-description</Description>
244+ <Location>location</Location>
245+ </CreateAffinityGroup>`)
246+
247+ input := CreateAffinityGroup{
248+ XMLNS: XMLNS,
249+ Name: "affinity-group-name",
250+ Label: "base64-encoded-affinity-group-label",
251+ Description: "affinity-group-description",
252+ Location: "location"}
253+
254+ observed, err := input.Serialize()
255+ c.Assert(err, IsNil)
256+ c.Assert(observed, Equals, expected)
257+}
258+
259+func (suite *xmlSuite) TestNewCreateAffinityGroup(c *C) {
260+ name := "name"
261+ label := "label"
262+ description := "description"
263+ location := "location"
264+ ag := NewCreateAffinityGroup(name, label, description, location)
265+ base64label := base64.StdEncoding.EncodeToString([]byte(label))
266+ c.Check(ag.XMLNS, Equals, XMLNS)
267+ c.Check(ag.Name, Equals, name)
268+ c.Check(ag.Label, Equals, base64label)
269+ c.Check(ag.Description, Equals, description)
270+ c.Check(ag.Location, Equals, location)
271+}
272+
273+func (suite *xmlSuite) TestUpdateAffinityGroup(c *C) {
274+ expected := dedent.Dedent(`
275+ <UpdateAffinityGroup xmlns="http://schemas.microsoft.com/windowsazure">
276+ <Label>base64-encoded-affinity-group-label</Label>
277+ <Description>affinity-group-description</Description>
278+ </UpdateAffinityGroup>`)
279+ input := UpdateAffinityGroup{
280+ XMLNS: XMLNS,
281+ Label: "base64-encoded-affinity-group-label",
282+ Description: "affinity-group-description"}
283+
284+ observed, err := input.Serialize()
285+ c.Assert(err, IsNil)
286+ c.Assert(observed, Equals, expected)
287+}
288+
289+func (suite *xmlSuite) TestNewUpdateAffinityGroup(c *C) {
290+ label := "label"
291+ description := "description"
292+ ag := NewUpdateAffinityGroup(label, description)
293+ base64label := base64.StdEncoding.EncodeToString([]byte(label))
294+ c.Check(ag.XMLNS, Equals, XMLNS)
295+ c.Check(ag.Label, Equals, base64label)
296+ c.Check(ag.Description, Equals, description)
297+}
298+
299 func (suite *xmlSuite) TestNetworkConfigurationDeserialize(c *C) {
300 // Template from
301 // http://msdn.microsoft.com/en-us/library/windowsazure/jj157196.aspx

Subscribers

People subscribed via source and target branches

to all changes: