David K. Bainbridge | 528b318 | 2017-01-23 08:51:59 -0800 | [diff] [blame] | 1 | // Copyright 2012-2016 Canonical Ltd. |
| 2 | // Licensed under the LGPLv3, see LICENCE file for details. |
| 3 | |
| 4 | package gomaasapi |
| 5 | |
| 6 | import ( |
| 7 | "encoding/json" |
| 8 | "fmt" |
| 9 | "io" |
| 10 | "net/http" |
| 11 | "net/url" |
| 12 | "regexp" |
| 13 | ) |
| 14 | |
| 15 | func getSpacesEndpoint(version string) string { |
| 16 | return fmt.Sprintf("/api/%s/spaces/", version) |
| 17 | } |
| 18 | |
| 19 | // TestSpace is the MAAS API space representation |
| 20 | type TestSpace struct { |
| 21 | Name string `json:"name"` |
| 22 | Subnets []TestSubnet `json:"subnets"` |
| 23 | ResourceURI string `json:"resource_uri"` |
| 24 | ID uint `json:"id"` |
| 25 | } |
| 26 | |
| 27 | // spacesHandler handles requests for '/api/<version>/spaces/'. |
| 28 | func spacesHandler(server *TestServer, w http.ResponseWriter, r *http.Request) { |
| 29 | values, err := url.ParseQuery(r.URL.RawQuery) |
| 30 | checkError(err) |
| 31 | op := values.Get("op") |
| 32 | if op != "" { |
| 33 | w.WriteHeader(http.StatusBadRequest) |
| 34 | return |
| 35 | } |
| 36 | |
| 37 | spacesURLRE := regexp.MustCompile(`/spaces/(.+?)/`) |
| 38 | spacesURLMatch := spacesURLRE.FindStringSubmatch(r.URL.Path) |
| 39 | spacesURL := getSpacesEndpoint(server.version) |
| 40 | |
| 41 | var ID uint |
| 42 | var gotID bool |
| 43 | if spacesURLMatch != nil { |
| 44 | ID, err = NameOrIDToID(spacesURLMatch[1], server.spaceNameToID, 1, uint(len(server.spaces))) |
| 45 | |
| 46 | if err != nil { |
| 47 | http.NotFoundHandler().ServeHTTP(w, r) |
| 48 | return |
| 49 | } |
| 50 | |
| 51 | gotID = true |
| 52 | } |
| 53 | |
| 54 | switch r.Method { |
| 55 | case "GET": |
| 56 | w.Header().Set("Content-Type", "application/vnd.api+json") |
| 57 | if len(server.spaces) == 0 { |
| 58 | // Until a space is registered, behave as if the endpoint |
| 59 | // does not exist. This way we can simulate older MAAS |
| 60 | // servers that do not support spaces. |
| 61 | http.NotFoundHandler().ServeHTTP(w, r) |
| 62 | return |
| 63 | } |
| 64 | |
| 65 | if r.URL.Path == spacesURL { |
| 66 | var spaces []*TestSpace |
| 67 | // Iterating by id rather than a dictionary iteration |
| 68 | // preserves the order of the spaces in the result. |
| 69 | for i := uint(1); i < server.nextSpace; i++ { |
| 70 | s, ok := server.spaces[i] |
| 71 | if ok { |
| 72 | server.setSubnetsOnSpace(s) |
| 73 | spaces = append(spaces, s) |
| 74 | } |
| 75 | } |
| 76 | err = json.NewEncoder(w).Encode(spaces) |
| 77 | } else if gotID == false { |
| 78 | w.WriteHeader(http.StatusBadRequest) |
| 79 | } else { |
| 80 | err = json.NewEncoder(w).Encode(server.spaces[ID]) |
| 81 | } |
| 82 | checkError(err) |
| 83 | case "POST": |
| 84 | //server.NewSpace(r.Body) |
| 85 | case "PUT": |
| 86 | //server.UpdateSpace(r.Body) |
| 87 | case "DELETE": |
| 88 | delete(server.spaces, ID) |
| 89 | w.WriteHeader(http.StatusOK) |
| 90 | default: |
| 91 | w.WriteHeader(http.StatusBadRequest) |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | // CreateSpace is used to create new spaces on the server. |
| 96 | type CreateSpace struct { |
| 97 | Name string `json:"name"` |
| 98 | } |
| 99 | |
| 100 | func decodePostedSpace(spaceJSON io.Reader) CreateSpace { |
| 101 | var postedSpace CreateSpace |
| 102 | decoder := json.NewDecoder(spaceJSON) |
| 103 | err := decoder.Decode(&postedSpace) |
| 104 | checkError(err) |
| 105 | return postedSpace |
| 106 | } |
| 107 | |
| 108 | // NewSpace creates a space in the test server |
| 109 | func (server *TestServer) NewSpace(spaceJSON io.Reader) *TestSpace { |
| 110 | postedSpace := decodePostedSpace(spaceJSON) |
| 111 | newSpace := &TestSpace{Name: postedSpace.Name} |
| 112 | newSpace.ID = server.nextSpace |
| 113 | newSpace.ResourceURI = fmt.Sprintf("/api/%s/spaces/%d/", server.version, int(server.nextSpace)) |
| 114 | server.spaces[server.nextSpace] = newSpace |
| 115 | server.spaceNameToID[newSpace.Name] = newSpace.ID |
| 116 | |
| 117 | server.nextSpace++ |
| 118 | return newSpace |
| 119 | } |
| 120 | |
| 121 | // setSubnetsOnSpace fetches the subnets for the specified space and adds them |
| 122 | // to it. |
| 123 | func (server *TestServer) setSubnetsOnSpace(space *TestSpace) { |
| 124 | subnets := []TestSubnet{} |
| 125 | for i := uint(1); i < server.nextSubnet; i++ { |
| 126 | subnet, ok := server.subnets[i] |
| 127 | if ok && subnet.Space == space.Name { |
| 128 | subnets = append(subnets, subnet) |
| 129 | } |
| 130 | } |
| 131 | space.Subnets = subnets |
| 132 | } |