-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Shubharanshu Mahapatra <[email protected]>
- Loading branch information
1 parent
94aa369
commit 60af05c
Showing
13 changed files
with
442 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package container | ||
|
||
import ( | ||
"net/http" | ||
"os" | ||
|
||
"github.com/containerd/containerd/namespaces" | ||
ncTypes "github.com/containerd/nerdctl/pkg/api/types" | ||
"github.com/gorilla/mux" | ||
|
||
"github.com/runfinch/finch-daemon/api/response" | ||
"github.com/runfinch/finch-daemon/pkg/errdefs" | ||
) | ||
|
||
// kill creates a new kill instance. | ||
func (h *handler) kill(w http.ResponseWriter, r *http.Request) { | ||
cid, ok := mux.Vars(r)["id"] | ||
if !ok || cid == "" { | ||
response.JSON(w, http.StatusBadRequest, response.NewErrorFromMsg("must specify a container ID")) | ||
return | ||
} | ||
|
||
ctx := namespaces.WithNamespace(r.Context(), h.Config.Namespace) | ||
|
||
signal := r.URL.Query().Get("signal") | ||
if signal == "" { | ||
signal = "SIGKILL" | ||
} | ||
|
||
devNull, err := os.OpenFile("/dev/null", os.O_WRONLY, 0644) | ||
if err != nil { | ||
response.JSON(w, http.StatusBadRequest, response.NewErrorFromMsg("failed to open /dev/null")) | ||
return | ||
} | ||
defer devNull.Close() | ||
|
||
globalOpt := ncTypes.GlobalCommandOptions(*h.Config) | ||
options := ncTypes.ContainerKillOptions{ | ||
GOptions: globalOpt, | ||
KillSignal: signal, | ||
Stdout: devNull, | ||
Stderr: devNull, | ||
} | ||
|
||
err = h.service.Kill(ctx, cid, options) | ||
if err != nil { | ||
var code int | ||
switch { | ||
case errdefs.IsNotFound(err): | ||
code = http.StatusNotFound | ||
case errdefs.IsConflict(err): | ||
code = http.StatusConflict | ||
default: | ||
code = http.StatusInternalServerError | ||
} | ||
response.JSON(w, code, response.NewError(err)) | ||
return | ||
} | ||
|
||
response.Status(w, http.StatusNoContent) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package container | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"net/http/httptest" | ||
|
||
ncTypes "github.com/containerd/nerdctl/pkg/api/types" | ||
"github.com/containerd/nerdctl/pkg/config" | ||
"github.com/golang/mock/gomock" | ||
"github.com/gorilla/mux" | ||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
"github.com/runfinch/finch-daemon/pkg/errdefs" | ||
|
||
"github.com/runfinch/finch-daemon/mocks/mocks_container" | ||
"github.com/runfinch/finch-daemon/mocks/mocks_logger" | ||
) | ||
|
||
var _ = Describe("Container Kill API", func() { | ||
var ( | ||
mockCtrl *gomock.Controller | ||
logger *mocks_logger.Logger | ||
service *mocks_container.MockService | ||
h *handler | ||
rr *httptest.ResponseRecorder | ||
_ ncTypes.GlobalCommandOptions | ||
_ error | ||
) | ||
|
||
BeforeEach(func() { | ||
mockCtrl = gomock.NewController(GinkgoT()) | ||
defer mockCtrl.Finish() | ||
logger = mocks_logger.NewLogger(mockCtrl) | ||
service = mocks_container.NewMockService(mockCtrl) | ||
c := config.Config{} | ||
h = newHandler(service, &c, logger) | ||
rr = httptest.NewRecorder() | ||
}) | ||
|
||
Context("kill handler", func() { | ||
It("should return 204 No Content on successful kill", func() { | ||
req, err := http.NewRequest(http.MethodPost, "/containers/id1/kill", nil) | ||
Expect(err).Should(BeNil()) | ||
req = mux.SetURLVars(req, map[string]string{"id": "id1"}) | ||
req.URL.RawQuery = "signal=SIGTERM" | ||
|
||
service.EXPECT().Kill(gomock.Any(), "id1", gomock.Any()).DoAndReturn(func(ctx context.Context, cid string, opts ncTypes.ContainerKillOptions) error { | ||
Expect(opts.KillSignal).Should(Equal("SIGTERM")) | ||
return nil | ||
}) | ||
|
||
h.kill(rr, req) | ||
Expect(rr.Body.String()).Should(BeEmpty()) | ||
Expect(rr).Should(HaveHTTPStatus(http.StatusNoContent)) | ||
}) | ||
|
||
It("should return 400 when container ID is missing", func() { | ||
req, err := http.NewRequest(http.MethodPost, "/containers//kill", nil) | ||
Expect(err).Should(BeNil()) | ||
req = mux.SetURLVars(req, map[string]string{"id": ""}) | ||
|
||
h.kill(rr, req) | ||
Expect(rr.Body).Should(MatchJSON(`{"message": "must specify a container ID"}`)) | ||
Expect(rr).Should(HaveHTTPStatus(http.StatusBadRequest)) | ||
}) | ||
It("should return 404 when service returns a not found error", func() { | ||
req, err := http.NewRequest(http.MethodPost, "/containers/id1/kill", nil) | ||
Expect(err).Should(BeNil()) | ||
req = mux.SetURLVars(req, map[string]string{"id": "id1"}) | ||
|
||
service.EXPECT().Kill(gomock.Any(), "id1", gomock.Any()).Return(errdefs.NewNotFound(fmt.Errorf("not found"))) | ||
|
||
h.kill(rr, req) | ||
Expect(rr.Body).Should(MatchJSON(`{"message": "not found"}`)) | ||
Expect(rr).Should(HaveHTTPStatus(http.StatusNotFound)) | ||
}) | ||
|
||
It("should return 409 when service returns a conflict error", func() { | ||
req, err := http.NewRequest(http.MethodPost, "/containers/id1/kill", nil) | ||
Expect(err).Should(BeNil()) | ||
req = mux.SetURLVars(req, map[string]string{"id": "id1"}) | ||
|
||
service.EXPECT().Kill(gomock.Any(), "id1", gomock.Any()).Return(errdefs.NewConflict(fmt.Errorf("conflict"))) | ||
|
||
h.kill(rr, req) | ||
Expect(rr.Body).Should(MatchJSON(`{"message": "conflict"}`)) | ||
Expect(rr).Should(HaveHTTPStatus(http.StatusConflict)) | ||
}) | ||
|
||
It("should return 500 when service returns an internal error", func() { | ||
req, err := http.NewRequest(http.MethodPost, "/containers/id1/kill", nil) | ||
Expect(err).Should(BeNil()) | ||
req = mux.SetURLVars(req, map[string]string{"id": "id1"}) | ||
|
||
service.EXPECT().Kill(gomock.Any(), "id1", gomock.Any()).Return(fmt.Errorf("unexpected error")) | ||
|
||
h.kill(rr, req) | ||
Expect(rr.Body).Should(MatchJSON(`{"message": "unexpected error"}`)) | ||
Expect(rr).Should(HaveHTTPStatus(http.StatusInternalServerError)) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package tests | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"time" | ||
|
||
. "github.com/onsi/ginkgo/v2" | ||
. "github.com/onsi/gomega" | ||
"github.com/runfinch/common-tests/command" | ||
"github.com/runfinch/common-tests/option" | ||
|
||
"github.com/runfinch/finch-daemon/api/response" | ||
"github.com/runfinch/finch-daemon/e2e/client" | ||
) | ||
|
||
func ContainerKill(opt *option.Option) { | ||
Describe("kill a container", func() { | ||
var ( | ||
uClient *http.Client | ||
version string | ||
apiUrl string | ||
) | ||
BeforeEach(func() { | ||
uClient = client.NewClient(GetDockerHostUrl()) | ||
version = GetDockerApiVersion() | ||
relativeUrl := fmt.Sprintf("/containers/%s/kill", testContainerName) | ||
apiUrl = client.ConvertToFinchUrl(version, relativeUrl) | ||
}) | ||
AfterEach(func() { | ||
command.RemoveAll(opt) | ||
}) | ||
It("should kill the container with default SIGKILL", func() { | ||
// start a container that keeps running | ||
command.Run(opt, "run", "-d", "--name", testContainerName, defaultImage, "sleep", "infinity") | ||
res, err := uClient.Post(apiUrl, "application/json", nil) | ||
Expect(err).Should(BeNil()) | ||
Expect(res.StatusCode).Should(Equal(http.StatusNoContent)) | ||
containerShouldNotBeRunning(opt, testContainerName) | ||
}) | ||
It("should fail to kill a non-existent container", func() { | ||
res, err := uClient.Post(apiUrl, "application/json", nil) | ||
Expect(err).Should(BeNil()) | ||
Expect(res.StatusCode).Should(Equal(http.StatusNotFound)) | ||
var body response.Error | ||
err = json.NewDecoder(res.Body).Decode(&body) | ||
Expect(err).Should(BeNil()) | ||
}) | ||
It("should fail to kill a non running container", func() { | ||
command.Run(opt, "create", "--name", testContainerName, defaultImage, "sleep", "infinity") | ||
res, err := uClient.Post(apiUrl, "application/json", nil) | ||
Expect(err).Should(BeNil()) | ||
Expect(res.StatusCode).Should(Equal(http.StatusConflict)) | ||
var body response.Error | ||
err = json.NewDecoder(res.Body).Decode(&body) | ||
Expect(err).Should(BeNil()) | ||
containerShouldExist(opt, testContainerName) | ||
}) | ||
It("should kill the container with SIGINT", func() { | ||
relativeUrl := fmt.Sprintf("/containers/%s/kill?signal=SIGINT", testContainerName) | ||
apiUrl = client.ConvertToFinchUrl(version, relativeUrl) | ||
// sleep infinity doesnot respond to SIGINT | ||
command.Run(opt, "run", "-d", "--name", testContainerName, defaultImage, "/bin/sh", "-c", "trap 'exit 0' SIGINT; while true; do sleep 1; done") | ||
res, err := uClient.Post(apiUrl, "application/json", nil) | ||
Expect(err).Should(BeNil()) | ||
Expect(res.StatusCode).Should(Equal(http.StatusNoContent)) | ||
// This is an async operation as a result we need to wait for the container to exit gracefully before checking the status | ||
time.Sleep(1 * time.Second) | ||
containerShouldNotBeRunning(opt, testContainerName) | ||
}) | ||
It("should not kill the container and throw error on unrecognized signal", func() { | ||
relativeUrl := fmt.Sprintf("/containers/%s/kill?signal=SIGRAND", testContainerName) | ||
apiUrl = client.ConvertToFinchUrl(version, relativeUrl) | ||
command.Run(opt, "run", "-d", "--name", testContainerName, defaultImage, "sleep", "infinity") | ||
res, err := uClient.Post(apiUrl, "application/json", nil) | ||
Expect(err).Should(BeNil()) | ||
Expect(res.StatusCode).Should(Equal(http.StatusInternalServerError)) | ||
containerShouldExist(opt, testContainerName) | ||
containerShouldBeRunning(opt, testContainerName) | ||
}) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package container | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/containerd/containerd" | ||
cerrdefs "github.com/containerd/errdefs" | ||
ncTypes "github.com/containerd/nerdctl/pkg/api/types" | ||
|
||
"github.com/runfinch/finch-daemon/pkg/errdefs" | ||
) | ||
|
||
func (s *service) Kill(ctx context.Context, cid string, options ncTypes.ContainerKillOptions) error { | ||
|
||
cont, err := s.getContainer(ctx, cid) | ||
if err != nil { | ||
if cerrdefs.IsNotFound(err) { | ||
return errdefs.NewNotFound(err) | ||
} | ||
return err | ||
} | ||
status := s.client.GetContainerStatus(ctx, cont) | ||
if status != containerd.Running { | ||
return errdefs.NewConflict(fmt.Errorf("Container is not running id: ", cid)) | ||
} | ||
|
||
err = s.nctlContainerSvc.KillContainer(ctx, cid, options) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.