From dd105eeb6ce61d9e63b3b013e29bed7dad6156b2 Mon Sep 17 00:00:00 2001
From: Florent Gluck <florent.gluck@hesge.ch>
Date: Wed, 7 Sep 2022 08:48:20 +0200
Subject: [PATCH] Fixed minor mistake in README.md Reworked VMs code a bit to
 have more conistent behavior among methods

---
 README.md                     |   4 +-
 src/router/routerTemplates.go |   2 +-
 src/router/routerVMs.go       | 140 +++++++++++++++++-----------------
 src/vms/vms.go                | 100 ++++++++++++------------
 4 files changed, 126 insertions(+), 120 deletions(-)

diff --git a/README.md b/README.md
index bc76ef9..7246ec8 100644
--- a/README.md
+++ b/README.md
@@ -375,8 +375,8 @@ Legend for the "Done" column:
 
 | Done | Route             | Description       | Method | Parameters             | Req. user cap.                |
 |---   |---                |---                |---     |---                     |---                            |
-|  x   | `/templates/vm`   | create a template | POST   | vmID,name,descr,access | `TPL_CREATE`                  |
-|  x   | `/templates/qcow` | create a template | POST   | qcow,name,descr,access | `TPL_CREATE`                  |
+|  x   | `/templates/vm`   | create a template | POST   | vmID,name,access       | `TPL_CREATE`                  |
+|  x   | `/templates/qcow` | create a template | POST   | qcow,name,access       | `TPL_CREATE`                  |
 |  x   | `/templates/{id}` | delete a template | DELETE |                        | `TPL_DESTROY_ANY|TPL_DESTROY` |
 |  x   | `/templates`      | list templates    | GET    |                        | `TPL_LIST_ANY|TPL_LIST`       |
 
diff --git a/src/router/routerTemplates.go b/src/router/routerTemplates.go
index 9e0dd5e..fae960a 100644
--- a/src/router/routerTemplates.go
+++ b/src/router/routerTemplates.go
@@ -185,7 +185,7 @@ func (r *RouterTemplates)CreateTemplateFromQCOW(c echo.Context) error {
 // CAP_TPL_DESTROY_ANY: any template can be deleted.
 // CAP_TPL_DESTROY:     only a template owned by the user can be deleted.
 // Remark: a template can only be deleted if no VM references it!
-// curl --cacert ca.pem -X DELETE https://localhost:1077/templates/Xubuntu_22.04_+_gcc_+_vscodium -H "Authorization: Bearer <AccessToken>"
+// curl --cacert ca.pem -X DELETE https://localhost:1077/templates/4913a2bb-edfe-4dfe-af53-38197a44523b -H "Authorization: Bearer <AccessToken>"
 func (r *RouterTemplates)DeleteTemplateByID(c echo.Context) error {
 	// Retrieves logged user from context.
 	user, err := getLoggedUser(r.users, c)
diff --git a/src/router/routerVMs.go b/src/router/routerVMs.go
index 278d7e5..6a30eff 100644
--- a/src/router/routerVMs.go
+++ b/src/router/routerVMs.go
@@ -359,75 +359,6 @@ func (r *RouterVMs)DeleteVMAccessForUser(c echo.Context) error {
 	return c.JSONPretty(http.StatusOK, jsonMsg("OK"), "    ")
 }
 
-// Helper function that returns a list of VMs that match either:
-// - the logged user has the userCapabilityAny capability.
-// - the VM access for the logged user matches the vmAccessCapability capability.
-// Also, VMs for which cond is false are filtered out.
-func (r *RouterVMs)performVMsList(c echo.Context, userCapabilityAny, vmAccessCapability string, cond vms.VMKeeperFn) error {
-	// Retrieves logged user from context.
-	user, err := getLoggedUser(r.users, c)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusUnauthorized, err.Error())
-	}
-
-	// If the logged user has the XX_ANY capability (userCapabilityAny), returns all VMs.
-	if user.HasCapability(userCapabilityAny) {
-		// Returns all VMs that pass the condition.
-		return c.JSONPretty(http.StatusOK, r.vms.GetVMs(cond), "    ")
-	} else {
-		// Returns all VMs for which VM access for the logged user matches the specified the VM Access capability (vmAccessCapability).
-		return c.JSONPretty(http.StatusOK, r.vms.GetVMs(
-			func(vm vms.VM) bool {
-				capabilities, exists := vm.Access[user.Email]
-				if exists {
-					_, visible := capabilities[vmAccessCapability]
-					return visible && cond(vm)
-				} else {
-					return false
-				}
-			  }), "    ")
-	}
-}
-
-// Helper function that performs an action on a VM based either on:
-// - the logged user has the userCapabilityAny capability.
-// - the VM access for the logged user matches the vmAccessCapability capability.
-func (r *RouterVMs)performVMAction(c echo.Context, userCapabilityAny, vmAccessCapability string, action vmActionFn) error {
-	// Retrieves the VM on which to perform the action.
-	id, err := uuid.Parse(c.Param("id"))
-	if err != nil {
-		return echo.NewHTTPError(http.StatusBadRequest, err.Error())
-	}
-	vm, err := r.vms.GetVM(id)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusNotFound, err.Error())
-	}
-
-	// Retrieves the logged user from context.
-	user, err := getLoggedUser(r.users, c)
-	if err != nil {
-		return echo.NewHTTPError(http.StatusUnauthorized, err.Error())
-	}
-
-	// First, checks if the user has the XX_ANY capability
-	if user.HasCapability(userCapabilityAny) {
-		return action(c, &vm)
-	} else {
-		// Check if the VM access for the logged user matches the required capability
-		userCaps, exists := vm.Access[user.Email]
-		if !exists {
-			return echo.NewHTTPError(http.StatusUnauthorized, msgInsufficientCaps)
-		}
-
-		_, hasAccess := userCaps[vmAccessCapability]
-		if !hasAccess {
-			return echo.NewHTTPError(http.StatusUnauthorized, msgInsufficientCaps)
-		}
-
-		return action(c, &vm)
-	}
-}
-
 // Exports a VM's directory into a .tar archive.
 // Requires either capability:
 // User cap: VM_READFS_ANY: any VM can have their filesystem read.
