← Back to team overview

launchpad-reviewers team mailing list archive

[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