Skip to content

Commit

Permalink
Report StatusConflict on Pod opt partial failures
Browse files Browse the repository at this point in the history
- When one or more containers in the Pod reports an error on an operation
report StatusConflict and report the error(s)

- jsoniter type encoding used to marshal error as string using error.Error()

- Update test framework to allow setting any flag when creating pods

- Fix test_resize() result check

Fixes containers#8865

Signed-off-by: Jhon Honce <[email protected]>

<MH: Fix cherry-pick conflicts>

Signed-off-by: Matthew Heon <[email protected]>
  • Loading branch information
jwhonce authored and mheon committed Feb 4, 2021
1 parent 6213225 commit b576ddd
Show file tree
Hide file tree
Showing 23 changed files with 345 additions and 192 deletions.
2 changes: 1 addition & 1 deletion pkg/api/handlers/compat/resize.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,5 @@ func ResizeTTY(w http.ResponseWriter, r *http.Request) {
// reasons.
status = http.StatusCreated
}
utils.WriteResponse(w, status, "")
w.WriteHeader(status)
}
90 changes: 50 additions & 40 deletions pkg/api/handlers/libpod/pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,19 +139,20 @@ func PodStop(w http.ResponseWriter, r *http.Request) {
logrus.Errorf("Error cleaning up pod %s container %s: %v", pod.ID(), id, err)
}
}
var errs []error //nolint

report := entities.PodStopReport{Id: pod.ID()}
for id, err := range responses {
errs = append(errs, errors.Wrapf(err, "error stopping container %s", id))
report.Errs = append(report.Errs, errors.Wrapf(err, "error stopping container %s", id))
}
report := entities.PodStopReport{
Errs: errs,
Id: pod.ID(),

code := http.StatusOK
if len(report.Errs) > 0 {
code = http.StatusConflict
}
utils.WriteResponse(w, http.StatusOK, report)
utils.WriteResponse(w, code, report)
}

func PodStart(w http.ResponseWriter, r *http.Request) {
var errs []error //nolint
runtime := r.Context().Value("runtime").(*libpod.Runtime)
name := utils.GetName(r)
pod, err := runtime.LookupPod(name)
Expand All @@ -168,19 +169,23 @@ func PodStart(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusNotModified, "")
return
}

responses, err := pod.Start(r.Context())
if err != nil && errors.Cause(err) != define.ErrPodPartialFail {
utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
utils.Error(w, "Something went wrong", http.StatusConflict, err)
return
}

report := entities.PodStartReport{Id: pod.ID()}
for id, err := range responses {
errs = append(errs, errors.Wrapf(err, "error starting container %s", id))
report.Errs = append(report.Errs, errors.Wrapf(err, "error starting container "+id))
}
report := entities.PodStartReport{
Errs: errs,
Id: pod.ID(),

code := http.StatusOK
if len(report.Errs) > 0 {
code = http.StatusConflict
}
utils.WriteResponse(w, http.StatusOK, report)
utils.WriteResponse(w, code, report)
}

func PodDelete(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -209,14 +214,11 @@ func PodDelete(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
return
}
report := entities.PodRmReport{
Id: pod.ID(),
}
report := entities.PodRmReport{Id: pod.ID()}
utils.WriteResponse(w, http.StatusOK, report)
}

func PodRestart(w http.ResponseWriter, r *http.Request) {
var errs []error //nolint
runtime := r.Context().Value("runtime").(*libpod.Runtime)
name := utils.GetName(r)
pod, err := runtime.LookupPod(name)
Expand All @@ -229,14 +231,17 @@ func PodRestart(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
return
}

report := entities.PodRestartReport{Id: pod.ID()}
for id, err := range responses {
errs = append(errs, errors.Wrapf(err, "error restarting container %s", id))
report.Errs = append(report.Errs, errors.Wrapf(err, "error restarting container %s", id))
}
report := entities.PodRestartReport{
Errs: errs,
Id: pod.ID(),

code := http.StatusOK
if len(report.Errs) > 0 {
code = http.StatusConflict
}
utils.WriteResponse(w, http.StatusOK, report)
utils.WriteResponse(w, code, report)
}

func PodPrune(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -267,7 +272,6 @@ func PodPruneHelper(r *http.Request) ([]*entities.PodPruneReport, error) {
}

func PodPause(w http.ResponseWriter, r *http.Request) {
var errs []error //nolint
runtime := r.Context().Value("runtime").(*libpod.Runtime)
name := utils.GetName(r)
pod, err := runtime.LookupPod(name)
Expand All @@ -280,18 +284,20 @@ func PodPause(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong", http.StatusInternalServerError, err)
return
}

report := entities.PodPauseReport{Id: pod.ID()}
for id, v := range responses {
errs = append(errs, errors.Wrapf(v, "error pausing container %s", id))
report.Errs = append(report.Errs, errors.Wrapf(v, "error pausing container %s", id))
}
report := entities.PodPauseReport{
Errs: errs,
Id: pod.ID(),

code := http.StatusOK
if len(report.Errs) > 0 {
code = http.StatusConflict
}
utils.WriteResponse(w, http.StatusOK, report)
utils.WriteResponse(w, code, report)
}

func PodUnpause(w http.ResponseWriter, r *http.Request) {
var errs []error //nolint
runtime := r.Context().Value("runtime").(*libpod.Runtime)
name := utils.GetName(r)
pod, err := runtime.LookupPod(name)
Expand All @@ -304,14 +310,17 @@ func PodUnpause(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "failed to pause pod", http.StatusInternalServerError, err)
return
}

report := entities.PodUnpauseReport{Id: pod.ID()}
for id, v := range responses {
errs = append(errs, errors.Wrapf(v, "error unpausing container %s", id))
report.Errs = append(report.Errs, errors.Wrapf(v, "error unpausing container %s", id))
}
report := entities.PodUnpauseReport{
Errs: errs,
Id: pod.ID(),

code := http.StatusOK
if len(report.Errs) > 0 {
code = http.StatusConflict
}
utils.WriteResponse(w, http.StatusOK, &report)
utils.WriteResponse(w, code, &report)
}

func PodTop(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -361,7 +370,6 @@ func PodKill(w http.ResponseWriter, r *http.Request) {
runtime = r.Context().Value("runtime").(*libpod.Runtime)
decoder = r.Context().Value("decoder").(*schema.Decoder)
signal = "SIGKILL"
errs []error //nolint
)
query := struct {
Signal string `schema:"signal"`
Expand Down Expand Up @@ -413,16 +421,18 @@ func PodKill(w http.ResponseWriter, r *http.Request) {
return
}

report := &entities.PodKillReport{Id: pod.ID()}
for _, v := range responses {
if v != nil {
errs = append(errs, v)
report.Errs = append(report.Errs, v)
}
}
report := &entities.PodKillReport{
Errs: errs,
Id: pod.ID(),

code := http.StatusOK
if len(report.Errs) > 0 {
code = http.StatusConflict
}
utils.WriteResponse(w, http.StatusOK, report)
utils.WriteResponse(w, code, report)
}

func PodExists(w http.ResponseWriter, r *http.Request) {
Expand Down
47 changes: 46 additions & 1 deletion pkg/api/handlers/utils/handler.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package utils

import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"unsafe"

"github.com/blang/semver"
"github.com/gorilla/mux"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -144,6 +145,50 @@ func WriteResponse(w http.ResponseWriter, code int, value interface{}) {
}
}

func init() {
jsoniter.RegisterTypeEncoderFunc("error", MarshalErrorJSON, MarshalErrorJSONIsEmpty)
jsoniter.RegisterTypeEncoderFunc("[]error", MarshalErrorSliceJSON, MarshalErrorSliceJSONIsEmpty)
}

var json = jsoniter.ConfigCompatibleWithStandardLibrary

// MarshalErrorJSON writes error to stream as string
func MarshalErrorJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
p := *((*error)(ptr))
if p == nil {
stream.WriteNil()
} else {
stream.WriteString(p.Error())
}
}

// MarshalErrorSliceJSON writes []error to stream as []string JSON blob
func MarshalErrorSliceJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
a := *((*[]error)(ptr))
switch {
case len(a) == 0:
stream.WriteNil()
default:
stream.WriteArrayStart()
for i, e := range a {
if i > 0 {
stream.WriteMore()
}
stream.WriteString(e.Error())
}
stream.WriteArrayEnd()
}
}

func MarshalErrorJSONIsEmpty(_ unsafe.Pointer) bool {
return false
}

func MarshalErrorSliceJSONIsEmpty(_ unsafe.Pointer) bool {
return false
}

// WriteJSON writes an interface value encoded as JSON to w
func WriteJSON(w http.ResponseWriter, code int, value interface{}) {
// FIXME: we don't need to write the header in all/some circumstances.
w.Header().Set("Content-Type", "application/json")
Expand Down
17 changes: 16 additions & 1 deletion pkg/api/server/register_pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
// $ref: "#/definitions/IdResponse"
// 400:
// $ref: "#/responses/BadParamError"
// 409:
// description: status conflict
// schema:
// type: string
// description: message describing error
// 500:
// $ref: "#/responses/InternalError"
r.Handle(VersionedPath("/libpod/pods/create"), s.APIHandler(libpod.PodCreate)).Methods(http.MethodPost)
Expand Down Expand Up @@ -149,7 +154,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
// 404:
// $ref: "#/responses/NoSuchPod"
// 409:
// $ref: "#/responses/ConflictError"
// $ref: "#/responses/PodKillReport"
// 500:
// $ref: "#/responses/InternalError"
r.Handle(VersionedPath("/libpod/pods/{name}/kill"), s.APIHandler(libpod.PodKill)).Methods(http.MethodPost)
Expand All @@ -170,6 +175,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
// $ref: '#/responses/PodPauseReport'
// 404:
// $ref: "#/responses/NoSuchPod"
// 409:
// $ref: '#/responses/PodPauseReport'
// 500:
// $ref: "#/responses/InternalError"
r.Handle(VersionedPath("/libpod/pods/{name}/pause"), s.APIHandler(libpod.PodPause)).Methods(http.MethodPost)
Expand All @@ -189,6 +196,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
// $ref: '#/responses/PodRestartReport'
// 404:
// $ref: "#/responses/NoSuchPod"
// 409:
// $ref: "#/responses/PodRestartReport"
// 500:
// $ref: "#/responses/InternalError"
r.Handle(VersionedPath("/libpod/pods/{name}/restart"), s.APIHandler(libpod.PodRestart)).Methods(http.MethodPost)
Expand All @@ -210,6 +219,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
// $ref: "#/responses/PodAlreadyStartedError"
// 404:
// $ref: "#/responses/NoSuchPod"
// 409:
// $ref: '#/responses/PodStartReport'
// 500:
// $ref: "#/responses/InternalError"
r.Handle(VersionedPath("/libpod/pods/{name}/start"), s.APIHandler(libpod.PodStart)).Methods(http.MethodPost)
Expand Down Expand Up @@ -237,6 +248,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
// $ref: "#/responses/BadParamError"
// 404:
// $ref: "#/responses/NoSuchPod"
// 409:
// $ref: "#/responses/PodStopReport"
// 500:
// $ref: "#/responses/InternalError"
r.Handle(VersionedPath("/libpod/pods/{name}/stop"), s.APIHandler(libpod.PodStop)).Methods(http.MethodPost)
Expand All @@ -256,6 +269,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
// $ref: '#/responses/PodUnpauseReport'
// 404:
// $ref: "#/responses/NoSuchPod"
// 409:
// $ref: '#/responses/PodUnpauseReport'
// 500:
// $ref: "#/responses/InternalError"
r.Handle(VersionedPath("/libpod/pods/{name}/unpause"), s.APIHandler(libpod.PodUnpause)).Methods(http.MethodPost)
Expand Down
Loading

0 comments on commit b576ddd

Please sign in to comment.