@@ -511,4 +442,73 @@ func (r *RouterVMs)ImportFilesToVM(c echo.Context) error {
 	
 		return c.JSONPretty(http.StatusCreated, jsonMsg("OK"), "    ")
 	})
-}
\ No newline at end of file
+}
+
+// Helper function that returns a list of VMs that match either:
+// - the logged user has the userCapabilityAny capability.
+// - the VM access for the logged user matches the vmAccessCapability capability.
+// Also, VMs for which cond is false are filtered out.
+func (r *RouterVMs)performVMsList(c echo.Context, userCapabilityAny, vmAccessCapability string, cond vms.VMKeeperFn) error {
+	// Retrieves logged user from context.
+	user, err := getLoggedUser(r.users, c)
+	if err != nil {
+		return echo.NewHTTPError(http.StatusUnauthorized, err.Error())
+	}
+
+	// If the logged user has the XX_ANY capability (userCapabilityAny), returns all VMs.
+	if user.HasCapability(userCapabilityAny) {
+		// Returns all VMs that pass the condition.
+		return c.JSONPretty(http.StatusOK, r.vms.GetVMs(cond), "    ")
+	} else {
+		// Returns all VMs for which VM access for the logged user matches the specified the VM Access capability (vmAccessCapability).
+		return c.JSONPretty(http.StatusOK, r.vms.GetVMs(
+			func(vm vms.VM) bool {
+				capabilities, exists := vm.Access[user.Email]
+				if exists {
+					_, visible := capabilities[vmAccessCapability]
+					return visible && cond(vm)
+				} else {
+					return false
+				}
+			  }), "    ")
+	}
+}
+
+// Helper function that performs an action on a VM based either on:
+// - the logged user has the userCapabilityAny capability.
+// - the VM access for the logged user matches the vmAccessCapability capability.
+func (r *RouterVMs)performVMAction(c echo.Context, userCapabilityAny, vmAccessCapability string, action vmActionFn) error {
+	// Retrieves the VM on which to perform the action.
+	id, err := uuid.Parse(c.Param("id"))
+	if err != nil {
+		return echo.NewHTTPError(http.StatusBadRequest, err.Error())
+	}
+	vm, err := r.vms.GetVM(id)
+	if err != nil {
+		return echo.NewHTTPError(http.StatusNotFound, err.Error())
+	}
+
+	// Retrieves the logged user from context.
+	user, err := getLoggedUser(r.users, c)
+	if err != nil {
+		return echo.NewHTTPError(http.StatusUnauthorized, err.Error())
+	}
+
+	// First, checks if the user has the XX_ANY capability
+	if user.HasCapability(userCapabilityAny) {
+		return action(c, &vm)
+	} else {
+		// Check if the VM access for the logged user matches the required capability
+		userCaps, exists := vm.Access[user.Email]
+		if !exists {
+			return echo.NewHTTPError(http.StatusUnauthorized, msgInsufficientCaps)
+		}
+
+		_, hasAccess := userCaps[vmAccessCapability]
+		if !hasAccess {
+			return echo.NewHTTPError(http.StatusUnauthorized, msgInsufficientCaps)
+		}
+
+		return action(c, &vm)
+	}
+}
diff --git a/src/vms/vms.go b/src/vms/vms.go
index 129a978..0e5277e 100644
--- a/src/vms/vms.go
+++ b/src/vms/vms.go
@@ -94,11 +94,11 @@ func InitVMs() error {
 }
 
 // Returns the list of VMs for which VMKeeperFn returns true.
-func (vms *VMs)GetVMs(keep VMKeeperFn) []VM {
+func (vms *VMs)GetVMs(keepFn VMKeeperFn) []VM {
 	vms.rwlock.RLock()
 	list := []VM{}
 	for _, vm := range vms.m {
-		if keep(vm) {
+		if keepFn(vm) {
 			list = append(list, vm)
 		}
 	}
@@ -115,6 +115,10 @@ func (vms *VMs)GetVMs(keep VMKeeperFn) []VM {
 func (vms *VMs)GetVM(vmID uuid.UUID) (VM, error) {
 	vms.rwlock.RLock()
 	defer vms.rwlock.RUnlock()
+	return vms.getVMUnsafe(vmID)
+}
+
+func (vms *VMs)getVMUnsafe(vmID uuid.UUID) (VM, error) {
 	vm, exists := vms.m[vmID.String()]
 	if !exists {
 		return dummyVM, errors.New("VM not found")
@@ -123,24 +127,25 @@ func (vms *VMs)GetVM(vmID uuid.UUID) (VM, error) {
 }
 
 // Deletes a VM by its ID and deletes its files.
-func (vms *VMs)DeleteVM(id uuid.UUID) error {
-	key := id.String()
+func (vms *VMs)DeleteVM(vmID uuid.UUID) error {
 	vms.rwlock.Lock()
 	defer vms.rwlock.Unlock()
-	vm, exists := vms.m[key]
-	if exists {
-		// Deletes the VM's files (and directories).
-		vm.mutex.Lock()
-		if err := vm.delete(); err != nil {
-			vm.mutex.Unlock()
-			return err
-		}
+
+	vm, err := vms.getVMUnsafe(vmID)
+	if err != nil {
+		return err
+	}
+
+	// Deletes the VM's files (and directories).
+	vm.mutex.Lock()
+	if err := vm.delete(); err != nil {
 		vm.mutex.Unlock()
-		// Removes the VM from the map.
-		delete(vms.m, key)
-		return nil
+		return err
 	}
-	return errors.New("VM not found")
+	vm.mutex.Unlock()
+	// Removes the VM from the map.
+	delete(vms.m, vm.ID.String())
+	return nil
 }
 
 // Adds a VM and writes its files.
@@ -165,13 +170,13 @@ func (vms *VMs)AddVM(vm *VM) error {
 
 // Starts a VM by its ID.
 // Returns the port on which the VM is running and the access password.
-func (vms *VMs)StartVM(id uuid.UUID) (int, string, error) {
+func (vms *VMs)StartVM(vmID uuid.UUID) (int, string, error) {
 	vms.rwlock.Lock()
 	defer vms.rwlock.Unlock()
 
-	vm, exists := vms.m[id.String()]
-	if !exists {
-		return -1, "", errors.New("VM not found")
+	vm, err := vms.getVMUnsafe(vmID)
+	if err != nil {
+		return 0, "", err
 	}
 
 	// Check there will be at least 1GB of RAM left after the VM has started,
@@ -215,33 +220,33 @@ func (vms *VMs)StartVM(id uuid.UUID) (int, string, error) {
 }
 
 // Stops by force a VM by its ID.
-func (vms *VMs)StopVM(id uuid.UUID) error {
+func (vms *VMs)StopVM(vmID uuid.UUID) error {
 	vms.rwlock.RLock()
 	defer vms.rwlock.RUnlock()
 
-	vm, exists := vms.m[id.String()]
-	if !exists {
-		return errors.New("VM not found")
+	vm, err := vms.getVMUnsafe(vmID)
+	if err != nil {
+		return err
 	}
 
 	vm.mutex.Lock()
-	err := vm.stop()
+	err = vm.stop()
 	vm.mutex.Unlock()
 	return err
 }
 
 // Gracefully stops a VM by its ID.
-func (vms *VMs)ShutdownVM(id uuid.UUID) error {
+func (vms *VMs)ShutdownVM(vmID uuid.UUID) error {
 	vms.rwlock.Lock()
 	defer vms.rwlock.Unlock()
 
-	vm, exists := vms.m[id.String()]
-	if !exists {
-		return errors.New("VM not found")
+	vm, err := vms.getVMUnsafe(vmID)
+	if err != nil {
+		return err
 	}
 
 	vm.mutex.Lock()
-	err := vm.shutdown()
+	err = vm.shutdown()
 	vm.mutex.Unlock()
 	return err
 }
@@ -261,10 +266,9 @@ func (vms *VMs)IsTemplateUsed(templateID string) bool {
 
 // Edit a VM' specs: name, cpus, ram, nic
 func (vms *VMs)EditVM(vmID uuid.UUID, name string, cpus, ram int, nic NicType) error {
-	// Retrieves the VM to be edited.
-	vm, err := vms.GetVM(vmID)
+	vm, err := vms.getVMUnsafe(vmID)
 	if err != nil {
-		return errors.New("VM not found")
+		return err
 	}
 
 	// Only updates fields that have changed.
@@ -305,12 +309,18 @@ func (vms *VMs)SetVMAccess(vmID uuid.UUID, loggedUserEmail, userEmail string, ne
 		return errors.New("Invalid capability")
 	}
 
+	vms.rwlock.Lock()
+	defer vms.rwlock.Unlock()
+
 	// Retrieves the VM for which the access caps must be changed.
-	vm, err := vms.GetVM(vmID)
+	vm, err := vms.getVMUnsafe(vmID)
 	if err != nil {
-		return errors.New("VM not found")
+		return err
 	}
 
+	vm.mutex.Lock()
+	defer vm.mutex.Unlock()
+
 	// Checks the logged user has VM_SET_ACCESS set in her/his VM access.
 	userCaps := vm.Access[loggedUserEmail]
 	_, exists := userCaps[caps.CAP_VM_SET_ACCESS]
@@ -320,11 +330,6 @@ func (vms *VMs)SetVMAccess(vmID uuid.UUID, loggedUserEmail, userEmail string, ne
 
 	vm.Access[userEmail] = newAccess
 
-	vm.mutex.Lock()
-	defer vm.mutex.Unlock()
-	vms.rwlock.Lock()
-	defer vms.rwlock.Unlock()
-
 	if err = vms.updateVM(&vm); err != nil {
 		return errors.New("Failed updating VM")
 	}
@@ -336,12 +341,18 @@ func (vms *VMs)SetVMAccess(vmID uuid.UUID, loggedUserEmail, userEmail string, ne
 // loggedUserEmail is the email of the currently logged user
 // userMail is the email of the user for which to remove the access
 func (vms *VMs)DeleteVMAccess(vmID uuid.UUID, loggedUserEmail, userEmail string) error {
+	vms.rwlock.Lock()
+	defer vms.rwlock.Unlock()
+
 	// Retrieves the VM for which the access caps must be changed.
-	vm, err := vms.GetVM(vmID)
+	vm, err := vms.getVMUnsafe(vmID)
 	if err != nil {
-		return errors.New("VM not found")
+		return err
 	}
 
+	vm.mutex.Lock()
+	defer vm.mutex.Unlock()
+
 	// Checks the user has VM_SET_ACCESS set in her/his VM access.
 	userCaps := vm.Access[loggedUserEmail]
 	_, exists := userCaps[caps.CAP_VM_SET_ACCESS]
@@ -352,11 +363,6 @@ func (vms *VMs)DeleteVMAccess(vmID uuid.UUID, loggedUserEmail, userEmail string)
 	// Removes the user from the Access map
 	delete(vm.Access, userEmail)
 
-	vm.mutex.Lock()
-	defer vm.mutex.Unlock()
-	vms.rwlock.Lock()
-	defer vms.rwlock.Unlock()
-
 	if err = vms.updateVM(&vm); err != nil {
 		return errors.New("Failed updating VM")
 	}
-- 
GitLab