Merge lp:~allenap/juju-core/azure-open-machine-ports into lp:~go-bot/juju-core/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 1568
Proposed branch: lp:~allenap/juju-core/azure-open-machine-ports
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 403 lines (+262/-13)
4 files modified
environs/azure/environ.go (+5/-5)
environs/azure/environ_test.go (+23/-4)
environs/azure/instance.go (+41/-1)
environs/azure/instance_test.go (+193/-3)
To merge this branch: bzr merge lp:~allenap/juju-core/azure-open-machine-ports
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+177717@code.launchpad.net

Commit message

Implement azureInstance.OpenPorts()

Description of the change

Implement azureInstance.OpenPorts()

https://codereview.appspot.com/12102045/

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

Reviewers: mp+177717_code.launchpad.net,

Message:
Please take a look.

Description:
Implement azureInstance.OpenPorts()

https://code.launchpad.net/~allenap/juju-core/azure-open-machine-ports/+merge/177717

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/12102045/

Affected files:
   A [revision details]
   M environs/azure/environ.go
   M environs/azure/environ_test.go
   M environs/azure/instance.go
   M environs/azure/instance_test.go

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

LGTM

Given that we always map one role to one deployment to one service, is
it strictly necessary to have that loop around deployments in
OpenPorts()?

Also does juju's identifier for a protocol match that of Azure's? The
doc for instance.Port does not say what "Protocol" is.

https://codereview.appspot.com/12102045/

Revision history for this message
Raphaël Badin (rvb) wrote :

On 2013/07/30 23:16:12, allenap wrote:
> Please take a look.

[0]

Julian has a point, only one deployment should be present in the
service, and it has the same name as the service
('azInstance.ServiceName'). That will save one API call.

[1]

We've used a "tag" to indicate all the places in the code which rely on
the assumption that one juju instance = one service = one deployment so
that if people change that model, they will have a marker indicating all
the places where things must be changed. Would you mind adding this
"tag" in a comment somwhere: "(instance==service)"?

https://codereview.appspot.com/12102045/

Revision history for this message
Raphaël Badin (rvb) wrote :
Revision history for this message
Raphaël Badin (rvb) wrote :

On 2013/07/31 09:00:46, rvb wrote:
> On 2013/07/30 23:16:12, allenap wrote:
> > Please take a look.

> [0]

> Julian has a point, only one deployment should be present in the
service, and it
> has the same name as the service ('azInstance.ServiceName'). That
will save one
> API call.

Actually, since you need to get all the roles for the deployment, I
don't think relying on the fact that we know there is only one
deployment named 'serviceName' will save us an API call…

https://codereview.appspot.com/12102045/

Revision history for this message
Gavin Panella (allenap) wrote :

On 2013/07/31 09:04:02, rvb wrote:
> Actually, since you need to get all the roles for the deployment, I
don't think
> relying on the fact that we know there is only one deployment named
> 'serviceName' will save us an API call…

The code as I've written it will work now and if the
service:deployment:role ratio changes so I think it's better to leave
it.

Thank you both for the reviews!

https://codereview.appspot.com/12102045/

Revision history for this message
Gavin Panella (allenap) wrote :

On 2013/07/30 23:53:07, julian.edwards wrote:
> Also does juju's identifier for a protocol match that of Azure's? The
doc for
> instance.Port does not say what "Protocol" is.

It seems to be either "tcp" or "udp". Azure's endpoint name seems to be
mainly for human consumption right now, albeit with undocumented
limitations on what form it may take.

https://codereview.appspot.com/12102045/

Revision history for this message
Raphaël Badin (rvb) wrote :

> The code as I've written it will work now and if the
service:deployment:role
> ratio changes so I think it's better to leave it.

I agree this is the way to go.

