From dd9ecf5b794594e9654c82962e246217db324f02 Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Tue, 26 Aug 2014 14:32:00 -0700 Subject: [PATCH] Add types for lifecycle events. --- pkg/api/types.go | 42 ++++++++++++++++++++++++++++++-------- pkg/api/v1beta1/types.go | 43 +++++++++++++++++++++++++++++++-------- pkg/health/exec_test.go | 6 +++--- pkg/health/health_test.go | 4 ++-- pkg/health/http_test.go | 24 +++++++++++----------- pkg/health/tcp_test.go | 22 ++++++++++---------- 6 files changed, 95 insertions(+), 46 deletions(-) diff --git a/pkg/api/types.go b/pkg/api/types.go index 1ef0f4028a1..8073713c88a 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -128,8 +128,8 @@ type EnvVar struct { Value string `yaml:"value,omitempty" json:"value,omitempty"` } -// HTTPGetProbe describes a liveness probe based on HTTP Get requests. -type HTTPGetProbe struct { +// HTTPGetAction describes an action based on HTTP Get requests. +type HTTPGetAction struct { // Optional: Path to access on the HTTP server. Path string `yaml:"path,omitempty" json:"path,omitempty"` // Required: Name or number of the port to access on the container. @@ -138,14 +138,14 @@ type HTTPGetProbe struct { Host string `yaml:"host,omitempty" json:"host,omitempty"` } -// TCPSocketProbe describes a liveness probe based on opening a socket. -type TCPSocketProbe struct { +// TCPSocketAction describes an action based on opening a socket +type TCPSocketAction struct { // Required: Port to connect to. Port util.IntOrString `yaml:"port,omitempty" json:"port,omitempty"` } -// ExecProbe describes a "run in container" health probe. -type ExecProbe struct { +// ExecAction describes a "run in container" action. +type ExecAction struct { // Command is the command line to execute inside the container, the working directory for the // command is root ('/') in the container's filesystem. The command is simply exec'd, it is // not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use @@ -154,15 +154,16 @@ type ExecProbe struct { } // LivenessProbe describes a liveness probe to be examined to the container. +// TODO: pass structured data to the actions, and document that data here. type LivenessProbe struct { // Type of liveness probe. Current legal values "http", "tcp" Type string `yaml:"type,omitempty" json:"type,omitempty"` // HTTPGetProbe parameters, required if Type == 'http' - HTTPGet *HTTPGetProbe `yaml:"httpGet,omitempty" json:"httpGet,omitempty"` + HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"` // TCPSocketProbe parameter, required if Type == 'tcp' - TCPSocket *TCPSocketProbe `yaml:"tcpSocket,omitempty" json:"tcpSocket,omitempty"` + TCPSocket *TCPSocketAction `yaml:"tcpSocket,omitempty" json:"tcpSocket,omitempty"` // ExecProbe parameter, required if Type == 'exec' - Exec *ExecProbe `yaml:"exec,omitempty" json:"exec,omitempty"` + Exec *ExecAction `yaml:"exec,omitempty" json:"exec,omitempty"` // Length of time before health checking is activated. In seconds. InitialDelaySeconds int64 `yaml:"initialDelaySeconds,omitempty" json:"initialDelaySeconds,omitempty"` } @@ -186,6 +187,29 @@ type Container struct { CPU int `yaml:"cpu,omitempty" json:"cpu,omitempty"` VolumeMounts []VolumeMount `yaml:"volumeMounts,omitempty" json:"volumeMounts,omitempty"` LivenessProbe *LivenessProbe `yaml:"livenessProbe,omitempty" json:"livenessProbe,omitempty"` + Lifecycle *Lifecycle `yaml:"lifecycle,omitempty" json:"lifecycle,omitempty"` +} + +// Handler defines a specific action that should be taken +// TODO: pass structured data to these actions, and document that data here. +type Handler struct { + // One and only one of the following should be specified. + // Exec specifies the action to take. + Exec *ExecAction `yaml:"exec,omitempty" json:"exec,omitempty"` + // HTTPGet specifies the http request to perform. + HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"` +} + +// Lifecycle describes actions that the management system should take in response to container lifecycle +// events. For the PostStart and PreStop lifecycle handlers, management of the container blocks +// until the action is complete, unless the container process fails, in which case the handler is aborted. +type Lifecycle struct { + // PostStart is called immediately after a container is created. If the handler fails, the container + // is terminated and restarted. + PostStart *Handler `yaml:"postStart,omitempty" json:"postStart,omitempty"` + // PreStop is called immediately before a container is terminated. The reason for termination is + // passed to the handler. Regardless of the outcome of the handler, the container is eventually terminated. + PreStop *Handler `yaml:"preStop,omitempty" json:"preStop,omitempty"` } // Event is the representation of an event logged to etcd backends. diff --git a/pkg/api/v1beta1/types.go b/pkg/api/v1beta1/types.go index fb0cfff2876..66d31670bf5 100644 --- a/pkg/api/v1beta1/types.go +++ b/pkg/api/v1beta1/types.go @@ -137,8 +137,8 @@ type EnvVar struct { Value string `yaml:"value,omitempty" json:"value,omitempty"` } -// HTTPGetProbe describes a liveness probe based on HTTP Get requests. -type HTTPGetProbe struct { +// HTTPGetAction describes an action based on HTTP Get requests. +type HTTPGetAction struct { // Optional: Path to access on the HTTP server. Path string `yaml:"path,omitempty" json:"path,omitempty"` // Required: Name or number of the port to access on the container. @@ -147,14 +147,14 @@ type HTTPGetProbe struct { Host string `yaml:"host,omitempty" json:"host,omitempty"` } -// TCPSocketProbe describes a liveness probe based on opening a socket. -type TCPSocketProbe struct { +// TCPSocketAction describes an action based on opening a socket +type TCPSocketAction struct { // Required: Port to connect to. Port util.IntOrString `yaml:"port,omitempty" json:"port,omitempty"` } -// ExecProbe describes a "run in container" health probe. -type ExecProbe struct { +// ExecAction describes a "run in container" action. +type ExecAction struct { // Command is the command line to execute inside the container, the working directory for the // command is root ('/') in the container's filesystem. The command is simply exec'd, it is // not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use @@ -164,15 +164,16 @@ type ExecProbe struct { } // LivenessProbe describes a liveness probe to be examined to the container. +// TODO: pass structured data to the actions, and document that data here. type LivenessProbe struct { // Type of liveness probe. Current legal values "http", "tcp" Type string `yaml:"type,omitempty" json:"type,omitempty"` // HTTPGetProbe parameters, required if Type == 'http' - HTTPGet *HTTPGetProbe `yaml:"httpGet,omitempty" json:"httpGet,omitempty"` + HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"` // TCPSocketProbe parameter, required if Type == 'tcp' - TCPSocket *TCPSocketProbe `yaml:"tcpSocket,omitempty" json:"tcpSocket,omitempty"` + TCPSocket *TCPSocketAction `yaml:"tcpSocket,omitempty" json:"tcpSocket,omitempty"` // ExecProbe parameter, required if Type == 'exec' - Exec *ExecProbe `yaml:"exec,omitempty" json:"exec,omitempty"` + Exec *ExecAction `yaml:"exec,omitempty" json:"exec,omitempty"` // Length of time before health checking is activated. In seconds. InitialDelaySeconds int64 `yaml:"initialDelaySeconds,omitempty" json:"initialDelaySeconds,omitempty"` } @@ -196,6 +197,30 @@ type Container struct { CPU int `yaml:"cpu,omitempty" json:"cpu,omitempty"` VolumeMounts []VolumeMount `yaml:"volumeMounts,omitempty" json:"volumeMounts,omitempty"` LivenessProbe *LivenessProbe `yaml:"livenessProbe,omitempty" json:"livenessProbe,omitempty"` + Lifecycle *Lifecycle `yaml:"lifecycle,omitempty" json:"lifecycle,omitempty"` +} + +// Handler defines a specific action that should be taken +// TODO: merge this with liveness probing? +// TODO: pass structured data to these actions, and document that data here. +type Handler struct { + // One and only one of the following should be specified. + // Exec specifies the action to take. + Exec *ExecAction `yaml:"exec,omitempty" json:"exec,omitempty"` + // HTTPGet specifies the http request to perform. + HTTPGet *HTTPGetAction `yaml:"httpGet,omitempty" json:"httpGet,omitempty"` +} + +// Lifecycle describes actions that the management system should take in response to container lifecycle +// events. For the PostStart and PreStop lifecycle handlers, management of the container blocks +// until the action is complete, unless the container process fails, in which case the handler is aborted. +type Lifecycle struct { + // PostStart is called immediately after a container is created. If the handler fails, the container + // is terminated and restarted. + PostStart *Handler `yaml:"postStart,omitempty" json:"postStart,omitempty"` + // PreStop is called immediately before a container is terminated. The reason for termination is + // passed to the handler. Regardless of the outcome of the handler, the container is eventually terminated. + PreStop *Handler `yaml:"preStop,omitempty" json:"preStop,omitempty"` } // Event is the representation of an event logged to etcd backends. diff --git a/pkg/health/exec_test.go b/pkg/health/exec_test.go index 907b3975453..0782b8afd44 100644 --- a/pkg/health/exec_test.go +++ b/pkg/health/exec_test.go @@ -53,19 +53,19 @@ func TestExec(t *testing.T) { // Ok {Healthy, &api.LivenessProbe{ Type: "exec", - Exec: &api.ExecProbe{Command: []string{"ls", "-l"}}, + Exec: &api.ExecAction{Command: []string{"ls", "-l"}}, }, false, []byte("OK"), nil}, // Run returns error {Unknown, &api.LivenessProbe{ Type: "exec", - Exec: &api.ExecProbe{ + Exec: &api.ExecAction{ Command: []string{"ls", "-l"}, }, }, true, []byte("OK, NOT"), fmt.Errorf("test error")}, // Command error {Unhealthy, &api.LivenessProbe{ Type: "exec", - Exec: &api.ExecProbe{ + Exec: &api.ExecAction{ Command: []string{"ls", "-l"}, }, }, false, []byte{}, &exec.ExitError{}}, diff --git a/pkg/health/health_test.go b/pkg/health/health_test.go index 88a0bdc371e..246f869f114 100644 --- a/pkg/health/health_test.go +++ b/pkg/health/health_test.go @@ -59,7 +59,7 @@ func TestHealthChecker(t *testing.T) { } container := api.Container{ LivenessProbe: &api.LivenessProbe{ - HTTPGet: &api.HTTPGetProbe{ + HTTPGet: &api.HTTPGetAction{ Port: util.NewIntOrStringFromString(port), Path: "/foo/bar", Host: host, @@ -128,7 +128,7 @@ func TestMuxHealthChecker(t *testing.T) { } container := api.Container{ LivenessProbe: &api.LivenessProbe{ - HTTPGet: &api.HTTPGetProbe{}, + HTTPGet: &api.HTTPGetAction{}, }, } container.LivenessProbe.Type = tt.probeType diff --git a/pkg/health/http_test.go b/pkg/health/http_test.go index 6a5c1418f7c..d2067231bd6 100644 --- a/pkg/health/http_test.go +++ b/pkg/health/http_test.go @@ -29,20 +29,20 @@ import ( func TestGetURLParts(t *testing.T) { testCases := []struct { - probe *api.HTTPGetProbe + probe *api.HTTPGetAction ok bool host string port int path string }{ - {&api.HTTPGetProbe{Host: "", Port: util.NewIntOrStringFromInt(-1), Path: ""}, false, "", -1, ""}, - {&api.HTTPGetProbe{Host: "", Port: util.NewIntOrStringFromString(""), Path: ""}, false, "", -1, ""}, - {&api.HTTPGetProbe{Host: "", Port: util.NewIntOrStringFromString("-1"), Path: ""}, false, "", -1, ""}, - {&api.HTTPGetProbe{Host: "", Port: util.NewIntOrStringFromString("not-found"), Path: ""}, false, "", -1, ""}, - {&api.HTTPGetProbe{Host: "", Port: util.NewIntOrStringFromString("found"), Path: ""}, true, "127.0.0.1", 93, ""}, - {&api.HTTPGetProbe{Host: "", Port: util.NewIntOrStringFromInt(76), Path: ""}, true, "127.0.0.1", 76, ""}, - {&api.HTTPGetProbe{Host: "", Port: util.NewIntOrStringFromString("118"), Path: ""}, true, "127.0.0.1", 118, ""}, - {&api.HTTPGetProbe{Host: "hostname", Port: util.NewIntOrStringFromInt(76), Path: "path"}, true, "hostname", 76, "path"}, + {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromInt(-1), Path: ""}, false, "", -1, ""}, + {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromString(""), Path: ""}, false, "", -1, ""}, + {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromString("-1"), Path: ""}, false, "", -1, ""}, + {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromString("not-found"), Path: ""}, false, "", -1, ""}, + {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromString("found"), Path: ""}, true, "127.0.0.1", 93, ""}, + {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromInt(76), Path: ""}, true, "127.0.0.1", 76, ""}, + {&api.HTTPGetAction{Host: "", Port: util.NewIntOrStringFromString("118"), Path: ""}, true, "127.0.0.1", 118, ""}, + {&api.HTTPGetAction{Host: "hostname", Port: util.NewIntOrStringFromInt(76), Path: "path"}, true, "hostname", 76, "path"}, } for _, test := range testCases { @@ -90,13 +90,13 @@ func TestFormatURL(t *testing.T) { func TestHTTPHealthChecker(t *testing.T) { testCases := []struct { - probe *api.HTTPGetProbe + probe *api.HTTPGetAction status int health Status }{ // The probe will be filled in below. This is primarily testing that an HTTP GET happens. - {&api.HTTPGetProbe{}, http.StatusOK, Healthy}, - {&api.HTTPGetProbe{}, -1, Unhealthy}, + {&api.HTTPGetAction{}, http.StatusOK, Healthy}, + {&api.HTTPGetAction{}, -1, Unhealthy}, {nil, -1, Unknown}, } hc := &HTTPHealthChecker{ diff --git a/pkg/health/tcp_test.go b/pkg/health/tcp_test.go index 2168e981d3a..bd17328a11b 100644 --- a/pkg/health/tcp_test.go +++ b/pkg/health/tcp_test.go @@ -29,18 +29,18 @@ import ( func TestGetTCPAddrParts(t *testing.T) { testCases := []struct { - probe *api.TCPSocketProbe + probe *api.TCPSocketAction ok bool host string port int }{ - {&api.TCPSocketProbe{Port: util.NewIntOrStringFromInt(-1)}, false, "", -1}, - {&api.TCPSocketProbe{Port: util.NewIntOrStringFromString("")}, false, "", -1}, - {&api.TCPSocketProbe{Port: util.NewIntOrStringFromString("-1")}, false, "", -1}, - {&api.TCPSocketProbe{Port: util.NewIntOrStringFromString("not-found")}, false, "", -1}, - {&api.TCPSocketProbe{Port: util.NewIntOrStringFromString("found")}, true, "1.2.3.4", 93}, - {&api.TCPSocketProbe{Port: util.NewIntOrStringFromInt(76)}, true, "1.2.3.4", 76}, - {&api.TCPSocketProbe{Port: util.NewIntOrStringFromString("118")}, true, "1.2.3.4", 118}, + {&api.TCPSocketAction{Port: util.NewIntOrStringFromInt(-1)}, false, "", -1}, + {&api.TCPSocketAction{Port: util.NewIntOrStringFromString("")}, false, "", -1}, + {&api.TCPSocketAction{Port: util.NewIntOrStringFromString("-1")}, false, "", -1}, + {&api.TCPSocketAction{Port: util.NewIntOrStringFromString("not-found")}, false, "", -1}, + {&api.TCPSocketAction{Port: util.NewIntOrStringFromString("found")}, true, "1.2.3.4", 93}, + {&api.TCPSocketAction{Port: util.NewIntOrStringFromInt(76)}, true, "1.2.3.4", 76}, + {&api.TCPSocketAction{Port: util.NewIntOrStringFromString("118")}, true, "1.2.3.4", 118}, } for _, test := range testCases { @@ -69,13 +69,13 @@ func TestGetTCPAddrParts(t *testing.T) { func TestTcpHealthChecker(t *testing.T) { tests := []struct { - probe *api.TCPSocketProbe + probe *api.TCPSocketAction expectedStatus Status expectError bool }{ // The probe will be filled in below. This is primarily testing that a connection is made. - {&api.TCPSocketProbe{}, Healthy, false}, - {&api.TCPSocketProbe{}, Unhealthy, false}, + {&api.TCPSocketAction{}, Healthy, false}, + {&api.TCPSocketAction{}, Unhealthy, false}, {nil, Unknown, true}, }