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
=== modified file 'example/management/run.go'
--- example/management/run.go 2013-07-09 20:17:57 +0000
+++ example/management/run.go 2013-07-10 09:03:28 +0000
@@ -87,6 +87,7 @@
87}87}
8888
89func ExerciseHostedServicesAPI(api *gwacl.ManagementAPI) {89func ExerciseHostedServicesAPI(api *gwacl.ManagementAPI) {
90 var err error
90 networkConfig, err := api.GetNetworkConfiguration()91 networkConfig, err := api.GetNetworkConfiguration()
91 checkError(err)92 checkError(err)
92 if networkConfig == nil {93 if networkConfig == nil {
@@ -100,6 +101,21 @@
100 location := "West US"101 location := "West US"
101 release := "13.04"102 release := "13.04"
102103
104 affinityGroupName := gwacl.MakeRandomHostname("affinitygroup")
105 fmt.Printf("Creating an affinity group...")
106 cag := gwacl.NewCreateAffinityGroup(affinityGroupName, "affinity-label", "affinity-description", location)
107 err = api.CreateAffinityGroup(&gwacl.CreateAffinityGroupRequest{
108 CreateAffinityGroup: cag})
109 checkError(err)
110 fmt.Println("created %s", affinityGroupName)
111
112 defer func() {
113 fmt.Println("Deleting affinity group %s", affinityGroupName)
114 api.DeleteAffinityGroup(&gwacl.DeleteAffinityGroupRequest{
115 Name: affinityGroupName})
116 fmt.Println("Done deleting affinity group %s", affinityGroupName)
117 }()
118
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)
104 images, err := api.ListOSImages()120 images, err := api.ListOSImages()
105 checkError(err)121 checkError(err)
106122
=== modified file 'management_base.go'
--- management_base.go 2013-07-09 20:11:22 +0000
+++ management_base.go 2013-07-10 09:03:28 +0000
@@ -358,6 +358,62 @@
358 return &role, nil358 return &role, nil
359}359}
360360
361type CreateAffinityGroupRequest struct {
362 CreateAffinityGroup *CreateAffinityGroup
363}
364
365// CreateAffinityGroup sends a request to make a new affinity group.
366func (api *ManagementAPI) CreateAffinityGroup(request *CreateAffinityGroupRequest) error {
367 var err error
368 url := "affinitygroups"
369 body, err := request.CreateAffinityGroup.Serialize()
370 if err != nil {
371 return err
372 }
373 response, err := api.session.post(url, []byte(body), "application/xml")
374 return api.blockUntilCompleted(response)
375}
376
377type UpdateAffinityGroupRequest struct {
378 Name string
379 UpdateAffinityGroup *UpdateAffinityGroup
380}
381
382// UpdateAffinityGroup sends a request to update the named affinity group.
383func (api *ManagementAPI) UpdateAffinityGroup(request *UpdateAffinityGroupRequest) error {
384 var err error
385 checkPathComponents(request.Name)
386 url := "affinitygroups/" + request.Name
387 body, err := request.UpdateAffinityGroup.Serialize()
388 if err != nil {
389 return err
390 }
391 response, err := api.session.put(url, []byte(body))
392 if err != nil {
393 return err
394 }
395 return api.blockUntilCompleted(response)
396}
397
398type DeleteAffinityGroupRequest struct {
399 Name string
400}
401
402// DeleteAffinityGroup requests a deletion of the named affinity group.
403func (api *ManagementAPI) DeleteAffinityGroup(request *DeleteAffinityGroupRequest) error {
404 var err error
405 checkPathComponents(request.Name)
406 url := "affinitygroups/" + request.Name
407 if err != nil {
408 return err
409 }
410 response, err := api.session.delete(url)
411 if err != nil {
412 return err
413 }
414 return api.blockUntilCompleted(response)
415}
416
361// GetNetworkConfiguration gets the network configuration for this417// GetNetworkConfiguration gets the network configuration for this
362// subscription. If there is no network configuration the configuration will418// subscription. If there is no network configuration the configuration will
363// be nil.419// be nil.
364420
=== modified file 'management_base_test.go'
--- management_base_test.go 2013-07-09 20:11:22 +0000
+++ management_base_test.go 2013-07-10 09:03:28 +0000
@@ -848,6 +848,65 @@
848 c.Check(role.RoleName, Equals, "rolename")848 c.Check(role.RoleName, Equals, "rolename")
849}849}
850850
851func (suite *managementBaseAPISuite) TestCreateAffinityGroup(c *C) {
852 api := makeAPI(c)
853 cag := NewCreateAffinityGroup(
854 "name", "label", "description", "location")
855 request := CreateAffinityGroupRequest{
856 CreateAffinityGroup: cag}
857 fixedResponse := x509Response{
858 StatusCode: http.StatusCreated}
859 rigFixedResponseDispatcher(&fixedResponse)
860 recordedRequests := make([]*X509Request, 0)
861 rigRecordingDispatcher(&recordedRequests)
862
863 err := api.CreateAffinityGroup(&request)
864 c.Assert(err, IsNil)
865
866 expectedURL := AZURE_URL + api.session.subscriptionId + "/affinitygroups"
867 expectedBody, _ := cag.Serialize()
868 checkOneRequest(c, &recordedRequests, expectedURL, []byte(expectedBody), "POST")
869}
870
871func (suite *managementBaseAPISuite) TestUpdateAffinityGroup(c *C) {
872 api := makeAPI(c)
873 uag := NewUpdateAffinityGroup("label", "description")
874 request := UpdateAffinityGroupRequest{
875 Name: "groupname",
876 UpdateAffinityGroup: uag}
877 fixedResponse := x509Response{
878 StatusCode: http.StatusCreated}
879 rigFixedResponseDispatcher(&fixedResponse)
880 recordedRequests := make([]*X509Request, 0)
881 rigRecordingDispatcher(&recordedRequests)
882
883 err := api.UpdateAffinityGroup(&request)
884 c.Assert(err, IsNil)
885
886 expectedURL := (AZURE_URL + api.session.subscriptionId +
887 "/affinitygroups/" + request.Name)
888 expectedBody, _ := uag.Serialize()
889 checkOneRequest(c, &recordedRequests, expectedURL, []byte(expectedBody), "PUT")
890}
891
892func (suite *managementBaseAPISuite) TestDeleteAffinityGroup(c *C) {
893 api := makeAPI(c)
894 request := DeleteAffinityGroupRequest{
895 Name: "groupname"}
896 fixedResponse := x509Response{
897 StatusCode: http.StatusCreated}
898 rigFixedResponseDispatcher(&fixedResponse)
899 recordedRequests := make([]*X509Request, 0)
900 rigRecordingDispatcher(&recordedRequests)
901
902 err := api.DeleteAffinityGroup(&request)
903 c.Assert(err, IsNil)
904
905 expectedURL := (AZURE_URL + api.session.subscriptionId +
906 "/affinitygroups/" + request.Name)
907 checkOneRequest(c, &recordedRequests, expectedURL, nil, "DELETE")
908}
909
851func makeNetworkConfiguration() *NetworkConfiguration {910func makeNetworkConfiguration() *NetworkConfiguration {
852 return &NetworkConfiguration{911 return &NetworkConfiguration{
853 XMLNS: XMLNS_NC,912 XMLNS: XMLNS_NC,
854913
=== modified file 'xmlobjects.go'
--- xmlobjects.go 2013-07-10 08:33:49 +0000
+++ xmlobjects.go 2013-07-10 09:03:28 +0000
@@ -634,6 +634,54 @@
634}634}
635635
636//636//
637// Affinity Group
638//
639
640// See http://msdn.microsoft.com/en-us/library/windowsazure/gg715317.aspx
641type CreateAffinityGroup struct {
642 XMLNS string `xml:"xmlns,attr"`
643 Name string `xml:"Name"`
644 Label string `xml:"Label"` // Must be base64 encoded.
645 Description string `xml:"Description",omitempty`
646 Location string `xml:"Location"` // Value comes from ListLocations.
647}
648
649func (c *CreateAffinityGroup) Serialize() (string, error) {
650 return toxml(c)
651}
652
653func NewCreateAffinityGroup(name, label, description, location string) *CreateAffinityGroup {
654 base64label := base64.StdEncoding.EncodeToString([]byte(label))
655 return &CreateAffinityGroup{
656 XMLNS: XMLNS,
657 Name: name,
658 Label: base64label,
659 Description: description,
660 Location: location,
661 }
662}
663
664// See http://msdn.microsoft.com/en-us/library/windowsazure/gg715316.aspx
665type UpdateAffinityGroup struct {
666 XMLNS string `xml:"xmlns,attr"`
667 Label string `xml:"Label"` // Must be base64 encoded.
668 Description string `xml:"Description",omitempty`
669}
670
671func (u *UpdateAffinityGroup) Serialize() (string, error) {
672 return toxml(u)
673}
674
675func NewUpdateAffinityGroup(label, description string) *UpdateAffinityGroup {
676 base64label := base64.StdEncoding.EncodeToString([]byte(label))
677 return &UpdateAffinityGroup{
678 XMLNS: XMLNS,
679 Label: base64label,
680 Description: description,
681 }
682}
683
684//
637// Storage Services bits685// Storage Services bits
638//686//
639687
640688
=== modified file 'xmlobjects_test.go'
--- xmlobjects_test.go 2013-07-10 08:41:26 +0000
+++ xmlobjects_test.go 2013-07-10 09:03:28 +0000
@@ -363,6 +363,67 @@
363 c.Assert(observed, Equals, expected)363 c.Assert(observed, Equals, expected)
364}364}
365365
366func (suite *xmlSuite) TestCreateAffinityGroup(c *C) {
367 expected := dedent.Dedent(`
368 <CreateAffinityGroup xmlns="http://schemas.microsoft.com/windowsazure">
369 <Name>affinity-group-name</Name>
370 <Label>base64-encoded-affinity-group-label</Label>
371 <Description>affinity-group-description</Description>
372 <Location>location</Location>
373 </CreateAffinityGroup>`)
374
375 input := CreateAffinityGroup{
376 XMLNS: XMLNS,
377 Name: "affinity-group-name",
378 Label: "base64-encoded-affinity-group-label",
379 Description: "affinity-group-description",
380 Location: "location"}
381
382 observed, err := input.Serialize()
383 c.Assert(err, IsNil)
384 c.Assert(observed, Equals, expected)
385}
386
387func (suite *xmlSuite) TestNewCreateAffinityGroup(c *C) {
388 name := "name"
389 label := "label"
390 description := "description"
391 location := "location"
392 ag := NewCreateAffinityGroup(name, label, description, location)
393 base64label := base64.StdEncoding.EncodeToString([]byte(label))
394 c.Check(ag.XMLNS, Equals, XMLNS)
395 c.Check(ag.Name, Equals, name)
396 c.Check(ag.Label, Equals, base64label)
397 c.Check(ag.Description, Equals, description)
398 c.Check(ag.Location, Equals, location)
399}
400
401func (suite *xmlSuite) TestUpdateAffinityGroup(c *C) {
402 expected := dedent.Dedent(`
403 <UpdateAffinityGroup xmlns="http://schemas.microsoft.com/windowsazure">
404 <Label>base64-encoded-affinity-group-label</Label>
405 <Description>affinity-group-description</Description>
406 </UpdateAffinityGroup>`)
407 input := UpdateAffinityGroup{
408 XMLNS: XMLNS,
409 Label: "base64-encoded-affinity-group-label",
410 Description: "affinity-group-description"}
411
412 observed, err := input.Serialize()
413 c.Assert(err, IsNil)
414 c.Assert(observed, Equals, expected)
415}
416
417func (suite *xmlSuite) TestNewUpdateAffinityGroup(c *C) {
418 label := "label"
419 description := "description"
420 ag := NewUpdateAffinityGroup(label, description)
421 base64label := base64.StdEncoding.EncodeToString([]byte(label))
422 c.Check(ag.XMLNS, Equals, XMLNS)
423 c.Check(ag.Label, Equals, base64label)
424 c.Check(ag.Description, Equals, description)
425}
426
366func (suite *xmlSuite) TestNetworkConfigurationDeserialize(c *C) {427func (suite *xmlSuite) TestNetworkConfigurationDeserialize(c *C) {
367 // Template from428 // Template from
368 // http://msdn.microsoft.com/en-us/library/windowsazure/jj157196.aspx429 // http://msdn.microsoft.com/en-us/library/windowsazure/jj157196.aspx

Subscribers

People subscribed via source and target branches

to all changes: