func podMetricsInNamespaceList(a *Api, request *restful.Request, response *restful.Response, namespace string) { selector := request.QueryParameter("labelSelector") labelSelector, err := labels.Parse(selector) if err != nil { errMsg := fmt.Errorf("Error while parsing selector %v: %v", selector, err) glog.Error(errMsg) response.WriteError(http.StatusBadRequest, errMsg) return } pods, err := a.podLister.Pods(namespace).List(labelSelector) if err != nil { errMsg := fmt.Errorf("Error while listing pods for selector %v: %v", selector, err) glog.Error(errMsg) response.WriteError(http.StatusInternalServerError, errMsg) return } res := v1alpha1.PodMetricsList{} for _, pod := range pods.Items { if m := a.getPodMetrics(&pod); m != nil { res.Items = append(res.Items, *m) } else { glog.Infof("No metrics for pod %s/%s", pod.Namespace, pod.Name) } } response.WriteEntity(&res) }
func (a *Api) nodeMetricsList(request *restful.Request, response *restful.Response) { selector := request.QueryParameter("labelSelector") labelSelector, err := labels.Parse(selector) if err != nil { errMsg := fmt.Errorf("Error while parsing selector %v: %v", selector, err) glog.Error(errMsg) response.WriteError(http.StatusBadRequest, errMsg) return } nodes, err := a.nodeLister.NodeCondition(func(node *kube_api.Node) bool { if labelSelector.Empty() { return true } return labelSelector.Matches(labels.Set(node.Labels)) }).List() if err != nil { errMsg := fmt.Errorf("Error while listing nodes: %v", err) glog.Error(errMsg) response.WriteError(http.StatusInternalServerError, errMsg) return } res := v1alpha1.NodeMetricsList{} for _, node := range nodes { if m := a.getNodeMetrics(node.Name); m != nil { res.Items = append(res.Items, *m) } } response.WriteEntity(&res) }
func getStartEndTime(request *restful.Request) (time.Time, time.Time, error) { start, err := parseTimeParam(request.QueryParameter("start"), time.Time{}) if err != nil { return time.Time{}, time.Time{}, err } end, err := parseTimeParam(request.QueryParameter("end"), nowFunc()) if err != nil { return time.Time{}, time.Time{}, err } return start, end, nil }
// parseRequestParam parses a time.Time from a named QueryParam. // parseRequestParam receives a request and a response as inputs, and returns the parsed time. func parseRequestParam(param string, request *restful.Request, response *restful.Response) time.Time { var err error query_param := request.QueryParameter(param) req_stamp := time.Time{} if query_param != "" { req_stamp, err = time.Parse(time.RFC3339, query_param) if err != nil { // Timestamp parameter cannot be parsed response.WriteError(http.StatusInternalServerError, err) glog.Errorf("timestamp argument cannot be parsed: %s", err) return time.Time{} } } return req_stamp }
// getRun handles requests to run a command inside a container. func (s *Server) getRun(request *restful.Request, response *restful.Response) { podNamespace, podID, uid, container := getContainerCoordinates(request) pod, ok := s.host.GetPodByName(podNamespace, podID) if !ok { response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist")) return } command := strings.Split(request.QueryParameter("cmd"), " ") data, err := s.host.RunInContainer(kubecontainer.GetPodFullName(pod), uid, container, command) if err != nil { response.WriteError(http.StatusInternalServerError, err) return } response.Write(data) }
// Handles get Replication Controller Pods API call. func (apiHandler *ApiHandler) handleGetReplicationControllerPods( request *restful.Request, response *restful.Response) { namespace := request.PathParameter("namespace") replicationController := request.PathParameter("replicationController") limit, err := strconv.Atoi(request.QueryParameter("limit")) if err != nil { limit = 0 } result, err := GetReplicationControllerPods(apiHandler.client, namespace, replicationController, limit) if err != nil { handleInternalError(response, err) return } response.WriteHeaderAndEntity(http.StatusCreated, result) }
// getRun handles requests to run a command inside a container. func (s *Server) getRun(request *restful.Request, response *restful.Response) { params := getRequestParams(request) pod, ok := s.host.GetPodByName(params.podNamespace, params.podName) if !ok { response.WriteError(http.StatusNotFound, fmt.Errorf("pod does not exist")) return } // For legacy reasons, run uses different query param than exec. params.cmd = strings.Split(request.QueryParameter("cmd"), " ") data, err := s.host.RunInContainer(kubecontainer.GetPodFullName(pod), params.podUID, params.containerName, params.cmd) if err != nil { response.WriteError(http.StatusInternalServerError, err) return } writeJsonResponse(response, data) }
func getLabels(request *restful.Request) (map[string]string, error) { labelsRaw := request.QueryParameter("labels") if labelsRaw == "" { return nil, nil } kvPairs := strings.Split(labelsRaw, ",") labels := make(map[string]string, len(kvPairs)) for _, kvPair := range kvPairs { kvSplit := strings.SplitN(kvPair, ":", 2) if len(kvSplit) != 2 || kvSplit[0] == "" || kvSplit[1] == "" { return nil, fmt.Errorf("invalid label pair %q", kvPair) } labels[kvSplit[0]] = kvSplit[1] } return labels, nil }
// Handles delete Replication Controller API call. // TODO(floreks): there has to be some kind of transaction here func (apiHandler *ApiHandler) handleDeleteReplicationController( request *restful.Request, response *restful.Response) { namespace := request.PathParameter("namespace") replicationController := request.PathParameter("replicationController") deleteServices, err := strconv.ParseBool(request.QueryParameter("deleteServices")) if err != nil { handleInternalError(response, err) return } if err := DeleteReplicationController(apiHandler.client, namespace, replicationController, deleteServices); err != nil { handleInternalError(response, err) return } response.WriteHeader(http.StatusOK) }
// Handles delete Daemon Set API call. func (apiHandler *ApiHandler) handleDeleteDaemonSet( request *restful.Request, response *restful.Response) { namespace := request.PathParameter("namespace") daemonSet := request.PathParameter("daemonSet") deleteServices, err := strconv.ParseBool(request.QueryParameter("deleteServices")) if err != nil { handleInternalError(response, err) return } if err := daemonset.DeleteDaemonSet(apiHandler.client, namespace, daemonSet, deleteServices); err != nil { handleInternalError(response, err) return } response.WriteHeader(http.StatusOK) }
// getBucketSize parses the bucket size specifier into a func getBucketSize(request *restful.Request) (time.Duration, error) { rawSize := request.QueryParameter("bucket") if rawSize == "" { return 0, nil } if len(rawSize) < 2 { return 0, fmt.Errorf("unable to parse bucket size: %q is too short to be a duration", rawSize) } var multiplier time.Duration var num string switch rawSize[len(rawSize)-1] { case 's': // could be s or ms if len(rawSize) < 3 || rawSize[len(rawSize)-2] != 'm' { multiplier = time.Second num = rawSize[:len(rawSize)-1] } else { multiplier = time.Millisecond num = rawSize[:len(rawSize)-2] } case 'h': multiplier = time.Hour num = rawSize[:len(rawSize)-1] case 'd': multiplier = 24 * time.Hour num = rawSize[:len(rawSize)-1] case 'm': multiplier = time.Minute num = rawSize[:len(rawSize)-1] default: return 0, fmt.Errorf("unable to parse bucket size: %q has no known duration suffix", rawSize) } parsedNum, err := strconv.ParseUint(num, 10, 64) if err != nil { return 0, err } return time.Duration(parsedNum) * multiplier, nil }
// getContainerLogs handles containerLogs request against the Kubelet func (s *Server) getContainerLogs(request *restful.Request, response *restful.Response) { podNamespace := request.PathParameter("podNamespace") podID := request.PathParameter("podID") containerName := request.PathParameter("containerName") if len(podID) == 0 { // TODO: Why return JSON when the rest return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podID."}`)) return } if len(containerName) == 0 { // TODO: Why return JSON when the rest return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing container name."}`)) return } if len(podNamespace) == 0 { // TODO: Why return JSON when the rest return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podNamespace."}`)) return } follow, _ := strconv.ParseBool(request.QueryParameter("follow")) previous, _ := strconv.ParseBool(request.QueryParameter("previous")) tail := request.QueryParameter("tail") pod, ok := s.host.GetPodByName(podNamespace, podID) if !ok { response.WriteError(http.StatusNotFound, fmt.Errorf("Pod %q does not exist", podID)) return } // Check if containerName is valid. containerExists := false for _, container := range pod.Spec.Containers { if container.Name == containerName { containerExists = true } } if !containerExists { response.WriteError(http.StatusNotFound, fmt.Errorf("Container %q not found in Pod %q", containerName, podID)) return } if _, ok := response.ResponseWriter.(http.Flusher); !ok { response.WriteError(http.StatusInternalServerError, fmt.Errorf("unable to convert %v into http.Flusher", response)) return } fw := flushwriter.Wrap(response) response.Header().Set("Transfer-Encoding", "chunked") response.WriteHeader(http.StatusOK) err := s.host.GetKubeletContainerLogs(kubecontainer.GetPodFullName(pod), containerName, tail, follow, previous, fw, fw) if err != nil { response.WriteError(http.StatusInternalServerError, err) return } }
func report_inverter(r *restful.Request, w *restful.Response) { sd := r.QueryParameter("sd") // startdate, "day/month/year" res := resultType{"x", sd} w.WriteAsJson(res) }
// getContainerLogs handles containerLogs request against the Kubelet func (s *Server) getContainerLogs(request *restful.Request, response *restful.Response) { podNamespace := request.PathParameter("podNamespace") podID := request.PathParameter("podID") containerName := request.PathParameter("containerName") if len(podID) == 0 { // TODO: Why return JSON when the rest return plaintext errors? // TODO: Why return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podID."}`)) return } if len(containerName) == 0 { // TODO: Why return JSON when the rest return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing container name."}`)) return } if len(podNamespace) == 0 { // TODO: Why return JSON when the rest return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podNamespace."}`)) return } query := request.Request.URL.Query() // backwards compatibility for the "tail" query parameter if tail := request.QueryParameter("tail"); len(tail) > 0 { query["tailLines"] = []string{tail} // "all" is the same as omitting tail if tail == "all" { delete(query, "tailLines") } } // container logs on the kubelet are locked to the v1 API version of PodLogOptions logOptions := &v1.PodLogOptions{} if err := api.ParameterCodec.DecodeParameters(query, v1.SchemeGroupVersion, logOptions); err != nil { response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Unable to decode query."}`)) return } logOptions.TypeMeta = metav1.TypeMeta{} if errs := validation.ValidatePodLogOptions(logOptions); len(errs) > 0 { response.WriteError(apierrs.StatusUnprocessableEntity, fmt.Errorf(`{"message": "Invalid request."}`)) return } pod, ok := s.host.GetPodByName(podNamespace, podID) if !ok { response.WriteError(http.StatusNotFound, fmt.Errorf("pod %q does not exist\n", podID)) return } // Check if containerName is valid. containerExists := false for _, container := range pod.Spec.Containers { if container.Name == containerName { containerExists = true } } if !containerExists { for _, container := range pod.Spec.InitContainers { if container.Name == containerName { containerExists = true } } } if !containerExists { response.WriteError(http.StatusNotFound, fmt.Errorf("container %q not found in pod %q\n", containerName, podID)) return } if _, ok := response.ResponseWriter.(http.Flusher); !ok { response.WriteError(http.StatusInternalServerError, fmt.Errorf("unable to convert %v into http.Flusher, cannot show logs\n", reflect.TypeOf(response))) return } fw := flushwriter.Wrap(response.ResponseWriter) // Byte limit logic is already implemented in kuberuntime. However, we still need this for // old runtime integration. // TODO(random-liu): Remove this once we switch to CRI integration. if logOptions.LimitBytes != nil { fw = limitwriter.New(fw, *logOptions.LimitBytes) } response.Header().Set("Transfer-Encoding", "chunked") if err := s.host.GetKubeletContainerLogs(kubecontainer.GetPodFullName(pod), containerName, logOptions, fw, fw); err != nil { if err != limitwriter.ErrMaximumWrite { response.WriteError(http.StatusBadRequest, err) } return } }
func (s *Server) createStreams(request *restful.Request, response *restful.Response) (io.Reader, io.WriteCloser, io.WriteCloser, io.WriteCloser, Closer, bool, bool) { tty := request.QueryParameter(api.ExecTTYParam) == "1" stdin := request.QueryParameter(api.ExecStdinParam) == "1" stdout := request.QueryParameter(api.ExecStdoutParam) == "1" stderr := request.QueryParameter(api.ExecStderrParam) == "1" if tty && stderr { // TODO: make this an error before we reach this method glog.V(4).Infof("Access to exec with tty and stderr is not supported, bypassing stderr") stderr = false } // count the streams client asked for, starting with 1 expectedStreams := 1 if stdin { expectedStreams++ } if stdout { expectedStreams++ } if stderr { expectedStreams++ } if expectedStreams == 1 { response.WriteError(http.StatusBadRequest, fmt.Errorf("you must specify at least 1 of stdin, stdout, stderr")) return nil, nil, nil, nil, nil, false, false } if wsstream.IsWebSocketRequest(request.Request) { // open the requested channels, and always open the error channel channels := append(standardShellChannels(stdin, stdout, stderr), wsstream.WriteChannel) conn := wsstream.NewConn(channels...) conn.SetIdleTimeout(s.host.StreamingConnectionIdleTimeout()) streams, err := conn.Open(httplog.Unlogged(response.ResponseWriter), request.Request) if err != nil { glog.Errorf("Unable to upgrade websocket connection: %v", err) return nil, nil, nil, nil, nil, false, false } // Send an empty message to the lowest writable channel to notify the client the connection is established // TODO: make generic to SDPY and WebSockets and do it outside of this method? switch { case stdout: streams[1].Write([]byte{}) case stderr: streams[2].Write([]byte{}) default: streams[3].Write([]byte{}) } return streams[0], streams[1], streams[2], streams[3], conn, tty, true } supportedStreamProtocols := []string{remotecommand.StreamProtocolV2Name, remotecommand.StreamProtocolV1Name} _, err := httpstream.Handshake(request.Request, response.ResponseWriter, supportedStreamProtocols, remotecommand.StreamProtocolV1Name) // negotiated protocol isn't used server side at the moment, but could be in the future if err != nil { return nil, nil, nil, nil, nil, false, false } streamCh := make(chan httpstream.Stream) upgrader := spdy.NewResponseUpgrader() conn := upgrader.UpgradeResponse(response.ResponseWriter, request.Request, func(stream httpstream.Stream) error { streamCh <- stream return nil }) // from this point on, we can no longer call methods on response if conn == nil { // The upgrader is responsible for notifying the client of any errors that // occurred during upgrading. All we can do is return here at this point // if we weren't successful in upgrading. return nil, nil, nil, nil, nil, false, false } conn.SetIdleTimeout(s.host.StreamingConnectionIdleTimeout()) // TODO make it configurable? expired := time.NewTimer(defaultStreamCreationTimeout) var errorStream, stdinStream, stdoutStream, stderrStream httpstream.Stream receivedStreams := 0 WaitForStreams: for { select { case stream := <-streamCh: streamType := stream.Headers().Get(api.StreamType) switch streamType { case api.StreamTypeError: errorStream = stream receivedStreams++ case api.StreamTypeStdin: stdinStream = stream receivedStreams++ case api.StreamTypeStdout: stdoutStream = stream receivedStreams++ case api.StreamTypeStderr: stderrStream = stream receivedStreams++ default: glog.Errorf("Unexpected stream type: '%s'", streamType) } if receivedStreams == expectedStreams { break WaitForStreams } case <-expired.C: // TODO find a way to return the error to the user. Maybe use a separate // stream to report errors? glog.Error("Timed out waiting for client to create streams") return nil, nil, nil, nil, nil, false, false } } return stdinStream, stdoutStream, stderrStream, errorStream, conn, tty, true }
func (s *Server) createStreams(request *restful.Request, response *restful.Response) (io.Reader, io.WriteCloser, io.WriteCloser, io.WriteCloser, httpstream.Connection, bool, bool) { // start at 1 for error stream expectedStreams := 1 if request.QueryParameter(api.ExecStdinParam) == "1" { expectedStreams++ } if request.QueryParameter(api.ExecStdoutParam) == "1" { expectedStreams++ } tty := request.QueryParameter(api.ExecTTYParam) == "1" if !tty && request.QueryParameter(api.ExecStderrParam) == "1" { expectedStreams++ } if expectedStreams == 1 { response.WriteError(http.StatusBadRequest, fmt.Errorf("you must specify at least 1 of stdin, stdout, stderr")) return nil, nil, nil, nil, nil, false, false } supportedStreamProtocols := []string{remotecommand.StreamProtocolV2Name, remotecommand.StreamProtocolV1Name} _, err := httpstream.Handshake(request.Request, response.ResponseWriter, supportedStreamProtocols, remotecommand.StreamProtocolV1Name) // negotiated protocol isn't used server side at the moment, but could be in the future if err != nil { return nil, nil, nil, nil, nil, false, false } streamCh := make(chan httpstream.Stream) upgrader := spdy.NewResponseUpgrader() conn := upgrader.UpgradeResponse(response.ResponseWriter, request.Request, func(stream httpstream.Stream) error { streamCh <- stream return nil }) // from this point on, we can no longer call methods on response if conn == nil { // The upgrader is responsible for notifying the client of any errors that // occurred during upgrading. All we can do is return here at this point // if we weren't successful in upgrading. return nil, nil, nil, nil, nil, false, false } conn.SetIdleTimeout(s.host.StreamingConnectionIdleTimeout()) // TODO make it configurable? expired := time.NewTimer(defaultStreamCreationTimeout) var errorStream, stdinStream, stdoutStream, stderrStream httpstream.Stream receivedStreams := 0 WaitForStreams: for { select { case stream := <-streamCh: streamType := stream.Headers().Get(api.StreamType) switch streamType { case api.StreamTypeError: errorStream = stream receivedStreams++ case api.StreamTypeStdin: stdinStream = stream receivedStreams++ case api.StreamTypeStdout: stdoutStream = stream receivedStreams++ case api.StreamTypeStderr: stderrStream = stream receivedStreams++ default: glog.Errorf("Unexpected stream type: '%s'", streamType) } if receivedStreams == expectedStreams { break WaitForStreams } case <-expired.C: // TODO find a way to return the error to the user. Maybe use a separate // stream to report errors? glog.Error("Timed out waiting for client to create streams") return nil, nil, nil, nil, nil, false, false } } return stdinStream, stdoutStream, stderrStream, errorStream, conn, tty, true }
// getContainerLogs handles containerLogs request against the Kubelet func (s *Server) getContainerLogs(request *restful.Request, response *restful.Response) { podNamespace := request.PathParameter("podNamespace") podID := request.PathParameter("podID") containerName := request.PathParameter("containerName") if len(podID) == 0 { // TODO: Why return JSON when the rest return plaintext errors? // TODO: Why return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podID."}`)) return } if len(containerName) == 0 { // TODO: Why return JSON when the rest return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing container name."}`)) return } if len(podNamespace) == 0 { // TODO: Why return JSON when the rest return plaintext errors? response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Missing podNamespace."}`)) return } query := request.Request.URL.Query() // backwards compatibility for the "tail" query parameter if tail := request.QueryParameter("tail"); len(tail) > 0 { query["tailLines"] = []string{tail} // "all" is the same as omitting tail if tail == "all" { delete(query, "tailLines") } } // container logs on the kubelet are locked to v1 versioned := &v1.PodLogOptions{} if err := api.Scheme.Convert(&query, versioned); err != nil { response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Unable to decode query."}`)) return } out, err := api.Scheme.ConvertToVersion(versioned, "") if err != nil { response.WriteError(http.StatusBadRequest, fmt.Errorf(`{"message": "Unable to convert request query."}`)) return } logOptions := out.(*api.PodLogOptions) logOptions.TypeMeta = unversioned.TypeMeta{} if errs := validation.ValidatePodLogOptions(logOptions); len(errs) > 0 { response.WriteError(apierrs.StatusUnprocessableEntity, fmt.Errorf(`{"message": "Invalid request."}`)) return } pod, ok := s.host.GetPodByName(podNamespace, podID) if !ok { response.WriteError(http.StatusNotFound, fmt.Errorf("Pod %q does not exist", podID)) return } // Check if containerName is valid. containerExists := false for _, container := range pod.Spec.Containers { if container.Name == containerName { containerExists = true } } if !containerExists { response.WriteError(http.StatusNotFound, fmt.Errorf("Container %q not found in Pod %q", containerName, podID)) return } if _, ok := response.ResponseWriter.(http.Flusher); !ok { response.WriteError(http.StatusInternalServerError, fmt.Errorf("unable to convert %v into http.Flusher", response)) return } fw := flushwriter.Wrap(response.ResponseWriter) if logOptions.LimitBytes != nil { fw = limitwriter.New(fw, *logOptions.LimitBytes) } response.Header().Set("Transfer-Encoding", "chunked") response.WriteHeader(http.StatusOK) if err := s.host.GetKubeletContainerLogs(kubecontainer.GetPodFullName(pod), containerName, logOptions, fw, fw); err != nil { if err != limitwriter.ErrMaximumWrite { response.WriteError(http.StatusInternalServerError, err) } return } }