https://codereview.appspot.com/12102045/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'environs/azure/environ.go'
2--- environs/azure/environ.go 2013-07-30 09:30:57 +0000
3+++ environs/azure/environ.go 2013-07-30 23:14:25 +0000
4@@ -483,7 +483,7 @@
5 if err != nil {
6 return nil, fmt.Errorf("could not get instance %q: %v", instanceName, err)
7 }
8- instance := &azureInstance{service.HostedServiceDescriptor}
9+ instance := &azureInstance{service.HostedServiceDescriptor, env}
10 return instance, nil
11 }
12
13@@ -627,7 +627,7 @@
14 return nil, environs.ErrNoInstances
15 }
16
17- instances := convertToInstances(services)
18+ instances := convertToInstances(services, env)
19
20 // Check if we got a partial result.
21 if len(ids) != len(instances) {
22@@ -652,7 +652,7 @@
23 if err != nil {
24 return nil, err
25 }
26- return convertToInstances(services), nil
27+ return convertToInstances(services, env), nil
28 }
29
30 // getEnvPrefix returns the prefix used to name the objects specific to this
31@@ -663,10 +663,10 @@
32
33 // convertToInstances converts a slice of gwacl.HostedServiceDescriptor objects
34 // into a slice of instance.Instance objects.
35-func convertToInstances(services []gwacl.HostedServiceDescriptor) []instance.Instance {
36+func convertToInstances(services []gwacl.HostedServiceDescriptor, env *azureEnviron) []instance.Instance {
37 instances := make([]instance.Instance, len(services))
38 for i, service := range services {
39- instances[i] = &azureInstance{service}
40+ instances[i] = &azureInstance{service, env}
41 }
42 return instances
43 }
44
45=== modified file 'environs/azure/environ_test.go'
46--- environs/azure/environ_test.go 2013-07-30 09:20:19 +0000
47+++ environs/azure/environ_test.go 2013-07-30 23:14:25 +0000
48@@ -586,7 +586,9 @@
49 responses := buildDestroyAzureServiceResponses(c, services)
50 requests := gwacl.PatchManagementAPIResponses(responses)
51 env := makeEnviron(c)
52- instances := convertToInstances([]gwacl.HostedServiceDescriptor{*service1Desc, *service2Desc})
53+ instances := convertToInstances(
54+ []gwacl.HostedServiceDescriptor{*service1Desc, *service2Desc},
55+ env)
56
57 err := env.StopInstances(instances)
58 c.Check(err, IsNil)
59@@ -630,7 +632,7 @@
60 cleanupResponses := getVnetAndAffinityGroupCleanupResponses(c)
61 responses = append(responses, cleanupResponses...)
62 gwacl.PatchManagementAPIResponses(responses)
63- instances := convertToInstances([]gwacl.HostedServiceDescriptor{})
64+ instances := convertToInstances([]gwacl.HostedServiceDescriptor{}, env)
65
66 err := env.Destroy(instances)
67 c.Check(err, IsNil)
68@@ -665,7 +667,7 @@
69 }
70 responses = append(responses, cleanupResponses...)
71 requests := gwacl.PatchManagementAPIResponses(responses)
72- instances := convertToInstances([]gwacl.HostedServiceDescriptor{})
73+ instances := convertToInstances([]gwacl.HostedServiceDescriptor{}, env)
74
75 err = env.Destroy(instances)
76 c.Check(err, IsNil)
77@@ -718,7 +720,9 @@
78 requests := gwacl.PatchManagementAPIResponses(responses)
79
80 // Call Destroy with service1 and service2.
81- instances := convertToInstances([]gwacl.HostedServiceDescriptor{*service1Desc, *service2Desc})
82+ instances := convertToInstances(
83+ []gwacl.HostedServiceDescriptor{*service1Desc, *service2Desc},
84+ env)
85 err := env.Destroy(instances)
86 c.Check(err, IsNil)
87
88@@ -751,6 +755,9 @@
89 c.Check(err, IsNil)
90
91 c.Check(string(instance.Id()), Equals, serviceName)
92+ c.Check(instance, FitsTypeOf, &azureInstance{})
93+ azInstance := instance.(*azureInstance)
94+ c.Check(azInstance.environ, Equals, env)
95 }
96
97 func (*EnvironSuite) TestNewOSVirtualDisk(c *C) {
98@@ -1008,3 +1015,15 @@
99 // may have to become configurable.
100 c.Check(env.getImageMetadataSigningRequired(), Equals, true)
101 }
102+
103+func (*EnvironSuite) TestConvertToInstances(c *C) {
104+ services := []gwacl.HostedServiceDescriptor{
105+ {ServiceName: "foo"}, {ServiceName: "bar"},
106+ }
107+ env := makeEnviron(c)
108+ instances := convertToInstances(services, env)
109+ c.Check(instances, DeepEquals, []instance.Instance{
110+ &azureInstance{services[0], env},
111+ &azureInstance{services[1], env},
112+ })
113+}
114
115=== modified file 'environs/azure/instance.go'
116--- environs/azure/instance.go 2013-07-19 01:18:26 +0000
117+++ environs/azure/instance.go 2013-07-30 23:14:25 +0000
118@@ -15,6 +15,7 @@
119 type azureInstance struct {
120 // An instance contains an Azure Service (instance==service).
121 gwacl.HostedServiceDescriptor
122+ environ *azureEnviron
123 }
124
125 // azureInstance implements Instance.
126@@ -50,7 +51,46 @@
127
128 // OpenPorts is specified in the Instance interface.
129 func (azInstance *azureInstance) OpenPorts(machineId string, ports []instance.Port) error {
130- // TODO: implement this.
131+ env := azInstance.environ
132+ context, err := env.getManagementAPI()
133+ if err != nil {
134+ return err
135+ }
136+ defer env.releaseManagementAPI(context)
137+
138+ env.Lock()
139+ defer env.Unlock()
140+
141+ deployments, err := context.ListAllDeployments(&gwacl.ListAllDeploymentsRequest{
142+ ServiceName: azInstance.ServiceName,
143+ })
144+ if err != nil {
145+ return err
146+ }
147+
148+ for _, deployment := range deployments {
149+ for _, role := range deployment.RoleList {
150+ request := &gwacl.AddRoleEndpointsRequest{
151+ ServiceName: azInstance.ServiceName,
152+ DeploymentName: deployment.Name,
153+ RoleName: role.RoleName,
154+ }
155+ for _, port := range ports {
156+ request.InputEndpoints = append(
157+ request.InputEndpoints, gwacl.InputEndpoint{
158+ LocalPort: port.Number,
159+ Name: fmt.Sprintf("%s%d", port.Protocol, port.Number),
160+ Port: port.Number,
161+ Protocol: port.Protocol,
162+ })
163+ }
164+ err := context.AddRoleEndpoints(request)
165+ if err != nil {
166+ return err
167+ }
168+ }
169+ }
170+
171 return nil
172 }
173
174
175=== modified file 'environs/azure/instance_test.go'
176--- environs/azure/instance_test.go 2013-07-16 03:10:34 +0000
177+++ environs/azure/instance_test.go 2013-07-30 23:14:25 +0000
178@@ -9,7 +9,9 @@
179 . "launchpad.net/gocheck"
180 "launchpad.net/gwacl"
181
182+ "fmt"
183 "launchpad.net/juju-core/instance"
184+ "net/http"
185 )
186
187 type InstanceSuite struct{}
188@@ -26,7 +28,7 @@
189 func (*StorageSuite) TestId(c *C) {
190 serviceName := "test-name"
191 testService := makeHostedServiceDescriptor(serviceName)
192- azInstance := azureInstance{*testService}
193+ azInstance := azureInstance{*testService, nil}
194 c.Check(azInstance.Id(), Equals, instance.Id(serviceName))
195 }
196
197@@ -34,7 +36,7 @@
198 // An instance's DNS name is computed from its hosted-service name.
199 host := "hostname"
200 testService := makeHostedServiceDescriptor(host)
201- azInstance := azureInstance{*testService}
202+ azInstance := azureInstance{*testService, nil}
203 dnsName, err := azInstance.DNSName()
204 c.Assert(err, IsNil)
205 c.Check(dnsName, Equals, host+"."+AZURE_DOMAIN_NAME)
206@@ -45,8 +47,196 @@
207 // waiting involved.
208 host := "hostname"
209 testService := makeHostedServiceDescriptor(host)
210- azInstance := azureInstance{*testService}
211+ azInstance := azureInstance{*testService, nil}
212 dnsName, err := azInstance.WaitDNSName()
213 c.Assert(err, IsNil)
214 c.Check(dnsName, Equals, host+"."+AZURE_DOMAIN_NAME)
215 }
216+
217+func makeRole(name string) gwacl.Role {
218+ return gwacl.Role{
219+ RoleName: name,
220+ ConfigurationSets: []gwacl.ConfigurationSet{
221+ {ConfigurationSetType: gwacl.CONFIG_SET_NETWORK},
222+ },
223+ }
224+}
225+
226+func makeDeployment(name string, roles ...gwacl.Role) gwacl.Deployment {
227+ return gwacl.Deployment{
228+ Name: name,
229+ RoleList: roles,
230+ }
231+}
232+
233+func makeInputEndpoint(port int, protocol string) gwacl.InputEndpoint {
234+ return gwacl.InputEndpoint{
235+ LocalPort: port,
236+ Name: fmt.Sprintf("%s%d", protocol, port),
237+ Port: port,
238+ Protocol: protocol,
239+ }
240+}
241+
242+func serialize(c *C, object gwacl.AzureObject) []byte {
243+ xml, err := object.Serialize()
244+ c.Assert(err, IsNil)
245+ return []byte(xml)
246+}
247+
248+func (*StorageSuite) TestOpenPorts(c *C) {
249+ service := makeHostedServiceDescriptor("service-name")
250+ deployments := []gwacl.Deployment{
251+ makeDeployment("deployment-one", makeRole("role-one"), makeRole("role-two")),
252+ makeDeployment("deployment-two", makeRole("role-three")),
253+ }
254+ responses := []gwacl.DispatcherResponse{
255+ // First, GetHostedServiceProperties
256+ gwacl.NewDispatcherResponse(
257+ serialize(c, &gwacl.HostedService{
258+ Deployments: deployments,
259+ HostedServiceDescriptor: *service,
260+ XMLNS: gwacl.XMLNS,
261+ }),
262+ http.StatusOK, nil),
263+ }
264+ for _, deployment := range deployments {
265+ for _, role := range deployment.RoleList {
266+ // GetRole returns a PersistentVMRole.
267+ persistentRole := &gwacl.PersistentVMRole{
268+ XMLNS: gwacl.XMLNS,
269+ RoleName: role.RoleName,
270+ ConfigurationSets: role.ConfigurationSets,
271+ }
272+ responses = append(responses, gwacl.NewDispatcherResponse(
273+ serialize(c, persistentRole), http.StatusOK, nil))
274+ // UpdateRole expects a 200 response, that's all.
275+ responses = append(responses,
276+ gwacl.NewDispatcherResponse(nil, http.StatusOK, nil))
277+ }
278+ }
279+ record := gwacl.PatchManagementAPIResponses(responses)
280+ env := makeEnviron(c)
281+ azInstance := azureInstance{*service, env}
282+
283+ err := azInstance.OpenPorts("machine-id", []instance.Port{
284+ {"tcp", 79}, {"tcp", 587}, {"udp", 9},
285+ })
286+
287+ c.Assert(err, IsNil)
288+ expected := []struct {
289+ method string
290+ urlpattern string
291+ }{
292+ {"GET", ".*/services/hostedservices/service-name[?].*"}, // GetHostedServiceProperties
293+ {"GET", ".*/deployments/deployment-one/roles/role-one"}, // GetRole
294+ {"PUT", ".*/deployments/deployment-one/roles/role-one"}, // UpdateRole
295+ {"GET", ".*/deployments/deployment-one/roles/role-two"}, // GetRole
296+ {"PUT", ".*/deployments/deployment-one/roles/role-two"}, // UpdateRole
297+ {"GET", ".*/deployments/deployment-two/roles/role-three"}, // GetRole
298+ {"PUT", ".*/deployments/deployment-two/roles/role-three"}, // UpdateRole
299+ }
300+ c.Assert(*record, HasLen, len(expected))
301+ for index, request := range *record {
302+ c.Check(request.Method, Equals, expected[index].method)
303+ c.Check(request.URL, Matches, expected[index].urlpattern)
304+ }
305+
306+ // A representative UpdateRole payload includes configuration for the
307+ // ports requested.
308+ role := &gwacl.PersistentVMRole{}
309+ err = role.Deserialize((*record)[2].Payload)
310+ c.Assert(err, IsNil)
311+ c.Check(
312+ *(role.ConfigurationSets[0].InputEndpoints),
313+ DeepEquals, []gwacl.InputEndpoint{
314+ makeInputEndpoint(79, "tcp"),
315+ makeInputEndpoint(587, "tcp"),
316+ makeInputEndpoint(9, "udp"),
317+ })
318+}
319+
320+func (*StorageSuite) TestOpenPortsFailsWhenUnableToGetServiceProperties(c *C) {
321+ service := makeHostedServiceDescriptor("service-name")
322+ responses := []gwacl.DispatcherResponse{
323+ // GetHostedServiceProperties breaks.
324+ gwacl.NewDispatcherResponse(nil, http.StatusInternalServerError, nil),
325+ }
326+ record := gwacl.PatchManagementAPIResponses(responses)
327+ env := makeEnviron(c)
328+ azInstance := azureInstance{*service, env}
329+
330+ err := azInstance.OpenPorts("machine-id", []instance.Port{
331+ {"tcp", 79}, {"tcp", 587}, {"udp", 9},
332+ })
333+
334+ c.Check(err, ErrorMatches, "GET request failed [(]500: Internal Server Error[)]")
335+ c.Check(*record, HasLen, 1)
336+}
337+
338+func (*StorageSuite) TestOpenPortsFailsWhenUnableToGetRole(c *C) {
339+ service := makeHostedServiceDescriptor("service-name")
340+ deployments := []gwacl.Deployment{
341+ makeDeployment("deployment-one", makeRole("role-one")),
342+ }
343+ responses := []gwacl.DispatcherResponse{
344+ // First, GetHostedServiceProperties
345+ gwacl.NewDispatcherResponse(
346+ serialize(c, &gwacl.HostedService{
347+ Deployments: deployments,
348+ HostedServiceDescriptor: *service,
349+ XMLNS: gwacl.XMLNS,
350+ }),
351+ http.StatusOK, nil),
352+ // Second, GetRole fails
353+ gwacl.NewDispatcherResponse(
354+ nil, http.StatusInternalServerError, nil),
355+ }
356+ record := gwacl.PatchManagementAPIResponses(responses)
357+ env := makeEnviron(c)
358+ azInstance := azureInstance{*service, env}
359+
360+ err := azInstance.OpenPorts("machine-id", []instance.Port{
361+ {"tcp", 79}, {"tcp", 587}, {"udp", 9},
362+ })
363+
364+ c.Check(err, ErrorMatches, "GET request failed [(]500: Internal Server Error[)]")
365+ c.Check(*record, HasLen, 2)
366+}
367+
368+func (*StorageSuite) TestOpenPortsFailsWhenUnableToUpdateRole(c *C) {
369+ service := makeHostedServiceDescriptor("service-name")
370+ deployments := []gwacl.Deployment{
371+ makeDeployment("deployment-one", makeRole("role-one")),
372+ }
373+ responses := []gwacl.DispatcherResponse{
374+ // First, GetHostedServiceProperties
375+ gwacl.NewDispatcherResponse(
376+ serialize(c, &gwacl.HostedService{
377+ Deployments: deployments,
378+ HostedServiceDescriptor: *service,
379+ XMLNS: gwacl.XMLNS,
380+ }),
381+ http.StatusOK, nil),
382+ // Seconds, GetRole
383+ gwacl.NewDispatcherResponse(
384+ serialize(c, &gwacl.PersistentVMRole{
385+ XMLNS: gwacl.XMLNS,
386+ RoleName: "role-one",
387+ }),
388+ http.StatusOK, nil),
389+ // Third, UpdateRole fails
390+ gwacl.NewDispatcherResponse(
391+ nil, http.StatusInternalServerError, nil),
392+ }
393+ record := gwacl.PatchManagementAPIResponses(responses)
394+ env := makeEnviron(c)
395+ azInstance := azureInstance{*service, env}
396+
397+ err := azInstance.OpenPorts("machine-id", []instance.Port{
398+ {"tcp", 79}, {"tcp", 587}, {"udp", 9},
399+ })
400+
401+ c.Check(err, ErrorMatches, "PUT request failed [(]500: Internal Server Error[)]")
402+ c.Check(*record, HasLen, 3)
403+}

Subscribers

People subscribed via source and target branches

to status/vote changes: