F20: add CleanupOnFailure() to ServiceInterface

* Add to ServiceInterface a new function:
 `CleanupOnFailure(states []ServiceState, executionErr error) error`
* Fill in the implementations of ServiceInterface with placeholders

This API allows to simple cleanup if execution of one of the states
fails. However, it is possible to use this API to do a Command pattern
with complete Undo() if that's feasible for the service:
States can keep all the data, necessary to undo the state.

BUG=None
TEST=unit

Change-Id: I9cfe890ec181c671756d0f38e4aeef96fe28f80a
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/3350913
Tested-by: Sergey Frolov <sfrolov@google.com>
Auto-Submit: Sergey Frolov <sfrolov@google.com>
Reviewed-by: Otabek Kasimov <otabek@google.com>
Reviewed-by: Jaques Clapauch <jaquesc@google.com>
Commit-Queue: Sergey Frolov <sfrolov@google.com>
diff --git a/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/ashservice/ashservice.go b/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/ashservice/ashservice.go
index 914e0cd..bfbb950 100644
--- a/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/ashservice/ashservice.go
+++ b/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/ashservice/ashservice.go
@@ -99,6 +99,13 @@
 	}
 }
 
+// CleanupOnFailure is called if one of service's states failes to Execute() and
+// should clean up the temporary files, and undo the execution, if feasible.
+func (a *AshService) CleanupOnFailure(states []services.ServiceState, executionErr error) error {
+	// TODO: evaluate whether cleanup is needed.
+	return nil
+}
+
 // CleanUpStagingDirectory simply deletes the staging directory
 func (a *AshService) CleanUpStagingDirectory(ctx context.Context) error {
 	return a.connection.DeleteDirectory(ctx, stagingDirectory)
diff --git a/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/crosservice/crosservice.go b/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/crosservice/crosservice.go
index 4892fa8..3fdb75e 100644
--- a/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/crosservice/crosservice.go
+++ b/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/crosservice/crosservice.go
@@ -58,6 +58,13 @@
 	}
 }
 
+// CleanupOnFailure is called if one of service's states failes to Execute() and
+// should clean up the temporary files, and undo the execution, if feasible.
+func (c *CrOSService) CleanupOnFailure(states []services.ServiceState, executionErr error) error {
+	// TODO: evaluate whether cleanup is needed.
+	return nil
+}
+
 /*
 	Constant Variables
 */
diff --git a/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/firmwareservice/firmwareservice.go b/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/firmwareservice/firmwareservice.go
index 8cb8d7e..ea395f9 100644
--- a/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/firmwareservice/firmwareservice.go
+++ b/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/firmwareservice/firmwareservice.go
@@ -121,6 +121,13 @@
 	return nil
 }
 
+// CleanupOnFailure is called if one of service's states failes to Execute() and
+// should clean up the temporary files, and undo the execution, if feasible.
+func (fws *FirmwareService) CleanupOnFailure(states []services.ServiceState, executionErr error) error {
+	// TODO(sfrolov): implement cleanup.
+	return nil
+}
+
 // CopyImageToDUT copies the desired image to the DUT, passing through the caching layer.
 func (fws *FirmwareService) CopyImageToDUT(ctx context.Context, remotePath *conf.StoragePath, localFilename string) error {
 	if remotePath.HostType == conf.StoragePath_LOCAL || remotePath.HostType == conf.StoragePath_HOSTTYPE_UNSPECIFIED {
diff --git a/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/lacrosservice/lacrosservice.go b/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/lacrosservice/lacrosservice.go
index 3bd4e50..be27c87 100644
--- a/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/lacrosservice/lacrosservice.go
+++ b/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/lacrosservice/lacrosservice.go
@@ -62,6 +62,13 @@
 	}
 }
 
+// CleanupOnFailure is called if one of service's states failes to Execute() and
+// should clean up the temporary files, and undo the execution, if feasible.
+func (c *LaCrOSService) CleanupOnFailure(states []services.ServiceState, executionErr error) error {
+	// TODO: evaluate whether cleanup is needed.
+	return nil
+}
+
 /*
 	The following consists of helper structs
 */
diff --git a/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/serviceinterface.go b/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/serviceinterface.go
index a6208ac..e899762 100644
--- a/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/serviceinterface.go
+++ b/src/chromiumos/test/provision/cmd/provisionserver/bootstrap/services/serviceinterface.go
@@ -25,4 +25,14 @@
 type ServiceInterface interface {
 	// GetFirstState returns the first state in this state machine
 	GetFirstState() ServiceState
+
+	// CleanupOnFailure "undoes" the service execution to the extent possible,
+	// removing temporary files, and, if feasible, reverting the run commands.
+	// CleanupOnFailure function will be called if any ServiceState returns an
+	// error when running Execute().
+	// |states| will include all ServiceStates that were run; naturally, all of
+	// them but last one would have succeeded to Execute().
+	// |executionErr| is the error returned by Execute() of the last (failed)
+	// ServiceState.
+	CleanupOnFailure(states []ServiceState, executionErr error) error
 }
diff --git a/src/chromiumos/test/provision/cmd/provisionserver/common.go b/src/chromiumos/test/provision/cmd/provisionserver/common.go
index ddf1c6f..4500660 100644
--- a/src/chromiumos/test/provision/cmd/provisionserver/common.go
+++ b/src/chromiumos/test/provision/cmd/provisionserver/common.go
@@ -209,8 +209,16 @@
 	default:
 	}
 
+	// states list keeps the executed and failed ServiceStates,
+	// so that they can be undone/cleaned up upon failure.
+	var states []services.ServiceState
+
 	for cs := si.GetFirstState(); cs != nil; cs = cs.Next() {
+		states = append(states, cs)
 		if err := cs.Execute(ctx); err != nil {
+			if cleanupErr := si.CleanupOnFailure(states, err); cleanupErr != nil {
+				s.logger.Println("CleanupOnFailure failed:", cleanupErr.Error())
+			}
 			fr := &api.InstallFailure{
 				Reason: api.InstallFailure_REASON_PROVISIONING_FAILED,
 			}