Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions hivesim/hive.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,26 @@ func (sim *Simulation) StopClient(testSuite SuiteID, test TestID, nodeid string)
return err
}

// PauseClient signals to the host that the node needs to be paused.
func (sim *Simulation) PauseClient(testSuite SuiteID, test TestID, nodeid string) error {
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/testsuite/%d/test/%d/node/%s/pause", sim.url, testSuite, test, nodeid), nil)
if err != nil {
return err
}
_, err = http.DefaultClient.Do(req)
return err
}

// UnpauseClient signals to the host that the node needs to be unpaused.
func (sim *Simulation) UnpauseClient(testSuite SuiteID, test TestID, nodeid string) error {
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/testsuite/%d/test/%d/node/%s/pause", sim.url, testSuite, test, nodeid), nil)
if err != nil {
return err
}
_, err = http.DefaultClient.Do(req)
return err
}

// ClientEnodeURL returns the enode URL of a running client.
func (sim *Simulation) ClientEnodeURL(testSuite SuiteID, test TestID, node string) (string, error) {
return sim.ClientEnodeURLNetwork(testSuite, test, node, "bridge")
Expand Down
10 changes: 10 additions & 0 deletions hivesim/testapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ func (c *Client) Exec(command ...string) (*ExecInfo, error) {
return c.test.Sim.ClientExec(c.test.SuiteID, c.test.TestID, c.Container, command)
}

// Pauses the client container.
func (c *Client) Pause() error {
return c.test.Sim.PauseClient(c.test.SuiteID, c.test.TestID, c.Container)
}

// Unpauses the client container.
func (c *Client) Unpause() error {
return c.test.Sim.UnpauseClient(c.test.SuiteID, c.test.TestID, c.Container)
}

// T is a running test. This is a lot like testing.T, but has some additional methods for
// launching clients.
//
Expand Down
24 changes: 20 additions & 4 deletions internal/fakes/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import (

// BackendHooks can be used to override the behavior of the fake backend.
type BackendHooks struct {
CreateContainer func(image string, opt libhive.ContainerOptions) (string, error)
StartContainer func(image, containerID string, opt libhive.ContainerOptions) (*libhive.ContainerInfo, error)
DeleteContainer func(containerID string) error
RunProgram func(containerID string, cmd []string) (*libhive.ExecInfo, error)
CreateContainer func(image string, opt libhive.ContainerOptions) (string, error)
StartContainer func(image, containerID string, opt libhive.ContainerOptions) (*libhive.ContainerInfo, error)
DeleteContainer func(containerID string) error
PauseContainer func(containerID string) error
UnpauseContainer func(containerID string) error
RunProgram func(containerID string, cmd []string) (*libhive.ExecInfo, error)

NetworkNameToID func(string) (string, error)
CreateNetwork func(string) (string, error)
Expand Down Expand Up @@ -145,6 +147,20 @@ func (b *fakeBackend) DeleteContainer(containerID string) error {
return err
}

func (b *fakeBackend) PauseContainer(containerID string) error {
if b.hooks.PauseContainer != nil {
return b.hooks.PauseContainer(containerID)
}
return nil
}

func (b *fakeBackend) UnpauseContainer(containerID string) error {
if b.hooks.UnpauseContainer != nil {
return b.hooks.UnpauseContainer(containerID)
}
return nil
}

func (b *fakeBackend) RunProgram(ctx context.Context, containerID string, cmd []string) (*libhive.ExecInfo, error) {
if b.hooks.RunProgram != nil {
return b.hooks.RunProgram(containerID, cmd)
Expand Down
20 changes: 20 additions & 0 deletions internal/libdocker/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,26 @@ func (b *ContainerBackend) DeleteContainer(containerID string) error {
return err
}

// PauseContainer pauses the given container.
func (b *ContainerBackend) PauseContainer(containerID string) error {
b.logger.Debug("pausing container", "container", containerID[:8])
err := b.client.PauseContainer(containerID)
if err != nil {
b.logger.Error("can't pause container", "container", containerID[:8], "err", err)
}
return err
}

// UnpauseContainer unpauses the given container.
func (b *ContainerBackend) UnpauseContainer(containerID string) error {
b.logger.Debug("unpausing container", "container", containerID[:8])
err := b.client.UnpauseContainer(containerID)
if err != nil {
b.logger.Error("can't unpause container", "container", containerID[:8], "err", err)
}
return err
}

// CreateNetwork creates a docker network.
func (b *ContainerBackend) CreateNetwork(name string) (string, error) {
network, err := b.client.CreateNetwork(docker.CreateNetworkOptions{
Expand Down
42 changes: 42 additions & 0 deletions internal/libhive/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ func newSimulationAPI(b ContainerBackend, env SimEnv, tm *TestManager) http.Hand
router.HandleFunc("/testsuite/{suite}/test/{test}/node/{node}", api.getNodeStatus).Methods("GET")
router.HandleFunc("/testsuite/{suite}/test/{test}/node", api.startClient).Methods("POST")
router.HandleFunc("/testsuite/{suite}/test/{test}/node/{node}", api.stopClient).Methods("DELETE")
router.HandleFunc("/testsuite/{suite}/test/{test}/node/{node}/pause", api.pauseClient).Methods("POST")
router.HandleFunc("/testsuite/{suite}/test/{test}/node/{node}/pause", api.unpauseClient).Methods("DELETE")
router.HandleFunc("/testsuite/{suite}/test", api.startTest).Methods("POST")
// post because the delete http verb does not always support a message body
router.HandleFunc("/testsuite/{suite}/test/{test}", api.endTest).Methods("POST")
Expand Down Expand Up @@ -367,6 +369,46 @@ func (api *simAPI) stopClient(w http.ResponseWriter, r *http.Request) {
}
}

// pauseClient pauses a client container.
func (api *simAPI) pauseClient(w http.ResponseWriter, r *http.Request) {
_, testID, err := api.requestSuiteAndTest(r)
if err != nil {
serveError(w, err, http.StatusBadRequest)
return
}
node := mux.Vars(r)["node"]

err = api.tm.PauseNode(testID, node)
switch {
case err == ErrNoSuchNode:
serveError(w, err, http.StatusNotFound)
case err != nil:
serveError(w, err, http.StatusInternalServerError)
default:
serveOK(w)
}
}

// unpauseClient unpauses a client container.
func (api *simAPI) unpauseClient(w http.ResponseWriter, r *http.Request) {
_, testID, err := api.requestSuiteAndTest(r)
if err != nil {
serveError(w, err, http.StatusBadRequest)
return
}
node := mux.Vars(r)["node"]

err = api.tm.UnpauseNode(testID, node)
switch {
case err == ErrNoSuchNode:
serveError(w, err, http.StatusNotFound)
case err != nil:
serveError(w, err, http.StatusInternalServerError)
default:
serveOK(w)
}
}

// getNodeStatus returns the status of a client container.
func (api *simAPI) getNodeStatus(w http.ResponseWriter, r *http.Request) {
suiteID, testID, err := api.requestSuiteAndTest(r)
Expand Down
2 changes: 2 additions & 0 deletions internal/libhive/dockerface.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ type ContainerBackend interface {
CreateContainer(ctx context.Context, image string, opt ContainerOptions) (string, error)
StartContainer(ctx context.Context, containerID string, opt ContainerOptions) (*ContainerInfo, error)
DeleteContainer(containerID string) error
PauseContainer(containerID string) error
UnpauseContainer(containerID string) error

// RunProgram runs a command in the given container and returns its outputs and exit code.
RunProgram(ctx context.Context, containerID string, cmdline []string) (*ExecInfo, error)
Expand Down
40 changes: 40 additions & 0 deletions internal/libhive/testmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,46 @@ func (manager *TestManager) StopNode(testID TestID, nodeID string) error {
return nil
}

// PauseNode pauses a client container.
func (manager *TestManager) PauseNode(testID TestID, nodeID string) error {
manager.testCaseMutex.Lock()
defer manager.testCaseMutex.Unlock()

testCase, ok := manager.runningTestCases[testID]
if !ok {
return ErrNoSuchNode
}
nodeInfo, ok := testCase.ClientInfo[nodeID]
if !ok {
return ErrNoSuchNode
}
// Pause the container.
if err := manager.backend.PauseContainer(nodeInfo.ID); err != nil {
return fmt.Errorf("unable to pause client: %v", err)
}
return nil
}

// UnpauseNode unpauses a client container.
func (manager *TestManager) UnpauseNode(testID TestID, nodeID string) error {
manager.testCaseMutex.Lock()
defer manager.testCaseMutex.Unlock()

testCase, ok := manager.runningTestCases[testID]
if !ok {
return ErrNoSuchNode
}
nodeInfo, ok := testCase.ClientInfo[nodeID]
if !ok {
return ErrNoSuchNode
}
// Unpause the container.
if err := manager.backend.UnpauseContainer(nodeInfo.ID); err != nil {
return fmt.Errorf("unable to unpause client: %v", err)
}
return nil
}

// writeSuiteFile writes the simulation result to the log directory.
func writeSuiteFile(s *TestSuite, logdir string) error {
suiteData, err := json.Marshal(s)
Expand Down