Implementing image update inside patchHandler 31/1431/2 abandoned dev/icn-v0.1.0
authorenyinna1234 <enyinna.ochulor@intel.com>
Mon, 19 Aug 2019 23:47:30 +0000 (16:47 -0700)
committerenyinna1234 <enyinna.ochulor@intel.com>
Sat, 24 Aug 2019 01:04:12 +0000 (18:04 -0700)
This updates the README, deletes file from file system
after DELETE, and does some refactoring.

Signed-off-by: Enyinna Ochulor <enyinna.ochulor@intel.com>
Change-Id: Ic9bacb798ba7cdc567229d6e3191688b9f301ca5

cmd/bpa-restapi-agent/README.md
cmd/bpa-restapi-agent/api/imagehandler.go
cmd/bpa-restapi-agent/internal/app/image.go
cmd/bpa-restapi-agent/sample.json

index a74434c..70555cb 100644 (file)
@@ -4,3 +4,184 @@ To run the server, follow these simple steps:
 ```
 go run main.go
 ```
+Integrated Cloud Native (ICN) RESTful API
+
+This is a Golang application providing a RESTful API to interact with and upload image objects.
+
+The API application source files are in the icn/cmd/bpa-restapi-agent directory.
+
+While the database back-end is extensible, this initial release requires mongodb.
+
+Install
+
+Install and start mongodb. For instructions: https://docs.mongodb.com/manual/installation/
+
+git clone "https://gerrit.akraino.org/r/icn"
+cd icn/cmd/bpa-restapi-agent
+
+Run the application
+go run main.go
+
+Output without a  config file:
+
+2019/08/22 14:08:41 Error loading config file. Using defaults
+2019/08/22 14:08:41 Starting Integrated Cloud Native API
+
+RESTful API usage examples
+
+Sample Post Request
+
+curl -i -F "metadata=<jsonfile;type=application/json" -F file=@/home/<username>/<dir>/jsonfile -X POST http://NODE_IP:9015//baremetalcluster/{owner}/{clustername}/<image_type>
+
+#image type can be binary_image, container_image, or os_image
+
+Example requests and responses:
+
+Create image - POST
+
+#Using a json file called sample.json
+#image_length in sample.json can be determined with the command
+ls -al <image_file>
+
+Request
+
+curl -i -F "metadata=<sample.json;type=application/json" -F file=@/home/enyi/workspace/icn/cmd/bpa-restapi-agent/sample.json -X POST http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images
+
+Response
+
+HTTP/1.1 100 Continue
+
+HTTP/1.1 201 Created
+Content-Type: application/json
+Date: Thu, 22 Aug 2019 22:56:16 GMT
+Content-Length: 239
+
+{"owner":"alpha","cluster_name":"beta","type":"container","image_name":"asdf246","image_offset":0,"image_length":29718177,"upload_complete":false,"description":{"image_records":[{"image_record_name":"iuysdi1234","repo":"icn","tag":"1"}]}}
+
+#this creates a database entry for the image, and an empty file in the file system
+
+List image - GET
+
+curl -i -X GET http://localhost:9015/v1/baremetalcluster/{owner}/{clustername}/<image_type>/{imgname}
+
+
+example:
+#continuing with our container image from above
+
+Request
+
+curl -i -X GET http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images/asdf246
+
+Response
+
+HTTP/1.1 200 OK
+Content-Type: application/json
+Date: Thu, 22 Aug 2019 22:57:10 GMT
+Content-Length: 239
+
+{"owner":"alpha","cluster_name":"beta","type":"container","image_name":"asdf246","image_offset":0,"image_length":29718177,"upload_complete":false,"description":{"image_records":[{"image_record_name":"iuysdi1234","repo":"icn","tag":"1"}]}}
+
+Upload container image - PATCH
+Request
+
+curl --request PATCH --data-binary "@/home/enyi/workspace/icn/cmd/bpa-restapi-agent/sample_image" http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images/asdf246 --header "Upload-Offset: 0" --header "Expect:" -i
+
+
+Response
+
+HTTP/1.1 204 No Content
+Upload-Offset: 29718177
+Date: Thu, 22 Aug 2019 23:19:44 GMT
+
+Check uploaded image - GET
+
+Request
+
+curl -i -X GET http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images/asdf246
+
+Response
+
+HTTP/1.1 200 OK
+Content-Type: application/json
+Date: Fri, 23 Aug 2019 17:12:07 GMT
+Content-Length: 245
+
+{"owner":"alpha","cluster_name":"beta","type":"container","image_name":"asdf246","image_offset":29718177,"image_length":29718177,"upload_complete":true,"description":{"image_records":[{"image_record_name":"iuysdi1234","repo":"icn","tag":"1"}]}}
+
+#after the upload, the image_offset is now the same as image_length and upload_complete changed to true
+#if upload was incomplete
+
+Resumable upload instructions
+
+Resumable upload -PATCH
+
+#this is the current resumable upload mechanism
+
+Request
+
+curl --request PATCH --data-binary "@/home/enyi/workspace/icn/cmd/bpa-restapi-agent/sample_image" http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images/asdf246 --header "Upload-Offset: 0" --header "Expect:" -i --limit-rate 200K
+
+#the above request limits transfer for testing purposes
+#'ctl c' out after a few seconds, to stop file transfer
+#check image_offset with a GET
+
+Check upload - GET
+
+Request
+
+curl -i -X GET http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images/asdf246
+
+Response
+
+HTTP/1.1 200 OK
+Content-Type: application/json
+Date: Sat, 24 Aug 2019 00:30:00 GMT
+Content-Length: 245
+
+{"owner":"alpha","cluster_name":"beta","type":"container","image_name":"asdf246","image_offset":4079616,"image_length":29718177,"upload_complete":false,"description":{"image_records":[{"image_record_name":"iuysdi1234","repo":"icn","tag":"2"}]}}
+
+#from our response you can see that image_offset is still less than image_length and #upload_complete is still false
+#next we use the dd command (no limiting this time)
+
+Request
+
+dd if=/home/enyi/workspace/icn/cmd/bpa-restapi-agent/sample_image skip=4079616 bs=1 | curl --request PATCH --data-binary @- http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images/asdf246 --header "Upload-Offset: 4079616" --header "Expect:" -i
+
+#the request skips already uploaded 4079616 bytes of data
+
+Response
+
+25638561+0 records in
+25638561+0 records out
+25638561 bytes (26 MB, 24 MiB) copied, 207.954 s, 123 kB/s
+HTTP/1.1 204 No Content
+Upload-Offset: 29718177
+Date: Sat, 24 Aug 2019 00:43:18 GMT
+
+Update image description - PUT
+
+# let's change the tag in description from 1 to latest
+# once the  change is made in sample.json (or your json file)
+
+Request
+
+curl -i -F "metadata=<sample.json;type=application/json" -F file=@/home/enyi/workspace/icn/cmd/bpa-restapi-agent/sample.json -X PUT http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images/asdf246
+
+Response
+
+HTTP/1.1 100 Continue
+
+HTTP/1.1 201 Created
+Content-Type: application/json
+Date: Fri, 23 Aug 2019 17:21:01 GMT
+Content-Length: 239
+
+{"owner":"alpha","cluster_name":"beta","type":"container","image_name":"asdf246","image_offset":0,"image_length":29718177,"upload_complete":false,"description":{"image_records":[{"image_record_name":"iuysdi1234","repo":"icn","tag":"2"}]}}
+
+Delete an image - DELETE
+
+Request
+
+curl -i -X DELETE http://localhost:9015/v1/baremetalcluster/alpha/beta/container_images/asdf246
+
+Response
index 0d7b787..fa7f697 100644 (file)
@@ -2,16 +2,14 @@ package api
 
 import (
        "bytes"
-       "encoding/base64"
+       //"encoding/base64"
        "encoding/json"
        "fmt"
        "io"
        "io/ioutil"
        "net/http"
        "os"
-       "os/user"
        "log"
-       "path"
        "strconv"
 
        image "bpa-restapi-agent/internal/app"
@@ -72,12 +70,6 @@ func (h imageHandler) createHandler(w http.ResponseWriter, r *http.Request) {
                return
        }
 
-       //Create file directory
-       dir, err := createFileDir(v.Type)
-       if err != nil {
-               log.Fatal("Error creating file server directory", err)
-       }
-
        //Read the file section and ignore the header
        file, _, err := r.FormFile("file")
        if err != nil {
@@ -87,31 +79,12 @@ func (h imageHandler) createHandler(w http.ResponseWriter, r *http.Request) {
 
        defer file.Close()
 
-       //Convert the file content to base64 for storage
-       content, err := ioutil.ReadAll(file)
-       if err != nil {
-               http.Error(w, "Unable to read file", http.StatusUnprocessableEntity)
-               return
-       }
-
-       v.Config = base64.StdEncoding.EncodeToString(content)
 
        ret, err := h.client.Create(v)
        if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
        }
-       h.dirPath = dir
-       filePath := path.Join(h.dirPath, v.ImageName)
-       file1, err := os.Create(filePath)
-       if err != nil {
-               e := "Error creating file in filesystem"
-               log.Printf("%s %s\n", e, err)
-               w.WriteHeader(http.StatusInternalServerError)
-               return
-       }
-
-       defer file1.Close()
 
        w.Header().Set("Content-Type", "application/json")
        w.WriteHeader(http.StatusCreated)
@@ -122,30 +95,11 @@ func (h imageHandler) createHandler(w http.ResponseWriter, r *http.Request) {
        }
 }
 
-// Create file
-
-func createFileDir(dirName string) (string, error) {
-    u, err := user.Current()
-    if err != nil {
-        log.Println("Error while fetching user home directory", err)
-        return "", err
-    }
-    home := u.HomeDir
-    dirPath := path.Join(home, "images", dirName)
-    err = os.MkdirAll(dirPath, 0744)
-    if err != nil {
-        log.Println("Error while creating file server directory", err)
-        return "", err
-    }
-    return dirPath, nil
-}
 
 // getHandler handles GET operations on a particular name
 // Returns an Image
 func (h imageHandler) getHandler(w http.ResponseWriter, r *http.Request) {
        vars := mux.Vars(r)
-       // ownerName := vars["owner"]
-       // clusterName := vars["clustername"]
        imageName := vars["imgname"]
 
        ret, err := h.client.Get(imageName)
@@ -166,8 +120,6 @@ func (h imageHandler) getHandler(w http.ResponseWriter, r *http.Request) {
 // deleteHandler handles DELETE operations on a particular record
 func (h imageHandler) deleteHandler(w http.ResponseWriter, r *http.Request) {
        vars := mux.Vars(r)
-       // ownerName := vars["owner"]
-       // clusterName := vars["clustername"]
        imageName := vars["imgname"]
 
        err := h.client.Delete(imageName)
@@ -223,15 +175,6 @@ func (h imageHandler) updateHandler(w http.ResponseWriter, r *http.Request) {
 
        defer file.Close()
 
-       //Convert the file content to base64 for storage
-       content, err := ioutil.ReadAll(file)
-       if err != nil {
-               http.Error(w, "Unable to read file", http.StatusUnprocessableEntity)
-               return
-       }
-
-       v.Config = base64.StdEncoding.EncodeToString(content)
-
        ret, err := h.client.Update(imageName, v)
        if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -301,15 +244,12 @@ func (h imageHandler) patchHandler(w http.ResponseWriter, r *http.Request) {
                log.Println("Size of received file ", len(body))
        }
 
-       u, err := user.Current()
+       fp, _, err := h.client.GetDirPath(imageName)
        if err != nil {
-                       log.Println("Error while fetching user home directory", err)
-                       return
+               log.Printf("unable to get file path %s\n", err)
+               w.WriteHeader(http.StatusInternalServerError)
+               return
        }
-       home := u.HomeDir
-       dir := path.Join(home, "images", file.Type)
-       h.dirPath = dir
-       fp := fmt.Sprintf("%s/%s", h.dirPath, imageName)
        f, err := os.OpenFile(fp, os.O_APPEND|os.O_WRONLY, 0644)
        if err != nil {
                log.Printf("unable to open file %s\n", err)
@@ -335,12 +275,12 @@ func (h imageHandler) patchHandler(w http.ResponseWriter, r *http.Request) {
                *file.UploadComplete = true
        }
 
-       // err = h.updateFile(file)
-       // if err != nil {
-       //      log.Println("Error while updating file", err)
-       //      w.WriteHeader(http.StatusInternalServerError)
-       //      return
-       // }
+       _, err = h.client.Update(imageName, file)
+       if err != nil {
+               log.Println("Error while updating file", err)
+               w.WriteHeader(http.StatusInternalServerError)
+               return
+       }
        w.WriteHeader(http.StatusNoContent)
 
        return
index a1beed8..1b0021b 100644 (file)
@@ -3,6 +3,9 @@ package app
 import (
   //"encoding/base64"
        "encoding/json"
+       "os"
+       "os/user"
+       "path"
        //"io/ioutil"
 
        "bpa-restapi-agent/internal/db"
@@ -16,7 +19,6 @@ type Image struct {
        ClusterName         string               `json:"cluster_name"`
        Type                string               `json:"type"`
        ImageName           string               `json:"image_name"`
-       Config                                                  string                                                   `json:"config"`
        ImageOffset                                     *int                                                       `json:"image_offset"`
        ImageLength                                     int                                                                      `json:"image_length"`
        UploadComplete                  *bool                                                            `json:"upload_complete"`
@@ -29,8 +31,6 @@ type ImageRecordList struct {
 
 // ImageKey is the key structure that is used in the database
 type ImageKey struct {
-       // Owner            string     `json:"owner"`
-       // ClusterName      string     `json:"cluster_name"`
        ImageName        string     `json:"image_name"`
 }
 
@@ -52,6 +52,7 @@ type ImageManager interface {
        Delete(imageName string) error
        Update(imageName string, c Image) (Image, error)
        GetImageRecordByName(imgname, imageName string) (map[string]string, error)
+       GetDirPath(imageName string) (string, string, error)
 }
 
 // ImageClient implements the ImageManager
@@ -66,21 +67,21 @@ type ImageClient struct {
 // which implements the ImageManager
 func NewBinaryImageClient() *ImageClient {
        return &ImageClient{
-               storeName: "binary_image",
+               storeName: "binary_images",
                tagMeta:   "metadata",
        }
 }
 
 func NewContainerImageClient() *ImageClient {
        return &ImageClient{
-               storeName: "container_image",
+               storeName: "container_images",
                tagMeta:   "metadata",
        }
 }
 
 func NewOSImageClient() *ImageClient {
        return &ImageClient{
-               storeName: "os_image",
+               storeName: "os_images",
                tagMeta:   "metadata",
        }
 }
@@ -90,8 +91,6 @@ func (v *ImageClient) Create(c Image) (Image, error) {
 
        //Construct composite key consisting of name
        key := ImageKey{
-               // Owner:       c.Owner,
-               // ClusterName: c.ClusterName,
                ImageName: c.ImageName,
        }
 
@@ -106,9 +105,39 @@ func (v *ImageClient) Create(c Image) (Image, error) {
                return Image{}, pkgerrors.Wrap(err, "Creating DB Entry")
        }
 
+       //Create file
+       err = v.CreateFile(v.storeName, c)
+       if err != nil {
+               return Image{}, pkgerrors.Wrap(err, "Creating File in FS")
+       }
+
        return c, nil
 }
 
+
+// Create file
+
+func (v *ImageClient) CreateFile(dirName string, c Image) error {
+
+    filePath, dirPath, err := v.GetDirPath(c.ImageName)
+               if err != nil {
+                       return pkgerrors.Wrap(err, "Get file path")
+               }
+    err = os.MkdirAll(dirPath, 0744)
+    if err != nil {
+                       return pkgerrors.Wrap(err, "Make image directory")
+    }
+               file1, err := os.Create(filePath)
+               if err != nil {
+                       return pkgerrors.Wrap(err, "Create image file")
+               }
+
+               defer file1.Close()
+
+
+    return nil
+}
+
 // Get returns Image for corresponding to name
 func (v *ImageClient) Get(imageName string) (Image, error) {
 
@@ -154,19 +183,39 @@ func (v *ImageClient) GetImageRecordByName(imgName string,
        return nil, pkgerrors.New("Image record " + imageRecordName + " not found")
 }
 
+func (v *ImageClient) GetDirPath(imageName string) (string, string, error) {
+       u, err := user.Current()
+       if err != nil {
+               return "", "", pkgerrors.Wrap(err, "Current user")
+       }
+       home := u.HomeDir
+       dirPath := path.Join(home, "images", v.storeName)
+       filePath := path.Join(dirPath, imageName)
+
+       return filePath, dirPath, err
+}
+
 // Delete the Image from database
 func (v *ImageClient) Delete(imageName string) error {
 
        //Construct the composite key to select the entry
        key := ImageKey{
-               // Owner:       ownerName,
-               // ClusterName: clusterName,
                ImageName: imageName,
        }
        err := db.DBconn.Delete(v.storeName, key, v.tagMeta)
        if err != nil {
                return pkgerrors.Wrap(err, "Delete Image")
        }
+       //Delete image from FS
+       filePath, _, err := v.GetDirPath(imageName)
+       if err != nil {
+               return pkgerrors.Wrap(err, "Get file path")
+       }
+       err = os.Remove(filePath)
+       if err != nil {
+               return pkgerrors.Wrap(err, "Delete image file")
+       }
+
        return nil
 }
 
index 97c2125..20b1d7e 100644 (file)
@@ -3,15 +3,15 @@
   "cluster_name": "beta",
   "type": "container",
   "image_name": "asdf246",
-  "image_length": 21579557,
+  "image_length": 29718177,
   "image_offset": 0,
   "upload_complete":  false,
   "description": {
     "image_records":  [
       {
         "image_record_name": "iuysdi1234",
-        "repo": "java",
-        "tag":  "8"
+        "repo": "icn",
+        "tag":  "2"
       }
     ]
   }