Merge lp:~axwalk/gomaasapi/maas-testserver-zones into lp:gomaasapi

Proposed by Andrew Wilkins
Status: Merged
Approved by: Andrew Wilkins
Approved revision: 56
Merged at revision: 56
Proposed branch: lp:~axwalk/gomaasapi/maas-testserver-zones
Merge into: lp:gomaasapi
Diff against target: 286 lines (+174/-14)
2 files modified
testservice.go (+93/-14)
testservice_test.go (+81/-0)
To merge this branch: bzr merge lp:~axwalk/gomaasapi/maas-testserver-zones
Reviewer Review Type Date Requested Status
Ian Booth Approve
Juju Engineering Pending
Review via email: mp+236447@code.launchpad.net

Commit message

Add "zones" handler to test service, so we can simulate MAAS installations with and without support for physical zones. This will be used in Juju to test zone placement and distribution.

Also, filter nodes when acquiring (by name, or by zone).

Description of the change

Add "zones" handler to test service, so we can simulate MAAS installations with and without support for physical zones. This will be used in Juju to test zone placement and distribution.

Also, filter nodes when acquiring (by name, or by zone).

To post a comment you must log in.
Revision history for this message
Ian Booth (wallyworld) wrote :

There needs to be tests added to testservice_test.go for the new zones functionality.
I also don't understand how the agent_name filtering works.

Revision history for this message
Andrew Wilkins (axwalk) wrote :

Will add tests.

56. By Andrew Wilkins

Add zones tests

