blob: 4de7e0ffa8092c8840fe9ac0046ac2eb04eae8f8 [file] [log] [blame]
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"go.skia.org/infra/go/alogin/proxylogin"
"go.skia.org/infra/go/now"
"go.skia.org/infra/go/roles"
"go.skia.org/infra/go/testutils"
"go.skia.org/infra/kube/go/authproxy"
"go.skia.org/infra/machine/go/machine"
changeSinkMocks "go.skia.org/infra/machine/go/machine/change/sink/mocks"
"go.skia.org/infra/machine/go/machine/store/mocks"
"go.skia.org/infra/machine/go/machineserver/rpc"
)
var (
fakeTime = time.Date(2021, time.September, 1, 2, 3, 4, 0, time.UTC)
errFake = errors.New("my fake error")
suppliedDimensions = machine.SwarmingDimensions{
"mykey": []string{"myvalue"},
}
suppliedDimensions2 = machine.SwarmingDimensions{
"mykey2": []string{"myvalue2"},
}
)
const (
machineID = "skia-rpi2-rack4-shelf1-001"
testUser = "somebody@example.org"
sshUserIP = "root@skia-spin513-001"
sshUserIP2 = "chrome-bot@skia-spin513-002"
)
func setupForTestLocalOrProd(t *testing.T, local bool) (context.Context, machine.Description, *server, *mux.Router, *httptest.ResponseRecorder) {
ctx := now.TimeTravelingContext(fakeTime)
desc := machine.NewDescription(ctx)
desc.Dimensions = machine.SwarmingDimensions{
machine.DimID: []string{machineID},
}
desc.Temperature = map[string]float64{
"cpu": 27.3,
}
desc.SuppliedDimensions = suppliedDimensions.Copy()
desc.SSHUserIP = sshUserIP
storeMock := mocks.NewStore(t)
changeSinkMock := changeSinkMocks.NewSink(t)
s := &server{
flags: &flags{
local: local,
},
store: storeMock,
sserChangeSink: changeSinkMock,
login: proxylogin.NewWithDefaults(),
}
// Put a mux.Router in place so the request path gets parsed.
router := mux.NewRouter()
s.AddHandlers(router)
w := httptest.NewRecorder()
return ctx, desc, s, router, w
}
func setupForTest(t *testing.T) (context.Context, machine.Description, *server, *mux.Router, *httptest.ResponseRecorder) {
return setupForTestLocalOrProd(t, true)
}
func newAuthorizedRequest(method, target string, body io.Reader) *http.Request {
ret := httptest.NewRequest(method, target, body)
ret.Header.Add(authproxy.WebAuthRoleHeaderName, string(roles.Editor))
return ret
}
func TestMachineMainPageHandler_UnauthorizedRequest_Status401(t *testing.T) {
_, _, _, router, w := setupForTestLocalOrProd(t, false)
r := httptest.NewRequest("POST", fmt.Sprintf("/_/machine/toggle_mode/%s", machineID), nil)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusUnauthorized, w.Code)
}
func TestMachineToggleModeHandler_Success(t *testing.T) {
_, _, s, router, w := setupForTest(t)
changeSinkMock := s.sserChangeSink.(*changeSinkMocks.Sink)
changeSinkMock.On("Send", testutils.AnyContext, machineID).Return(nil)
storeMock := s.store.(*mocks.Store)
storeMock.On("Update", testutils.AnyContext, machineID, mock.Anything).Return(nil)
r := newAuthorizedRequest("POST", fmt.Sprintf("/_/machine/toggle_mode/%s", machineID), nil)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusOK, w.Code)
}
func TestToggleMode_ChangesModeAndAddsAnnotation(t *testing.T) {
ctx, desc, _, _, _ := setupForTest(t)
desc.MaintenanceMode = ""
retDesc := toggleMode(ctx, testUser, desc)
expected := machine.Annotation{
Message: `Enabled Maintenance Mode`,
User: "somebody@example.org",
Timestamp: fakeTime,
}
require.Equal(t, expected, retDesc.Annotation)
require.Equal(t, "somebody@example.org 2021-09-01T02:03:04Z", retDesc.MaintenanceMode)
}
func TestMachineToggleModeHandler_FailOnMissingID(t *testing.T) {
_, _, _, router, w := setupForTest(t)
r := newAuthorizedRequest("POST", "/_/machine/toggle_mode/", nil)
router.ServeHTTP(w, r)
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestMachineTogglePowerCycleHandler_Success(t *testing.T) {
_, _, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
storeMock.On("Update", testutils.AnyContext, machineID, mock.Anything).Return(nil)
r := newAuthorizedRequest("POST", fmt.Sprintf("/_/machine/toggle_powercycle/%s", machineID), nil)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusOK, w.Code)
}
func TestMachineTogglePowerCycleHandler_FailOnMissingID(t *testing.T) {
_, _, _, router, w := setupForTest(t)
r := newAuthorizedRequest("POST", "/_/machine/toggle_powercycle/", nil)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusNotFound, w.Code)
}
func TestTogglePowerCycle_SetsFlagAndAddsAnnotation(t *testing.T) {
ctx, desc, _, _, _ := setupForTest(t)
retDesc := togglePowerCycle(ctx, machineID, testUser, desc)
expected := machine.Annotation{
Message: `Requested powercycle for "skia-rpi2-rack4-shelf1-001"`,
User: "somebody@example.org",
Timestamp: fakeTime,
}
require.Equal(t, expected, retDesc.Annotation)
require.True(t, retDesc.PowerCycle)
}
func TestMachineSetAttachedDeviceHandler_Success(t *testing.T) {
_, _, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
body := testutils.MarshalJSONReader(t,
rpc.SetAttachedDevice{
AttachedDevice: machine.AttachedDeviceIOS,
})
storeMock.On("Update", testutils.AnyContext, machineID, mock.Anything).Return(nil)
changeSinkMock := s.sserChangeSink.(*changeSinkMocks.Sink)
changeSinkMock.On("Send", testutils.AnyContext, machineID).Return(nil)
r := newAuthorizedRequest("POST", fmt.Sprintf("/_/machine/set_attached_device/%s", machineID), body)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusOK, w.Code)
}
func TestMachineSetAttachedDevice_FailOnInvalidJSON(t *testing.T) {
_, _, _, router, w := setupForTest(t)
r := newAuthorizedRequest("POST", fmt.Sprintf("/_/machine/set_attached_device/%s", machineID), strings.NewReader("not valid json"))
router.ServeHTTP(w, r)
require.Equal(t, http.StatusBadRequest, w.Code)
}
func TestMachineSetAttachedDevice_FailOnMissingID(t *testing.T) {
_, _, _, router, w := setupForTest(t)
r := newAuthorizedRequest("POST", "/_/machine/set_attached_device/", nil)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusNotFound, w.Code)
}
func TestSetAttachedDevice_UpdatesAttachedDeviceField(t *testing.T) {
_, desc, _, _, _ := setupForTest(t)
retDesc := setAttachedDevice(machine.AttachedDeviceIOS, desc)
require.Equal(t, machine.AttachedDeviceIOS, retDesc.AttachedDevice)
}
func TestMachineRemoveDeviceHandler_Success(t *testing.T) {
_, _, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
body := testutils.MarshalJSONReader(t,
rpc.SetAttachedDevice{
AttachedDevice: machine.AttachedDeviceIOS,
})
storeMock.On("Update", testutils.AnyContext, machineID, mock.Anything).Return(nil)
changeSinkMock := s.sserChangeSink.(*changeSinkMocks.Sink)
changeSinkMock.On("Send", testutils.AnyContext, machineID).Return(nil)
r := newAuthorizedRequest("POST", fmt.Sprintf("/_/machine/remove_device/%s", machineID), body)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusOK, w.Code)
}
func TestMachineRemoveDevice_FailOnMissingID(t *testing.T) {
_, _, _, router, w := setupForTest(t)
r := newAuthorizedRequest("POST", "/_/machine/remove_device/", nil)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusNotFound, w.Code)
}
func TestRemoveDevice(t *testing.T) {
ctx, desc, _, _, _ := setupForTest(t)
retDesc := removeDevice(ctx, machineID, testUser, desc)
expected := machine.Annotation{
Message: `Requested device removal of "skia-rpi2-rack4-shelf1-001"`,
User: "somebody@example.org",
Timestamp: fakeTime,
}
require.Equal(t, expected, retDesc.Annotation)
require.Empty(t, retDesc.Dimensions)
require.Empty(t, retDesc.Temperature)
require.Empty(t, retDesc.SuppliedDimensions)
}
func TestMachineDeleteMachineHandler_Success(t *testing.T) {
_, _, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
storeMock.On("Delete", testutils.AnyContext, machineID).Return(nil)
changeSinkMock := s.sserChangeSink.(*changeSinkMocks.Sink)
changeSinkMock.On("Send", testutils.AnyContext, machineID).Return(nil)
r := newAuthorizedRequest("POST", fmt.Sprintf("/_/machine/delete_machine/%s", machineID), nil)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusOK, w.Code)
}
func TestMachineDeleteMachineHandler_DeleteFails_ReturnsStatusBadRequest(t *testing.T) {
_, _, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
storeMock.On("Delete", testutils.AnyContext, machineID).Return(errFake)
r := newAuthorizedRequest("POST", fmt.Sprintf("/_/machine/delete_machine/%s", machineID), nil)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestMachineDeleteMachineHandler_MissingMachineID_ReturnsStatusNotFound(t *testing.T) {
_, _, _, router, w := setupForTest(t)
r := newAuthorizedRequest("POST", "/_/machine/delete_machine/", nil)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusNotFound, w.Code)
}
func TestMachineSetNoteHandler_Success(t *testing.T) {
_, _, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
body := testutils.MarshalJSONReader(t,
rpc.SetNoteRequest{
Message: "this is a message",
})
storeMock.On("Update", testutils.AnyContext, machineID, mock.Anything).Return(nil)
changeSinkMock := s.sserChangeSink.(*changeSinkMocks.Sink)
changeSinkMock.On("Send", testutils.AnyContext, machineID).Return(nil)
r := newAuthorizedRequest("POST", fmt.Sprintf("/_/machine/set_note/%s", machineID), body)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusOK, w.Code)
}
func TestMachineSetNoteHandler_ReceivesInvalidJSON_ReturnsStatusBadRequest(t *testing.T) {
_, _, _, router, w := setupForTest(t)
r := newAuthorizedRequest("POST", fmt.Sprintf("/_/machine/set_note/%s", machineID), strings.NewReader("this is not valid json"))
router.ServeHTTP(w, r)
require.Equal(t, http.StatusBadRequest, w.Code)
}
func TestMachineSetNoteHandler_MissingID_ReturnsStatusNotFound(t *testing.T) {
_, _, _, router, w := setupForTest(t)
r := newAuthorizedRequest("POST", "/_/machine/set_note/", strings.NewReader("this is not valid json"))
router.ServeHTTP(w, r)
require.Equal(t, http.StatusNotFound, w.Code)
}
func TestSetNote_AddsAnnotationWithTimestamp(t *testing.T) {
ctx, desc, _, _, _ := setupForTest(t)
const message = "This is a test message."
note := rpc.SetNoteRequest{
Message: message,
}
retDesc := setNote(ctx, testUser, note, desc)
expected := machine.Annotation{
Message: message,
User: "somebody@example.org",
Timestamp: fakeTime,
}
require.Equal(t, expected, retDesc.Note)
}
func TestMachineSupplyChromeOSInfoHandler_Success(t *testing.T) {
_, _, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
body := testutils.MarshalJSONReader(t,
rpc.SupplyChromeOSRequest{
SSHUserIP: sshUserIP2,
SuppliedDimensions: suppliedDimensions2,
})
storeMock.On("Update", testutils.AnyContext, machineID, mock.Anything).Return(nil)
changeSinkMock := s.sserChangeSink.(*changeSinkMocks.Sink)
changeSinkMock.On("Send", testutils.AnyContext, machineID).Return(nil)
r := newAuthorizedRequest("POST", fmt.Sprintf("/_/machine/supply_chromeos/%s", machineID), body)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusOK, w.Code)
}
func TestMachineSupplyChromeOSInfoHandler_SSHUserIPMissing_ReturnsStatusBadRequest(t *testing.T) {
_, _, _, router, w := setupForTest(t)
body := testutils.MarshalJSONReader(t,
rpc.SupplyChromeOSRequest{
SSHUserIP: "",
SuppliedDimensions: suppliedDimensions2,
})
r := newAuthorizedRequest("POST", fmt.Sprintf("/_/machine/supply_chromeos/%s", machineID), body)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusBadRequest, w.Code)
}
func TestMachineSupplyChromeOSInfoHandler_MissingMachineID_ReturnsStatusNotFound(t *testing.T) {
_, _, _, router, w := setupForTest(t)
r := newAuthorizedRequest("POST", "/_/machine/supply_chromeos/", nil)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusNotFound, w.Code)
}
func TestSetChromeOSInfo_SuppliedDimensionsChange(t *testing.T) {
ctx, desc, _, _, _ := setupForTest(t)
req := rpc.SupplyChromeOSRequest{
SSHUserIP: sshUserIP2,
SuppliedDimensions: suppliedDimensions2,
}
retDesc := setChromeOSInfo(ctx, req, desc)
require.Equal(t, sshUserIP2, retDesc.SSHUserIP)
require.Equal(t, suppliedDimensions2, retDesc.SuppliedDimensions)
require.Equal(t, fakeTime, retDesc.LastUpdated)
}
func TestApiMachineDescriptionHandler_GoodMachineID_Description(t *testing.T) {
ctx, desc, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
storeMock.On("Get", testutils.AnyContext, machineID).Return(desc, nil)
r := newAuthorizedRequest("GET", fmt.Sprintf("/json/v1/machine/description/%s", machineID), nil)
r = r.WithContext(ctx)
// Make the request.
router.ServeHTTP(w, r)
var actual machine.Description
err := json.Unmarshal(w.Body.Bytes(), &actual)
require.NoError(t, err)
assert.Equal(t, desc, actual)
}
func TestApiMachineDescriptionHandler_StoreGetFails_ReturnsInternalServerError(t *testing.T) {
_, desc, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
storeMock.On("Get", testutils.AnyContext, machineID).Return(desc, errFake)
r := newAuthorizedRequest("GET", fmt.Sprintf("/json/v1/machine/description/%s", machineID), nil)
// Make the request.
router.ServeHTTP(w, r)
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestApiMachineDescriptionHandler_NoMachineIDSupplied_ReturnsNotFound(t *testing.T) {
_, _, _, router, w := setupForTest(t)
r := newAuthorizedRequest("GET", "/json/v1/machine/description/", nil)
// Make the request.
router.ServeHTTP(w, r)
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestApiPowerCycleListHandler_NoMachinesNeedPowerCycling_ReturnsEmptyList(t *testing.T) {
_, _, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
machines := []string{}
storeMock.On("ListPowerCycle", testutils.AnyContext).Return(machines, nil)
r := newAuthorizedRequest("GET", "/json/v1/powercycle/list", nil)
// Make the request.
router.ServeHTTP(w, r)
var actual rpc.ListPowerCycleResponse
err := json.Unmarshal(w.Body.Bytes(), &actual)
require.NoError(t, err)
assert.Equal(t, rpc.ToListPowerCycleResponse(machines), actual)
}
func TestApiPowerCycleListHandler_OneMachineNeedsPowerCycling_ReturnsOneMachineInList(t *testing.T) {
_, _, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
machines := []string{machineID}
storeMock.On("ListPowerCycle", testutils.AnyContext).Return(machines, nil)
r := newAuthorizedRequest("GET", "/json/v1/powercycle/list", nil)
// Make the request.
router.ServeHTTP(w, r)
var actual rpc.ListPowerCycleResponse
err := json.Unmarshal(w.Body.Bytes(), &actual)
require.NoError(t, err)
assert.Equal(t, rpc.ToListPowerCycleResponse(machines), actual)
}
func TestApiPowerCycleListHandler_ListPowerCycleReturnsError_ReturnsInternalServerError(t *testing.T) {
_, _, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
storeMock.On("ListPowerCycle", testutils.AnyContext).Return(nil, errFake)
r := newAuthorizedRequest("GET", "/json/v1/powercycle/list", nil)
// Make the request.
router.ServeHTTP(w, r)
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestApiPowerCycleCompleteHandler_UpdateSucceeds_ReturnStatusOK(t *testing.T) {
_, _, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
storeMock.On("Update", testutils.AnyContext, machineID, mock.AnythingOfType("store.UpdateCallback")).Return(nil)
r := newAuthorizedRequest("POST", fmt.Sprintf("/json/v1/powercycle/complete/%s", machineID), nil)
// Make the request.
router.ServeHTTP(w, r)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestApiPowerCycleCompleteHandler_UpdateFails_ReturnStatusInternalServerError(t *testing.T) {
_, _, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
storeMock.On("Update", testutils.AnyContext, machineID, mock.AnythingOfType("store.UpdateCallback")).Return(errFake)
r := newAuthorizedRequest("POST", fmt.Sprintf("/json/v1/powercycle/complete/%s", machineID), nil)
// Make the request.
router.ServeHTTP(w, r)
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestSetPowerCycleFalse_PowerCycleIsTrue_PowerCycleBecomesFalse(t *testing.T) {
ctx := context.Background()
desc := machine.NewDescription(ctx)
desc.PowerCycle = true
desc = setPowerCycleFalse(desc)
require.False(t, desc.PowerCycle)
}
func TestSetPowerCycleStateNotAvailable_PowerCycleStateIsAvailable_PowerCycleStateBecomesNotAvailable(t *testing.T) {
ctx := context.Background()
desc := machine.NewDescription(ctx)
desc.PowerCycleState = machine.Available
desc = setPowerCycleState(machine.NotAvailable, desc)
require.Equal(t, machine.NotAvailable, desc.PowerCycleState)
}
var validUpdatePowerCycleStateRequest = rpc.UpdatePowerCycleStateRequest{
Machines: []rpc.PowerCycleStateForMachine{
{
MachineID: machineID,
PowerCycleState: machine.Available,
},
},
}
func TestApiPowerCycleStateUpdateHandler_MachineDoesNotExist_TheMachineIsSkippedAndUpdateIsNeverCalled(t *testing.T) {
_, _, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
storeMock.On("Get", testutils.AnyContext, machineID).Return(machine.Description{}, errFake)
r := newAuthorizedRequest("POST", rpc.PowerCycleStateUpdateURL, testutils.MarshalJSONReader(t, validUpdatePowerCycleStateRequest))
// Make the request.
router.ServeHTTP(w, r)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestApiPowerCycleStateUpdateHandler_UpdateFails_ReturnStatusInternalServerError(t *testing.T) {
_, _, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
storeMock.On("Update", testutils.AnyContext, machineID, mock.AnythingOfType("store.UpdateCallback")).Return(errFake)
storeMock.On("Get", testutils.AnyContext, machineID).Return(machine.Description{}, nil)
r := newAuthorizedRequest("POST", rpc.PowerCycleStateUpdateURL, testutils.MarshalJSONReader(t, validUpdatePowerCycleStateRequest))
// Make the request.
router.ServeHTTP(w, r)
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestApiPowerCycleStateUpdateHandler_InvalidJSON_ReturnStatusBadRequest(t *testing.T) {
_, _, _, router, w := setupForTest(t)
r := newAuthorizedRequest("POST", rpc.PowerCycleStateUpdateURL, strings.NewReader("this isn't valid json"))
// Make the request.
router.ServeHTTP(w, r)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestApiPowerCycleStateUpdateHandler_ValidRequest_DescriptionsAreSuccessfullyUpdated(t *testing.T) {
_, _, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
storeMock.On("Update", testutils.AnyContext, machineID, mock.AnythingOfType("store.UpdateCallback")).Return(nil).Once()
storeMock.On("Get", testutils.AnyContext, machineID).Return(machine.Description{}, nil)
r := newAuthorizedRequest("POST", rpc.PowerCycleStateUpdateURL, testutils.MarshalJSONReader(t, validUpdatePowerCycleStateRequest))
// Make the request.
router.ServeHTTP(w, r)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestMachineClearQuarantineHandler_Success(t *testing.T) {
_, _, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
storeMock.On("Update", testutils.AnyContext, machineID, mock.Anything).Return(nil)
changeSinkMock := s.sserChangeSink.(*changeSinkMocks.Sink)
changeSinkMock.On("Send", testutils.AnyContext, machineID).Return(nil)
r := newAuthorizedRequest("POST", fmt.Sprintf("/_/machine/clear_quarantined/%s", machineID), nil)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusOK, w.Code)
}
func TestMachineClearQuarantineHandler_StoreReturnsError_ReturnsInternalServerError(t *testing.T) {
_, _, s, router, w := setupForTest(t)
storeMock := s.store.(*mocks.Store)
storeMock.On("Update", testutils.AnyContext, machineID, mock.Anything).Return(errFake)
r := newAuthorizedRequest("POST", fmt.Sprintf("/_/machine/clear_quarantined/%s", machineID), nil)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestMachineClearQuarantineHandler_MachineIDNotSupplied_ReturnsNotFound(t *testing.T) {
_, _, _, router, w := setupForTest(t)
r := newAuthorizedRequest("POST", "/_/machine/clear_quarantined/", nil)
router.ServeHTTP(w, r)
require.Equal(t, http.StatusNotFound, w.Code)
}
func TestClearQuarantined(t *testing.T) {
require.False(t, clearQuarantined(machine.Description{IsQuarantined: true}).IsQuarantined)
}