launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #15169
[Merge] lp:~rvb/gomaasapi/gomaasapi-newfilestorage into lp:gomaasapi
Raphaël Badin has proposed merging lp:~rvb/gomaasapi/gomaasapi-newfilestorage into lp:gomaasapi.
Commit message:
Add support for file deletion/file listing in testservice.
Requested reviews:
MAAS Maintainers (maas-maintainers)
For more details, see:
https://code.launchpad.net/~rvb/gomaasapi/gomaasapi-newfilestorage/+merge/148499
- Improve the testservice to support (newly implemented) file listing and file deletion.
- Update the example script.
(start a test instance)
go run example/live_example.go <<EOF
your:api:key
http://0.0.0.0:5240/api/1.0/
EOF
- Drive-by fix: update ManipulateFiles and ManipulateNodes to use pointers (gomaasapi.NewMAAS now returns a pointer to a MAASObject)
--
https://code.launchpad.net/~rvb/gomaasapi/gomaasapi-newfilestorage/+merge/148499
Your team MAAS Maintainers is requested to review the proposed merge of lp:~rvb/gomaasapi/gomaasapi-newfilestorage into lp:gomaasapi.
=== modified file 'example/live_example.go'
--- example/live_example.go 2013-02-13 09:27:09 +0000
+++ example/live_example.go 2013-02-14 16:42:29 +0000
@@ -56,7 +56,7 @@
// ManipulateFiles exercises the /api/1.0/files/ API endpoint. Most precisely,
// it uploads a files and then fetches it, making sure the received content
// is the same as the one that was sent.
-func ManipulateFiles(maas gomaasapi.MAASObject) {
+func ManipulateFiles(maas *gomaasapi.MAASObject) {
files := maas.GetSubObject("files")
fileContent := []byte("test file content")
fileName := "filename"
@@ -78,12 +78,33 @@
panic("Received content differs from the content sent!")
}
fmt.Println("Got file.")
+
+ // Fetch list of files.
+ listFiles, err := files.CallGet("list", url.Values{})
+ checkError(err)
+ listFilesArray, err := listFiles.GetArray()
+ checkError(err)
+ fmt.Printf("We've got %v file(s)\n", len(listFilesArray))
+
+ // Delete the file.
+ fmt.Println("Deleting the file...")
+ fileObject, err := listFilesArray[0].GetMAASObject()
+ checkError(err)
+ errDelete := fileObject.Delete()
+ checkError(errDelete)
+
+ // Count the files.
+ listFiles, err = files.CallGet("list", url.Values{})
+ checkError(err)
+ listFilesArray, err = listFiles.GetArray()
+ checkError(err)
+ fmt.Printf("We've got %v file(s)\n", len(listFilesArray))
}
// ManipulateFiles exercises the /api/1.0/nodes/ API endpoint. Most precisely,
// it lists the existing nodes, creates a new node, updates it and then
// deletes it.
-func ManipulateNodes(maas gomaasapi.MAASObject) {
+func ManipulateNodes(maas *gomaasapi.MAASObject) {
nodeListing := maas.GetSubObject("nodes")
// List nodes.
=== modified file 'testservice.go'
--- testservice.go 2013-02-12 15:19:39 +0000
+++ testservice.go 2013-02-14 16:42:29 +0000
@@ -5,6 +5,7 @@
import (
"bufio"
+ "encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
@@ -13,6 +14,8 @@
"net/http/httptest"
"net/url"
"regexp"
+ "sort"
+ "strings"
)
// TestMAASObject is a fake MAAS server MAASObject.
@@ -46,7 +49,7 @@
client Client
nodes map[string]MAASObject
nodeOperations map[string][]string
- files map[string][]byte
+ files map[string]MAASObject
version string
}
@@ -54,12 +57,16 @@
return fmt.Sprintf("/api/%s/nodes/%s/", version, systemId)
}
+func getFileURI(version, filename string) string {
+ return fmt.Sprintf("/api/%s/files/%s/", version, filename)
+}
+
// Clear clears all the fake data stored and recorded by the test server
// (nodes, recorded operations, etc.).
func (server *TestServer) Clear() {
server.nodes = make(map[string]MAASObject)
server.nodeOperations = make(map[string][]string)
- server.files = make(map[string][]byte)
+ server.files = make(map[string]MAASObject)
}
// NodeOperations returns the map containing the list of the operations
@@ -106,11 +113,18 @@
}
// NewFile creates a file in the test MAAS server.
-func (server *TestServer) NewFile(filename string, filecontent []byte) {
- server.files[filename] = filecontent
+func (server *TestServer) NewFile(filename string, filecontent []byte) MAASObject {
+ attrs := make(map[string]interface{})
+ attrs[resourceURI] = getFileURI(server.version, filename)
+ base64Content := base64.StdEncoding.EncodeToString(filecontent)
+ attrs["content"] = base64Content
+ attrs["filename"] = filename
+ obj := newJSONMAASObject(attrs, server.client)
+ server.files[filename] = obj
+ return obj
}
-func (server *TestServer) Files() map[string][]byte {
+func (server *TestServer) Files() map[string]MAASObject {
return server.files
}
@@ -136,6 +150,11 @@
return fmt.Sprintf("/api/%s/files/", version)
}
+func getFileURLRE(version string) *regexp.Regexp {
+ reString := fmt.Sprintf("^/api/%s/files/([^/]*)/$", version)
+ return regexp.MustCompile(reString)
+}
+
// NewTestServer starts and returns a new MAAS test server. The caller should call Close when finished, to shut it down.
func NewTestServer(version string) *TestServer {
server := &TestServer{version: version}
@@ -147,7 +166,7 @@
nodesHandler(server, w, r)
})
filesURL := getFilesURL(server.version)
- // Register handler for '/api/<version>/files/'.
+ // Register handler for '/api/<version>/files/*'.
serveMux.HandleFunc(filesURL, func(w http.ResponseWriter, r *http.Request) {
filesHandler(server, w, r)
})
@@ -244,31 +263,88 @@
fmt.Fprint(w, string(res))
}
-// filesHandler handles requests for '/api/<version>/files/'.
+// filesHandler handles requests for '/api/<version>/files/*'.
func filesHandler(server *TestServer, w http.ResponseWriter, r *http.Request) {
values, _ := url.ParseQuery(r.URL.RawQuery)
op := values.Get("op")
+ fileURLRE := getFileURLRE(server.version)
+ fileURLMatch := fileURLRE.FindStringSubmatch(r.URL.Path)
+ fileListingURL := getFilesURL(server.version)
switch {
- case op == "get" && r.Method == "GET":
+ case r.Method == "GET" && op == "list" && r.URL.Path == fileListingURL:
+ // File listing operation.
+ fileListingHandler(server, w, r)
+ case op == "get" && r.Method == "GET" && r.URL.Path == fileListingURL:
getFileHandler(server, w, r)
- case op == "add" && r.Method == "POST":
+ case op == "add" && r.Method == "POST" && r.URL.Path == fileListingURL:
addFileHandler(server, w, r)
- default:
- // Default handler: not found.
- http.NotFoundHandler().ServeHTTP(w, r)
- }
-
-}
-
-// filesHandler handles requests for '/api/<version>/files/?op=get&filename=filename'.
+ case fileURLMatch != nil:
+ // Request for a single file.
+ fileHandler(server, w, r, fileURLMatch[1], op)
+ default:
+ // Default handler: not found.
+ http.NotFoundHandler().ServeHTTP(w, r)
+ }
+
+}
+
+// fileListingHandler handles requests for '/api/<version>/files/?op=list'.
+func fileListingHandler(server *TestServer, w http.ResponseWriter, r *http.Request) {
+ values, _ := url.ParseQuery(r.URL.RawQuery)
+ prefix := values.Get("prefix")
+ var convertedFiles = []map[string]JSONObject{}
+ // Create slice of selected filenames.
+ var filenames = []string{}
+ for filename, _ := range server.files {
+ if strings.Index(filename, prefix) == 0 {
+ filenames = append(filenames, filename)
+ }
+ }
+ // Sort filenames.
+ sort.Strings(filenames)
+ // Build result.
+ for _, filename := range filenames {
+ file, _ := server.files[filename]
+ fileMap := file.GetMap()
+ delete(fileMap, "content")
+ convertedFiles = append(convertedFiles, fileMap)
+ }
+ res, _ := json.Marshal(convertedFiles)
+ w.WriteHeader(http.StatusOK)
+ fmt.Fprint(w, string(res))
+}
+
+// fileHandler handles requests for '/api/<version>/files/<filename>/'.
+func fileHandler(server *TestServer, w http.ResponseWriter, r *http.Request, filename string, operation string) {
+ switch {
+ case r.Method == "DELETE":
+ delete(server.files, filename)
+ w.WriteHeader(http.StatusOK)
+ return
+ default:
+ // Default handler: not found.
+ http.NotFoundHandler().ServeHTTP(w, r)
+ }
+}
+
+// getFileHandler handles requests for
+// '/api/<version>/files/?op=get&filename=filename'.
func getFileHandler(server *TestServer, w http.ResponseWriter, r *http.Request) {
values, _ := url.ParseQuery(r.URL.RawQuery)
filename := values.Get("filename")
- content, found := server.files[filename]
+ file, found := server.files[filename]
if !found {
http.NotFoundHandler().ServeHTTP(w, r)
return
}
+ base64Content, err := file.GetField("content")
+ if err != nil {
+ http.NotFoundHandler().ServeHTTP(w, r)
+ }
+ content, err := base64.StdEncoding.DecodeString(base64Content)
+ if err != nil {
+ http.NotFoundHandler().ServeHTTP(w, r)
+ }
w.Write(content)
}
@@ -304,6 +380,6 @@
if err != nil {
panic(err)
}
- server.files[filename] = content
+ server.NewFile(filename, content)
w.WriteHeader(http.StatusOK)
}
=== modified file 'testservice_test.go'
--- testservice_test.go 2013-02-11 12:15:14 +0000
+++ testservice_test.go 2013-02-14 16:42:29 +0000
@@ -5,6 +5,7 @@
import (
"bytes"
+ "encoding/base64"
"encoding/json"
"fmt"
"io"
@@ -184,8 +185,10 @@
c.Check(err, IsNil)
c.Check(resp.StatusCode, Equals, http.StatusOK)
c.Check(len(suite.server.files), Equals, 1)
- expectedFiles := map[string][]byte{"filename": fileContent}
- c.Check(suite.server.files, DeepEquals, expectedFiles)
+ file, _ := suite.server.files["filename"]
+ field, err := file.GetField("content")
+ c.Assert(err, IsNil)
+ c.Check(field, Equals, base64.StdEncoding.EncodeToString(fileContent))
}
func (suite *TestServerSuite) TestHandlesGetFile(c *C) {
@@ -204,8 +207,61 @@
c.Check(content, DeepEquals, fileContent)
}
+func (suite *TestServerSuite) TestHandlesListReturnedSortedFilenames(c *C) {
+ fileName1 := "filename1"
+ suite.server.NewFile(fileName1, []byte("test file content"))
+ fileName2 := "filename2"
+ suite.server.NewFile(fileName2, []byte("test file content"))
+ getURI := fmt.Sprintf("/api/%s/files/?op=list", suite.server.version)
+
+ resp, err := http.Get(suite.server.Server.URL + getURI)
+ c.Check(err, IsNil)
+ c.Check(resp.StatusCode, Equals, http.StatusOK)
+ content, err := ioutil.ReadAll(resp.Body)
+ c.Check(err, IsNil)
+ var files []map[string]string
+ err = json.Unmarshal(content, &files)
+ c.Check(len(files), Equals, 2)
+ c.Check(files[0]["filename"], Equals, fileName1)
+ c.Check(files[1]["filename"], Equals, fileName2)
+}
+
+func (suite *TestServerSuite) TestHandlesListReturnedFilteredFiles(c *C) {
+ fileName1 := "filename1"
+ suite.server.NewFile(fileName1, []byte("test file content"))
+ fileName2 := "prefixFilename"
+ suite.server.NewFile(fileName2, []byte("test file content"))
+ getURI := fmt.Sprintf("/api/%s/files/?op=list&prefix=prefix", suite.server.version)
+
+ resp, err := http.Get(suite.server.Server.URL + getURI)
+
+ c.Check(err, IsNil)
+ c.Check(resp.StatusCode, Equals, http.StatusOK)
+ content, err := ioutil.ReadAll(resp.Body)
+ c.Check(err, IsNil)
+ var files []map[string]string
+ err = json.Unmarshal(content, &files)
+ c.Check(len(files), Equals, 1)
+ c.Check(files[0]["filename"], Equals, fileName2)
+}
+
+func (suite *TestServerSuite) TestDeleteFile(c *C) {
+ fileName1 := "filename1"
+ suite.server.NewFile(fileName1, []byte("test file content"))
+ deleteURI := fmt.Sprintf("/api/%s/files/filename1/", suite.server.version)
+
+ req, err := http.NewRequest("DELETE", suite.server.Server.URL+deleteURI, nil)
+ c.Check(err, IsNil)
+ var client http.Client
+ resp, err := client.Do(req)
+
+ c.Check(err, IsNil)
+ c.Check(resp.StatusCode, Equals, http.StatusOK)
+ c.Check(suite.server.Files(), DeepEquals, map[string]MAASObject{})
+}
+
// TestMAASObjectSuite validates that the object created by
-// TestMAASObject can be used by the gomaasapi library as if it were a real
+// NewTestMAAS can be used by the gomaasapi library as if it were a real
// MAAS server.
type TestMAASObjectSuite struct {
TestMAASObject *TestMAASObject