Revision history for this message
Ian Booth (wallyworld) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'testservice.go'
2--- testservice.go 2014-09-15 04:14:56 +0000
3+++ testservice.go 2014-10-15 03:13:00 +0000
4@@ -75,6 +75,7 @@
5 version string
6 macAddressesPerNetwork map[string]map[string]JSONObject
7 nodeDetails map[string]string
8+ zones map[string]JSONObject
9 // bootImages is a map of nodegroup UUIDs to boot-image objects.
10 bootImages map[string][]JSONObject
11 }
12@@ -146,6 +147,10 @@
13 return regexp.MustCompile(reString)
14 }
15
16+func getZonesEndpoint(version string) string {
17+ return fmt.Sprintf("/api/%s/zones/", version)
18+}
19+
20 // Clear clears all the fake data stored and recorded by the test server
21 // (nodes, recorded operations, etc.).
22 func (server *TestServer) Clear() {
23@@ -161,6 +166,7 @@
24 server.macAddressesPerNetwork = make(map[string]map[string]JSONObject)
25 server.nodeDetails = make(map[string]string)
26 server.bootImages = make(map[string][]JSONObject)
27+ server.zones = make(map[string]JSONObject)
28 }
29
30 // NodesOperations returns the list of operations performed at the /nodes/
31@@ -190,26 +196,25 @@
32
33 func parseRequestValues(request *http.Request) url.Values {
34 var requestValues url.Values
35- if request.Body != nil && request.Header.Get("Content-Type") == "application/x-www-form-urlencoded" {
36- body, err := readAndClose(request.Body)
37- if err != nil {
38- panic(err)
39- }
40- requestValues, err = url.ParseQuery(string(body))
41- if err != nil {
42- panic(err)
43- }
44+ if request.Header.Get("Content-Type") == "application/x-www-form-urlencoded" {
45+ if request.PostForm == nil {
46+ if err := request.ParseForm(); err != nil {
47+ panic(err)
48+ }
49+ }
50+ requestValues = request.PostForm
51 }
52 return requestValues
53 }
54
55-func (server *TestServer) addNodesOperation(operation string, request *http.Request) {
56+func (server *TestServer) addNodesOperation(operation string, request *http.Request) url.Values {
57 requestValues := parseRequestValues(request)
58 server.nodesOperations = append(server.nodesOperations, operation)
59 server.nodesOperationRequestValues = append(server.nodesOperationRequestValues, requestValues)
60+ return requestValues
61 }
62
63-func (server *TestServer) addNodeOperation(systemId, operation string, request *http.Request) {
64+func (server *TestServer) addNodeOperation(systemId, operation string, request *http.Request) url.Values {
65 operations, present := server.nodeOperations[systemId]
66 operationRequestValues, present2 := server.nodeOperationRequestValues[systemId]
67 if present != present2 {
68@@ -225,6 +230,7 @@
69 }
70 server.nodeOperations[systemId] = operations
71 server.nodeOperationRequestValues[systemId] = operationRequestValues
72+ return requestValues
73 }
74
75 // NewNode creates a MAAS node. The provided string should be a valid json
76@@ -367,6 +373,16 @@
77 server.bootImages[nodegroupUUID] = append(server.bootImages[nodegroupUUID], obj)
78 }
79
80+// AddZone adds a physical zone to the server.
81+func (server *TestServer) AddZone(name, description string) {
82+ attrs := map[string]interface{}{
83+ "name": name,
84+ "description": description,
85+ }
86+ obj := maasify(server.client, attrs)
87+ server.zones[name] = obj
88+}
89+
90 // NewTestServer starts and returns a new MAAS test server. The caller should call Close when finished, to shut it down.
91 func NewTestServer(version string) *TestServer {
92 server := &TestServer{version: version}
93@@ -397,6 +413,11 @@
94 serveMux.HandleFunc(nodegroupsURL, func(w http.ResponseWriter, r *http.Request) {
95 nodegroupsHandler(server, w, r)
96 })
97+ // Register handler for '/api/<version>/zones/*'.
98+ zonesURL := getZonesEndpoint(server.version)
99+ serveMux.HandleFunc(zonesURL, func(w http.ResponseWriter, r *http.Request) {
100+ zonesHandler(server, w, r)
101+ })
102
103 newServer := httptest.NewServer(serveMux)
104 client, err := NewAnonymousClient(newServer.URL, "1.0")
105@@ -500,20 +521,53 @@
106 fmt.Fprint(w, string(res))
107 }
108
109-// findFreeNode looks for a node that is currently available.
110-func findFreeNode(server *TestServer) *MAASObject {
111+// findFreeNode looks for a node that is currently available, and
112+// matches the specified filter.
113+func findFreeNode(server *TestServer, filter url.Values) *MAASObject {
114 for systemID, node := range server.Nodes() {
115 _, present := server.OwnedNodes()[systemID]
116 if !present {
117+ var agentName, nodeName, zoneName string
118+ for k := range filter {
119+ switch k {
120+ case "agent_name":
121+ agentName = filter.Get(k)
122+ case "name":
123+ nodeName = filter.Get(k)
124+ case "zone":
125+ zoneName = filter.Get(k)
126+ }
127+ }
128+ if nodeName != "" && !matchField(node, "hostname", nodeName) {
129+ continue
130+ }
131+ if zoneName != "" && !matchField(node, "zone", zoneName) {
132+ continue
133+ }
134+ if agentName != "" {
135+ agentNameObj := maasify(server.client, agentName)
136+ node.GetMap()["agent_name"] = agentNameObj
137+ } else {
138+ delete(node.GetMap(), "agent_name")
139+ }
140 return &node
141 }
142 }
143 return nil
144 }
145
146+func matchField(node MAASObject, k, v string) bool {
147+ field, err := node.GetField(k)
148+ if err != nil {
149+ return false
150+ }
151+ return field == v
152+}
153+
154 // nodesAcquireHandler simulates acquiring a node.
155 func nodesAcquireHandler(server *TestServer, w http.ResponseWriter, r *http.Request) {
156- node := findFreeNode(server)
157+ requestValues := server.addNodesOperation("acquire", r)
158+ node := findFreeNode(server, requestValues)
159 if node == nil {
160 w.WriteHeader(http.StatusConflict)
161 } else {
162@@ -888,3 +942,28 @@
163 w.WriteHeader(http.StatusOK)
164 fmt.Fprint(w, string(res))
165 }
166+
167+// zonesHandler handles requests for '/api/<version>/zones/'.
168+func zonesHandler(server *TestServer, w http.ResponseWriter, r *http.Request) {
169+ if r.Method != "GET" {
170+ w.WriteHeader(http.StatusBadRequest)
171+ return
172+ }
173+
174+ if len(server.zones) == 0 {
175+ // Until a zone is registered, behave as if the endpoint
176+ // does not exist. This way we can simulate older MAAS
177+ // servers that do not support zones.
178+ http.NotFoundHandler().ServeHTTP(w, r)
179+ return
180+ }
181+
182+ zones := make([]JSONObject, 0, len(server.zones))
183+ for _, zone := range server.zones {
184+ zones = append(zones, zone)
185+ }
186+ res, err := json.Marshal(zones)
187+ checkError(err)
188+ w.WriteHeader(http.StatusOK)
189+ fmt.Fprint(w, string(res))
190+}
191
192=== modified file 'testservice_test.go'
193--- testservice_test.go 2014-09-15 05:07:10 +0000
194+++ testservice_test.go 2014-10-15 03:13:00 +0000
195@@ -357,6 +357,16 @@
196 c.Check(suite.server.Files(), DeepEquals, map[string]MAASObject{})
197 }
198
199+func (suite *TestServerSuite) TestListZonesNotSupported(c *C) {
200+ // Older versions of MAAS do not support zones. We simulate
201+ // this behaviour by returning 404 if no zones are defined.
202+ zonesURL := getZonesEndpoint(suite.server.version)
203+ resp, err := http.Get(suite.server.Server.URL + zonesURL)
204+
205+ c.Check(err, IsNil)
206+ c.Check(resp.StatusCode, Equals, http.StatusNotFound)
207+}
208+
209 // TestMAASObjectSuite validates that the object created by
210 // NewTestMAAS can be used by the gomaasapi library as if it were a real
211 // MAAS server.
212@@ -845,3 +855,74 @@
213 sort.Strings(bootImages)
214 c.Assert(bootImages, DeepEquals, expectedBootImages)
215 }
216+
217+func (suite *TestMAASObjectSuite) TestListZones(c *C) {
218+ expected := map[string]string{
219+ "zone0": "zone0 is very nice",
220+ "zone1": "zone1 is much nicer than zone0",
221+ }
222+ for name, desc := range expected {
223+ suite.TestMAASObject.TestServer.AddZone(name, desc)
224+ }
225+
226+ result, err := suite.TestMAASObject.GetSubObject("zones").CallGet("", nil)
227+ c.Assert(err, IsNil)
228+ c.Assert(result, NotNil)
229+
230+ list, err := result.GetArray()
231+ c.Assert(err, IsNil)
232+ c.Assert(list, HasLen, len(expected))
233+
234+ m := make(map[string]string)
235+ for _, item := range list {
236+ itemMap, err := item.GetMap()
237+ c.Assert(err, IsNil)
238+ name, err := itemMap["name"].GetString()
239+ c.Assert(err, IsNil)
240+ desc, err := itemMap["description"].GetString()
241+ c.Assert(err, IsNil)
242+ m[name] = desc
243+ }
244+ c.Assert(m, DeepEquals, expected)
245+}
246+
247+func (suite *TestMAASObjectSuite) TestAcquireNodeZone(c *C) {
248+ suite.TestMAASObject.TestServer.AddZone("z0", "rox")
249+ suite.TestMAASObject.TestServer.AddZone("z1", "sux")
250+ suite.TestMAASObject.TestServer.NewNode(`{"system_id": "n0", "zone": "z0"}`)
251+ suite.TestMAASObject.TestServer.NewNode(`{"system_id": "n1", "zone": "z1"}`)
252+ suite.TestMAASObject.TestServer.NewNode(`{"system_id": "n2", "zone": "z1"}`)
253+ nodesObj := suite.TestMAASObject.GetSubObject("nodes")
254+
255+ acquire := func(zone string) (string, string, error) {
256+ var params url.Values
257+ if zone != "" {
258+ params = url.Values{"zone": []string{zone}}
259+ }
260+ jsonResponse, err := nodesObj.CallPost("acquire", params)
261+ if err != nil {
262+ return "", "", err
263+ }
264+ acquiredNode, err := jsonResponse.GetMAASObject()
265+ c.Assert(err, IsNil)
266+ systemId, err := acquiredNode.GetField("system_id")
267+ c.Assert(err, IsNil)
268+ assignedZone, err := acquiredNode.GetField("zone")
269+ c.Assert(err, IsNil)
270+ if zone != "" {
271+ c.Assert(assignedZone, Equals, zone)
272+ }
273+ return systemId, assignedZone, nil
274+ }
275+
276+ id, _, err := acquire("z0")
277+ c.Assert(err, IsNil)
278+ c.Assert(id, Equals, "n0")
279+ id, _, err = acquire("z0")
280+ c.Assert(err.(ServerError).StatusCode, Equals, http.StatusConflict)
281+
282+ id, zone, err := acquire("")
283+ c.Assert(err, IsNil)
284+ c.Assert(id, Not(Equals), "n0")
285+ c.Assert(zone, Equals, "z1")
286+}

Subscribers

People subscribed via source and target branches

to all changes: