diff --git a/testservice.go b/testservice.go index 5177b89..c5cdbb1 100644 --- a/testservice.go +++ b/testservice.go @@ -82,8 +82,10 @@ type TestServer struct { ipAddressesPerNetwork map[string][]string version string macAddressesPerNetwork map[string]map[string]JSONObject + tagsPerNode map[string][]string nodeDetails map[string]string zones map[string]JSONObject + tags map[string]JSONObject // bootImages is a map of nodegroup UUIDs to boot-image objects. bootImages map[string][]JSONObject // nodegroupsInterfaces is a map of nodegroup UUIDs to interface @@ -209,6 +211,19 @@ func getZonesEndpoint(version string) string { return fmt.Sprintf("/api/%s/zones/", version) } +func getTagsEndpoint(version string) string { + return fmt.Sprintf("/api/%s/tags/", version) +} + +func getTagURL(version, tag_name string) string { + return fmt.Sprintf("/api/%s/tags/%s/", version, tag_name) +} + +func getTagURLRE(version string) *regexp.Regexp { + reString := fmt.Sprintf("^/api/%s/tags/([^/]*)/$", regexp.QuoteMeta(version)) + return regexp.MustCompile(reString) +} + // Clear clears all the fake data stored and recorded by the test server // (nodes, recorded operations, etc.). func (server *TestServer) Clear() { @@ -223,11 +238,13 @@ func (server *TestServer) Clear() { server.networks = make(map[string]MAASObject) server.networksPerNode = make(map[string][]string) server.ipAddressesPerNetwork = make(map[string][]string) + server.tagsPerNode = make(map[string][]string) server.macAddressesPerNetwork = make(map[string]map[string]JSONObject) server.nodeDetails = make(map[string]string) server.bootImages = make(map[string][]JSONObject) server.nodegroupsInterfaces = make(map[string][]JSONObject) server.zones = make(map[string]JSONObject) + server.tags = make(map[string]JSONObject) server.versionJSON = `{"capabilities": ["networks-management","static-ipaddresses","devices-management","network-deployment-ubuntu"]}` server.devices = make(map[string]*TestDevice) server.subnets = make(map[uint]TestSubnet) @@ -546,6 +563,17 @@ func (server *TestServer) AddZone(name, description string) { server.zones[name] = obj } +// AddTah adds a tag to the server. +func (server *TestServer) AddTag(name, comment string) { + attrs := map[string]interface{}{ + "name": name, + "comment": comment, + resourceURI: getTagURL(server.version, name), + } + obj := maasify(server.client, attrs) + server.tags[name] = obj +} + func (server *TestServer) AddDevice(device *TestDevice) { server.devices[device.SystemId] = device } @@ -601,6 +629,12 @@ func NewTestServer(version string) *TestServer { zonesHandler(server, w, r) }) + // Register handler for '/api//zones/*'. + tagsURL := getTagsEndpoint(server.version) + serveMux.HandleFunc(tagsURL, func(w http.ResponseWriter, r *http.Request) { + tagsHandler(server, w, r) + }) + subnetsURL := getSubnetsEndpoint(server.version) serveMux.HandleFunc(subnetsURL, func(w http.ResponseWriter, r *http.Request) { subnetsHandler(server, w, r) @@ -1015,7 +1049,7 @@ func findFreeNode(server *TestServer, filter url.Values) *MAASObject { for systemID, node := range server.Nodes() { _, present := server.OwnedNodes()[systemID] if !present { - var agentName, nodeName, zoneName, mem, cpuCores, arch string + var agentName, nodeName, zoneName, tagName, mem, cpuCores, arch string for k := range filter { switch k { case "agent_name": @@ -1024,6 +1058,8 @@ func findFreeNode(server *TestServer, filter url.Values) *MAASObject { nodeName = filter.Get(k) case "zone": zoneName = filter.Get(k) + case "tags": + tagName = filter.Get(k) case "mem": mem = filter.Get(k) case "arch": @@ -1038,6 +1074,9 @@ func findFreeNode(server *TestServer, filter url.Values) *MAASObject { if zoneName != "" && !matchField(node, "zone", zoneName) { continue } + if tagName != "" && !matchField(node, "tag_names", tagName) { + continue + } if mem != "" && !matchNumericField(node, "memory", mem) { continue } @@ -1679,3 +1718,158 @@ func zonesHandler(server *TestServer, w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) fmt.Fprint(w, string(res)) } + +// tagsHandler handles requests for '/api//tags/'. +func tagsHandler(server *TestServer, w http.ResponseWriter, r *http.Request) { + tagURLRE := getTagURLRE(server.version) + tagURLMatch := tagURLRE.FindStringSubmatch(r.URL.Path) + tagsURL := getTagsEndpoint(server.version) + err := r.ParseForm() + checkError(err) + values := r.PostForm + names, hasName := getValues(values, "name") + quary, err := url.ParseQuery(r.URL.RawQuery) + checkError(err) + op := quary.Get("op") + if r.URL.Path == tagsURL { + if r.Method == "GET" { + tags := make([]JSONObject, 0, len(server.zones)) + for _, tag := range server.tags { + tags = append(tags, tag) + } + res, err := json.MarshalIndent(tags, "", " ") + checkError(err) + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, string(res)) + } else if r.Method == "POST" && hasName { + if op == "" || op == "new" { + for _, name := range names { + newTagHandler(server, w, r, name, values) + } + } else { + w.WriteHeader(http.StatusBadRequest) + } + } else { + w.WriteHeader(http.StatusBadRequest) + } + } else if tagURLMatch != nil { + // Request for a single tag + tagHandler(server, w, r, tagURLMatch[1], op, values) + } else { + http.NotFoundHandler().ServeHTTP(w, r) + } +} + +// newTagHandler creates, stores and returns new tag. +func newTagHandler(server *TestServer, w http.ResponseWriter, r *http.Request, name string, values url.Values) { + comment, hascomment := getValue(values, "comment") + var attrs map[string]interface{} + if hascomment { + attrs = map[string]interface{}{ + "name": name, + "comment": comment, + resourceURI: getTagURL(server.version, name), + } + } else { + attrs = map[string]interface{}{ + "name": name, + resourceURI: getTagURL(server.version, name), + } + } + obj := maasify(server.client, attrs) + server.tags[name] = obj + res, err := json.MarshalIndent(obj, "", " ") + checkError(err) + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, res) +} + +// tagHandler handles requests for '/api//tag//'. +func tagHandler(server *TestServer, w http.ResponseWriter, r *http.Request, name string, operation string, values url.Values) { + switch r.Method { + case "GET": + switch operation { + case "node": + var convertedNodes = []map[string]JSONObject{} + for systemID, node := range server.nodes { + for _, nodetag := range server.tagsPerNode[systemID] { + if name == nodetag { + convertedNodes = append(convertedNodes, node.GetMap()) + } + } + } + res, err := json.MarshalIndent(convertedNodes, "", " ") + checkError(err) + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, string(res)) + default: + res, err := json.MarshalIndent(server.tags[name], "", " ") + checkError(err) + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, string(res)) + } + case "POST": + if operation == "update_nodes" { + addNodes, hasAdd := getValues(values, "add") + delNodes, hasRemove := getValues(values, "remove") + addremovecount := map[string]int{"add": len(addNodes), "remove": len(delNodes)} + if !hasAdd && !hasRemove { + w.WriteHeader(http.StatusBadRequest) + return + } + for _, systemID := range addNodes { + _, ok := server.nodes[systemID] + if !ok { + w.WriteHeader(http.StatusBadRequest) + return + } + var newTags []string + for _, tag := range server.tagsPerNode[systemID] { + if tag != name { + newTags = append(newTags, tag) + } + } + server.tagsPerNode[systemID] = append(newTags, name) + newTagsObj := make([]JSONObject, len(server.tagsPerNode[systemID])) + for i, tagsofnode := range server.tagsPerNode[systemID] { + newTagsObj[i] = server.tags[tagsofnode] + } + tagNamesObj := JSONObject{ + value: newTagsObj, + } + server.nodes[systemID].values["tag_names"] = tagNamesObj + } + for _, systemID := range delNodes { + _, ok := server.nodes[systemID] + if !ok { + w.WriteHeader(http.StatusBadRequest) + return + } + var newTags []string + for _, tag := range server.tagsPerNode[systemID] { + if tag != name { + newTags = append(newTags, tag) + } + } + server.tagsPerNode[systemID] = newTags + newTagsObj := make([]JSONObject, len(server.tagsPerNode[systemID])) + for i, tagsofnode := range server.tagsPerNode[systemID] { + newTagsObj[i] = server.tags[tagsofnode] + } + tagNamesObj := JSONObject{ + value: newTagsObj, + } + server.nodes[systemID].values["tag_names"] = tagNamesObj + } + res, err := json.MarshalIndent(addremovecount, "", " ") + checkError(err) + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, string(res)) + } + case "PUT": + newTagHandler(server, w, r, name, values) + case "DELETE": + delete(server.tags, name) + w.WriteHeader(http.StatusOK) + } +} diff --git a/testservice_test.go b/testservice_test.go index d5d3fd2..45d1062 100644 --- a/testservice_test.go +++ b/testservice_test.go @@ -1920,6 +1920,36 @@ func (suite *TestMAASObjectSuite) TestListZones(c *C) { c.Assert(m, DeepEquals, expected) } +func (suite *TestMAASObjectSuite) TestListTags(c *C) { + expected := map[string]string{ + "tag0": "Develop", + "tag1": "Lack01", + } + for name, comment := range expected { + suite.TestMAASObject.TestServer.AddTag(name, comment) + } + + result, err := suite.TestMAASObject.GetSubObject("tags").CallGet("", nil) + c.Assert(err, IsNil) + c.Assert(result, NotNil) + + list, err := result.GetArray() + c.Assert(err, IsNil) + c.Assert(list, HasLen, len(expected)) + + m := make(map[string]string) + for _, item := range list { + itemMap, err := item.GetMap() + c.Assert(err, IsNil) + name, err := itemMap["name"].GetString() + c.Assert(err, IsNil) + comment, err := itemMap["comment"].GetString() + c.Assert(err, IsNil) + m[name] = comment + } + c.Assert(m, DeepEquals, expected) +} + func (suite *TestMAASObjectSuite) TestAcquireNodeZone(c *C) { suite.TestMAASObject.TestServer.AddZone("z0", "rox") suite.TestMAASObject.TestServer.AddZone("z1", "sux") @@ -2001,6 +2031,19 @@ func (suite *TestMAASObjectSuite) TestAcquireFilterArch(c *C) { c.Assert(arch, Equals, "arm/generic") } +func (suite *TestMAASObjectSuite) TestAcquireFilterTag(c *C) { + suite.TestMAASObject.TestServer.NewNode(`{"system_id": "n0", "tag_names": "Develop"}`) + suite.TestMAASObject.TestServer.NewNode(`{"system_id": "n1", "tag_names": "GPU"}`) + nodeListing := suite.TestMAASObject.GetSubObject("nodes") + jsonResponse, err := nodeListing.CallPost("acquire", url.Values{"tags": []string{"GPU"}}) + c.Assert(err, IsNil) + acquiredNode, err := jsonResponse.GetMAASObject() + c.Assert(err, IsNil) + fmt.Printf("%v\n", acquiredNode) + tag, _ := acquiredNode.GetField("tag_names") + c.Assert(tag, Equals, "GPU") +} + func (suite *TestMAASObjectSuite) TestDeploymentStatus(c *C) { suite.TestMAASObject.TestServer.NewNode(`{"system_id": "n0", "status": "6"}`) suite.TestMAASObject.TestServer.NewNode(`{"system_id": "n1", "status": "1"}`)