Commit e079e23e authored by Brendan Burns's avatar Brendan Burns

Merge pull request #6597 from fabioy/kubectl-validate

Add "kubectl validate" command to do a cluster health check.
parents ba95c8a1 c6ef2ddf
...@@ -14,11 +14,7 @@ ...@@ -14,11 +14,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# Bring up a Kubernetes cluster. # Validates that the cluster is healthy.
#
# If the full release name (gs://<bucket>/<release>) is passed in then we take
# that directly. If not then we assume we are doing development stuff and take
# the defaults in the release config.
set -o errexit set -o errexit
set -o nounset set -o nounset
...@@ -28,12 +24,9 @@ KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. ...@@ -28,12 +24,9 @@ KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
source "${KUBE_ROOT}/cluster/kube-env.sh" source "${KUBE_ROOT}/cluster/kube-env.sh"
source "${KUBE_ROOT}/cluster/${KUBERNETES_PROVIDER}/util.sh" source "${KUBE_ROOT}/cluster/${KUBERNETES_PROVIDER}/util.sh"
get-password
detect-master > /dev/null
detect-minions > /dev/null
MINIONS_FILE=/tmp/minions-$$ MINIONS_FILE=/tmp/minions-$$
trap 'rm -rf "${MINIONS_FILE}"' EXIT trap 'rm -rf "${MINIONS_FILE}"' EXIT
# Make several attempts to deal with slow cluster birth. # Make several attempts to deal with slow cluster birth.
attempt=0 attempt=0
while true; do while true; do
...@@ -54,62 +47,39 @@ done ...@@ -54,62 +47,39 @@ done
echo "Found ${found} nodes." echo "Found ${found} nodes."
cat -n "${MINIONS_FILE}" cat -n "${MINIONS_FILE}"
# On vSphere, use minion IPs as their names attempt=0
if [[ "${KUBERNETES_PROVIDER}" == "vsphere" || "${KUBERNETES_PROVIDER}" == "vagrant" || "${KUBERNETES_PROVIDER}" == "libvirt-coreos" || "${KUBERNETES_PROVIDER}" == "juju" ]] ; then while true; do
MINION_NAMES=("${KUBE_MINION_IP_ADDRESSES[@]}") kubectl_output=$("${KUBE_ROOT}/cluster/kubectl.sh" get componentstatus)
fi
# On AWS we can't really name the minions, so just trust that if the number is right, the right names are there.
if [[ "${KUBERNETES_PROVIDER}" == "aws" ]]; then
MINION_NAMES=("$(cat ${MINIONS_FILE})")
# /healthz validation isn't working for some reason on AWS. So just hope for the best.
# TODO: figure out why and fix, it must be working in some form, or else clusters wouldn't work.
echo "Kubelet health checking on AWS isn't currently supported, assuming everything is good..."
echo -e "${color_green}Cluster validation succeeded${color_norm}"
exit 0
fi
for (( i=0; i<${#MINION_NAMES[@]}; i++)); do # The "kubectl validate" output is four columns like this:
# Grep returns an exit status of 1 when line is not found, so we need the : to always return a 0 exit status #
count=$(grep -c "${MINION_NAMES[$i]}" "${MINIONS_FILE}") || : # COMPONENT HEALTH MSG ERR
if [[ "${count}" == "0" ]]; then # controller-manager Healthy ok nil
echo -e "${color_red}Failed to find ${MINION_NAMES[$i]}, cluster is probably broken.${color_norm}" #
cat -n "${MINIONS_FILE}" # Parse the output to capture the value of the second column("HEALTH"), then use grep to
exit 1 # count the number of times it doesn't match "success".
fi # Because of the header, the actual unsuccessful count is 1 minus the count.
name="${MINION_NAMES[$i]}" non_success_count=$(echo "${kubectl_output}" | \
if [[ "$KUBERNETES_PROVIDER" != "vsphere" && "$KUBERNETES_PROVIDER" != "vagrant" && "$KUBERNETES_PROVIDER" != "libvirt-coreos" && "$KUBERNETES_PROVIDER" != "juju" ]]; then sed -n 's/^\([[:alnum:][:punct:]]\+\)\s\+\([[:alnum:][:punct:]]\+\)\s\+.*/\2/p' | \
# Grab fully qualified name grep 'Healthy' --invert-match -c)
name=$(grep "${MINION_NAMES[$i]}\." "${MINIONS_FILE}")
fi
# Make sure the kubelet is healthy. if ((non_success_count > 1)); then
# Make several attempts to deal with slow cluster birth. if ((attempt < 5)); then
attempt=0 echo -e "${color_yellow}Cluster not working yet.${color_norm}"
while true; do
echo -n "Attempt $((attempt+1)) at checking Kubelet installation on node ${MINION_NAMES[$i]} ..."
if [[ "$KUBERNETES_PROVIDER" != "libvirt-coreos" && "$KUBERNETES_PROVIDER" != "juju" ]]; then
curl_output=$(curl -s --insecure --user "${KUBE_USER}:${KUBE_PASSWORD}" \
"https://${KUBE_MASTER_IP}/api/v1beta1/proxy/minions/${name}/healthz")
else
curl_output=$(curl -s \
"http://${KUBE_MASTER_IP}:8080/api/v1beta1/proxy/minions/${name}/healthz")
fi
if [[ "${curl_output}" != "ok" ]]; then
if (( attempt > 5 )); then
echo
echo -e "${color_red}Kubelet failed to install on node ${MINION_NAMES[$i]}. Your cluster is unlikely to work correctly."
echo -e "Please run ./cluster/kube-down.sh and re-create the cluster. (sorry!)${color_norm}"
exit 1
fi
else
echo -e " ${color_green}[working]${color_norm}"
break
fi
echo -e " ${color_yellow}[not working yet]${color_norm}"
attempt=$((attempt+1)) attempt=$((attempt+1))
sleep 30 sleep 30
done else
echo -e " ${color_yellow}Validate output:${color_norm}"
echo "${kubectl_output}"
echo -e "${color_red}Validation returned one or more failed components. Cluster is probably broken.${color_norm}"
exit 1
fi
else
break
fi
done done
echo "Validate output:"
echo "${kubectl_output}"
echo -e "${color_green}Cluster validation succeeded${color_norm}" echo -e "${color_green}Cluster validation succeeded${color_norm}"
...@@ -242,6 +242,7 @@ _kubectl_get() ...@@ -242,6 +242,7 @@ _kubectl_get()
must_have_one_flag=() must_have_one_flag=()
must_have_one_noun=() must_have_one_noun=()
must_have_one_noun+=("componentstatus")
must_have_one_noun+=("endpoints") must_have_one_noun+=("endpoints")
must_have_one_noun+=("event") must_have_one_noun+=("event")
must_have_one_noun+=("limitrange") must_have_one_noun+=("limitrange")
......
...@@ -66,4 +66,4 @@ kubectl ...@@ -66,4 +66,4 @@ kubectl
* [kubectl update](kubectl_update.md) - Update a resource by filename or stdin. * [kubectl update](kubectl_update.md) - Update a resource by filename or stdin.
* [kubectl version](kubectl_version.md) - Print the client and server version information. * [kubectl version](kubectl_version.md) - Print the client and server version information.
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.392549632 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.488963312 +0000 UTC
...@@ -50,4 +50,4 @@ kubectl api-versions ...@@ -50,4 +50,4 @@ kubectl api-versions
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.39227534 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.488505223 +0000 UTC
...@@ -50,4 +50,4 @@ kubectl cluster-info ...@@ -50,4 +50,4 @@ kubectl cluster-info
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.392162759 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.48831375 +0000 UTC
...@@ -63,4 +63,4 @@ kubectl config SUBCOMMAND ...@@ -63,4 +63,4 @@ kubectl config SUBCOMMAND
* [kubectl config use-context](kubectl_config_use-context.md) - Sets the current-context in a kubeconfig file * [kubectl config use-context](kubectl_config_use-context.md) - Sets the current-context in a kubeconfig file
* [kubectl config view](kubectl_config_view.md) - displays Merged kubeconfig settings or a specified kubeconfig file. * [kubectl config view](kubectl_config_view.md) - displays Merged kubeconfig settings or a specified kubeconfig file.
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.392043616 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.488116168 +0000 UTC
...@@ -65,4 +65,4 @@ $ kubectl config set-cluster e2e --insecure-skip-tls-verify=true ...@@ -65,4 +65,4 @@ $ kubectl config set-cluster e2e --insecure-skip-tls-verify=true
### SEE ALSO ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [kubectl config](kubectl_config.md) - config modifies kubeconfig files
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.39119629 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.486460859 +0000 UTC
...@@ -58,4 +58,4 @@ $ kubectl config set-context gce --user=cluster-admin ...@@ -58,4 +58,4 @@ $ kubectl config set-context gce --user=cluster-admin
### SEE ALSO ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [kubectl config](kubectl_config.md) - config modifies kubeconfig files
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.391488399 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.486736724 +0000 UTC
...@@ -78,4 +78,4 @@ $ kubectl set-credentials cluster-admin --client-certificate=~/.kube/admin.crt - ...@@ -78,4 +78,4 @@ $ kubectl set-credentials cluster-admin --client-certificate=~/.kube/admin.crt -
### SEE ALSO ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [kubectl config](kubectl_config.md) - config modifies kubeconfig files
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.391323192 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.486604006 +0000 UTC
...@@ -52,4 +52,4 @@ kubectl config set PROPERTY_NAME PROPERTY_VALUE ...@@ -52,4 +52,4 @@ kubectl config set PROPERTY_NAME PROPERTY_VALUE
### SEE ALSO ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [kubectl config](kubectl_config.md) - config modifies kubeconfig files
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.391618859 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.486861123 +0000 UTC
...@@ -51,4 +51,4 @@ kubectl config unset PROPERTY_NAME ...@@ -51,4 +51,4 @@ kubectl config unset PROPERTY_NAME
### SEE ALSO ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [kubectl config](kubectl_config.md) - config modifies kubeconfig files
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.391735806 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.487685494 +0000 UTC
...@@ -50,4 +50,4 @@ kubectl config use-context CONTEXT_NAME ...@@ -50,4 +50,4 @@ kubectl config use-context CONTEXT_NAME
### SEE ALSO ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [kubectl config](kubectl_config.md) - config modifies kubeconfig files
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.391848246 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.487888021 +0000 UTC
...@@ -72,4 +72,4 @@ $ kubectl config view -o template --template='{{range .users}}{{ if eq .name "e2 ...@@ -72,4 +72,4 @@ $ kubectl config view -o template --template='{{range .users}}{{ if eq .name "e2
### SEE ALSO ### SEE ALSO
* [kubectl config](kubectl_config.md) - config modifies kubeconfig files * [kubectl config](kubectl_config.md) - config modifies kubeconfig files
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.391073075 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.486319118 +0000 UTC
...@@ -63,4 +63,4 @@ $ cat pod.json | kubectl create -f - ...@@ -63,4 +63,4 @@ $ cat pod.json | kubectl create -f -
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.388588064 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.48343431 +0000 UTC
...@@ -81,4 +81,4 @@ $ kubectl delete pods --all ...@@ -81,4 +81,4 @@ $ kubectl delete pods --all
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.389412973 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.483731878 +0000 UTC
...@@ -53,4 +53,4 @@ kubectl describe RESOURCE ID ...@@ -53,4 +53,4 @@ kubectl describe RESOURCE ID
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.388410556 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.483293174 +0000 UTC
...@@ -64,4 +64,4 @@ $ kubectl exec -p 123456-7890 -c ruby-container -i -t -- bash -il ...@@ -64,4 +64,4 @@ $ kubectl exec -p 123456-7890 -c ruby-container -i -t -- bash -il
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.390127525 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.484697863 +0000 UTC
...@@ -82,4 +82,4 @@ $ kubectl expose streamer --port=4100 --protocol=udp --service-name=video-stream ...@@ -82,4 +82,4 @@ $ kubectl expose streamer --port=4100 --protocol=udp --service-name=video-stream
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.390792874 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.485803902 +0000 UTC
...@@ -8,7 +8,7 @@ Display one or many resources ...@@ -8,7 +8,7 @@ Display one or many resources
Display one or many resources. Display one or many resources.
Possible resources include pods (po), replication controllers (rc), services Possible resources include pods (po), replication controllers (rc), services
(svc), minions (mi), or events (ev). (svc), minions (mi), events (ev), or component statuses (cs).
By specifying the output as 'template' and providing a Go template as the value By specifying the output as 'template' and providing a Go template as the value
of the --template flag, you can filter the attributes of the fetched resource(s). of the --template flag, you can filter the attributes of the fetched resource(s).
...@@ -85,4 +85,4 @@ $ kubectl get rc/web service/frontend pods/web-pod-13je7 ...@@ -85,4 +85,4 @@ $ kubectl get rc/web service/frontend pods/web-pod-13je7
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.387483074 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.482589064 +0000 UTC
...@@ -81,4 +81,4 @@ $ kubectl label pods foo bar- ...@@ -81,4 +81,4 @@ $ kubectl label pods foo bar-
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.390937166 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.486060232 +0000 UTC
...@@ -62,4 +62,4 @@ $ kubectl log -f 123456-7890 ruby-container ...@@ -62,4 +62,4 @@ $ kubectl log -f 123456-7890 ruby-container
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.389728881 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.484139739 +0000 UTC
...@@ -53,4 +53,4 @@ kubectl namespace [namespace] ...@@ -53,4 +53,4 @@ kubectl namespace [namespace]
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.389609191 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.483937463 +0000 UTC
...@@ -68,4 +68,4 @@ $ kubectl port-forward -p mypod 0:5000 ...@@ -68,4 +68,4 @@ $ kubectl port-forward -p mypod 0:5000
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.390241417 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.484899751 +0000 UTC
...@@ -65,4 +65,4 @@ $ kubectl proxy --api-prefix=k8s-api ...@@ -65,4 +65,4 @@ $ kubectl proxy --api-prefix=k8s-api
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.390360738 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.485099157 +0000 UTC
...@@ -68,4 +68,4 @@ $ kubectl resize --current-replicas=2 --replicas=3 replicationcontrollers foo ...@@ -68,4 +68,4 @@ $ kubectl resize --current-replicas=2 --replicas=3 replicationcontrollers foo
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.389989377 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.484493463 +0000 UTC
...@@ -68,4 +68,4 @@ $ cat frontend-v2.json | kubectl rolling-update frontend-v1 -f - ...@@ -68,4 +68,4 @@ $ cat frontend-v2.json | kubectl rolling-update frontend-v1 -f -
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.38985117 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.484316119 +0000 UTC
...@@ -78,4 +78,4 @@ $ kubectl run-container nginx --image=nginx --overrides='{ "apiVersion": "v1beta ...@@ -78,4 +78,4 @@ $ kubectl run-container nginx --image=nginx --overrides='{ "apiVersion": "v1beta
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.390501802 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.485362986 +0000 UTC
...@@ -72,4 +72,4 @@ $ kubectl stop -f path/to/resources ...@@ -72,4 +72,4 @@ $ kubectl stop -f path/to/resources
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.390631789 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.48555328 +0000 UTC
...@@ -67,4 +67,4 @@ $ kubectl update pods my-pod --patch='{ "apiVersion": "v1beta1", "desiredState": ...@@ -67,4 +67,4 @@ $ kubectl update pods my-pod --patch='{ "apiVersion": "v1beta1", "desiredState":
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.388743178 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.483572524 +0000 UTC
...@@ -51,4 +51,4 @@ kubectl version ...@@ -51,4 +51,4 @@ kubectl version
### SEE ALSO ### SEE ALSO
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-04-16 17:04:37.392395408 +0000 UTC ###### Auto generated by spf13/cobra at 2015-04-17 18:59:11.488692518 +0000 UTC
...@@ -17,7 +17,7 @@ Display one or many resources. ...@@ -17,7 +17,7 @@ Display one or many resources.
.PP .PP
Possible resources include pods (po), replication controllers (rc), services Possible resources include pods (po), replication controllers (rc), services
(svc), minions (mi), or events (ev). (svc), minions (mi), events (ev), or component statuses (cs).
.PP .PP
By specifying the output as 'template' and providing a Go template as the value By specifying the output as 'template' and providing a Go template as the value
......
...@@ -138,13 +138,17 @@ func kindToResource(kind string, mixedCase bool) (plural, singular string) { ...@@ -138,13 +138,17 @@ func kindToResource(kind string, mixedCase bool) (plural, singular string) {
} else { } else {
singular = strings.ToLower(kind) singular = strings.ToLower(kind)
} }
switch string(singular[len(singular)-1]) { if strings.HasSuffix(singular, "status") {
case "s": plural = strings.TrimSuffix(singular, "status") + "statuses"
plural = singular } else {
case "y": switch string(singular[len(singular)-1]) {
plural = strings.TrimSuffix(singular, "y") + "ies" case "s":
default: plural = singular
plural = singular + "s" case "y":
plural = strings.TrimSuffix(singular, "y") + "ies"
default:
plural = singular + "s"
}
} }
return return
} }
...@@ -215,7 +219,7 @@ func (m *DefaultRESTMapper) RESTMapping(kind string, versions ...string) (*RESTM ...@@ -215,7 +219,7 @@ func (m *DefaultRESTMapper) RESTMapping(kind string, versions ...string) (*RESTM
return nil, fmt.Errorf("the provided version %q has no relevant versions", version) return nil, fmt.Errorf("the provided version %q has no relevant versions", version)
} }
return &RESTMapping{ retVal := &RESTMapping{
Resource: resource, Resource: resource,
APIVersion: version, APIVersion: version,
Kind: kind, Kind: kind,
...@@ -224,7 +228,9 @@ func (m *DefaultRESTMapper) RESTMapping(kind string, versions ...string) (*RESTM ...@@ -224,7 +228,9 @@ func (m *DefaultRESTMapper) RESTMapping(kind string, versions ...string) (*RESTM
Codec: interfaces.Codec, Codec: interfaces.Codec,
ObjectConvertor: interfaces.ObjectConvertor, ObjectConvertor: interfaces.ObjectConvertor,
MetadataAccessor: interfaces.MetadataAccessor, MetadataAccessor: interfaces.MetadataAccessor,
}, nil }
return retVal, nil
} }
// aliasToResource is used for mapping aliases to resources // aliasToResource is used for mapping aliases to resources
......
...@@ -60,6 +60,8 @@ func init() { ...@@ -60,6 +60,8 @@ func init() {
&PodLogOptions{}, &PodLogOptions{},
&PodExecOptions{}, &PodExecOptions{},
&PodProxyOptions{}, &PodProxyOptions{},
&ComponentStatus{},
&ComponentStatusList{},
) )
// Legacy names are supported // Legacy names are supported
Scheme.AddKnownTypeWithName("", "Minion", &Node{}) Scheme.AddKnownTypeWithName("", "Minion", &Node{})
...@@ -101,3 +103,5 @@ func (*ListOptions) IsAnAPIObject() {} ...@@ -101,3 +103,5 @@ func (*ListOptions) IsAnAPIObject() {}
func (*PodLogOptions) IsAnAPIObject() {} func (*PodLogOptions) IsAnAPIObject() {}
func (*PodExecOptions) IsAnAPIObject() {} func (*PodExecOptions) IsAnAPIObject() {}
func (*PodProxyOptions) IsAnAPIObject() {} func (*PodProxyOptions) IsAnAPIObject() {}
func (*ComponentStatus) IsAnAPIObject() {}
func (*ComponentStatusList) IsAnAPIObject() {}
...@@ -1827,3 +1827,34 @@ func AddToNodeAddresses(addresses *[]NodeAddress, addAddresses ...NodeAddress) { ...@@ -1827,3 +1827,34 @@ func AddToNodeAddresses(addresses *[]NodeAddress, addAddresses ...NodeAddress) {
} }
} }
} }
// Type and constants for component health validation.
type ComponentConditionType string
// These are the valid conditions for the component.
const (
ComponentHealthy ComponentConditionType = "Healthy"
)
type ComponentCondition struct {
Type ComponentConditionType `json:"type" description:"the type of condition"`
Status ConditionStatus `json:"status" description:"the status of this condition"`
Message string `json:"message,omitempty" description:"health check message received from the component"`
Error string `json:"error,omitempty" description:"error code from health check attempt (if any)"`
}
// ComponentStatus (and ComponentStatusList) holds the cluster validation info.
type ComponentStatus struct {
TypeMeta `json:",inline"`
ObjectMeta `json:"metadata,omitempty"`
Name string `json:"name,omitempty" description:"name of the component"`
Conditions []ComponentCondition `json:"conditions,omitempty" description:"list of component condition objects"`
}
type ComponentStatusList struct {
TypeMeta `json:",inline"`
ListMeta `json:"metadata,omitempty"`
Items []ComponentStatus `json:"items" description:"items is a list of component status objects"`
}
...@@ -68,6 +68,8 @@ func init() { ...@@ -68,6 +68,8 @@ func init() {
&PodLogOptions{}, &PodLogOptions{},
&PodExecOptions{}, &PodExecOptions{},
&PodProxyOptions{}, &PodProxyOptions{},
&ComponentStatus{},
&ComponentStatusList{},
) )
// Future names are supported // Future names are supported
api.Scheme.AddKnownTypeWithName("v1beta1", "Node", &Minion{}) api.Scheme.AddKnownTypeWithName("v1beta1", "Node", &Minion{})
...@@ -110,3 +112,5 @@ func (*ListOptions) IsAnAPIObject() {} ...@@ -110,3 +112,5 @@ func (*ListOptions) IsAnAPIObject() {}
func (*PodLogOptions) IsAnAPIObject() {} func (*PodLogOptions) IsAnAPIObject() {}
func (*PodExecOptions) IsAnAPIObject() {} func (*PodExecOptions) IsAnAPIObject() {}
func (*PodProxyOptions) IsAnAPIObject() {} func (*PodProxyOptions) IsAnAPIObject() {}
func (*ComponentStatus) IsAnAPIObject() {}
func (*ComponentStatusList) IsAnAPIObject() {}
...@@ -1592,3 +1592,32 @@ type GlusterfsVolumeSource struct { ...@@ -1592,3 +1592,32 @@ type GlusterfsVolumeSource struct {
// the Glusterfs volume to be mounted with read-only permissions // the Glusterfs volume to be mounted with read-only permissions
ReadOnly bool `json:"readOnly,omitempty" description:"Glusterfs volume to be mounted with read-only permissions"` ReadOnly bool `json:"readOnly,omitempty" description:"Glusterfs volume to be mounted with read-only permissions"`
} }
// Type and constants for component health validation.
type ComponentConditionType string
// These are the valid conditions for the component.
const (
ComponentHealthy ComponentConditionType = "Healthy"
)
type ComponentCondition struct {
Type ComponentConditionType `json:"type"`
Status ConditionStatus `json:"status"`
Message string `json:"message,omitempty" description:"health check message received from the component"`
Error string `json:"error,omitempty" description:"error code from health check attempt (if any)"`
}
// ComponentStatus (and ComponentStatusList) holds the cluster validation info.
type ComponentStatus struct {
TypeMeta `json:",inline"`
Name string `json:"name,omitempty" description:"name of the component"`
Conditions []ComponentCondition `json:"conditions,omitempty"`
}
type ComponentStatusList struct {
TypeMeta `json:",inline"`
Items []ComponentStatus `json:"items" description:"items is a list of component status objects"`
}
...@@ -68,6 +68,8 @@ func init() { ...@@ -68,6 +68,8 @@ func init() {
&PodLogOptions{}, &PodLogOptions{},
&PodExecOptions{}, &PodExecOptions{},
&PodProxyOptions{}, &PodProxyOptions{},
&ComponentStatus{},
&ComponentStatusList{},
) )
// Future names are supported // Future names are supported
api.Scheme.AddKnownTypeWithName("v1beta2", "Node", &Minion{}) api.Scheme.AddKnownTypeWithName("v1beta2", "Node", &Minion{})
...@@ -110,3 +112,5 @@ func (*ListOptions) IsAnAPIObject() {} ...@@ -110,3 +112,5 @@ func (*ListOptions) IsAnAPIObject() {}
func (*PodLogOptions) IsAnAPIObject() {} func (*PodLogOptions) IsAnAPIObject() {}
func (*PodExecOptions) IsAnAPIObject() {} func (*PodExecOptions) IsAnAPIObject() {}
func (*PodProxyOptions) IsAnAPIObject() {} func (*PodProxyOptions) IsAnAPIObject() {}
func (*ComponentStatus) IsAnAPIObject() {}
func (*ComponentStatusList) IsAnAPIObject() {}
...@@ -1655,3 +1655,32 @@ type SecretList struct { ...@@ -1655,3 +1655,32 @@ type SecretList struct {
Items []Secret `json:"items" description:"items is a list of secret objects"` Items []Secret `json:"items" description:"items is a list of secret objects"`
} }
// Type and constants for component health validation.
type ComponentConditionType string
// These are the valid conditions for the component.
const (
ComponentHealthy ComponentConditionType = "Healthy"
)
type ComponentCondition struct {
Type ComponentConditionType `json:"type"`
Status ConditionStatus `json:"status"`
Message string `json:"message,omitempty" description:"health check message received from the component"`
Error string `json:"error,omitempty" description:"error code from health check attempt (if any)"`
}
// ComponentStatus (and ComponentStatusList) holds the cluster validation info.
type ComponentStatus struct {
TypeMeta `json:",inline"`
Name string `json:"name,omitempty" description:"name of the component"`
Conditions []ComponentCondition `json:"conditions,omitempty"`
}
type ComponentStatusList struct {
TypeMeta `json:",inline"`
Items []ComponentStatus `json:"items" description:"items is a list of component status objects"`
}
...@@ -61,6 +61,8 @@ func init() { ...@@ -61,6 +61,8 @@ func init() {
&PodLogOptions{}, &PodLogOptions{},
&PodExecOptions{}, &PodExecOptions{},
&PodProxyOptions{}, &PodProxyOptions{},
&ComponentStatus{},
&ComponentStatusList{},
) )
// Legacy names are supported // Legacy names are supported
api.Scheme.AddKnownTypeWithName("v1beta3", "Minion", &Node{}) api.Scheme.AddKnownTypeWithName("v1beta3", "Minion", &Node{})
...@@ -102,3 +104,5 @@ func (*ListOptions) IsAnAPIObject() {} ...@@ -102,3 +104,5 @@ func (*ListOptions) IsAnAPIObject() {}
func (*PodLogOptions) IsAnAPIObject() {} func (*PodLogOptions) IsAnAPIObject() {}
func (*PodExecOptions) IsAnAPIObject() {} func (*PodExecOptions) IsAnAPIObject() {}
func (*PodProxyOptions) IsAnAPIObject() {} func (*PodProxyOptions) IsAnAPIObject() {}
func (*ComponentStatus) IsAnAPIObject() {}
func (*ComponentStatusList) IsAnAPIObject() {}
...@@ -1672,3 +1672,34 @@ type SecretList struct { ...@@ -1672,3 +1672,34 @@ type SecretList struct {
Items []Secret `json:"items" description:"items is a list of secret objects"` Items []Secret `json:"items" description:"items is a list of secret objects"`
} }
// Type and constants for component health validation.
type ComponentConditionType string
// These are the valid conditions for the component.
const (
ComponentHealthy ComponentConditionType = "Healthy"
)
type ComponentCondition struct {
Type ComponentConditionType `json:"type" description:"type of component condition, currently only Healthy"`
Status ConditionStatus `json:"status" description:"current status of this component condition"`
Message string `json:"message,omitempty" description:"health check message received from the component"`
Error string `json:"error,omitempty" description:"error code from health check attempt (if any)"`
}
// ComponentStatus (and ComponentStatusList) holds the cluster validation info.
type ComponentStatus struct {
TypeMeta `json:",inline"`
ObjectMeta `json:"metadata,omitempty"`
Name string `json:"name,omitempty" description:"name of the component"`
Conditions []ComponentCondition `json:"conditions,omitempty" description:"Conditions is a list of ComponentCondition objects"`
}
type ComponentStatusList struct {
TypeMeta `json:",inline"`
ListMeta `json:"metadata,omitempty"`
Items []ComponentStatus `json:"items" description:"items is a list of ComponentStatus objects"`
}
...@@ -140,7 +140,8 @@ func (g *APIGroupVersion) InstallREST(container *restful.Container) error { ...@@ -140,7 +140,8 @@ func (g *APIGroupVersion) InstallREST(container *restful.Container) error {
return errors.NewAggregate(registrationErrors) return errors.NewAggregate(registrationErrors)
} }
// TODO: Convert to go-restful // TODO: This endpoint is deprecated and should be removed at some point.
// Use "componentstatus" API instead.
func InstallValidator(mux Mux, servers func() map[string]Server) { func InstallValidator(mux Mux, servers func() map[string]Server) {
mux.Handle("/validate", NewValidator(servers)) mux.Handle("/validate", NewValidator(servers))
} }
......
...@@ -48,13 +48,39 @@ type validator struct { ...@@ -48,13 +48,39 @@ type validator struct {
rt http.RoundTripper rt http.RoundTripper
} }
type ServerStatus struct {
Component string `json:"component,omitempty"`
Health string `json:"health,omitempty"`
HealthCode probe.Result `json:"healthCode,omitempty"`
Msg string `json:"msg,omitempty"`
Err string `json:"err,omitempty"`
}
// TODO: can this use pkg/probe/http // TODO: can this use pkg/probe/http
func (s *Server) check(client httpGet) (probe.Result, string, error) { func (server *Server) DoServerCheck(rt http.RoundTripper) (probe.Result, string, error) {
var client *http.Client
scheme := "http://" scheme := "http://"
if s.EnableHTTPS { if server.EnableHTTPS {
// TODO(roberthbailey): The servers that use HTTPS are currently the
// kubelets, and we should be using a standard kubelet client library
// to talk to them rather than a separate http client.
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client = &http.Client{Transport: transport}
scheme = "https://" scheme = "https://"
} else {
client = &http.Client{Transport: rt}
} }
resp, err := client.Get(scheme + net.JoinHostPort(s.Addr, strconv.Itoa(s.Port)) + s.Path)
resp, err := client.Get(scheme + net.JoinHostPort(server.Addr, strconv.Itoa(server.Port)) + server.Path)
if err != nil { if err != nil {
return probe.Unknown, "", err return probe.Unknown, "", err
} }
...@@ -70,14 +96,6 @@ func (s *Server) check(client httpGet) (probe.Result, string, error) { ...@@ -70,14 +96,6 @@ func (s *Server) check(client httpGet) (probe.Result, string, error) {
return probe.Success, string(data), nil return probe.Success, string(data), nil
} }
type ServerStatus struct {
Component string `json:"component,omitempty"`
Health string `json:"health,omitempty"`
HealthCode probe.Result `json:"healthCode,omitempty"`
Msg string `json:"msg,omitempty"`
Err string `json:"err,omitempty"`
}
func (v *validator) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (v *validator) ServeHTTP(w http.ResponseWriter, r *http.Request) {
verb := "get" verb := "get"
apiResource := "" apiResource := ""
...@@ -88,21 +106,7 @@ func (v *validator) ServeHTTP(w http.ResponseWriter, r *http.Request) { ...@@ -88,21 +106,7 @@ func (v *validator) ServeHTTP(w http.ResponseWriter, r *http.Request) {
reply := []ServerStatus{} reply := []ServerStatus{}
for name, server := range v.servers() { for name, server := range v.servers() {
transport := v.rt transport := v.rt
if server.EnableHTTPS { status, msg, err := server.DoServerCheck(transport)
// TODO(roberthbailey): The servers that use HTTPS are currently the
// kubelets, and we should be using a standard kubelet client library
// to talk to them rather than a separate http client.
transport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
}
status, msg, err := server.check(&http.Client{Transport: transport})
var errorMsg string var errorMsg string
if err != nil { if err != nil {
errorMsg = err.Error() errorMsg = err.Error()
......
...@@ -65,8 +65,7 @@ func TestValidate(t *testing.T) { ...@@ -65,8 +65,7 @@ func TestValidate(t *testing.T) {
StatusCode: test.code, StatusCode: test.code,
}, },
} }
fake := &http.Client{Transport: fakeRT} status, data, err := s.DoServerCheck(fakeRT)
status, data, err := s.check(fake)
expect := fmt.Sprintf("http://%s:%d/healthz", s.Addr, s.Port) expect := fmt.Sprintf("http://%s:%d/healthz", s.Addr, s.Port)
if fakeRT.url != expect { if fakeRT.url != expect {
t.Errorf("expected %s, got %s", expect, fakeRT.url) t.Errorf("expected %s, got %s", expect, fakeRT.url)
......
...@@ -43,6 +43,7 @@ type Interface interface { ...@@ -43,6 +43,7 @@ type Interface interface {
NamespacesInterface NamespacesInterface
PersistentVolumesInterface PersistentVolumesInterface
PersistentVolumeClaimsNamespacer PersistentVolumeClaimsNamespacer
ComponentStatusesInterface
} }
func (c *Client) ReplicationControllers(namespace string) ReplicationControllerInterface { func (c *Client) ReplicationControllers(namespace string) ReplicationControllerInterface {
...@@ -92,6 +93,10 @@ func (c *Client) PersistentVolumeClaims(namespace string) PersistentVolumeClaimI ...@@ -92,6 +93,10 @@ func (c *Client) PersistentVolumeClaims(namespace string) PersistentVolumeClaimI
return newPersistentVolumeClaims(c, namespace) return newPersistentVolumeClaims(c, namespace)
} }
func (c *Client) ComponentStatuses() ComponentStatusInterface {
return newComponentStatuses(c)
}
// VersionInterface has a method to retrieve the server version. // VersionInterface has a method to retrieve the server version.
type VersionInterface interface { type VersionInterface interface {
ServerVersion() (*version.Info, error) ServerVersion() (*version.Info, error)
...@@ -137,6 +142,25 @@ func (c *Client) ServerAPIVersions() (*api.APIVersions, error) { ...@@ -137,6 +142,25 @@ func (c *Client) ServerAPIVersions() (*api.APIVersions, error) {
return &v, nil return &v, nil
} }
type ComponentValidatorInterface interface {
ValidateComponents() (*api.ComponentStatusList, error)
}
// ValidateComponents retrieves and parses the master's self-monitored cluster state.
// TODO: This should hit the versioned endpoint when that is implemented.
func (c *Client) ValidateComponents() (*api.ComponentStatusList, error) {
body, err := c.Get().AbsPath("/validate").DoRaw()
if err != nil {
return nil, err
}
statuses := []api.ComponentStatus{}
if err := json.Unmarshal(body, &statuses); err != nil {
return nil, fmt.Errorf("got '%s': %v", string(body), err)
}
return &api.ComponentStatusList{Items: statuses}, nil
}
// IsTimeout tests if this is a timeout error in the underlying transport. // IsTimeout tests if this is a timeout error in the underlying transport.
// This is unbelievably ugly. // This is unbelievably ugly.
// See: http://stackoverflow.com/questions/23494950/specifically-check-for-timeout-error for details // See: http://stackoverflow.com/questions/23494950/specifically-check-for-timeout-error for details
......
/*
Copyright 2015 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package client
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
)
type ComponentStatusesInterface interface {
ComponentStatuses() ComponentStatusInterface
}
// ComponentStatusInterface contains methods to retrieve ComponentStatus
type ComponentStatusInterface interface {
List(label labels.Selector, field fields.Selector) (*api.ComponentStatusList, error)
Get(name string) (*api.ComponentStatus, error)
// TODO: It'd be nice to have watch support at some point
//Watch(label labels.Selector, field fields.Selector, resourceVersion string) (watch.Interface, error)
}
// componentStatuses implements ComponentStatusesInterface
type componentStatuses struct {
client *Client
}
func newComponentStatuses(c *Client) *componentStatuses {
return &componentStatuses{c}
}
func (c *componentStatuses) List(label labels.Selector, field fields.Selector) (result *api.ComponentStatusList, err error) {
result = &api.ComponentStatusList{}
err = c.client.Get().
Resource("componentStatuses").
LabelsSelectorParam(label).
FieldsSelectorParam(field).
Do().
Into(result)
return result, err
}
func (c *componentStatuses) Get(name string) (result *api.ComponentStatus, err error) {
result = &api.ComponentStatus{}
err = c.client.Get().Resource("componentStatuses").Name(name).Do().Into(result)
return
}
/*
Copyright 2015 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package testclient
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
)
// Fake implements ComponentStatusInterface.
type FakeComponentStatuses struct {
Fake *Fake
}
func (c *FakeComponentStatuses) List(label labels.Selector, field fields.Selector) (result *api.ComponentStatusList, err error) {
obj, err := c.Fake.Invokes(FakeAction{Action: "list-componentstatuses"}, &api.ComponentStatusList{})
return obj.(*api.ComponentStatusList), err
}
func (c *FakeComponentStatuses) Get(name string) (*api.ComponentStatus, error) {
obj, err := c.Fake.Invokes(FakeAction{Action: "get-componentstatus", Value: name}, &api.ComponentStatus{})
// c.Actions = append(c.Actions, FakeAction{Action: "get-componentstatuses", Value: nil})
// testStatus := &api.ComponentStatus{
// Name: "test",
// Health: "ok",
// HealthCode: int(probe.Success),
// Message: "ok",
// Error: "",
// }
// return &api.ComponentStatusList{Items: []api.ComponentStatus{*testStatus}}, nil
return obj.(*api.ComponentStatus), err
}
...@@ -125,3 +125,7 @@ func (c *Fake) ServerAPIVersions() (*api.APIVersions, error) { ...@@ -125,3 +125,7 @@ func (c *Fake) ServerAPIVersions() (*api.APIVersions, error) {
c.Actions = append(c.Actions, FakeAction{Action: "get-apiversions", Value: nil}) c.Actions = append(c.Actions, FakeAction{Action: "get-apiversions", Value: nil})
return &api.APIVersions{Versions: []string{"v1beta1", "v1beta2"}}, nil return &api.APIVersions{Versions: []string{"v1beta1", "v1beta2"}}, nil
} }
func (c *Fake) ComponentStatuses() client.ComponentStatusInterface {
return &FakeComponentStatuses{Fake: c}
}
...@@ -150,6 +150,13 @@ func NewAPIFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) { ...@@ -150,6 +150,13 @@ func NewAPIFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) {
Object: func() (meta.RESTMapper, runtime.ObjectTyper) { Object: func() (meta.RESTMapper, runtime.ObjectTyper) {
return latest.RESTMapper, api.Scheme return latest.RESTMapper, api.Scheme
}, },
Client: func() (*client.Client, error) {
// Swap out the HTTP client out of the client with the fake's version.
fakeClient := t.Client.(*client.FakeRESTClient)
c := client.NewOrDie(t.ClientConfig)
c.Client = fakeClient.Client
return c, t.Err
},
RESTClient: func(*meta.RESTMapping) (resource.RESTClient, error) { RESTClient: func(*meta.RESTMapping) (resource.RESTClient, error) {
return t.Client, t.Err return t.Client, t.Err
}, },
......
...@@ -34,7 +34,7 @@ const ( ...@@ -34,7 +34,7 @@ const (
get_long = `Display one or many resources. get_long = `Display one or many resources.
Possible resources include pods (po), replication controllers (rc), services Possible resources include pods (po), replication controllers (rc), services
(svc), minions (mi), or events (ev). (svc), minions (mi), events (ev), or component statuses (cs).
By specifying the output as 'template' and providing a Go template as the value By specifying the output as 'template' and providing a Go template as the value
of the --template flag, you can filter the attributes of the fetched resource(s).` of the --template flag, you can filter the attributes of the fetched resource(s).`
......
...@@ -87,6 +87,31 @@ func testData() (*api.PodList, *api.ServiceList, *api.ReplicationControllerList) ...@@ -87,6 +87,31 @@ func testData() (*api.PodList, *api.ServiceList, *api.ReplicationControllerList)
return pods, svc, rc return pods, svc, rc
} }
func testComponentStatusData() *api.ComponentStatusList {
good := &api.ComponentStatus{
Name: "servergood",
Conditions: []api.ComponentCondition{
{Type: api.ComponentHealthy, Status: api.ConditionTrue, Message: "ok", Error: "nil"},
},
}
bad := &api.ComponentStatus{
Name: "serverbad",
Conditions: []api.ComponentCondition{
{Type: api.ComponentHealthy, Status: api.ConditionFalse, Message: "", Error: "bad status: 500"},
},
}
unknown := &api.ComponentStatus{
Name: "serverunknown",
Conditions: []api.ComponentCondition{
{Type: api.ComponentHealthy, Status: api.ConditionUnknown, Message: "", Error: "fizzbuzz error"},
},
}
return &api.ComponentStatusList{
Items: []api.ComponentStatus{*good, *bad, *unknown},
}
}
// Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get. // Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get.
func TestGetUnknownSchemaObject(t *testing.T) { func TestGetUnknownSchemaObject(t *testing.T) {
f, tf, codec := NewTestFactory() f, tf, codec := NewTestFactory()
...@@ -188,6 +213,32 @@ func TestGetListObjects(t *testing.T) { ...@@ -188,6 +213,32 @@ func TestGetListObjects(t *testing.T) {
} }
} }
func TestGetListComponentStatus(t *testing.T) {
statuses := testComponentStatusData()
f, tf, codec := NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &client.FakeRESTClient{
Codec: codec,
Resp: &http.Response{StatusCode: 200, Body: objBody(codec, statuses)},
}
tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{})
cmd := NewCmdGet(f, buf)
cmd.SetOutput(buf)
cmd.Run(cmd, []string{"componentstatuses"})
expected := []runtime.Object{statuses}
actual := tf.Printer.(*testPrinter).Objects
if !reflect.DeepEqual(expected, actual) {
t.Errorf("unexpected object: %#v %#v", expected, actual)
}
if len(buf.String()) == 0 {
t.Errorf("unexpected empty output")
}
}
func TestGetMultipleTypeObjects(t *testing.T) { func TestGetMultipleTypeObjects(t *testing.T) {
pods, svc, _ := testData() pods, svc, _ := testData()
......
...@@ -96,17 +96,19 @@ func (e ShortcutExpander) VersionAndKindForResource(resource string) (defaultVer ...@@ -96,17 +96,19 @@ func (e ShortcutExpander) VersionAndKindForResource(resource string) (defaultVer
// indeed a shortcut. Otherwise, will return resource unmodified. // indeed a shortcut. Otherwise, will return resource unmodified.
func expandResourceShortcut(resource string) string { func expandResourceShortcut(resource string) string {
shortForms := map[string]string{ shortForms := map[string]string{
"po": "pods", // Please keep this alphabetized
"rc": "replicationcontrollers", "cs": "componentstatus",
// DEPRECATED: will be removed before 1.0
"se": "services",
"svc": "services",
"mi": "minions",
"ev": "events", "ev": "events",
"limits": "limitRanges", "limits": "limitRanges",
"quota": "resourceQuotas", "mi": "minions",
"po": "pods",
"pv": "persistentVolumes", "pv": "persistentVolumes",
"pvc": "persistentVolumeClaims", "pvc": "persistentVolumeClaims",
"quota": "resourceQuotas",
"rc": "replicationcontrollers",
// DEPRECATED: will be removed before 1.0
"se": "services",
"svc": "services",
} }
if expanded, ok := shortForms[resource]; ok { if expanded, ok := shortForms[resource]; ok {
return expanded return expanded
......
...@@ -256,6 +256,7 @@ var namespaceColumns = []string{"NAME", "LABELS", "STATUS"} ...@@ -256,6 +256,7 @@ var namespaceColumns = []string{"NAME", "LABELS", "STATUS"}
var secretColumns = []string{"NAME", "DATA"} var secretColumns = []string{"NAME", "DATA"}
var persistentVolumeColumns = []string{"NAME", "LABELS", "CAPACITY", "ACCESSMODES", "STATUS", "CLAIM"} var persistentVolumeColumns = []string{"NAME", "LABELS", "CAPACITY", "ACCESSMODES", "STATUS", "CLAIM"}
var persistentVolumeClaimColumns = []string{"NAME", "LABELS", "STATUS", "VOLUME"} var persistentVolumeClaimColumns = []string{"NAME", "LABELS", "STATUS", "VOLUME"}
var componentStatusColumns = []string{"NAME", "STATUS", "MESSAGE", "ERROR"}
// addDefaultHandlers adds print handlers for default Kubernetes types. // addDefaultHandlers adds print handlers for default Kubernetes types.
func (h *HumanReadablePrinter) addDefaultHandlers() { func (h *HumanReadablePrinter) addDefaultHandlers() {
...@@ -284,6 +285,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() { ...@@ -284,6 +285,8 @@ func (h *HumanReadablePrinter) addDefaultHandlers() {
h.Handler(persistentVolumeClaimColumns, printPersistentVolumeClaimList) h.Handler(persistentVolumeClaimColumns, printPersistentVolumeClaimList)
h.Handler(persistentVolumeColumns, printPersistentVolume) h.Handler(persistentVolumeColumns, printPersistentVolume)
h.Handler(persistentVolumeColumns, printPersistentVolumeList) h.Handler(persistentVolumeColumns, printPersistentVolumeList)
h.Handler(componentStatusColumns, printComponentStatus)
h.Handler(componentStatusColumns, printComponentStatusList)
} }
func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error { func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error {
...@@ -644,6 +647,36 @@ func printResourceQuotaList(list *api.ResourceQuotaList, w io.Writer) error { ...@@ -644,6 +647,36 @@ func printResourceQuotaList(list *api.ResourceQuotaList, w io.Writer) error {
return nil return nil
} }
func printComponentStatus(item *api.ComponentStatus, w io.Writer) error {
status := "Unknown"
message := ""
error := ""
for _, condition := range item.Conditions {
if condition.Type == api.ComponentHealthy {
if condition.Status == api.ConditionTrue {
status = "Healthy"
} else {
status = "Unhealthy"
}
message = condition.Message
error = condition.Error
break
}
}
_, err := fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", item.Name, status, message, error)
return err
}
func printComponentStatusList(list *api.ComponentStatusList, w io.Writer) error {
for _, item := range list.Items {
if err := printComponentStatus(&item, w); err != nil {
return err
}
}
return nil
}
// PrintObj prints the obj in a human-friendly format according to the type of the obj. // PrintObj prints the obj in a human-friendly format according to the type of the obj.
func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error { func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error {
w := tabwriter.NewWriter(output, 10, 4, 3, ' ', 0) w := tabwriter.NewWriter(output, 10, 4, 3, ' ', 0)
......
...@@ -43,6 +43,7 @@ import ( ...@@ -43,6 +43,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields" "github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports" "github.com/GoogleCloudPlatform/kubernetes/pkg/master/ports"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/componentstatus"
controlleretcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/controller/etcd" controlleretcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/controller/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint" "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint"
endpointsetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint/etcd" endpointsetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/endpoint/etcd"
...@@ -416,6 +417,8 @@ func (m *Master) init(c *Config) { ...@@ -416,6 +417,8 @@ func (m *Master) init(c *Config) {
"persistentVolumes/status": persistentVolumeStatusStorage, "persistentVolumes/status": persistentVolumeStatusStorage,
"persistentVolumeClaims": persistentVolumeClaimStorage, "persistentVolumeClaims": persistentVolumeClaimStorage,
"persistentVolumeClaims/status": persistentVolumeClaimStatusStorage, "persistentVolumeClaims/status": persistentVolumeClaimStatusStorage,
"componentStatuses": componentstatus.NewStorage(func() map[string]apiserver.Server { return m.getServersToValidate(c) }),
} }
apiVersions := []string{"v1beta1", "v1beta2"} apiVersions := []string{"v1beta1", "v1beta2"}
......
/*
Copyright 2015 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package componentstatus provides interfaces and implementation for retrieving cluster
// component status.
package componentstatus
/*
Copyright 2015 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package componentstatus
import (
"fmt"
"net/http"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/probe"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
)
type REST struct {
GetServersToValidate func() map[string]apiserver.Server
rt http.RoundTripper
}
// NewStorage returns a new REST.
func NewStorage(serverRetriever func() map[string]apiserver.Server) *REST {
return &REST{
GetServersToValidate: serverRetriever,
rt: http.DefaultTransport,
}
}
func (rs *REST) New() runtime.Object {
return &api.ComponentStatus{}
}
func (rs *REST) NewList() runtime.Object {
return &api.ComponentStatusList{}
}
// Returns the list of component status. Note that the label and field are both ignored.
// Note that this call doesn't support labels or selectors.
func (rs *REST) List(ctx api.Context, label labels.Selector, field fields.Selector) (runtime.Object, error) {
servers := rs.GetServersToValidate()
// TODO: This should be parallelized.
reply := []api.ComponentStatus{}
for name, server := range servers {
status := rs.getComponentStatus(name, server)
reply = append(reply, *status)
}
return &api.ComponentStatusList{Items: reply}, nil
}
func (rs *REST) Get(ctx api.Context, name string) (runtime.Object, error) {
servers := rs.GetServersToValidate()
if server, ok := servers[name]; !ok {
return nil, fmt.Errorf("Component not found: %s", name)
} else {
return rs.getComponentStatus(name, server), nil
}
}
func ToConditionStatus(s probe.Result) api.ConditionStatus {
switch s {
case probe.Success:
return api.ConditionTrue
case probe.Failure:
return api.ConditionFalse
default:
return api.ConditionUnknown
}
}
func (rs *REST) getComponentStatus(name string, server apiserver.Server) *api.ComponentStatus {
transport := rs.rt
status, msg, err := server.DoServerCheck(transport)
var errorMsg string
if err != nil {
errorMsg = err.Error()
} else {
errorMsg = "nil"
}
c := &api.ComponentCondition{
Type: api.ComponentHealthy,
Status: ToConditionStatus(status),
Message: msg,
Error: errorMsg,
}
retVal := &api.ComponentStatus{
Name: name,
Conditions: []api.ComponentCondition{*c},
}
return retVal
}
/*
Copyright 2015 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package componentstatus
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"reflect"
"strings"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
type fakeRoundTripper struct {
err error
resp *http.Response
url string
}
func (f *fakeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
f.url = req.URL.String()
return f.resp, f.err
}
type testResponse struct {
code int
data string
err error
}
func NewTestREST(resp testResponse) *REST {
return &REST{
GetServersToValidate: func() map[string]apiserver.Server {
return map[string]apiserver.Server{
"test1": {Addr: "testserver1", Port: 8000, Path: "/healthz"},
}
},
rt: &fakeRoundTripper{
err: resp.err,
resp: &http.Response{
Body: ioutil.NopCloser(bytes.NewBufferString(resp.data)),
StatusCode: resp.code,
},
},
}
}
func createTestStatus(name string, status api.ConditionStatus, msg string, err string) *api.ComponentStatus {
return &api.ComponentStatus{
Name: name,
Conditions: []api.ComponentCondition{
{Type: api.ComponentHealthy, Status: status, Message: msg, Error: err},
},
}
}
func TestList_NoError(t *testing.T) {
r := NewTestREST(testResponse{code: 200, data: "ok"})
got, err := r.List(api.NewContext(), labels.Everything(), fields.Everything())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
expect := &api.ComponentStatusList{
Items: []api.ComponentStatus{*(createTestStatus("test1", api.ConditionTrue, "ok", "nil"))},
}
if e, a := expect, got; !reflect.DeepEqual(e, a) {
t.Errorf("Got unexpected object. Diff: %s", util.ObjectDiff(e, a))
}
}
func TestList_FailedCheck(t *testing.T) {
r := NewTestREST(testResponse{code: 500, data: ""})
got, err := r.List(api.NewContext(), labels.Everything(), fields.Everything())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
expect := &api.ComponentStatusList{
Items: []api.ComponentStatus{
*(createTestStatus("test1", api.ConditionFalse, "", "unhealthy http status code: 500 ()"))},
}
if e, a := expect, got; !reflect.DeepEqual(e, a) {
t.Errorf("Got unexpected object. Diff: %s", util.ObjectDiff(e, a))
}
}
func TestList_UnknownError(t *testing.T) {
r := NewTestREST(testResponse{code: 500, data: "", err: fmt.Errorf("fizzbuzz error")})
got, err := r.List(api.NewContext(), labels.Everything(), fields.Everything())
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
expect := &api.ComponentStatusList{
Items: []api.ComponentStatus{
*(createTestStatus("test1", api.ConditionUnknown, "", "Get http://testserver1:8000/healthz: fizzbuzz error"))},
}
if e, a := expect, got; !reflect.DeepEqual(e, a) {
t.Errorf("Got unexpected object. Diff: %s", util.ObjectDiff(e, a))
}
}
func TestGet_NoError(t *testing.T) {
r := NewTestREST(testResponse{code: 200, data: "ok"})
got, err := r.Get(api.NewContext(), "test1")
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
expect := createTestStatus("test1", api.ConditionTrue, "ok", "nil")
if e, a := expect, got; !reflect.DeepEqual(e, a) {
t.Errorf("Got unexpected object. Diff: %s", util.ObjectDiff(e, a))
}
}
func TestGet_BadName(t *testing.T) {
r := NewTestREST(testResponse{code: 200, data: "ok"})
_, err := r.Get(api.NewContext(), "invalidname")
if err == nil {
t.Fatalf("Expected error, but did not get one")
}
if !strings.Contains(err.Error(), "Component not found: invalidname") {
t.Fatalf("Got unexpected error: %v", err)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment