12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691 |
- /*
- Copyright 2014 The Kubernetes Authors.
- 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 server
- import (
- "bytes"
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "net"
- "net/http"
- "net/http/httptest"
- "net/http/httputil"
- "net/url"
- "reflect"
- "strconv"
- "strings"
- "testing"
- "time"
- cadvisorapi "github.com/google/cadvisor/info/v1"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "k8s.io/api/core/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/types"
- "k8s.io/apimachinery/pkg/util/httpstream"
- "k8s.io/apimachinery/pkg/util/httpstream/spdy"
- "k8s.io/apimachinery/pkg/util/sets"
- "k8s.io/apiserver/pkg/authentication/authenticator"
- "k8s.io/apiserver/pkg/authentication/user"
- "k8s.io/apiserver/pkg/authorization/authorizer"
- "k8s.io/client-go/tools/remotecommand"
- utiltesting "k8s.io/client-go/util/testing"
- runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
- api "k8s.io/kubernetes/pkg/apis/core"
- statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
- // Do some initialization to decode the query parameters correctly.
- _ "k8s.io/kubernetes/pkg/apis/core/install"
- "k8s.io/kubernetes/pkg/kubelet/cm"
- kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
- "k8s.io/kubernetes/pkg/kubelet/server/portforward"
- remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
- "k8s.io/kubernetes/pkg/kubelet/server/stats"
- "k8s.io/kubernetes/pkg/kubelet/server/streaming"
- "k8s.io/kubernetes/pkg/volume"
- )
- const (
- testUID = "9b01b80f-8fb4-11e4-95ab-4200af06647"
- testContainerID = "container789"
- testPodSandboxID = "pod0987"
- )
- type fakeKubelet struct {
- podByNameFunc func(namespace, name string) (*v1.Pod, bool)
- containerInfoFunc func(podFullName string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error)
- rawInfoFunc func(query *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error)
- machineInfoFunc func() (*cadvisorapi.MachineInfo, error)
- podsFunc func() []*v1.Pod
- runningPodsFunc func() ([]*v1.Pod, error)
- logFunc func(w http.ResponseWriter, req *http.Request)
- runFunc func(podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error)
- getExecCheck func(string, types.UID, string, []string, remotecommandserver.Options)
- getAttachCheck func(string, types.UID, string, remotecommandserver.Options)
- getPortForwardCheck func(string, string, types.UID, portforward.V4Options)
- containerLogsFunc func(ctx context.Context, podFullName, containerName string, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) error
- hostnameFunc func() string
- resyncInterval time.Duration
- loopEntryTime time.Time
- plegHealth bool
- streamingRuntime streaming.Server
- }
- func (fk *fakeKubelet) ResyncInterval() time.Duration {
- return fk.resyncInterval
- }
- func (fk *fakeKubelet) LatestLoopEntryTime() time.Time {
- return fk.loopEntryTime
- }
- func (fk *fakeKubelet) GetPodByName(namespace, name string) (*v1.Pod, bool) {
- return fk.podByNameFunc(namespace, name)
- }
- func (fk *fakeKubelet) GetContainerInfo(podFullName string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) {
- return fk.containerInfoFunc(podFullName, uid, containerName, req)
- }
- func (fk *fakeKubelet) GetRawContainerInfo(containerName string, req *cadvisorapi.ContainerInfoRequest, subcontainers bool) (map[string]*cadvisorapi.ContainerInfo, error) {
- return fk.rawInfoFunc(req)
- }
- func (fk *fakeKubelet) GetCachedMachineInfo() (*cadvisorapi.MachineInfo, error) {
- return fk.machineInfoFunc()
- }
- func (*fakeKubelet) GetVersionInfo() (*cadvisorapi.VersionInfo, error) {
- return &cadvisorapi.VersionInfo{}, nil
- }
- func (fk *fakeKubelet) GetPods() []*v1.Pod {
- return fk.podsFunc()
- }
- func (fk *fakeKubelet) GetRunningPods() ([]*v1.Pod, error) {
- return fk.runningPodsFunc()
- }
- func (fk *fakeKubelet) ServeLogs(w http.ResponseWriter, req *http.Request) {
- fk.logFunc(w, req)
- }
- func (fk *fakeKubelet) GetKubeletContainerLogs(ctx context.Context, podFullName, containerName string, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) error {
- return fk.containerLogsFunc(ctx, podFullName, containerName, logOptions, stdout, stderr)
- }
- func (fk *fakeKubelet) GetHostname() string {
- return fk.hostnameFunc()
- }
- func (fk *fakeKubelet) RunInContainer(podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error) {
- return fk.runFunc(podFullName, uid, containerName, cmd)
- }
- type fakeRuntime struct {
- execFunc func(string, []string, io.Reader, io.WriteCloser, io.WriteCloser, bool, <-chan remotecommand.TerminalSize) error
- attachFunc func(string, io.Reader, io.WriteCloser, io.WriteCloser, bool, <-chan remotecommand.TerminalSize) error
- portForwardFunc func(string, int32, io.ReadWriteCloser) error
- }
- func (f *fakeRuntime) Exec(containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
- return f.execFunc(containerID, cmd, stdin, stdout, stderr, tty, resize)
- }
- func (f *fakeRuntime) Attach(containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
- return f.attachFunc(containerID, stdin, stdout, stderr, tty, resize)
- }
- func (f *fakeRuntime) PortForward(podSandboxID string, port int32, stream io.ReadWriteCloser) error {
- return f.portForwardFunc(podSandboxID, port, stream)
- }
- type testStreamingServer struct {
- streaming.Server
- fakeRuntime *fakeRuntime
- testHTTPServer *httptest.Server
- }
- func newTestStreamingServer(streamIdleTimeout time.Duration) (s *testStreamingServer, err error) {
- s = &testStreamingServer{}
- s.testHTTPServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- s.ServeHTTP(w, r)
- }))
- defer func() {
- if err != nil {
- s.testHTTPServer.Close()
- }
- }()
- testURL, err := url.Parse(s.testHTTPServer.URL)
- if err != nil {
- return nil, err
- }
- s.fakeRuntime = &fakeRuntime{}
- config := streaming.DefaultConfig
- config.BaseURL = testURL
- if streamIdleTimeout != 0 {
- config.StreamIdleTimeout = streamIdleTimeout
- }
- s.Server, err = streaming.NewServer(config, s.fakeRuntime)
- if err != nil {
- return nil, err
- }
- return s, nil
- }
- func (fk *fakeKubelet) GetExec(podFullName string, podUID types.UID, containerName string, cmd []string, streamOpts remotecommandserver.Options) (*url.URL, error) {
- if fk.getExecCheck != nil {
- fk.getExecCheck(podFullName, podUID, containerName, cmd, streamOpts)
- }
- // Always use testContainerID
- resp, err := fk.streamingRuntime.GetExec(&runtimeapi.ExecRequest{
- ContainerId: testContainerID,
- Cmd: cmd,
- Tty: streamOpts.TTY,
- Stdin: streamOpts.Stdin,
- Stdout: streamOpts.Stdout,
- Stderr: streamOpts.Stderr,
- })
- if err != nil {
- return nil, err
- }
- return url.Parse(resp.GetUrl())
- }
- func (fk *fakeKubelet) GetAttach(podFullName string, podUID types.UID, containerName string, streamOpts remotecommandserver.Options) (*url.URL, error) {
- if fk.getAttachCheck != nil {
- fk.getAttachCheck(podFullName, podUID, containerName, streamOpts)
- }
- // Always use testContainerID
- resp, err := fk.streamingRuntime.GetAttach(&runtimeapi.AttachRequest{
- ContainerId: testContainerID,
- Tty: streamOpts.TTY,
- Stdin: streamOpts.Stdin,
- Stdout: streamOpts.Stdout,
- Stderr: streamOpts.Stderr,
- })
- if err != nil {
- return nil, err
- }
- return url.Parse(resp.GetUrl())
- }
- func (fk *fakeKubelet) GetPortForward(podName, podNamespace string, podUID types.UID, portForwardOpts portforward.V4Options) (*url.URL, error) {
- if fk.getPortForwardCheck != nil {
- fk.getPortForwardCheck(podName, podNamespace, podUID, portForwardOpts)
- }
- // Always use testPodSandboxID
- resp, err := fk.streamingRuntime.GetPortForward(&runtimeapi.PortForwardRequest{
- PodSandboxId: testPodSandboxID,
- Port: portForwardOpts.Ports,
- })
- if err != nil {
- return nil, err
- }
- return url.Parse(resp.GetUrl())
- }
- // Unused functions
- func (*fakeKubelet) GetNode() (*v1.Node, error) { return nil, nil }
- func (*fakeKubelet) GetNodeConfig() cm.NodeConfig { return cm.NodeConfig{} }
- func (*fakeKubelet) GetPodCgroupRoot() string { return "" }
- func (*fakeKubelet) GetPodByCgroupfs(cgroupfs string) (*v1.Pod, bool) { return nil, false }
- func (fk *fakeKubelet) ListVolumesForPod(podUID types.UID) (map[string]volume.Volume, bool) {
- return map[string]volume.Volume{}, true
- }
- func (*fakeKubelet) RootFsStats() (*statsapi.FsStats, error) { return nil, nil }
- func (*fakeKubelet) ListPodStats() ([]statsapi.PodStats, error) { return nil, nil }
- func (*fakeKubelet) ListPodStatsAndUpdateCPUNanoCoreUsage() ([]statsapi.PodStats, error) {
- return nil, nil
- }
- func (*fakeKubelet) ListPodCPUAndMemoryStats() ([]statsapi.PodStats, error) { return nil, nil }
- func (*fakeKubelet) ImageFsStats() (*statsapi.FsStats, error) { return nil, nil }
- func (*fakeKubelet) RlimitStats() (*statsapi.RlimitStats, error) { return nil, nil }
- func (*fakeKubelet) GetCgroupStats(cgroupName string, updateStats bool) (*statsapi.ContainerStats, *statsapi.NetworkStats, error) {
- return nil, nil, nil
- }
- func (*fakeKubelet) GetCgroupCPUAndMemoryStats(cgroupName string, updateStats bool) (*statsapi.ContainerStats, error) {
- return nil, nil
- }
- type fakeAuth struct {
- authenticateFunc func(*http.Request) (*authenticator.Response, bool, error)
- attributesFunc func(user.Info, *http.Request) authorizer.Attributes
- authorizeFunc func(authorizer.Attributes) (authorized authorizer.Decision, reason string, err error)
- }
- func (f *fakeAuth) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
- return f.authenticateFunc(req)
- }
- func (f *fakeAuth) GetRequestAttributes(u user.Info, req *http.Request) authorizer.Attributes {
- return f.attributesFunc(u, req)
- }
- func (f *fakeAuth) Authorize(a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
- return f.authorizeFunc(a)
- }
- type serverTestFramework struct {
- serverUnderTest *Server
- fakeKubelet *fakeKubelet
- fakeAuth *fakeAuth
- testHTTPServer *httptest.Server
- fakeRuntime *fakeRuntime
- testStreamingHTTPServer *httptest.Server
- criHandler *utiltesting.FakeHandler
- }
- func newServerTest() *serverTestFramework {
- return newServerTestWithDebug(true, false, nil)
- }
- func newServerTestWithDebug(enableDebugging, redirectContainerStreaming bool, streamingServer streaming.Server) *serverTestFramework {
- fw := &serverTestFramework{}
- fw.fakeKubelet = &fakeKubelet{
- hostnameFunc: func() string {
- return "127.0.0.1"
- },
- podByNameFunc: func(namespace, name string) (*v1.Pod, bool) {
- return &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: namespace,
- Name: name,
- UID: testUID,
- },
- }, true
- },
- plegHealth: true,
- streamingRuntime: streamingServer,
- }
- fw.fakeAuth = &fakeAuth{
- authenticateFunc: func(req *http.Request) (*authenticator.Response, bool, error) {
- return &authenticator.Response{User: &user.DefaultInfo{Name: "test"}}, true, nil
- },
- attributesFunc: func(u user.Info, req *http.Request) authorizer.Attributes {
- return &authorizer.AttributesRecord{User: u}
- },
- authorizeFunc: func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
- return authorizer.DecisionAllow, "", nil
- },
- }
- fw.criHandler = &utiltesting.FakeHandler{
- StatusCode: http.StatusOK,
- }
- server := NewServer(
- fw.fakeKubelet,
- stats.NewResourceAnalyzer(fw.fakeKubelet, time.Minute),
- fw.fakeAuth,
- true,
- enableDebugging,
- false,
- redirectContainerStreaming,
- fw.criHandler)
- fw.serverUnderTest = &server
- fw.testHTTPServer = httptest.NewServer(fw.serverUnderTest)
- return fw
- }
- // A helper function to return the correct pod name.
- func getPodName(name, namespace string) string {
- if namespace == "" {
- namespace = metav1.NamespaceDefault
- }
- return name + "_" + namespace
- }
- func TestContainerInfo(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- expectedInfo := &cadvisorapi.ContainerInfo{}
- podID := "somepod"
- expectedPodID := getPodName(podID, "")
- expectedContainerName := "goodcontainer"
- fw.fakeKubelet.containerInfoFunc = func(podID string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) {
- if podID != expectedPodID || containerName != expectedContainerName {
- return nil, fmt.Errorf("bad podID or containerName: podID=%v; containerName=%v", podID, containerName)
- }
- return expectedInfo, nil
- }
- resp, err := http.Get(fw.testHTTPServer.URL + fmt.Sprintf("/stats/%v/%v", podID, expectedContainerName))
- if err != nil {
- t.Fatalf("Got error GETing: %v", err)
- }
- defer resp.Body.Close()
- var receivedInfo cadvisorapi.ContainerInfo
- err = json.NewDecoder(resp.Body).Decode(&receivedInfo)
- if err != nil {
- t.Fatalf("received invalid json data: %v", err)
- }
- if !receivedInfo.Eq(expectedInfo) {
- t.Errorf("received wrong data: %#v", receivedInfo)
- }
- }
- func TestContainerInfoWithUidNamespace(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- expectedInfo := &cadvisorapi.ContainerInfo{}
- podID := "somepod"
- expectedNamespace := "custom"
- expectedPodID := getPodName(podID, expectedNamespace)
- expectedContainerName := "goodcontainer"
- fw.fakeKubelet.containerInfoFunc = func(podID string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) {
- if podID != expectedPodID || string(uid) != testUID || containerName != expectedContainerName {
- return nil, fmt.Errorf("bad podID or uid or containerName: podID=%v; uid=%v; containerName=%v", podID, uid, containerName)
- }
- return expectedInfo, nil
- }
- resp, err := http.Get(fw.testHTTPServer.URL + fmt.Sprintf("/stats/%v/%v/%v/%v", expectedNamespace, podID, testUID, expectedContainerName))
- if err != nil {
- t.Fatalf("Got error GETing: %v", err)
- }
- defer resp.Body.Close()
- var receivedInfo cadvisorapi.ContainerInfo
- err = json.NewDecoder(resp.Body).Decode(&receivedInfo)
- if err != nil {
- t.Fatalf("received invalid json data: %v", err)
- }
- if !receivedInfo.Eq(expectedInfo) {
- t.Errorf("received wrong data: %#v", receivedInfo)
- }
- }
- func TestContainerNotFound(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- podID := "somepod"
- expectedNamespace := "custom"
- expectedContainerName := "slowstartcontainer"
- fw.fakeKubelet.containerInfoFunc = func(podID string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) {
- return nil, kubecontainer.ErrContainerNotFound
- }
- resp, err := http.Get(fw.testHTTPServer.URL + fmt.Sprintf("/stats/%v/%v/%v/%v", expectedNamespace, podID, testUID, expectedContainerName))
- if err != nil {
- t.Fatalf("Got error GETing: %v", err)
- }
- if resp.StatusCode != http.StatusNotFound {
- t.Fatalf("Received status %d expecting %d", resp.StatusCode, http.StatusNotFound)
- }
- defer resp.Body.Close()
- }
- func TestRootInfo(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- expectedInfo := &cadvisorapi.ContainerInfo{
- ContainerReference: cadvisorapi.ContainerReference{
- Name: "/",
- },
- }
- fw.fakeKubelet.rawInfoFunc = func(req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) {
- return map[string]*cadvisorapi.ContainerInfo{
- expectedInfo.Name: expectedInfo,
- }, nil
- }
- resp, err := http.Get(fw.testHTTPServer.URL + "/stats")
- if err != nil {
- t.Fatalf("Got error GETing: %v", err)
- }
- defer resp.Body.Close()
- var receivedInfo cadvisorapi.ContainerInfo
- err = json.NewDecoder(resp.Body).Decode(&receivedInfo)
- if err != nil {
- t.Fatalf("received invalid json data: %v", err)
- }
- if !receivedInfo.Eq(expectedInfo) {
- t.Errorf("received wrong data: %#v, expected %#v", receivedInfo, expectedInfo)
- }
- }
- func TestSubcontainerContainerInfo(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- const kubeletContainer = "/kubelet"
- const kubeletSubContainer = "/kubelet/sub"
- expectedInfo := map[string]*cadvisorapi.ContainerInfo{
- kubeletContainer: {
- ContainerReference: cadvisorapi.ContainerReference{
- Name: kubeletContainer,
- },
- },
- kubeletSubContainer: {
- ContainerReference: cadvisorapi.ContainerReference{
- Name: kubeletSubContainer,
- },
- },
- }
- fw.fakeKubelet.rawInfoFunc = func(req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) {
- return expectedInfo, nil
- }
- request := fmt.Sprintf("{\"containerName\":%q, \"subcontainers\": true}", kubeletContainer)
- resp, err := http.Post(fw.testHTTPServer.URL+"/stats/container", "application/json", bytes.NewBuffer([]byte(request)))
- if err != nil {
- t.Fatalf("Got error GETing: %v", err)
- }
- defer resp.Body.Close()
- var receivedInfo map[string]*cadvisorapi.ContainerInfo
- err = json.NewDecoder(resp.Body).Decode(&receivedInfo)
- if err != nil {
- t.Fatalf("Received invalid json data: %v", err)
- }
- if len(receivedInfo) != len(expectedInfo) {
- t.Errorf("Received wrong data: %#v, expected %#v", receivedInfo, expectedInfo)
- }
- for _, containerName := range []string{kubeletContainer, kubeletSubContainer} {
- if _, ok := receivedInfo[containerName]; !ok {
- t.Errorf("Expected container %q to be present in result: %#v", containerName, receivedInfo)
- }
- if !receivedInfo[containerName].Eq(expectedInfo[containerName]) {
- t.Errorf("Invalid result for %q: Expected %#v, received %#v", containerName, expectedInfo[containerName], receivedInfo[containerName])
- }
- }
- }
- func TestMachineInfo(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- expectedInfo := &cadvisorapi.MachineInfo{
- NumCores: 4,
- MemoryCapacity: 1024,
- }
- fw.fakeKubelet.machineInfoFunc = func() (*cadvisorapi.MachineInfo, error) {
- return expectedInfo, nil
- }
- resp, err := http.Get(fw.testHTTPServer.URL + "/spec")
- if err != nil {
- t.Fatalf("Got error GETing: %v", err)
- }
- defer resp.Body.Close()
- var receivedInfo cadvisorapi.MachineInfo
- err = json.NewDecoder(resp.Body).Decode(&receivedInfo)
- if err != nil {
- t.Fatalf("received invalid json data: %v", err)
- }
- if !reflect.DeepEqual(&receivedInfo, expectedInfo) {
- t.Errorf("received wrong data: %#v", receivedInfo)
- }
- }
- func TestServeLogs(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- content := string(`<pre><a href="kubelet.log">kubelet.log</a><a href="google.log">google.log</a></pre>`)
- fw.fakeKubelet.logFunc = func(w http.ResponseWriter, req *http.Request) {
- w.WriteHeader(http.StatusOK)
- w.Header().Add("Content-Type", "text/html")
- w.Write([]byte(content))
- }
- resp, err := http.Get(fw.testHTTPServer.URL + "/logs/")
- if err != nil {
- t.Fatalf("Got error GETing: %v", err)
- }
- defer resp.Body.Close()
- body, err := httputil.DumpResponse(resp, true)
- if err != nil {
- // copying the response body did not work
- t.Errorf("Cannot copy resp: %#v", err)
- }
- result := string(body)
- if !strings.Contains(result, "kubelet.log") || !strings.Contains(result, "google.log") {
- t.Errorf("Received wrong data: %s", result)
- }
- }
- func TestServeRunInContainer(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- output := "foo bar"
- podNamespace := "other"
- podName := "foo"
- expectedPodName := getPodName(podName, podNamespace)
- expectedContainerName := "baz"
- expectedCommand := "ls -a"
- fw.fakeKubelet.runFunc = func(podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error) {
- if podFullName != expectedPodName {
- t.Errorf("expected %s, got %s", expectedPodName, podFullName)
- }
- if containerName != expectedContainerName {
- t.Errorf("expected %s, got %s", expectedContainerName, containerName)
- }
- if strings.Join(cmd, " ") != expectedCommand {
- t.Errorf("expected: %s, got %v", expectedCommand, cmd)
- }
- return []byte(output), nil
- }
- resp, err := http.Post(fw.testHTTPServer.URL+"/run/"+podNamespace+"/"+podName+"/"+expectedContainerName+"?cmd=ls%20-a", "", nil)
- if err != nil {
- t.Fatalf("Got error POSTing: %v", err)
- }
- defer resp.Body.Close()
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- // copying the response body did not work
- t.Errorf("Cannot copy resp: %#v", err)
- }
- result := string(body)
- if result != output {
- t.Errorf("expected %s, got %s", output, result)
- }
- }
- func TestServeRunInContainerWithUID(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- output := "foo bar"
- podNamespace := "other"
- podName := "foo"
- expectedPodName := getPodName(podName, podNamespace)
- expectedContainerName := "baz"
- expectedCommand := "ls -a"
- fw.fakeKubelet.runFunc = func(podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error) {
- if podFullName != expectedPodName {
- t.Errorf("expected %s, got %s", expectedPodName, podFullName)
- }
- if string(uid) != testUID {
- t.Errorf("expected %s, got %s", testUID, uid)
- }
- if containerName != expectedContainerName {
- t.Errorf("expected %s, got %s", expectedContainerName, containerName)
- }
- if strings.Join(cmd, " ") != expectedCommand {
- t.Errorf("expected: %s, got %v", expectedCommand, cmd)
- }
- return []byte(output), nil
- }
- resp, err := http.Post(fw.testHTTPServer.URL+"/run/"+podNamespace+"/"+podName+"/"+testUID+"/"+expectedContainerName+"?cmd=ls%20-a", "", nil)
- if err != nil {
- t.Fatalf("Got error POSTing: %v", err)
- }
- defer resp.Body.Close()
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- // copying the response body did not work
- t.Errorf("Cannot copy resp: %#v", err)
- }
- result := string(body)
- if result != output {
- t.Errorf("expected %s, got %s", output, result)
- }
- }
- func TestHealthCheck(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- fw.fakeKubelet.hostnameFunc = func() string {
- return "127.0.0.1"
- }
- // Test with correct hostname, Docker version
- assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz")
- // Test with incorrect hostname
- fw.fakeKubelet.hostnameFunc = func() string {
- return "fake"
- }
- assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz")
- }
- func assertHealthFails(t *testing.T, httpURL string, expectedErrorCode int) {
- resp, err := http.Get(httpURL)
- if err != nil {
- t.Fatalf("Got error GETing: %v", err)
- }
- defer resp.Body.Close()
- if resp.StatusCode != expectedErrorCode {
- t.Errorf("expected status code %d, got %d", expectedErrorCode, resp.StatusCode)
- }
- }
- type authTestCase struct {
- Method string
- Path string
- }
- func TestAuthFilters(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- testcases := []authTestCase{}
- // This is a sanity check that the Handle->HandleWithFilter() delegation is working
- // Ideally, these would move to registered web services and this list would get shorter
- expectedPaths := []string{"/healthz", "/metrics", "/metrics/cadvisor"}
- paths := sets.NewString(fw.serverUnderTest.restfulCont.RegisteredHandlePaths()...)
- for _, expectedPath := range expectedPaths {
- if !paths.Has(expectedPath) {
- t.Errorf("Expected registered handle path %s was missing", expectedPath)
- }
- }
- // Test all the non-web-service handlers
- for _, path := range fw.serverUnderTest.restfulCont.RegisteredHandlePaths() {
- testcases = append(testcases, authTestCase{"GET", path})
- testcases = append(testcases, authTestCase{"POST", path})
- // Test subpaths for directory handlers
- if strings.HasSuffix(path, "/") {
- testcases = append(testcases, authTestCase{"GET", path + "foo"})
- testcases = append(testcases, authTestCase{"POST", path + "foo"})
- }
- }
- // Test all the generated web-service paths
- for _, ws := range fw.serverUnderTest.restfulCont.RegisteredWebServices() {
- for _, r := range ws.Routes() {
- testcases = append(testcases, authTestCase{r.Method, r.Path})
- }
- }
- methodToAPIVerb := map[string]string{"GET": "get", "POST": "create", "PUT": "update"}
- pathToSubresource := func(path string) string {
- switch {
- // Cases for subpaths we expect specific subresources for
- case isSubpath(path, statsPath):
- return "stats"
- case isSubpath(path, specPath):
- return "spec"
- case isSubpath(path, logsPath):
- return "log"
- case isSubpath(path, metricsPath):
- return "metrics"
- // Cases for subpaths we expect to map to the "proxy" subresource
- case isSubpath(path, "/attach"),
- isSubpath(path, "/configz"),
- isSubpath(path, "/containerLogs"),
- isSubpath(path, "/debug"),
- isSubpath(path, "/exec"),
- isSubpath(path, "/healthz"),
- isSubpath(path, "/pods"),
- isSubpath(path, "/portForward"),
- isSubpath(path, "/run"),
- isSubpath(path, "/runningpods"),
- isSubpath(path, "/cri"):
- return "proxy"
- default:
- panic(fmt.Errorf(`unexpected kubelet API path %s.
- The kubelet API has likely registered a handler for a new path.
- If the new path has a use case for partitioned authorization when requested from the kubelet API,
- add a specific subresource for it in auth.go#GetRequestAttributes() and in TestAuthFilters().
- Otherwise, add it to the expected list of paths that map to the "proxy" subresource in TestAuthFilters()`, path))
- }
- }
- attributesGetter := NewNodeAuthorizerAttributesGetter(types.NodeName("test"))
- for _, tc := range testcases {
- var (
- expectedUser = &user.DefaultInfo{Name: "test"}
- expectedAttributes = authorizer.AttributesRecord{
- User: expectedUser,
- APIGroup: "",
- APIVersion: "v1",
- Verb: methodToAPIVerb[tc.Method],
- Resource: "nodes",
- Name: "test",
- Subresource: pathToSubresource(tc.Path),
- ResourceRequest: true,
- Path: tc.Path,
- }
- calledAuthenticate = false
- calledAuthorize = false
- calledAttributes = false
- )
- fw.fakeAuth.authenticateFunc = func(req *http.Request) (*authenticator.Response, bool, error) {
- calledAuthenticate = true
- return &authenticator.Response{User: expectedUser}, true, nil
- }
- fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes {
- calledAttributes = true
- if u != expectedUser {
- t.Fatalf("%s: expected user %v, got %v", tc.Path, expectedUser, u)
- }
- return attributesGetter.GetRequestAttributes(u, req)
- }
- fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
- calledAuthorize = true
- if a != expectedAttributes {
- t.Fatalf("%s: expected attributes\n\t%#v\ngot\n\t%#v", tc.Path, expectedAttributes, a)
- }
- return authorizer.DecisionNoOpinion, "", nil
- }
- req, err := http.NewRequest(tc.Method, fw.testHTTPServer.URL+tc.Path, nil)
- if err != nil {
- t.Errorf("%s: unexpected error: %v", tc.Path, err)
- continue
- }
- resp, err := http.DefaultClient.Do(req)
- if err != nil {
- t.Errorf("%s: unexpected error: %v", tc.Path, err)
- continue
- }
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusForbidden {
- t.Errorf("%s: unexpected status code %d", tc.Path, resp.StatusCode)
- continue
- }
- if !calledAuthenticate {
- t.Errorf("%s: Authenticate was not called", tc.Path)
- continue
- }
- if !calledAttributes {
- t.Errorf("%s: Attributes were not called", tc.Path)
- continue
- }
- if !calledAuthorize {
- t.Errorf("%s: Authorize was not called", tc.Path)
- continue
- }
- }
- }
- func TestAuthenticationError(t *testing.T) {
- var (
- expectedUser = &user.DefaultInfo{Name: "test"}
- expectedAttributes = &authorizer.AttributesRecord{User: expectedUser}
- calledAuthenticate = false
- calledAuthorize = false
- calledAttributes = false
- )
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- fw.fakeAuth.authenticateFunc = func(req *http.Request) (*authenticator.Response, bool, error) {
- calledAuthenticate = true
- return &authenticator.Response{User: expectedUser}, true, nil
- }
- fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes {
- calledAttributes = true
- return expectedAttributes
- }
- fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
- calledAuthorize = true
- return authorizer.DecisionNoOpinion, "", errors.New("Failed")
- }
- assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusInternalServerError)
- if !calledAuthenticate {
- t.Fatalf("Authenticate was not called")
- }
- if !calledAttributes {
- t.Fatalf("Attributes was not called")
- }
- if !calledAuthorize {
- t.Fatalf("Authorize was not called")
- }
- }
- func TestAuthenticationFailure(t *testing.T) {
- var (
- expectedUser = &user.DefaultInfo{Name: "test"}
- expectedAttributes = &authorizer.AttributesRecord{User: expectedUser}
- calledAuthenticate = false
- calledAuthorize = false
- calledAttributes = false
- )
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- fw.fakeAuth.authenticateFunc = func(req *http.Request) (*authenticator.Response, bool, error) {
- calledAuthenticate = true
- return nil, false, nil
- }
- fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes {
- calledAttributes = true
- return expectedAttributes
- }
- fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
- calledAuthorize = true
- return authorizer.DecisionNoOpinion, "", nil
- }
- assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusUnauthorized)
- if !calledAuthenticate {
- t.Fatalf("Authenticate was not called")
- }
- if calledAttributes {
- t.Fatalf("Attributes was called unexpectedly")
- }
- if calledAuthorize {
- t.Fatalf("Authorize was called unexpectedly")
- }
- }
- func TestAuthorizationSuccess(t *testing.T) {
- var (
- expectedUser = &user.DefaultInfo{Name: "test"}
- expectedAttributes = &authorizer.AttributesRecord{User: expectedUser}
- calledAuthenticate = false
- calledAuthorize = false
- calledAttributes = false
- )
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- fw.fakeAuth.authenticateFunc = func(req *http.Request) (*authenticator.Response, bool, error) {
- calledAuthenticate = true
- return &authenticator.Response{User: expectedUser}, true, nil
- }
- fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes {
- calledAttributes = true
- return expectedAttributes
- }
- fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
- calledAuthorize = true
- return authorizer.DecisionAllow, "", nil
- }
- assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz")
- if !calledAuthenticate {
- t.Fatalf("Authenticate was not called")
- }
- if !calledAttributes {
- t.Fatalf("Attributes were not called")
- }
- if !calledAuthorize {
- t.Fatalf("Authorize was not called")
- }
- }
- func TestSyncLoopCheck(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- fw.fakeKubelet.hostnameFunc = func() string {
- return "127.0.0.1"
- }
- fw.fakeKubelet.resyncInterval = time.Minute
- fw.fakeKubelet.loopEntryTime = time.Now()
- // Test with correct hostname, Docker version
- assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz")
- fw.fakeKubelet.loopEntryTime = time.Now().Add(time.Minute * -10)
- assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusInternalServerError)
- }
- // returns http response status code from the HTTP GET
- func assertHealthIsOk(t *testing.T, httpURL string) {
- resp, err := http.Get(httpURL)
- if err != nil {
- t.Fatalf("Got error GETing: %v", err)
- }
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusOK {
- t.Errorf("expected status code %d, got %d", http.StatusOK, resp.StatusCode)
- }
- body, readErr := ioutil.ReadAll(resp.Body)
- if readErr != nil {
- // copying the response body did not work
- t.Fatalf("Cannot copy resp: %#v", readErr)
- }
- result := string(body)
- if !strings.Contains(result, "ok") {
- t.Errorf("expected body contains ok, got %s", result)
- }
- }
- func setPodByNameFunc(fw *serverTestFramework, namespace, pod, container string) {
- fw.fakeKubelet.podByNameFunc = func(namespace, name string) (*v1.Pod, bool) {
- return &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: namespace,
- Name: pod,
- },
- Spec: v1.PodSpec{
- Containers: []v1.Container{
- {
- Name: container,
- },
- },
- },
- }, true
- }
- }
- func setGetContainerLogsFunc(fw *serverTestFramework, t *testing.T, expectedPodName, expectedContainerName string, expectedLogOptions *v1.PodLogOptions, output string) {
- fw.fakeKubelet.containerLogsFunc = func(_ context.Context, podFullName, containerName string, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) error {
- if podFullName != expectedPodName {
- t.Errorf("expected %s, got %s", expectedPodName, podFullName)
- }
- if containerName != expectedContainerName {
- t.Errorf("expected %s, got %s", expectedContainerName, containerName)
- }
- if !reflect.DeepEqual(expectedLogOptions, logOptions) {
- t.Errorf("expected %#v, got %#v", expectedLogOptions, logOptions)
- }
- io.WriteString(stdout, output)
- return nil
- }
- }
- // TODO: I really want to be a table driven test
- func TestContainerLogs(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- output := "foo bar"
- podNamespace := "other"
- podName := "foo"
- expectedPodName := getPodName(podName, podNamespace)
- expectedContainerName := "baz"
- setPodByNameFunc(fw, podNamespace, podName, expectedContainerName)
- setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, &v1.PodLogOptions{}, output)
- resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName)
- if err != nil {
- t.Errorf("Got error GETing: %v", err)
- }
- defer resp.Body.Close()
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- t.Errorf("Error reading container logs: %v", err)
- }
- result := string(body)
- if result != output {
- t.Errorf("Expected: '%v', got: '%v'", output, result)
- }
- }
- func TestContainerLogsWithTail(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- output := "foo bar"
- podNamespace := "other"
- podName := "foo"
- expectedPodName := getPodName(podName, podNamespace)
- expectedContainerName := "baz"
- expectedTail := int64(5)
- setPodByNameFunc(fw, podNamespace, podName, expectedContainerName)
- setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, &v1.PodLogOptions{TailLines: &expectedTail}, output)
- resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?tailLines=5")
- if err != nil {
- t.Errorf("Got error GETing: %v", err)
- }
- defer resp.Body.Close()
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- t.Errorf("Error reading container logs: %v", err)
- }
- result := string(body)
- if result != output {
- t.Errorf("Expected: '%v', got: '%v'", output, result)
- }
- }
- func TestContainerLogsWithLegacyTail(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- output := "foo bar"
- podNamespace := "other"
- podName := "foo"
- expectedPodName := getPodName(podName, podNamespace)
- expectedContainerName := "baz"
- expectedTail := int64(5)
- setPodByNameFunc(fw, podNamespace, podName, expectedContainerName)
- setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, &v1.PodLogOptions{TailLines: &expectedTail}, output)
- resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?tail=5")
- if err != nil {
- t.Errorf("Got error GETing: %v", err)
- }
- defer resp.Body.Close()
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- t.Errorf("Error reading container logs: %v", err)
- }
- result := string(body)
- if result != output {
- t.Errorf("Expected: '%v', got: '%v'", output, result)
- }
- }
- func TestContainerLogsWithTailAll(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- output := "foo bar"
- podNamespace := "other"
- podName := "foo"
- expectedPodName := getPodName(podName, podNamespace)
- expectedContainerName := "baz"
- setPodByNameFunc(fw, podNamespace, podName, expectedContainerName)
- setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, &v1.PodLogOptions{}, output)
- resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?tail=all")
- if err != nil {
- t.Errorf("Got error GETing: %v", err)
- }
- defer resp.Body.Close()
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- t.Errorf("Error reading container logs: %v", err)
- }
- result := string(body)
- if result != output {
- t.Errorf("Expected: '%v', got: '%v'", output, result)
- }
- }
- func TestContainerLogsWithInvalidTail(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- output := "foo bar"
- podNamespace := "other"
- podName := "foo"
- expectedPodName := getPodName(podName, podNamespace)
- expectedContainerName := "baz"
- setPodByNameFunc(fw, podNamespace, podName, expectedContainerName)
- setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, &v1.PodLogOptions{}, output)
- resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?tail=-1")
- if err != nil {
- t.Errorf("Got error GETing: %v", err)
- }
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusUnprocessableEntity {
- t.Errorf("Unexpected non-error reading container logs: %#v", resp)
- }
- }
- func TestContainerLogsWithFollow(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- output := "foo bar"
- podNamespace := "other"
- podName := "foo"
- expectedPodName := getPodName(podName, podNamespace)
- expectedContainerName := "baz"
- setPodByNameFunc(fw, podNamespace, podName, expectedContainerName)
- setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, &v1.PodLogOptions{Follow: true}, output)
- resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?follow=1")
- if err != nil {
- t.Errorf("Got error GETing: %v", err)
- }
- defer resp.Body.Close()
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- t.Errorf("Error reading container logs: %v", err)
- }
- result := string(body)
- if result != output {
- t.Errorf("Expected: '%v', got: '%v'", output, result)
- }
- }
- func TestServeExecInContainerIdleTimeout(t *testing.T) {
- ss, err := newTestStreamingServer(100 * time.Millisecond)
- require.NoError(t, err)
- defer ss.testHTTPServer.Close()
- fw := newServerTestWithDebug(true, false, ss)
- defer fw.testHTTPServer.Close()
- podNamespace := "other"
- podName := "foo"
- expectedContainerName := "baz"
- url := fw.testHTTPServer.URL + "/exec/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?c=ls&c=-a&" + api.ExecStdinParam + "=1"
- upgradeRoundTripper := spdy.NewSpdyRoundTripper(nil, true, true)
- c := &http.Client{Transport: upgradeRoundTripper}
- resp, err := c.Post(url, "", nil)
- if err != nil {
- t.Fatalf("Got error POSTing: %v", err)
- }
- defer resp.Body.Close()
- upgradeRoundTripper.Dialer = &net.Dialer{
- Deadline: time.Now().Add(60 * time.Second),
- Timeout: 60 * time.Second,
- }
- conn, err := upgradeRoundTripper.NewConnection(resp)
- if err != nil {
- t.Fatalf("Unexpected error creating streaming connection: %s", err)
- }
- if conn == nil {
- t.Fatal("Unexpected nil connection")
- }
- <-conn.CloseChan()
- }
- func testExecAttach(t *testing.T, verb string) {
- tests := map[string]struct {
- stdin bool
- stdout bool
- stderr bool
- tty bool
- responseStatusCode int
- uid bool
- redirect bool
- }{
- "no input or output": {responseStatusCode: http.StatusBadRequest},
- "stdin": {stdin: true, responseStatusCode: http.StatusSwitchingProtocols},
- "stdout": {stdout: true, responseStatusCode: http.StatusSwitchingProtocols},
- "stderr": {stderr: true, responseStatusCode: http.StatusSwitchingProtocols},
- "stdout and stderr": {stdout: true, stderr: true, responseStatusCode: http.StatusSwitchingProtocols},
- "stdout stderr and tty": {stdout: true, stderr: true, tty: true, responseStatusCode: http.StatusSwitchingProtocols},
- "stdin stdout and stderr": {stdin: true, stdout: true, stderr: true, responseStatusCode: http.StatusSwitchingProtocols},
- "stdin stdout stderr with uid": {stdin: true, stdout: true, stderr: true, responseStatusCode: http.StatusSwitchingProtocols, uid: true},
- "stdout with redirect": {stdout: true, responseStatusCode: http.StatusFound, redirect: true},
- }
- for desc, test := range tests {
- test := test
- t.Run(desc, func(t *testing.T) {
- ss, err := newTestStreamingServer(0)
- require.NoError(t, err)
- defer ss.testHTTPServer.Close()
- fw := newServerTestWithDebug(true, test.redirect, ss)
- defer fw.testHTTPServer.Close()
- fmt.Println(desc)
- podNamespace := "other"
- podName := "foo"
- expectedPodName := getPodName(podName, podNamespace)
- expectedContainerName := "baz"
- expectedCommand := "ls -a"
- expectedStdin := "stdin"
- expectedStdout := "stdout"
- expectedStderr := "stderr"
- done := make(chan struct{})
- clientStdoutReadDone := make(chan struct{})
- clientStderrReadDone := make(chan struct{})
- execInvoked := false
- attachInvoked := false
- checkStream := func(podFullName string, uid types.UID, containerName string, streamOpts remotecommandserver.Options) {
- assert.Equal(t, expectedPodName, podFullName, "podFullName")
- if test.uid {
- assert.Equal(t, testUID, string(uid), "uid")
- }
- assert.Equal(t, expectedContainerName, containerName, "containerName")
- assert.Equal(t, test.stdin, streamOpts.Stdin, "stdin")
- assert.Equal(t, test.stdout, streamOpts.Stdout, "stdout")
- assert.Equal(t, test.tty, streamOpts.TTY, "tty")
- assert.Equal(t, !test.tty && test.stderr, streamOpts.Stderr, "stderr")
- }
- fw.fakeKubelet.getExecCheck = func(podFullName string, uid types.UID, containerName string, cmd []string, streamOpts remotecommandserver.Options) {
- execInvoked = true
- assert.Equal(t, expectedCommand, strings.Join(cmd, " "), "cmd")
- checkStream(podFullName, uid, containerName, streamOpts)
- }
- fw.fakeKubelet.getAttachCheck = func(podFullName string, uid types.UID, containerName string, streamOpts remotecommandserver.Options) {
- attachInvoked = true
- checkStream(podFullName, uid, containerName, streamOpts)
- }
- testStream := func(containerID string, in io.Reader, out, stderr io.WriteCloser, tty bool, done chan struct{}) error {
- close(done)
- assert.Equal(t, testContainerID, containerID, "containerID")
- assert.Equal(t, test.tty, tty, "tty")
- require.Equal(t, test.stdin, in != nil, "in")
- require.Equal(t, test.stdout, out != nil, "out")
- require.Equal(t, !test.tty && test.stderr, stderr != nil, "err")
- if test.stdin {
- b := make([]byte, 10)
- n, err := in.Read(b)
- assert.NoError(t, err, "reading from stdin")
- assert.Equal(t, expectedStdin, string(b[0:n]), "content from stdin")
- }
- if test.stdout {
- _, err := out.Write([]byte(expectedStdout))
- assert.NoError(t, err, "writing to stdout")
- out.Close()
- <-clientStdoutReadDone
- }
- if !test.tty && test.stderr {
- _, err := stderr.Write([]byte(expectedStderr))
- assert.NoError(t, err, "writing to stderr")
- stderr.Close()
- <-clientStderrReadDone
- }
- return nil
- }
- ss.fakeRuntime.execFunc = func(containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
- assert.Equal(t, expectedCommand, strings.Join(cmd, " "), "cmd")
- return testStream(containerID, stdin, stdout, stderr, tty, done)
- }
- ss.fakeRuntime.attachFunc = func(containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
- return testStream(containerID, stdin, stdout, stderr, tty, done)
- }
- var url string
- if test.uid {
- url = fw.testHTTPServer.URL + "/" + verb + "/" + podNamespace + "/" + podName + "/" + testUID + "/" + expectedContainerName + "?ignore=1"
- } else {
- url = fw.testHTTPServer.URL + "/" + verb + "/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?ignore=1"
- }
- if verb == "exec" {
- url += "&command=ls&command=-a"
- }
- if test.stdin {
- url += "&" + api.ExecStdinParam + "=1"
- }
- if test.stdout {
- url += "&" + api.ExecStdoutParam + "=1"
- }
- if test.stderr && !test.tty {
- url += "&" + api.ExecStderrParam + "=1"
- }
- if test.tty {
- url += "&" + api.ExecTTYParam + "=1"
- }
- var (
- resp *http.Response
- upgradeRoundTripper httpstream.UpgradeRoundTripper
- c *http.Client
- )
- if test.redirect {
- c = &http.Client{}
- // Don't follow redirects, since we want to inspect the redirect response.
- c.CheckRedirect = func(*http.Request, []*http.Request) error {
- return http.ErrUseLastResponse
- }
- } else {
- upgradeRoundTripper = spdy.NewRoundTripper(nil, true, true)
- c = &http.Client{Transport: upgradeRoundTripper}
- }
- resp, err = c.Post(url, "", nil)
- require.NoError(t, err, "POSTing")
- defer resp.Body.Close()
- _, err = ioutil.ReadAll(resp.Body)
- assert.NoError(t, err, "reading response body")
- require.Equal(t, test.responseStatusCode, resp.StatusCode, "response status")
- if test.responseStatusCode != http.StatusSwitchingProtocols {
- return
- }
- conn, err := upgradeRoundTripper.NewConnection(resp)
- require.NoError(t, err, "creating streaming connection")
- defer conn.Close()
- h := http.Header{}
- h.Set(api.StreamType, api.StreamTypeError)
- _, err = conn.CreateStream(h)
- require.NoError(t, err, "creating error stream")
- if test.stdin {
- h.Set(api.StreamType, api.StreamTypeStdin)
- stream, err := conn.CreateStream(h)
- require.NoError(t, err, "creating stdin stream")
- _, err = stream.Write([]byte(expectedStdin))
- require.NoError(t, err, "writing to stdin stream")
- }
- var stdoutStream httpstream.Stream
- if test.stdout {
- h.Set(api.StreamType, api.StreamTypeStdout)
- stdoutStream, err = conn.CreateStream(h)
- require.NoError(t, err, "creating stdout stream")
- }
- var stderrStream httpstream.Stream
- if test.stderr && !test.tty {
- h.Set(api.StreamType, api.StreamTypeStderr)
- stderrStream, err = conn.CreateStream(h)
- require.NoError(t, err, "creating stderr stream")
- }
- if test.stdout {
- output := make([]byte, 10)
- n, err := stdoutStream.Read(output)
- close(clientStdoutReadDone)
- assert.NoError(t, err, "reading from stdout stream")
- assert.Equal(t, expectedStdout, string(output[0:n]), "stdout")
- }
- if test.stderr && !test.tty {
- output := make([]byte, 10)
- n, err := stderrStream.Read(output)
- close(clientStderrReadDone)
- assert.NoError(t, err, "reading from stderr stream")
- assert.Equal(t, expectedStderr, string(output[0:n]), "stderr")
- }
- // wait for the server to finish before checking if the attach/exec funcs were invoked
- <-done
- if verb == "exec" {
- assert.True(t, execInvoked, "exec should be invoked")
- assert.False(t, attachInvoked, "attach should not be invoked")
- } else {
- assert.True(t, attachInvoked, "attach should be invoked")
- assert.False(t, execInvoked, "exec should not be invoked")
- }
- })
- }
- }
- func TestServeExecInContainer(t *testing.T) {
- testExecAttach(t, "exec")
- }
- func TestServeAttachContainer(t *testing.T) {
- testExecAttach(t, "attach")
- }
- func TestServePortForwardIdleTimeout(t *testing.T) {
- ss, err := newTestStreamingServer(100 * time.Millisecond)
- require.NoError(t, err)
- defer ss.testHTTPServer.Close()
- fw := newServerTestWithDebug(true, false, ss)
- defer fw.testHTTPServer.Close()
- podNamespace := "other"
- podName := "foo"
- url := fw.testHTTPServer.URL + "/portForward/" + podNamespace + "/" + podName
- upgradeRoundTripper := spdy.NewRoundTripper(nil, true, true)
- c := &http.Client{Transport: upgradeRoundTripper}
- resp, err := c.Post(url, "", nil)
- if err != nil {
- t.Fatalf("Got error POSTing: %v", err)
- }
- defer resp.Body.Close()
- conn, err := upgradeRoundTripper.NewConnection(resp)
- if err != nil {
- t.Fatalf("Unexpected error creating streaming connection: %s", err)
- }
- if conn == nil {
- t.Fatal("Unexpected nil connection")
- }
- defer conn.Close()
- <-conn.CloseChan()
- }
- func TestServePortForward(t *testing.T) {
- tests := map[string]struct {
- port string
- uid bool
- clientData string
- containerData string
- redirect bool
- shouldError bool
- }{
- "no port": {port: "", shouldError: true},
- "none number port": {port: "abc", shouldError: true},
- "negative port": {port: "-1", shouldError: true},
- "too large port": {port: "65536", shouldError: true},
- "0 port": {port: "0", shouldError: true},
- "min port": {port: "1", shouldError: false},
- "normal port": {port: "8000", shouldError: false},
- "normal port with data forward": {port: "8000", clientData: "client data", containerData: "container data", shouldError: false},
- "max port": {port: "65535", shouldError: false},
- "normal port with uid": {port: "8000", uid: true, shouldError: false},
- "normal port with redirect": {port: "8000", redirect: true, shouldError: false},
- }
- podNamespace := "other"
- podName := "foo"
- for desc, test := range tests {
- test := test
- t.Run(desc, func(t *testing.T) {
- ss, err := newTestStreamingServer(0)
- require.NoError(t, err)
- defer ss.testHTTPServer.Close()
- fw := newServerTestWithDebug(true, test.redirect, ss)
- defer fw.testHTTPServer.Close()
- portForwardFuncDone := make(chan struct{})
- fw.fakeKubelet.getPortForwardCheck = func(name, namespace string, uid types.UID, opts portforward.V4Options) {
- assert.Equal(t, podName, name, "pod name")
- assert.Equal(t, podNamespace, namespace, "pod namespace")
- if test.uid {
- assert.Equal(t, testUID, string(uid), "uid")
- }
- }
- ss.fakeRuntime.portForwardFunc = func(podSandboxID string, port int32, stream io.ReadWriteCloser) error {
- defer close(portForwardFuncDone)
- assert.Equal(t, testPodSandboxID, podSandboxID, "pod sandbox id")
- // The port should be valid if it reaches here.
- testPort, err := strconv.ParseInt(test.port, 10, 32)
- require.NoError(t, err, "parse port")
- assert.Equal(t, int32(testPort), port, "port")
- if test.clientData != "" {
- fromClient := make([]byte, 32)
- n, err := stream.Read(fromClient)
- assert.NoError(t, err, "reading client data")
- assert.Equal(t, test.clientData, string(fromClient[0:n]), "client data")
- }
- if test.containerData != "" {
- _, err := stream.Write([]byte(test.containerData))
- assert.NoError(t, err, "writing container data")
- }
- return nil
- }
- var url string
- if test.uid {
- url = fmt.Sprintf("%s/portForward/%s/%s/%s", fw.testHTTPServer.URL, podNamespace, podName, testUID)
- } else {
- url = fmt.Sprintf("%s/portForward/%s/%s", fw.testHTTPServer.URL, podNamespace, podName)
- }
- var (
- upgradeRoundTripper httpstream.UpgradeRoundTripper
- c *http.Client
- )
- if test.redirect {
- c = &http.Client{}
- // Don't follow redirects, since we want to inspect the redirect response.
- c.CheckRedirect = func(*http.Request, []*http.Request) error {
- return http.ErrUseLastResponse
- }
- } else {
- upgradeRoundTripper = spdy.NewRoundTripper(nil, true, true)
- c = &http.Client{Transport: upgradeRoundTripper}
- }
- resp, err := c.Post(url, "", nil)
- require.NoError(t, err, "POSTing")
- defer resp.Body.Close()
- if test.redirect {
- assert.Equal(t, http.StatusFound, resp.StatusCode, "status code")
- return
- }
- assert.Equal(t, http.StatusSwitchingProtocols, resp.StatusCode, "status code")
- conn, err := upgradeRoundTripper.NewConnection(resp)
- require.NoError(t, err, "creating streaming connection")
- defer conn.Close()
- headers := http.Header{}
- headers.Set("streamType", "error")
- headers.Set("port", test.port)
- _, err = conn.CreateStream(headers)
- assert.Equal(t, test.shouldError, err != nil, "expect error")
- if test.shouldError {
- return
- }
- headers.Set("streamType", "data")
- headers.Set("port", test.port)
- dataStream, err := conn.CreateStream(headers)
- require.NoError(t, err, "create stream")
- if test.clientData != "" {
- _, err := dataStream.Write([]byte(test.clientData))
- assert.NoError(t, err, "writing client data")
- }
- if test.containerData != "" {
- fromContainer := make([]byte, 32)
- n, err := dataStream.Read(fromContainer)
- assert.NoError(t, err, "reading container data")
- assert.Equal(t, test.containerData, string(fromContainer[0:n]), "container data")
- }
- <-portForwardFuncDone
- })
- }
- }
- func TestCRIHandler(t *testing.T) {
- fw := newServerTest()
- defer fw.testHTTPServer.Close()
- const (
- path = "/cri/exec/123456abcdef"
- query = "cmd=echo+foo"
- )
- resp, err := http.Get(fw.testHTTPServer.URL + path + "?" + query)
- require.NoError(t, err)
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- assert.Equal(t, "GET", fw.criHandler.RequestReceived.Method)
- assert.Equal(t, path, fw.criHandler.RequestReceived.URL.Path)
- assert.Equal(t, query, fw.criHandler.RequestReceived.URL.RawQuery)
- }
- func TestDebuggingDisabledHandlers(t *testing.T) {
- fw := newServerTestWithDebug(false, false, nil)
- defer fw.testHTTPServer.Close()
- paths := []string{
- "/run", "/exec", "/attach", "/portForward", "/containerLogs", "/runningpods",
- "/run/", "/exec/", "/attach/", "/portForward/", "/containerLogs/", "/runningpods/",
- "/run/xxx", "/exec/xxx", "/attach/xxx", "/debug/pprof/profile", "/logs/kubelet.log",
- }
- for _, p := range paths {
- resp, err := http.Get(fw.testHTTPServer.URL + p)
- require.NoError(t, err)
- assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
- body, err := ioutil.ReadAll(resp.Body)
- require.NoError(t, err)
- assert.Equal(t, "Debug endpoints are disabled.\n", string(body))
- resp, err = http.Post(fw.testHTTPServer.URL+p, "", nil)
- require.NoError(t, err)
- assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
- body, err = ioutil.ReadAll(resp.Body)
- require.NoError(t, err)
- assert.Equal(t, "Debug endpoints are disabled.\n", string(body))
- }
- // test some other paths, make sure they're working
- containerInfo := &cadvisorapi.ContainerInfo{
- ContainerReference: cadvisorapi.ContainerReference{
- Name: "/",
- },
- }
- fw.fakeKubelet.rawInfoFunc = func(req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) {
- return map[string]*cadvisorapi.ContainerInfo{
- containerInfo.Name: containerInfo,
- }, nil
- }
- resp, err := http.Get(fw.testHTTPServer.URL + "/stats")
- require.NoError(t, err)
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- machineInfo := &cadvisorapi.MachineInfo{
- NumCores: 4,
- MemoryCapacity: 1024,
- }
- fw.fakeKubelet.machineInfoFunc = func() (*cadvisorapi.MachineInfo, error) {
- return machineInfo, nil
- }
- resp, err = http.Get(fw.testHTTPServer.URL + "/spec")
- require.NoError(t, err)
- assert.Equal(t, http.StatusOK, resp.StatusCode)
- }
- func TestTrimURLPath(t *testing.T) {
- tests := []struct {
- path, expected string
- }{
- {"", ""},
- {"//", ""},
- {"/pods", "pods"},
- {"pods", "pods"},
- {"pods/", "pods"},
- {"good/", "good"},
- {"pods/probes", "pods"},
- {"metrics", "metrics"},
- {"metrics/resource", "metrics/resource"},
- {"metrics/hello", "metrics/hello"},
- }
- for _, test := range tests {
- assert.Equal(t, test.expected, trimURLPath(test.path), fmt.Sprintf("path is: %s", test.path))
- }
- }
|