Repository: coreos/fleet
Pull request #1701 (2016-11-09 12:00:09)

fleetctl: take experimentalAPI into account in getClient
`getClient()` has not taken into account the case of `!experimentalAPI`, before calling registryClient. It has set endPoint to a specific value of URLs, but the new value would not be passed to `getRegistryClient()`, which simply fetches endPoint again. This was a regression since 848d3561 ("fleetctl: convert cli to cobra").

To fix that, introduce `getEndpoint()` that does GetString("endpoint") as well as the special handling for experimentalAPI. And make `getRegistryClient()` call `getEndpoint()`.

Fortunately, the experimentalAPI flag is set to true by default, so this special handling path is not actively used after all. Just for correctness.


Patch:
diff --git a/api/state.go b/api/state.go
index 702c71f1..18f60518 100644
--- a/api/state.go
+++ b/api/state.go
@@ -28,7 +28,6 @@ func wireUpStateResource(mux *http.ServeMux, prefix string, tokenLimit int, cAPI
 	base := path.Join(prefix, "state")
 	sr := stateResource{cAPI, base, uint16(tokenLimit)}
 	mux.Handle(base, &sr)
-	mux.Handle(base+"/", &sr)
 }
 
 type stateResource struct {
@@ -38,23 +37,12 @@ type stateResource struct {
 }
 
 func (sr *stateResource) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
-	if isCollectionPath(sr.basePath, req.URL.Path) {
-		switch req.Method {
-		case "GET":
-			sr.list(rw, req)
-		default:
-			sendError(rw, http.StatusMethodNotAllowed, errors.New("only GET supported against this resource"))
-		}
-	} else if item, ok := isItemPath(sr.basePath, req.URL.Path); ok {
-		switch req.Method {
-		case "GET":
-			sr.get(rw, req, item)
-		default:
-			sendError(rw, http.StatusMethodNotAllowed, errors.New("only GET supported against this resource"))
-		}
-	} else {
-		sendError(rw, http.StatusNotFound, nil)
+	if req.Method != "GET" {
+		sendError(rw, http.StatusMethodNotAllowed, errors.New("only GET supported against this resource"))
+		return
 	}
+
+	sr.list(rw, req)
 }
 
 func (sr *stateResource) list(rw http.ResponseWriter, req *http.Request) {
@@ -89,22 +77,6 @@ func (sr *stateResource) list(rw http.ResponseWriter, req *http.Request) {
 	sendResponse(rw, http.StatusOK, &page)
 }
 
-func (sr *stateResource) get(rw http.ResponseWriter, req *http.Request, item string) {
-	us, err := sr.cAPI.UnitState(item)
-	if err != nil {
-		log.Errorf("Failed fetching UnitState(%s) from Registry: %v", item, err)
-		sendError(rw, http.StatusInternalServerError, nil)
-		return
-	}
-
-	if us == nil {
-		sendError(rw, http.StatusNotFound, errors.New("unit state does not exist"))
-		return
-	}
-
-	sendResponse(rw, http.StatusOK, *us)
-}
-
 func getUnitStatePage(cAPI client.API, machineID, unitName string, tok PageToken) (*schema.UnitStatePage, error) {
 	states, err := cAPI.UnitStates()
 	if err != nil {
diff --git a/client/api.go b/client/api.go
index 8d28d2ff..6ec2efb0 100644
--- a/client/api.go
+++ b/client/api.go
@@ -24,7 +24,6 @@ type API interface {
 
 	Unit(string) (*schema.Unit, error)
 	Units() ([]*schema.Unit, error)
-	UnitState(string) (*schema.UnitState, error)
 	UnitStates() ([]*schema.UnitState, error)
 
 	SetUnitTargetState(name, target string) error
diff --git a/client/http.go b/client/http.go
index 97eab1b5..8e5896b9 100644
--- a/client/http.go
+++ b/client/http.go
@@ -117,14 +117,6 @@ func (c *HTTPClient) UnitStates() ([]*schema.UnitState, error) {
 	return states, nil
 }
 
-func (c *HTTPClient) UnitState(name string) (*schema.UnitState, error) {
-	u, err := c.svc.UnitState.Get(name).Do()
-	if err != nil && !is404(err) {
-		return nil, err
-	}
-	return u, nil
-}
-
 func (c *HTTPClient) DestroyUnit(name string) error {
 	return c.svc.Units.Delete(name).Do()
 }
diff --git a/client/registry.go b/client/registry.go
index ccf68d61..c275c929 100644
--- a/client/registry.go
+++ b/client/registry.go
@@ -86,15 +86,6 @@ func (rc *RegistryClient) CreateUnit(u *schema.Unit) error {
 	return rc.Registry.CreateUnit(&rUnit)
 }
 
-func (rc *RegistryClient) UnitState(name string) (*schema.UnitState, error) {
-	rUnitState, err := rc.Registry.UnitState(name)
-	if err != nil {
-		return nil, err
-	}
-
-	return schema.MapUnitStateToSchemaUnitState(rUnitState), nil
-}
-
 func (rc *RegistryClient) UnitStates() ([]*schema.UnitState, error) {
 	rUnitStates, err := rc.Registry.UnitStates()
 	if err != nil {
diff --git a/fleetctl/fleetctl.go b/fleetctl/fleetctl.go
index 337e7f18..94050708 100644
--- a/fleetctl/fleetctl.go
+++ b/fleetctl/fleetctl.go
@@ -64,7 +64,7 @@ recommended to upgrade fleetctl to prevent incompatibility issues.
 	clientDriverEtcd = "etcd"
 
 	defaultEndpoint  = "unix:///var/run/fleet.sock"
-	defaultSleepTime = 2000 * time.Millisecond
+	defaultSleepTime = 500 * time.Millisecond
 )
 
 var (
@@ -177,6 +177,12 @@ type Command struct {
 
 }
 
+type suState struct {
+	LoadState   string
+	ActiveState string
+	SubState    string
+}
+
 func getFlags(flagset *flag.FlagSet) (flags []*flag.Flag) {
 	flags = make([]*flag.Flag, 0)
 	flagset.VisitAll(func(f *flag.Flag) {
@@ -313,20 +319,7 @@ func getClientAPI(cCmd *cobra.Command) client.API {
 
 // getClient initializes a client of fleet based on CLI flags
 func getClient(cCmd *cobra.Command) (client.API, error) {
-	// The user explicitly set --experimental-api=false, so it trumps the
-	// --driver flag. This behavior exists for backwards-compatibilty.
-	experimentalAPI, _ := cmdFleet.PersistentFlags().GetBool("experimental-api")
-	endPoint, _ := cmdFleet.PersistentFlags().GetString("endpoint")
 	clientDriver, _ := cmdFleet.PersistentFlags().GetString("driver")
-	if !experimentalAPI {
-		// Additionally, if the user set --experimental-api=false and did
-		// not change the value of --endpoint, they likely want to use the
-		// old default value.
-		if endPoint == defaultEndpoint {
-			endPoint = "http://127.0.0.1:2379,http://127.0.0.1:4001"
-		}
-		return getRegistryClient(cCmd)
-	}
 
 	switch clientDriver {
 	case clientDriverAPI:
@@ -431,6 +424,22 @@ func getHTTPClient(cCmd *cobra.Command) (client.API, error) {
 	return client.NewHTTPClient(&hc, *ep)
 }
 
+func getEndpoint() string {
+	// The user explicitly set --experimental-api=false, so it trumps the
+	// --driver flag. This behavior exists for backwards-compatibilty.
+	experimentalAPI, _ := cmdFleet.PersistentFlags().GetBool("experimental-api")
+	endPoint, _ := cmdFleet.PersistentFlags().GetString("endpoint")
+	if !experimentalAPI {
+		// Additionally, if the user set --experimental-api=false and did
+		// not change the value of --endpoint, they likely want to use the
+		// old default value.
+		if endPoint == defaultEndpoint {
+			endPoint = "http://127.0.0.1:2379,http://127.0.0.1:4001"
+		}
+	}
+	return endPoint
+}
+
 func getRegistryClient(cCmd *cobra.Command) (client.API, error) {
 	var dial func(string, string) (net.Conn, error)
 	SSHUserName, _ := cmdFleet.PersistentFlags().GetString("ssh-username")
@@ -463,9 +472,8 @@ func getRegistryClient(cCmd *cobra.Command) (client.API, error) {
 		TLSClientConfig: tlsConfig,
 	}
 
-	endPoint, _ := cmdFleet.PersistentFlags().GetString("endpoint")
 	eCfg := etcd.Config{
-		Endpoints:               strings.Split(endPoint, ","),
+		Endpoints:               strings.Split(getEndpoint(), ","),
 		Transport:               trans,
 		HeaderTimeoutPerRequest: getRequestTimeoutFlag(cCmd),
 	}
@@ -1006,64 +1014,63 @@ func waitForUnitStates(units []string, js job.JobState, maxAttempts int, out io.
 func checkUnitState(name string, js job.JobState, maxAttempts int, out io.Writer, wg *sync.WaitGroup, errchan chan error) {
 	defer wg.Done()
 
+	sleep := defaultSleepTime
+
 	if maxAttempts < 1 {
 		for {
 			if assertUnitState(name, js, out) {
 				return
 			}
+			time.Sleep(sleep)
 		}
 	} else {
 		for attempt := 0; attempt < maxAttempts; attempt++ {
 			if assertUnitState(name, js, out) {
 				return
 			}
+			time.Sleep(sleep)
 		}
 		errchan <- fmt.Errorf("timed out waiting for unit %s to report state %s", name, js)
 	}
 }
 
-func assertUnitState(name string, js job.JobState, out io.Writer) bool {
-	fetchUnitState := func() error {
-		var state string
+func assertUnitState(name string, js job.JobState, out io.Writer) (ret bool) {
+	var state string
 
-		u, err := cAPI.Unit(name)
-		if err != nil {
-			return fmt.Errorf("Error retrieving Unit(%s) from Registry: %v", name, err)
-		}
-		if u == nil {
-			return fmt.Errorf("Unit %s not found", name)
-		}
+	u, err := cAPI.Unit(name)
+	if err != nil {
+		log.Warningf("Error retrieving Unit(%s) from Registry: %v", name, err)
+		return
+	}
+	if u == nil {
+		log.Warningf("Unit %s not found", name)
+		return
+	}
 
-		// If this is a global unit, CurrentState will never be set. Instead, wait for DesiredState.
-		if suToGlobal(*u) {
-			state = u.DesiredState
-		} else {
-			state = u.CurrentState
-		}
+	// If this is a global unit, CurrentState will never be set. Instead, wait for DesiredState.
+	if suToGlobal(*u) {
+		state = u.DesiredState
+	} else {
+		state = u.CurrentState
+	}
 
-		if job.JobState(state) != js {
-			return fmt.Errorf("Waiting for Unit(%s) state(%s) to be %s", name, job.JobState(state), js)
-		}
+	if job.JobState(state) != js {
+		log.Debugf("Waiting for Unit(%s) state(%s) to be %s", name, job.JobState(state), js)
+		return
+	}
 
-		msg := fmt.Sprintf("Unit %s %s", name, u.CurrentState)
+	ret = true
+	msg := fmt.Sprintf("Unit %s %s", name, u.CurrentState)
 
-		if u.MachineID != "" {
-			ms := cachedMachineState(u.MachineID)
-			if ms != nil {
-				msg = fmt.Sprintf("%s on %s", msg, machineFullLegend(*ms, false))
-			}
+	if u.MachineID != "" {
+		ms := cachedMachineState(u.MachineID)
+		if ms != nil {
+			msg = fmt.Sprintf("%s on %s", msg, machineFullLegend(*ms, false))
 		}
-
-		fmt.Fprintln(out, msg)
-		return nil
-	}
-	timeout, err := waitForState(fetchUnitState)
-	if err != nil {
-		log.Errorf("Failed to find unit %s within %v, err: %v", name, timeout, err)
-		return false
 	}
 
-	return true
+	fmt.Fprintln(out, msg)
+	return
 }
 
 // tryWaitForSystemdActiveState tries to wait for systemd units to reach an
@@ -1090,11 +1097,17 @@ func tryWaitForSystemdActiveState(units []string, maxAttempts int) (err error) {
 // waitForSystemdActiveState tries to assert that the given unit becomes
 // active, making use of multiple goroutines that check unit states.
 func waitForSystemdActiveState(units []string, maxAttempts int) (errch chan error) {
+	apiStates, err := cAPI.UnitStates()
+	if err != nil {
+		errch <- fmt.Errorf("Error retrieving list of units: %v", err)
+		return
+	}
+
 	errchan := make(chan error)
 	var wg sync.WaitGroup
 	for _, name := range units {
 		wg.Add(1)
-		go checkSystemdActiveState(name, maxAttempts, &wg, errchan)
+		go checkSystemdActiveState(apiStates, name, maxAttempts, &wg, errchan)
 	}
 
 	go func() {
@@ -1105,7 +1118,7 @@ func waitForSystemdActiveState(units []string, maxAttempts int) (errch chan erro
 	return errchan
 }
 
-func checkSystemdActiveState(name string, maxAttempts int, wg *sync.WaitGroup, errchan chan error) {
+func checkSystemdActiveState(apiStates []*schema.UnitState, name string, maxAttempts int, wg *sync.WaitGroup, errchan chan error) {
 	defer wg.Done()
 
 	// "isInf == true" means "blocking forever until it succeeded".
@@ -1117,7 +1130,7 @@ func checkSystemdActiveState(name string, maxAttempts int, wg *sync.WaitGroup, e
 	}
 
 	for attempt := 0; attempt < maxAttempts; attempt++ {
-		if err := assertSystemdActiveState(name); err == nil {
+		if err := assertFetchSystemdActiveState(apiStates, name); err == nil {
 			return
 		} else {
 			errchan <- err
@@ -1129,34 +1142,64 @@ func checkSystemdActiveState(name string, maxAttempts int, wg *sync.WaitGroup, e
 	}
 }
 
-// assertSystemdActiveState determines if a given systemd unit is actually
-// in the active state, making use of cAPI.
-// It repeatedly checks up to defaultSleepTimeout. If ActiveState of the given
-// unit is active and LoadState of the given unit is loaded.
-// If it cannot get the expected states within the period, return error.
-func assertSystemdActiveState(unitName string) error {
-	fetchSystemdActiveState := func() error {
-		us, err := cAPI.UnitState(unitName)
-		if err != nil {
-			return fmt.Errorf("Error getting unit state of %s: %v", unitName, err)
-		}
-
-		// Get systemd state and check the state is active & loaded.
-		if us.SystemdActiveState != "active" || us.SystemdLoadState != "loaded" {
-			return fmt.Errorf("Failed to find an active unit %s", unitName)
-		}
+func assertFetchSystemdActiveState(apiStates []*schema.UnitState, name string) error {
+	if err := assertSystemdActiveState(apiStates, name); err == nil {
 		return nil
 	}
 
-	timeout, err := waitForState(fetchSystemdActiveState)
+	// If the assertion failed, we again need to get unit states via cAPI,
+	// to retry the assertion repeatedly.
+	//
+	// NOTE: Ideally we should be able to fetch the state only for a single
+	// unit. However, we cannot do that for now, because cAPI.UnitState()
+	// is not available. In the future we need to implement cAPI.UnitState()
+	// and all dependendent parts all over the tree in fleet, (schema,
+	// etcdRegistry, rpcRegistry, etc) to replace UnitStates() in this place
+	// with the new method UnitState(). In practice, calling UnitStates() here
+	// is not as badly inefficient as it looks, because it will be anyway
+	// rarely called only when the assertion failed. - dpark 20160907
+
+	time.Sleep(defaultSleepTime)
+
+	var errU error
+	apiStates, errU = cAPI.UnitStates()
+	if errU != nil {
+		return fmt.Errorf("Error retrieving list of units: %v", errU)
+	}
+	return nil
+}
+
+// assertSystemdActiveState determines if a given systemd unit is actually
+// in the active state, making use of cAPI
+func assertSystemdActiveState(apiStates []*schema.UnitState, unitName string) error {
+	uState, err := getSingleUnitState(apiStates, unitName)
 	if err != nil {
-		return fmt.Errorf("Failed to find an active unit %s within %v, err: %v",
-			unitName, timeout, err)
+		return err
+	}
+
+	// Get systemd state and check the state is active & loaded.
+	if uState.ActiveState != "active" || uState.LoadState != "loaded" {
+		return fmt.Errorf("Failed to find an active unit %s", unitName)
 	}
 
 	return nil
 }
 
+// getSingleUnitState returns a single uState of type suState, which consists
+// of necessary systemd states, only for a given unit name.
+func getSingleUnitState(apiStates []*schema.UnitState, unitName string) (suState, error) {
+	for _, us := range apiStates {
+		if us.Name == unitName {
+			return suState{
+				us.SystemdLoadState,
+				us.SystemdActiveState,
+				us.SystemdSubState,
+			}, nil
+		}
+	}
+	return suState{}, fmt.Errorf("unit %s not found", unitName)
+}
+
 func machineState(machID string) (*machine.MachineState, error) {
 	machines, err := cAPI.Machines()
 	if err != nil {
@@ -1222,26 +1265,3 @@ func runWrapper(cf func(cCmd *cobra.Command, args []string) (exit int)) func(cCm
 		cmdExitCode = cf(cCmd, args)
 	}
 }
-
-// waitForState is a generic helper for repeatedly checking the status.
-// It gets a generic function stateCheckFunc() to be checked, which returns
-// nil on success, error otherwise. In case of failure, waitForState
-// retries to run stateCheckFunc, once in 250 msec, up to defaultSleepTime.
-func waitForState(stateCheckFunc func() error) (time.Duration, error) {
-	timeout := defaultSleepTime
-	alarm := time.After(timeout)
-	ticker := time.Tick(250 * time.Millisecond)
-
-	for {
-		select {
-		case <-alarm:
-			return timeout, fmt.Errorf("Failed to fetch systemd active states within %v", timeout)
-		case <-ticker:
-			err := stateCheckFunc()
-			if err == nil {
-				return timeout, nil
-			}
-			log.Debug("Retrying assertion of systemd active states. err: %v", err)
-		}
-	}
-}
diff --git a/functional/platform/cluster.go b/functional/platform/cluster.go
index dc51afeb..a03a7dd3 100644
--- a/functional/platform/cluster.go
+++ b/functional/platform/cluster.go
@@ -31,7 +31,8 @@ type Cluster interface {
 	DestroyMember(Member) error
 	ReplaceMember(Member) (Member, error)
 	Members() []Member
-	MemberCommand(Member, ...string) (string, string, error)
+	MemberCommand(Member, ...string) (string, error)
+	MemberCommandStderr(Member, ...string) (string, string, error)
 	Destroy(t *testing.T) error
 
 	// client operations
diff --git a/functional/platform/nspawn.go b/functional/platform/nspawn.go
index 0ee68cc1..11d727a0 100644
--- a/functional/platform/nspawn.go
+++ b/functional/platform/nspawn.go
@@ -379,7 +379,28 @@ func (nc *nspawnCluster) Members() []Member {
 	return ms
 }
 
-func (nc *nspawnCluster) MemberCommand(m Member, args ...string) (string, string, error) {
+func (nc *nspawnCluster) MemberCommand(m Member, args ...string) (string, error) {
+	baseArgs := []string{"-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", fmt.Sprintf("core@%s", m.IP())}
+	args = append(baseArgs, args...)
+	log.Printf("ssh %s", strings.Join(args, " "))
+	var stdoutBytes bytes.Buffer
+	cmd := exec.Command("ssh", args...)
+	cmd.Stdout = &stdoutBytes
+	err := cmd.Run()
+	return stdoutBytes.String(), err
+}
+
+// MemberCommandStderr() is like MemberCommand(), except that it returns
+// both stdout and stderr from the running command. This is definitely useful
+// for most cases, especially when the caller needs to check for both stdout
+// and stderr.
+//
+// NOTE: Ideally we should remove MemberCommand() above, and rename
+// MemberCommandStderr() to MemberCommand(). For doing that, however, we need
+// to change every call site to replace MemberCommand() with MemberCommandStderr().
+// Of course that would be a lot of work. I'll leave that to future work.
+// - dpark 20161102
+func (nc *nspawnCluster) MemberCommandStderr(m Member, args ...string) (string, string, error) {
 	baseArgs := []string{"-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", fmt.Sprintf("core@%s", m.IP())}
 	args = append(baseArgs, args...)
 	log.Printf("ssh %s", strings.Join(args, " "))
diff --git a/protobuf/fleet.pb.go b/protobuf/fleet.pb.go
index 9eb28e3b..347295c3 100644
--- a/protobuf/fleet.pb.go
+++ b/protobuf/fleet.pb.go
@@ -1592,7 +1592,6 @@ type RegistryClient interface {
 	GetUnit(ctx context.Context, in *UnitName, opts ...grpc.CallOption) (*MaybeUnit, error)
 	GetUnits(ctx context.Context, in *UnitFilter, opts ...grpc.CallOption) (*Units, error)
 	// global status <= pretty much like list-unit-files
-	GetUnitState(ctx context.Context, in *UnitName, opts ...grpc.CallOption) (*UnitState, error)
 	GetUnitStates(ctx context.Context, in *UnitStateFilter, opts ...grpc.CallOption) (*UnitStates, error)
 	ClearUnitHeartbeat(ctx context.Context, in *UnitName, opts ...grpc.CallOption) (*GenericReply, error)
 	CreateUnit(ctx context.Context, in *Unit, opts ...grpc.CallOption) (*GenericReply, error)
@@ -1653,15 +1652,6 @@ func (c *registryClient) GetUnits(ctx context.Context, in *UnitFilter, opts ...g
 	return out, nil
 }
 
-func (c *registryClient) GetUnitState(ctx context.Context, in *UnitName, opts ...grpc.CallOption) (*UnitState, error) {
-	out := new(UnitState)
-	err := grpc.Invoke(ctx, "/rpc.Registry/GetUnitState", in, out, c.cc, opts...)
-	if err != nil {
-		return nil, err
-	}
-	return out, nil
-}
-
 func (c *registryClient) GetUnitStates(ctx context.Context, in *UnitStateFilter, opts ...grpc.CallOption) (*UnitStates, error) {
 	out := new(UnitStates)
 	err := grpc.Invoke(ctx, "/rpc.Registry/GetUnitStates", in, out, c.cc, opts...)
@@ -1804,7 +1794,6 @@ type RegistryServer interface {
 	GetUnit(context.Context, *UnitName) (*MaybeUnit, error)
 	GetUnits(context.Context, *UnitFilter) (*Units, error)
 	// global status <= pretty much like list-unit-files
-	GetUnitState(context.Context, *UnitName) (*UnitState, error)
 	GetUnitStates(context.Context, *UnitStateFilter) (*UnitStates, error)
 	ClearUnitHeartbeat(context.Context, *UnitName) (*GenericReply, error)
 	CreateUnit(context.Context, *Unit) (*GenericReply, error)
@@ -1873,18 +1862,6 @@ func _Registry_GetUnits_Handler(srv interface{}, ctx context.Context, dec func(i
 	return out, nil
 }
 
-func _Registry_GetUnitState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
-	in := new(UnitName)
-	if err := dec(in); err != nil {
-		return nil, err
-	}
-	out, err := srv.(RegistryServer).GetUnitState(ctx, in)
-	if err != nil {
-		return nil, err
-	}
-	return out, nil
-}
-
 func _Registry_GetUnitStates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {
 	in := new(UnitStateFilter)
 	if err := dec(in); err != nil {
@@ -2058,10 +2035,6 @@ var _Registry_serviceDesc = grpc.ServiceDesc{
 			MethodName: "GetUnits",
 			Handler:    _Registry_GetUnits_Handler,
 		},
-		{
-			MethodName: "GetUnitState",
-			Handler:    _Registry_GetUnitState_Handler,
-		},
 		{
 			MethodName: "GetUnitStates",
 			Handler:    _Registry_GetUnitStates_Handler,
diff --git a/protobuf/fleet.proto b/protobuf/fleet.proto
index 0c272806..15263e09 100644
--- a/protobuf/fleet.proto
+++ b/protobuf/fleet.proto
@@ -15,7 +15,6 @@ service Registry {
 	rpc GetUnit(UnitName) returns (MaybeUnit);
 	rpc GetUnits(UnitFilter) returns (Units); // => global status ?
 	// global status <= pretty much like list-unit-files
-	rpc GetUnitState(UnitName) returns (UnitState);
 	rpc GetUnitStates(UnitStateFilter) returns (UnitStates);
 	rpc ClearUnitHeartbeat(UnitName) returns (GenericReply);
 	rpc CreateUnit(Unit) returns (GenericReply);
diff --git a/registry/fake.go b/registry/fake.go
index 52be56ee..a78babd3 100644
--- a/registry/fake.go
+++ b/registry/fake.go
@@ -16,7 +16,6 @@ package registry
 
 import (
 	"errors"
-	"fmt"
 	"sort"
 	"sync"
 	"time"
@@ -282,32 +281,6 @@ func (f *FakeRegistry) UnitStates() ([]*unit.UnitState, error) {
 	return states, nil
 }
 
-func (f *FakeRegistry) UnitState(unitName string) (*unit.UnitState, error) {
-	f.Lock()
-	defer f.Unlock()
-
-	var us *unit.UnitState
-	for name := range f.jobStates {
-		sMIDs := make([]string, 0)
-		for machineID := range f.jobStates[name] {
-			sMIDs = append(sMIDs, machineID)
-		}
-		for _, mID := range sMIDs {
-			js := f.jobStates[name][mID]
-			if name == js.UnitName {
-				us = js
-				break
-			}
-		}
-	}
-
-	if us == nil {
-		return nil, fmt.Errorf("Cannot find unit(%s) from fakeRegistry", unitName)
-	}
-
-	return us, nil
-}
-
 func (f *FakeRegistry) UnitHeartbeat(name, machID string, ttl time.Duration) error {
 	return nil
 }
diff --git a/registry/interface.go b/registry/interface.go
index 70508efc..e8ccd3e7 100644
--- a/registry/interface.go
+++ b/registry/interface.go
@@ -49,7 +49,6 @@ type UnitRegistry interface {
 	ScheduledUnit(name string) (*job.ScheduledUnit, error)
 	Unit(name string) (*job.Unit, error)
 	Units() ([]job.Unit, error)
-	UnitState(name string) (*unit.UnitState, error)
 	UnitStates() ([]*unit.UnitState, error)
 }
 
diff --git a/registry/rpc/inmemory.go b/registry/rpc/inmemory.go
index ac049f3c..25ecef69 100644
--- a/registry/rpc/inmemory.go
+++ b/registry/rpc/inmemory.go
@@ -151,16 +151,6 @@ func (r *inmemoryRegistry) Units() []pb.Unit {
 	return units
 }
 
-func (r *inmemoryRegistry) UnitState(name string) *pb.UnitState {
-	if DebugInmemoryRegistry {
-		defer debug.Exit_(debug.Enter_())
-	}
-	r.unitStatesMu.Lock()
-	defer r.unitStatesMu.Unlock()
-
-	return r.stateByMUSKey(name)
-}
-
 func (r *inmemoryRegistry) UnitStates() []*pb.UnitState {
 	if DebugInmemoryRegistry {
 		defer debug.Exit_(debug.Enter_())
@@ -323,25 +313,6 @@ func (r *inmemoryRegistry) CreateUnit(u *pb.Unit) {
 	r.unitsCache[u.Name] = *u
 }
 
-func (r *inmemoryRegistry) stateByMUSKey(name string) *pb.UnitState {
-	state := pb.UnitState{}
-
-	for unitname, unitStates := range r.unitStates {
-		if name != unitname {
-			continue
-		}
-		for _, heartbeat := range unitStates {
-			if heartbeat.isValid() {
-				s := *heartbeat.state
-				state = s
-				break
-			}
-		}
-	}
-
-	return &state
-}
-
 func (r *inmemoryRegistry) statesByMUSKey() map[registry.MUSKey]*pb.UnitState {
 	states := map[registry.MUSKey]*pb.UnitState{}
 
diff --git a/registry/rpc/registrymux.go b/registry/rpc/registrymux.go
index dc84635a..eb4ed9f1 100644
--- a/registry/rpc/registrymux.go
+++ b/registry/rpc/registrymux.go
@@ -314,10 +314,6 @@ func (r *RegistryMux) Units() ([]job.Unit, error) {
 	return r.getRegistry().Units()
 }
 
-func (r *RegistryMux) UnitState(name string) (*unit.UnitState, error) {
-	return r.getRegistry().UnitState(name)
-}
-
 func (r *RegistryMux) UnitStates() ([]*unit.UnitState, error) {
 	return r.getRegistry().UnitStates()
 }
diff --git a/registry/rpc/rpcregistry.go b/registry/rpc/rpcregistry.go
index fc05893b..c75eb049 100644
--- a/registry/rpc/rpcregistry.go
+++ b/registry/rpc/rpcregistry.go
@@ -338,26 +338,6 @@ func (r *RPCRegistry) Units() ([]job.Unit, error) {
 	return jobUnits, nil
 }
 
-func (r *RPCRegistry) UnitState(unitName string) (*unit.UnitState, error) {
-	if DebugRPCRegistry {
-		defer debug.Exit_(debug.Enter_(unitName))
-	}
-
-	state, err := r.getClient().GetUnitState(r.ctx(), &pb.UnitName{unitName})
-	if err != nil {
-		return nil, err
-	}
-
-	return &unit.UnitState{
-		UnitName:    state.Name,
-		MachineID:   state.MachineID,
-		UnitHash:    state.Hash,
-		LoadState:   state.LoadState,
-		ActiveState: state.ActiveState,
-		SubState:    state.SubState,
-	}, nil
-}
-
 func (r *RPCRegistry) UnitStates() ([]*unit.UnitState, error) {
 	if DebugRPCRegistry {
 		defer debug.Exit_(debug.Enter_())
diff --git a/registry/rpc/rpcserver.go b/registry/rpc/rpcserver.go
index 351fa212..ef7a4501 100644
--- a/registry/rpc/rpcserver.go
+++ b/registry/rpc/rpcserver.go
@@ -195,16 +195,6 @@ func (s *rpcserver) GetUnits(ctx context.Context, filter *pb.UnitFilter) (*pb.Un
 	return &pb.Units{Units: units}, nil
 }
 
-func (s *rpcserver) GetUnitState(ctx context.Context, name *pb.UnitName) (*pb.UnitState, error) {
-	if debugRPCServer {
-		defer debug.Exit_(debug.Enter_(name.Name))
-	}
-
-	state := s.localRegistry.UnitState(name.Name)
-
-	return state, nil
-}
-
 func (s *rpcserver) GetUnitStates(ctx context.Context, filter *pb.UnitStateFilter) (*pb.UnitStates, error) {
 	if debugRPCServer {
 		defer debug.Exit_(debug.Enter_())
diff --git a/registry/unit_state.go b/registry/unit_state.go
index 40d2f40a..9c0055b5 100644
--- a/registry/unit_state.go
+++ b/registry/unit_state.go
@@ -53,18 +53,6 @@ func (r *EtcdRegistry) unitStatePath(machID, jobName string) string {
 	return path.Join(r.unitStatesNamespace(jobName), machID)
 }
 
-// UnitState returns a single UnitState stored in the registry for a given unit
-// name.
-func (r *EtcdRegistry) UnitState(name string) (state *unit.UnitState, err error) {
-	var us *unit.UnitState
-	us, err = r.stateByMUSKey(name)
-	if err != nil {
-		return nil, err
-	}
-
-	return us, nil
-}
-
 // UnitStates returns a list of all UnitStates stored in the registry, sorted
 // by unit name and then machine ID.
 func (r *EtcdRegistry) UnitStates() (states []*unit.UnitState, err error) {
@@ -136,42 +124,6 @@ func (r *EtcdRegistry) statesByMUSKey() (map[MUSKey]*unit.UnitState, error) {
 	return mus, nil
 }
 
-// stateByMUSKey returns a single UnitState stored in the registry indexed by MUSKey
-// that matches with the given unit name
-func (r *EtcdRegistry) stateByMUSKey(uName string) (*unit.UnitState, error) {
-	key := r.prefixed(statesPrefix)
-	opts := &etcd.GetOptions{
-		Recursive: true,
-	}
-	res, err := r.kAPI.Get(context.Background(), key, opts)
-	if err != nil && !isEtcdError(err, etcd.ErrorCodeKeyNotFound) {
-		return nil, err
-	}
-	if res == nil {
-		return nil, nil
-	}
-
-	for _, dir := range res.Node.Nodes {
-		_, name := path.Split(dir.Key)
-		if name != uName {
-			continue
-		}
-		for _, node := range dir.Nodes {
-			_, machID := path.Split(node.Key)
-			var usm unitStateModel
-			if err := unmarshal(node.Value, &usm); err != nil {
-				log.Errorf("Error unmarshalling UnitState(%s) from Machine(%s): %v", name, machID, err)
-				continue
-			}
-			us := modelToUnitState(&usm, name)
-			if us != nil {
-				return us, nil
-			}
-		}
-	}
-	return nil, nil
-}
-
 // getUnitState retrieves the current UnitState, if any exists, for the
 // given unit that originates from the indicated machine
 func (r *EtcdRegistry) getUnitState(uName, machID string) (*unit.UnitState, error) {
diff --git a/schema/v1-gen.go b/schema/v1-gen.go
index 56561e5a..37abd3e9 100644
--- a/schema/v1-gen.go
+++ b/schema/v1-gen.go
@@ -236,78 +236,6 @@ func (c *MachinesListCall) Do() (*MachinePage, error) {
 
 }
 
-// method id "fleet.UnitState.Get":
-
-type UnitStateGetCall struct {
-	s        *Service
-	unitName string
-	opt_     map[string]interface{}
-}
-
-// Get: Retrieve a page of a single UnitState object.
-func (r *UnitStateService) Get(unitName string) *UnitStateGetCall {
-	c := &UnitStateGetCall{s: r.s, opt_: make(map[string]interface{})}
-	c.unitName = unitName
-	return c
-}
-
-// Fields allows partial responses to be retrieved.
-// See https://developers.google.com/gdata/docs/2.0/basics#PartialResponse
-// for more information.
-func (c *UnitStateGetCall) Fields(s ...googleapi.Field) *UnitStateGetCall {
-	c.opt_["fields"] = googleapi.CombineFields(s)
-	return c
-}
-
-func (c *UnitStateGetCall) Do() (*UnitState, error) {
-	var body io.Reader = nil
-	params := make(url.Values)
-	params.Set("alt", "json")
-	if v, ok := c.opt_["fields"]; ok {
-		params.Set("fields", fmt.Sprintf("%v", v))
-	}
-	urls := googleapi.ResolveRelative(c.s.BasePath, "state/{unitName}")
-	urls += "?" + params.Encode()
-	req, _ := http.NewRequest("GET", urls, body)
-	googleapi.Expand(req.URL, map[string]string{
-		"unitName": c.unitName,
-	})
-	req.Header.Set("User-Agent", "google-api-go-client/0.5")
-	res, err := c.s.client.Do(req)
-	if err != nil {
-		return nil, err
-	}
-	defer googleapi.CloseBody(res)
-	if err := googleapi.CheckResponse(res); err != nil {
-		return nil, err
-	}
-	var ret *UnitState
-	if err := json.NewDecoder(res.Body).Decode(&ret); err != nil {
-		return nil, err
-	}
-	return ret, nil
-	// {
-	//   "description": "Retrieve a page of a single UnitState object.",
-	//   "httpMethod": "GET",
-	//   "id": "fleet.UnitState.Get",
-	//   "parameterOrder": [
-	//     "unitName"
-	//   ],
-	//   "parameters": {
-	//     "unitName": {
-	//       "location": "path",
-	//       "required": true,
-	//       "type": "string"
-	//     }
-	//   },
-	//   "path": "state/{unitName}",
-	//   "response": {
-	//     "$ref": "UnitState"
-	//   }
-	// }
-
-}
-
 // method id "fleet.UnitState.List":
 
 type UnitStateListCall struct {
diff --git a/schema/v1-json.go b/schema/v1-json.go
index 7d3520c9..88e3c251 100644
--- a/schema/v1-json.go
+++ b/schema/v1-json.go
@@ -276,25 +276,6 @@ const DiscoveryJSON = `{
     },
     "UnitState": {
       "methods": {
-        "Get": {
-          "id": "fleet.UnitState.Get",
-          "description": "Retrieve a page of a single UnitState object.",
-          "httpMethod": "GET",
-          "path": "state/{unitName}",
-          "parameters": {
-            "unitName": {
-              "type": "string",
-              "location": "path",
-              "required": true
-            }
-          },
-          "parameterOrder": [
-            "unitName"
-          ],
-          "response": {
-            "$ref": "UnitState"
-          }
-        },
         "List": {
           "id": "fleet.UnitState.List",
           "description": "Retrieve a page of UnitState objects.",
@@ -322,4 +303,4 @@ const DiscoveryJSON = `{
     }
   }
 }
-`
\ No newline at end of file
+`
diff --git a/schema/v1.json b/schema/v1.json
index 189f15cf..f0f6a8de 100644
--- a/schema/v1.json
+++ b/schema/v1.json
@@ -255,25 +255,6 @@
     },
     "UnitState": {
       "methods": {
-        "Get": {
-          "id": "fleet.UnitState.Get",
-          "description": "Retrieve a page of a single UnitState object.",
-          "httpMethod": "GET",
-          "path": "state/{unitName}",
-          "parameters": {
-            "unitName": {
-              "type": "string",
-              "location": "path",
-              "required": true
-            }
-          },
-          "parameterOrder": [
-            "unitName"
-          ],
-          "response": {
-            "$ref": "UnitState"
-          }
-        },
         "List": {
           "id": "fleet.UnitState.List",
           "description": "Retrieve a page of UnitState objects.",


Test patch:
diff --git a/functional/connectivity-loss_test.go b/functional/connectivity-loss_test.go
index 7b704e93..702dbd26 100644
--- a/functional/connectivity-loss_test.go
+++ b/functional/connectivity-loss_test.go
@@ -142,9 +142,8 @@ func TestSingleNodeConnectivityLoss(t *testing.T) {
 	// Cut connection to etcd.
 	//
 	// We use REJECT here, so fleet knows immediately that it's disconnected, rather than waiting for a timeout.
-	stdout, stderr, err := cluster.MemberCommand(m0, "sudo", "iptables", "-I", "OUTPUT", "-p", "tcp", "-m", "multiport", "--dports=2379,4001", "-j", "REJECT")
-	if err != nil {
-		t.Fatalf("Failed to cut connection to etcd.\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+	if _, err = cluster.MemberCommand(m0, "sudo", "iptables", "-I", "OUTPUT", "-p", "tcp", "-m", "multiport", "--dports=2379,4001", "-j", "REJECT"); err != nil {
+		t.Fatal(err)
 	}
 
 	// Wait long enough to be reasonably confident that no more state changes will happen.
@@ -160,8 +159,9 @@ func TestSingleNodeConnectivityLoss(t *testing.T) {
 	// as fleet is not available to give us this information...
 	// We have to go deeper, and try to obtain the information from systemd directly.
 	actualSystemdFiles := map[string]string{}
+	var stdout string
 	for name, _ := range expectedSystemdFiles {
-		stdout, _, _ := cluster.MemberCommand(m0, "systemctl", "is-enabled", name)
+		stdout, _ := cluster.MemberCommand(m0, "systemctl", "is-enabled", name)
 		// do not check for error, as systemctl is-enabled returns exit status 1 for linked-runtime.
 		stdout = strings.TrimSpace(stdout)
 		if stdout == "" {
@@ -174,9 +174,9 @@ func TestSingleNodeConnectivityLoss(t *testing.T) {
 		t.Fatalf("Units files not in expected state after losing connectivity.\nExpected: %v\nActual: %v", expectedSystemdFiles, actualSystemdFiles)
 	}
 
-	stdout, stderr, err = cluster.MemberCommand(m0, "systemctl", "list-units", "-t", "service", "--no-legend", "single@*.service", "global@*.service")
+	stdout, err = cluster.MemberCommand(m0, "systemctl", "list-units", "-t", "service", "--no-legend", "single@*.service", "global@*.service")
 	if err != nil {
-		t.Fatalf("Failed to retrieve systemd unit states.\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatalf("Failed to retrieve systemd unit states: %v", err)
 	}
 	stdout = strings.TrimSpace(stdout)
 	actualSystemdStates := map[string][]string{}
@@ -191,8 +191,8 @@ func TestSingleNodeConnectivityLoss(t *testing.T) {
 	}
 
 	// Restore etcd connection.
-	if stdout, stderr, err = cluster.MemberCommand(m0, "sudo", "iptables", "-D", "OUTPUT", "-p", "tcp", "-m", "multiport", "--dports=2379,4001", "-j", "REJECT"); err != nil {
-		t.Fatalf("Failed to restore etcd connection.\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+	if _, err = cluster.MemberCommand(m0, "sudo", "iptables", "-D", "OUTPUT", "-p", "tcp", "-m", "multiport", "--dports=2379,4001", "-j", "REJECT"); err != nil {
+		t.Fatal(err)
 	}
 
 	// Again, wait long enough to be reasonably confident that no more state changes will happen.
@@ -219,9 +219,9 @@ func TestSingleNodeConnectivityLoss(t *testing.T) {
 	}
 
 	// Additionally check the logs of all active units for possible temporary state flapping.
-	stdout, stderr, err = cluster.MemberCommand(m0, "journalctl", "_PID=1")
+	stdout, err = cluster.MemberCommand(m0, "journalctl", "_PID=1")
 	if err != nil {
-		t.Fatalf("Failed to retrieve journal.\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatalf("Failed to retrieve journal: %v", err)
 	}
 	if strings.Contains(stdout, "Stopping single@") || strings.Contains(stdout, "Stopping global@") {
 		t.Fatalf("Units were unexpectedly stopped at some point:\n%s", stdout)
diff --git a/functional/node_test.go b/functional/node_test.go
index 3192f251..6beca2bb 100644
--- a/functional/node_test.go
+++ b/functional/node_test.go
@@ -62,8 +62,8 @@ func TestNodeShutdown(t *testing.T) {
 	}
 
 	// Stop the fleet process on the first member
-	if stdout, stderr, err = cluster.MemberCommand(m0, "sudo", "systemctl", "stop", "fleet"); err != nil {
-		t.Fatalf("Failed stopping fleet service:\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+	if stdout, err = cluster.MemberCommand(m0, "sudo", "systemctl", "stop", "fleet"); err != nil {
+		t.Fatalf("Failed stopping fleet service: %v\nstdout: %s\n", err, stdout)
 	}
 
 	// The first member should quickly remove itself from the published
@@ -81,10 +81,11 @@ func TestNodeShutdown(t *testing.T) {
 	// NOTE: In case of no units, systemd v230 or older prints out
 	// "Active: inactive" to stdout, while systemd v231 or newer prints out
 	// "Unit NAME could not be found" to stderr. So we need to check for
-	// both cases.
-	stdout, stderr, err = cluster.MemberCommand(m0, "systemctl", "status", "hello.service")
+	// both cases. Use MemberCommandStderr() to retrieve both stdout and stderr,
+	// and check for each case.
+	stdout, stderr, err = cluster.MemberCommandStderr(m0, "systemctl", "status", "hello.service")
 	if !strings.Contains(stdout, "Active: inactive") && !strings.Contains(stderr, "could not be found") {
-		t.Fatalf("Unit hello.service not reported as inactive:\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatalf("Unit hello.service not reported as inactive:\n%s\n", stdout)
 	}
 }
 
@@ -117,35 +118,35 @@ func TestDetectMachineId(t *testing.T) {
 
 	// Restart fleet service, and check if its systemd status is still active.
 	restartFleetService := func(m platform.Member) error {
-		stdout, stderr, err := cluster.MemberCommand(m, "sudo", "systemctl", "restart", "fleet.service")
+		stdout, err := cluster.MemberCommand(m, "sudo", "systemctl", "restart", "fleet.service")
 		if err != nil {
-			return fmt.Errorf("Failed to restart fleet service\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+			return fmt.Errorf("Failed to restart fleet service\nstdout: %s\nerr: %v", stdout, err)
 		}
 
-		stdout, stderr, err = cluster.MemberCommand(m, "systemctl", "show", "--property=ActiveState", "fleet")
+		stdout, err = cluster.MemberCommand(m, "systemctl", "show", "--property=ActiveState", "fleet")
 		if strings.TrimSpace(stdout) != "ActiveState=active" {
-			return fmt.Errorf("Fleet unit not reported as active:\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+			return fmt.Errorf("Fleet unit not reported as active:\nstdout:%s\nerr: %v", stdout, err)
 		}
-		stdout, stderr, err = cluster.MemberCommand(m, "systemctl", "show", "--property=Result", "fleet")
+		stdout, err = cluster.MemberCommand(m, "systemctl", "show", "--property=Result", "fleet")
 		if strings.TrimSpace(stdout) != "Result=success" {
-			return fmt.Errorf("Result for fleet unit not reported as success:\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+			return fmt.Errorf("Result for fleet unit not reported as success:\nstdout:%s\nerr: %v", stdout, err)
 		}
 		return nil
 	}
 
-	stdout, stderr, err := cluster.MemberCommand(m0, "cat", machineIdFile)
+	stdout, err := cluster.MemberCommand(m0, "cat", machineIdFile)
 	if err != nil {
-		t.Fatalf("Failed to get machine-id\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatalf("Failed to get machine-id\nstdout: %s\nerr: %v", stdout, err)
 	}
 	m0_machine_id := strings.TrimSpace(stdout)
 
 	// If the two machine IDs are different with each other,
 	// set the m1's ID to the same one as m0, to intentionally
 	// trigger an error case of duplication of machine ID.
-	stdout, stderr, err = cluster.MemberCommand(m1,
+	stdout, err = cluster.MemberCommand(m1,
 		"echo", m0_machine_id, "|", "sudo", "tee", machineIdFile)
 	if err != nil {
-		t.Fatalf("Failed to replace machine-id\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatalf("Failed to replace machine-id\nstdout: %s\nerr: %v", stdout, err)
 	}
 
 	if err := restartFleetService(m1); err != nil {
@@ -155,7 +156,7 @@ func TestDetectMachineId(t *testing.T) {
 	// fleetd should actually be running, but failing to list machines.
 	// So we should expect a specific error after running fleetctl list-machines,
 	// like "googlapi: Error 503: fleet server unable to communicate with etcd".
-	stdout, stderr, err = cluster.Fleetctl(m1, "list-machines", "--no-legend")
+	stdout, stderr, err := cluster.Fleetctl(m1, "list-machines", "--no-legend")
 	if err != nil {
 		if !strings.Contains(err.Error(), "exit status 1") ||
 			!strings.Contains(stderr, "fleet server unable to communicate with etcd") {
@@ -170,10 +171,10 @@ func TestDetectMachineId(t *testing.T) {
 	// Trigger another test case of m0's ID getting different from m1's.
 	// Then it's expected that m0 and m1 would be working properly with distinct
 	// machine IDs, after having restarted fleet.service both on m0 and m1.
-	stdout, stderr, err = cluster.MemberCommand(m0,
+	stdout, err = cluster.MemberCommand(m0,
 		"echo", util.NewMachineID(), "|", "sudo", "tee", machineIdFile)
 	if err != nil {
-		t.Fatalf("m0: Failed to replace machine-id\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatalf("m0: Failed to replace machine-id\nstdout: %s\nerr: %v", stdout, err)
 	}
 
 	// Restart fleet service on m0, and see that it's still working.
diff --git a/functional/server_test.go b/functional/server_test.go
index 1a54044c..7de0a207 100644
--- a/functional/server_test.go
+++ b/functional/server_test.go
@@ -65,9 +65,9 @@ func TestReconfigureServer(t *testing.T) {
 
 	// Send a SIGHUP to fleetd, and periodically checks if a message
 	// "Reloading configuration" appears in fleet's journal, up to timeout (15) seconds.
-	stdout, stderr, err = cluster.MemberCommand(m0, "sudo", "systemctl", "kill", "-s", "SIGHUP", "fleet")
+	stdout, _ = cluster.MemberCommand(m0, "sudo", "systemctl", "kill", "-s", "SIGHUP", "fleet")
 	if strings.TrimSpace(stdout) != "" {
-		t.Fatalf("Sending SIGHUP to fleetd returned.\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatalf("Sending SIGHUP to fleetd returned: %s", stdout)
 	}
 
 	// Watch the logs if fleet was correctly reloaded
@@ -97,19 +97,19 @@ func TestReconfigureServer(t *testing.T) {
 	}
 
 	// Check for HTTP listener error looking into the fleetd journal
-	stdout, stderr, err = cluster.MemberCommand(m0, "journalctl _PID=$(pidof fleetd)")
+	stdout, _ = cluster.MemberCommand(m0, "journalctl _PID=$(pidof fleetd)")
 	if strings.Contains(strings.TrimSpace(stdout), "Failed serving HTTP on listener:") {
-		t.Fatalf("Fleetd log returned error on HTTP listeners.\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatalf("Fleetd log returned error on HTTP listeners: %s", stdout)
 	}
 
 	// Check expected state after reconfiguring fleetd
-	stdout, stderr, err = cluster.MemberCommand(m0, "systemctl", "show", "--property=ActiveState", "fleet")
+	stdout, _ = cluster.MemberCommand(m0, "systemctl", "show", "--property=ActiveState", "fleet")
 	if strings.TrimSpace(stdout) != "ActiveState=active" {
-		t.Fatalf("Fleet unit not reported as active.\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatalf("Fleet unit not reported as active: %s", stdout)
 	}
-	stdout, stderr, err = cluster.MemberCommand(m0, "systemctl", "show", "--property=Result", "fleet")
+	stdout, _ = cluster.MemberCommand(m0, "systemctl", "show", "--property=Result", "fleet")
 	if strings.TrimSpace(stdout) != "Result=success" {
-		t.Fatalf("Result for fleet unit not reported as success.\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatalf("Result for fleet unit not reported as success: %s", stdout)
 	}
 }
 
@@ -123,7 +123,7 @@ func waitForReloadConfig(cluster platform.Cluster, m0 platform.Member) (err erro
 			// "journalctl -u fleet | grep \"Reloading configuration\"" is racy
 			// in a subtle way, so that it sometimes fails only on semaphoreci.
 			// - dpark 20160408
-			stdout, _, _ := cluster.MemberCommand(m0, "sudo", "journalctl --priority=info _PID=$(pidof fleetd)")
+			stdout, _ := cluster.MemberCommand(m0, "sudo", "journalctl --priority=info _PID=$(pidof fleetd)")
 			journalfleet := strings.TrimSpace(stdout)
 			if !strings.Contains(journalfleet, "Reloading configuration") {
 				fmt.Errorf("Fleetd is not fully reconfigured, retrying... entire fleet journal:\n%v", journalfleet)
@@ -144,7 +144,7 @@ func waitForReloadConfig(cluster platform.Cluster, m0 platform.Member) (err erro
 func waitForFleetdSocket(cluster platform.Cluster, m0 platform.Member) (err error) {
 	_, err = util.WaitForState(
 		func() bool {
-			stdout, _, _ := cluster.MemberCommand(m0, "test -S /var/run/fleet.sock && echo 1")
+			stdout, _ := cluster.MemberCommand(m0, "test -S /var/run/fleet.sock && echo 1")
 			if strings.TrimSpace(stdout) == "" {
 				fmt.Errorf("Fleetd is not fully started, retrying...")
 				return false
diff --git a/functional/shutdown_test.go b/functional/shutdown_test.go
index 33e9573e..cfc60a75 100644
--- a/functional/shutdown_test.go
+++ b/functional/shutdown_test.go
@@ -39,19 +39,18 @@ func TestShutdown(t *testing.T) {
 	}
 
 	// Stop the fleet process.
-	stdout, stderr, err := cluster.MemberCommand(m0, "sudo", "systemctl", "stop", "fleet")
-	if err != nil {
-		t.Fatalf("Failed to stop fleet process.\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+	if _, err = cluster.MemberCommand(m0, "sudo", "systemctl", "stop", "fleet"); err != nil {
+		t.Fatal(err)
 	}
 
 	// Check expected state after stop.
-	stdout, stderr, err = cluster.MemberCommand(m0, "systemctl", "show", "--property=ActiveState", "fleet")
+	stdout, _ := cluster.MemberCommand(m0, "systemctl", "show", "--property=ActiveState", "fleet")
 	if strings.TrimSpace(stdout) != "ActiveState=inactive" {
-		t.Fatalf("Fleet unit not reported as inactive.\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatalf("Fleet unit not reported as inactive: %s", stdout)
 	}
-	stdout, stderr, err = cluster.MemberCommand(m0, "systemctl", "show", "--property=Result", "fleet")
+	stdout, _ = cluster.MemberCommand(m0, "systemctl", "show", "--property=Result", "fleet")
 	if strings.TrimSpace(stdout) != "Result=success" {
-		t.Fatalf("Result for fleet unit not reported as success.\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatalf("Result for fleet unit not reported as success: %s", stdout)
 	}
 }
 
@@ -75,32 +74,32 @@ func TestShutdownVsMonitor(t *testing.T) {
 	// Cut connection to etcd.
 	//
 	// This will result in a failed health check, and consequently the monitor will attempt a restart.
-	stdout, stderr, err := cluster.MemberCommand(m0, "sudo", "iptables", "-I", "OUTPUT", "-p", "tcp", "-m", "multiport", "--dports=2379,4001", "-j", "DROP")
+	stdout, err := cluster.MemberCommand(m0, "sudo", "iptables", "-I", "OUTPUT", "-p", "tcp", "-m", "multiport", "--dports=2379,4001", "-j", "DROP")
 	if err != nil {
-		t.Fatalf("Failed inserting iptables rule:\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatalf("Failed inserting iptables rule:\nstdout: %s\nerr: %v", stdout, err)
 	}
 
 	// Wait for the monitor to trigger the restart.
 	//
 	// This will never complete, as long as there is no connectivity.
-	stdout, stderr, err = cluster.MemberCommand(m0, "sudo", "sh", "-c", `'until journalctl -u fleet | grep -q "Server monitor triggered: Monitor timed out before successful heartbeat"; do sleep 1; done'`)
+	stdout, err = cluster.MemberCommand(m0, "sudo", "sh", "-c", `'until journalctl -u fleet | grep -q "Server monitor triggered: Monitor timed out before successful heartbeat"; do sleep 1; done'`)
 	if err != nil {
-		t.Fatalf("Failed checking journal message:\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatalf("Failed checking journal message:\nstdout: %s\nerr: %v", stdout, err)
 	}
 
 	// Stop fleetd while the restart is still in progress.
-	stdout, stderr, err = cluster.MemberCommand(m0, "sudo", "systemctl", "stop", "fleet")
+	stdout, err = cluster.MemberCommand(m0, "sudo", "systemctl", "stop", "fleet")
 	if err != nil {
-		t.Fatalf("Failed stopping fleet service:\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatalf("Failed stopping fleet service:\nstdout: %s\nerr: %v", stdout, err)
 	}
 
 	// Verify that fleetd was shut down cleanly in spite of the concurrent restart.
-	stdout, stderr, err = cluster.MemberCommand(m0, "systemctl", "show", "--property=ActiveState", "fleet")
+	stdout, _ = cluster.MemberCommand(m0, "systemctl", "show", "--property=ActiveState", "fleet")
 	if strings.TrimSpace(stdout) != "ActiveState=inactive" {
-		t.Fatalf("Fleet unit not reported as inactive:\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatalf("Fleet unit not reported as inactive: %s", stdout)
 	}
-	stdout, stderr, err = cluster.MemberCommand(m0, "systemctl", "show", "--property=Result", "fleet")
+	stdout, _ = cluster.MemberCommand(m0, "systemctl", "show", "--property=Result", "fleet")
 	if strings.TrimSpace(stdout) != "Result=success" {
-		t.Fatalf("Result for fleet unit not reported as success:\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatalf("Result for fleet unit not reported as success: %s", stdout)
 	}
 }
diff --git a/functional/unit_action_test.go b/functional/unit_action_test.go
index 613161d4..8a5d9512 100644
--- a/functional/unit_action_test.go
+++ b/functional/unit_action_test.go
@@ -307,8 +307,8 @@ func TestUnitStatus(t *testing.T) {
 	stdout, stderr, err = cluster.Fleetctl(m,
 		"--strict-host-key-checking=false", "status", path.Base(unitFile))
 	if !strings.Contains(stdout, "Loaded: loaded") {
-		t.Errorf("Could not find expected string in status output:\nstdout: %s\nstderr:\nerr: %s",
-			stdout, stderr, err)
+		t.Errorf("Could not find expected string in status output:\n%s\nstderr:\n%s",
+			stdout, stderr)
 	}
 }
 
@@ -345,12 +345,12 @@ func TestListUnitFilesOrder(t *testing.T) {
 	// make sure that all unit files will show up
 	_, err = cluster.WaitForNUnitFiles(m, 20)
 	if err != nil {
-		t.Fatalf("Failed to run list-unit-files: %v", err)
+		t.Fatal("Failed to run list-unit-files: %v", err)
 	}
 
 	stdout, stderr, err := cluster.Fleetctl(m, "list-unit-files", "--no-legend", "--fields", "unit")
 	if err != nil {
-		t.Fatalf("Failed to run list-unit-files:\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+		t.Fatal("Failed to run list-unit-files:\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
 	}
 
 	outUnits := strings.Split(strings.TrimSpace(stdout), "\n")
@@ -607,13 +607,13 @@ func checkListUnits(cl platform.Cluster, m platform.Member, cmd string, ufs []st
 
 		if cmd == "start" {
 			// Check expected systemd state after starting units
-			stdout, stderr, err := cl.MemberCommand(m, "systemctl", "show", "--property=ActiveState", ufs[i])
+			stdout, _ := cl.MemberCommand(m, "systemctl", "show", "--property=ActiveState", ufs[i])
 			if strings.TrimSpace(stdout) != "ActiveState=active" {
-				return fmt.Errorf("Fleet unit not reported as active:\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+				return fmt.Errorf("Fleet unit not reported as active: %s", stdout)
 			}
-			stdout, stderr, err = cl.MemberCommand(m, "systemctl", "show", "--property=Result", ufs[i])
+			stdout, _ = cl.MemberCommand(m, "systemctl", "show", "--property=Result", ufs[i])
 			if strings.TrimSpace(stdout) != "Result=success" {
-				return fmt.Errorf("Result for fleet unit not reported as success:\nstdout: %s\nstderr: %s\nerr: %v", stdout, stderr, err)
+				return fmt.Errorf("Result for fleet unit not reported as success: %s", stdout)
 			}
 		}
 	}
diff --git a/registry/rpc/inmemory_test.go b/registry/rpc/inmemory_test.go
index 87a81656..36f5eda0 100644
--- a/registry/rpc/inmemory_test.go
+++ b/registry/rpc/inmemory_test.go
@@ -122,54 +122,3 @@ Description = Foo
 		t.Fatalf("unexpected amount of unit states in the in-memory registry got %d expected 1", len(inmemoryRegistry.UnitStates()))
 	}
 }
-
-func TestInMemoryUnitState(t *testing.T) {
-	inmemoryRegistry := newInmemoryRegistry()
-
-	scheduleUnit := &pb.ScheduledUnit{
-		Name:         "foo",
-		CurrentState: pb.TargetState_INACTIVE,
-		MachineID:    "machine1",
-	}
-	inmemoryRegistry.scheduledUnits[scheduleUnit.Name] = *scheduleUnit
-	contents := `
-[Unit]
-Description = Foo
-`
-	unitFile, err := unit.NewUnitFile(contents)
-	if err != nil {
-		t.Fatalf("unexpected error parsing unit %q: %v", contents, err)
-	}
-	unit := &pb.Unit{
-		Name:         "foo",
-		Unit:         unitFile.ToPB(),
-		DesiredState: pb.TargetState_LOADED,
-	}
-	machineID := "testMachine"
-	ttl := 2 * time.Second
-	inmemoryRegistry.CreateUnit(unit)
-	inmemoryRegistry.ScheduleUnit(unit.Name, machineID)
-
-	stateLoaded := &pb.UnitState{
-		Name:        unit.Name,
-		Hash:        "heh",
-		LoadState:   "active",
-		ActiveState: "loaded",
-		SubState:    "active",
-		MachineID:   machineID,
-	}
-
-	inmemoryRegistry.SaveUnitState(unit.Name, stateLoaded, ttl)
-	if !inmemoryRegistry.isUnitLoaded(unit.Name, machineID) {
-		u, ok := inmemoryRegistry.Unit(unit.Name)
-		if !ok {
-			t.Fatalf("unexpected error unit not found %s", unit.Name)
-		}
-		t.Fatalf("unexpected error unit expected to be loaded %v", u)
-	}
-
-	resUs := inmemoryRegistry.UnitState(unit.Name)
-	if resUs == nil || len(resUs.Name) == 0 {
-		t.Fatal("Invalid unit state in the in-memory registry")
-	}
-}
diff --git a/registry/unit_state_test.go b/registry/unit_state_test.go
index 46972624..3b5c4ba3 100644
--- a/registry/unit_state_test.go
+++ b/registry/unit_state_test.go
@@ -495,82 +495,6 @@ func TestUnitStates(t *testing.T) {
 	}
 }
 
-func TestUnitState(t *testing.T) {
-	fus1 := unit.UnitState{
-		LoadState:   "abc",
-		ActiveState: "def",
-		SubState:    "ghi",
-		MachineID:   "mID1",
-		UnitHash:    "zzz",
-		UnitName:    "foo",
-	}
-	// Multiple new unit states reported for the same unit
-	foo := &etcd.Node{
-		Key: "/fleet/states/foo",
-		Nodes: []*etcd.Node{
-			&etcd.Node{
-				Key:   "/fleet/states/foo/mID1",
-				Value: usToJson(t, &fus1),
-			},
-		},
-	}
-	// Bogus new unit state which we won't expect to see in results
-	bar := &etcd.Node{
-		Key: "/fleet/states/bar",
-		Nodes: []*etcd.Node{
-			&etcd.Node{
-				Key:   "/fleet/states/bar/asdf",
-				Value: `total garbage`,
-			},
-		},
-	}
-	// Response from crawling the new "states" namespace
-	res2 := &etcd.Response{
-		Node: &etcd.Node{
-			Key:   "/fleet/states",
-			Nodes: []*etcd.Node{foo, bar},
-		},
-	}
-	e := &testEtcdKeysAPI{
-		res: []*etcd.Response{res2},
-	}
-	r := &EtcdRegistry{kAPI: e, keyPrefix: "/fleet/"}
-
-	got, err := r.UnitState(fus1.UnitName)
-	if err != nil {
-		t.Errorf("unexpected error calling UnitState(%s): %v", fus1.UnitName, err)
-	}
-
-	want := &fus1
-	if !reflect.DeepEqual(got, want) {
-		t.Errorf("UnitState() returned unexpected result")
-		t.Log("got:")
-		t.Logf("%#v", got)
-		t.Log("want:")
-		t.Logf("%#v", want)
-	}
-
-	// Ensure UnitState handles different error scenarios appropriately
-	for i, tt := range []struct {
-		errs []error
-		fail bool
-	}{
-		{[]error{etcd.Error{Code: etcd.ErrorCodeKeyNotFound}}, false},
-		{[]error{etcd.Error{Code: etcd.ErrorCodeKeyNotFound}}, false},
-		{[]error{nil}, false}, // No errors, no responses should succeed
-		{[]error{errors.New("ur registry don't work")}, true},
-		{[]error{errors.New("ur registry don't work")}, true},
-	} {
-		e = &testEtcdKeysAPI{err: tt.errs}
-		r = &EtcdRegistry{kAPI: e, keyPrefix: "/fleet"}
-		got, err = r.UnitState(fus1.UnitName)
-		if (err != nil) != tt.fail {
-			t.Errorf("case %d: unexpected error state calling UnitState(%s): got %v, want %v",
-				i, fus1.UnitName, err, tt.fail)
-		}
-	}
-}
-
 func TestMUSKeys(t *testing.T) {
 	equal := func(a MUSKeys, b []MUSKey) bool {
 		if len(a) != len(b) {