server_test.go 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package server
  14. import (
  15. "bytes"
  16. "context"
  17. "encoding/json"
  18. "errors"
  19. "fmt"
  20. "io"
  21. "io/ioutil"
  22. "net"
  23. "net/http"
  24. "net/http/httptest"
  25. "net/http/httputil"
  26. "net/url"
  27. "reflect"
  28. "strconv"
  29. "strings"
  30. "testing"
  31. "time"
  32. cadvisorapi "github.com/google/cadvisor/info/v1"
  33. "github.com/stretchr/testify/assert"
  34. "github.com/stretchr/testify/require"
  35. "k8s.io/api/core/v1"
  36. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  37. "k8s.io/apimachinery/pkg/types"
  38. "k8s.io/apimachinery/pkg/util/httpstream"
  39. "k8s.io/apimachinery/pkg/util/httpstream/spdy"
  40. "k8s.io/apiserver/pkg/authentication/authenticator"
  41. "k8s.io/apiserver/pkg/authentication/user"
  42. "k8s.io/apiserver/pkg/authorization/authorizer"
  43. "k8s.io/client-go/tools/remotecommand"
  44. utiltesting "k8s.io/client-go/util/testing"
  45. runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
  46. api "k8s.io/kubernetes/pkg/apis/core"
  47. statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
  48. "k8s.io/utils/pointer"
  49. // Do some initialization to decode the query parameters correctly.
  50. _ "k8s.io/kubernetes/pkg/apis/core/install"
  51. "k8s.io/kubernetes/pkg/kubelet/cm"
  52. kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
  53. "k8s.io/kubernetes/pkg/kubelet/server/portforward"
  54. remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
  55. "k8s.io/kubernetes/pkg/kubelet/server/stats"
  56. "k8s.io/kubernetes/pkg/kubelet/server/streaming"
  57. "k8s.io/kubernetes/pkg/volume"
  58. )
  59. const (
  60. testUID = "9b01b80f-8fb4-11e4-95ab-4200af06647"
  61. testContainerID = "container789"
  62. testPodSandboxID = "pod0987"
  63. )
  64. type fakeKubelet struct {
  65. podByNameFunc func(namespace, name string) (*v1.Pod, bool)
  66. containerInfoFunc func(podFullName string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error)
  67. rawInfoFunc func(query *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error)
  68. machineInfoFunc func() (*cadvisorapi.MachineInfo, error)
  69. podsFunc func() []*v1.Pod
  70. runningPodsFunc func() ([]*v1.Pod, error)
  71. logFunc func(w http.ResponseWriter, req *http.Request)
  72. runFunc func(podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error)
  73. getExecCheck func(string, types.UID, string, []string, remotecommandserver.Options)
  74. getAttachCheck func(string, types.UID, string, remotecommandserver.Options)
  75. getPortForwardCheck func(string, string, types.UID, portforward.V4Options)
  76. containerLogsFunc func(ctx context.Context, podFullName, containerName string, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) error
  77. hostnameFunc func() string
  78. resyncInterval time.Duration
  79. loopEntryTime time.Time
  80. plegHealth bool
  81. streamingRuntime streaming.Server
  82. }
  83. func (fk *fakeKubelet) ResyncInterval() time.Duration {
  84. return fk.resyncInterval
  85. }
  86. func (fk *fakeKubelet) LatestLoopEntryTime() time.Time {
  87. return fk.loopEntryTime
  88. }
  89. func (fk *fakeKubelet) GetPodByName(namespace, name string) (*v1.Pod, bool) {
  90. return fk.podByNameFunc(namespace, name)
  91. }
  92. func (fk *fakeKubelet) GetContainerInfo(podFullName string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) {
  93. return fk.containerInfoFunc(podFullName, uid, containerName, req)
  94. }
  95. func (fk *fakeKubelet) GetRawContainerInfo(containerName string, req *cadvisorapi.ContainerInfoRequest, subcontainers bool) (map[string]*cadvisorapi.ContainerInfo, error) {
  96. return fk.rawInfoFunc(req)
  97. }
  98. func (fk *fakeKubelet) GetCachedMachineInfo() (*cadvisorapi.MachineInfo, error) {
  99. return fk.machineInfoFunc()
  100. }
  101. func (*fakeKubelet) GetVersionInfo() (*cadvisorapi.VersionInfo, error) {
  102. return &cadvisorapi.VersionInfo{}, nil
  103. }
  104. func (fk *fakeKubelet) GetPods() []*v1.Pod {
  105. return fk.podsFunc()
  106. }
  107. func (fk *fakeKubelet) GetRunningPods() ([]*v1.Pod, error) {
  108. return fk.runningPodsFunc()
  109. }
  110. func (fk *fakeKubelet) ServeLogs(w http.ResponseWriter, req *http.Request) {
  111. fk.logFunc(w, req)
  112. }
  113. func (fk *fakeKubelet) GetKubeletContainerLogs(ctx context.Context, podFullName, containerName string, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) error {
  114. return fk.containerLogsFunc(ctx, podFullName, containerName, logOptions, stdout, stderr)
  115. }
  116. func (fk *fakeKubelet) GetHostname() string {
  117. return fk.hostnameFunc()
  118. }
  119. func (fk *fakeKubelet) RunInContainer(podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error) {
  120. return fk.runFunc(podFullName, uid, containerName, cmd)
  121. }
  122. type fakeRuntime struct {
  123. execFunc func(string, []string, io.Reader, io.WriteCloser, io.WriteCloser, bool, <-chan remotecommand.TerminalSize) error
  124. attachFunc func(string, io.Reader, io.WriteCloser, io.WriteCloser, bool, <-chan remotecommand.TerminalSize) error
  125. portForwardFunc func(string, int32, io.ReadWriteCloser) error
  126. }
  127. func (f *fakeRuntime) Exec(containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
  128. return f.execFunc(containerID, cmd, stdin, stdout, stderr, tty, resize)
  129. }
  130. func (f *fakeRuntime) Attach(containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
  131. return f.attachFunc(containerID, stdin, stdout, stderr, tty, resize)
  132. }
  133. func (f *fakeRuntime) PortForward(podSandboxID string, port int32, stream io.ReadWriteCloser) error {
  134. return f.portForwardFunc(podSandboxID, port, stream)
  135. }
  136. type testStreamingServer struct {
  137. streaming.Server
  138. fakeRuntime *fakeRuntime
  139. testHTTPServer *httptest.Server
  140. }
  141. func newTestStreamingServer(streamIdleTimeout time.Duration) (s *testStreamingServer, err error) {
  142. s = &testStreamingServer{}
  143. s.testHTTPServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  144. s.ServeHTTP(w, r)
  145. }))
  146. defer func() {
  147. if err != nil {
  148. s.testHTTPServer.Close()
  149. }
  150. }()
  151. testURL, err := url.Parse(s.testHTTPServer.URL)
  152. if err != nil {
  153. return nil, err
  154. }
  155. s.fakeRuntime = &fakeRuntime{}
  156. config := streaming.DefaultConfig
  157. config.BaseURL = testURL
  158. if streamIdleTimeout != 0 {
  159. config.StreamIdleTimeout = streamIdleTimeout
  160. }
  161. s.Server, err = streaming.NewServer(config, s.fakeRuntime)
  162. if err != nil {
  163. return nil, err
  164. }
  165. return s, nil
  166. }
  167. func (fk *fakeKubelet) GetExec(podFullName string, podUID types.UID, containerName string, cmd []string, streamOpts remotecommandserver.Options) (*url.URL, error) {
  168. if fk.getExecCheck != nil {
  169. fk.getExecCheck(podFullName, podUID, containerName, cmd, streamOpts)
  170. }
  171. // Always use testContainerID
  172. resp, err := fk.streamingRuntime.GetExec(&runtimeapi.ExecRequest{
  173. ContainerId: testContainerID,
  174. Cmd: cmd,
  175. Tty: streamOpts.TTY,
  176. Stdin: streamOpts.Stdin,
  177. Stdout: streamOpts.Stdout,
  178. Stderr: streamOpts.Stderr,
  179. })
  180. if err != nil {
  181. return nil, err
  182. }
  183. return url.Parse(resp.GetUrl())
  184. }
  185. func (fk *fakeKubelet) GetAttach(podFullName string, podUID types.UID, containerName string, streamOpts remotecommandserver.Options) (*url.URL, error) {
  186. if fk.getAttachCheck != nil {
  187. fk.getAttachCheck(podFullName, podUID, containerName, streamOpts)
  188. }
  189. // Always use testContainerID
  190. resp, err := fk.streamingRuntime.GetAttach(&runtimeapi.AttachRequest{
  191. ContainerId: testContainerID,
  192. Tty: streamOpts.TTY,
  193. Stdin: streamOpts.Stdin,
  194. Stdout: streamOpts.Stdout,
  195. Stderr: streamOpts.Stderr,
  196. })
  197. if err != nil {
  198. return nil, err
  199. }
  200. return url.Parse(resp.GetUrl())
  201. }
  202. func (fk *fakeKubelet) GetPortForward(podName, podNamespace string, podUID types.UID, portForwardOpts portforward.V4Options) (*url.URL, error) {
  203. if fk.getPortForwardCheck != nil {
  204. fk.getPortForwardCheck(podName, podNamespace, podUID, portForwardOpts)
  205. }
  206. // Always use testPodSandboxID
  207. resp, err := fk.streamingRuntime.GetPortForward(&runtimeapi.PortForwardRequest{
  208. PodSandboxId: testPodSandboxID,
  209. Port: portForwardOpts.Ports,
  210. })
  211. if err != nil {
  212. return nil, err
  213. }
  214. return url.Parse(resp.GetUrl())
  215. }
  216. // Unused functions
  217. func (*fakeKubelet) GetNode() (*v1.Node, error) { return nil, nil }
  218. func (*fakeKubelet) GetNodeConfig() cm.NodeConfig { return cm.NodeConfig{} }
  219. func (*fakeKubelet) GetPodCgroupRoot() string { return "" }
  220. func (*fakeKubelet) GetPodByCgroupfs(cgroupfs string) (*v1.Pod, bool) { return nil, false }
  221. func (fk *fakeKubelet) ListVolumesForPod(podUID types.UID) (map[string]volume.Volume, bool) {
  222. return map[string]volume.Volume{}, true
  223. }
  224. func (*fakeKubelet) RootFsStats() (*statsapi.FsStats, error) { return nil, nil }
  225. func (*fakeKubelet) ListPodStats() ([]statsapi.PodStats, error) { return nil, nil }
  226. func (*fakeKubelet) ListPodStatsAndUpdateCPUNanoCoreUsage() ([]statsapi.PodStats, error) {
  227. return nil, nil
  228. }
  229. func (*fakeKubelet) ListPodCPUAndMemoryStats() ([]statsapi.PodStats, error) { return nil, nil }
  230. func (*fakeKubelet) ImageFsStats() (*statsapi.FsStats, error) { return nil, nil }
  231. func (*fakeKubelet) RlimitStats() (*statsapi.RlimitStats, error) { return nil, nil }
  232. func (*fakeKubelet) GetCgroupStats(cgroupName string, updateStats bool) (*statsapi.ContainerStats, *statsapi.NetworkStats, error) {
  233. return nil, nil, nil
  234. }
  235. func (*fakeKubelet) GetCgroupCPUAndMemoryStats(cgroupName string, updateStats bool) (*statsapi.ContainerStats, error) {
  236. return nil, nil
  237. }
  238. type fakeAuth struct {
  239. authenticateFunc func(*http.Request) (*authenticator.Response, bool, error)
  240. attributesFunc func(user.Info, *http.Request) authorizer.Attributes
  241. authorizeFunc func(authorizer.Attributes) (authorized authorizer.Decision, reason string, err error)
  242. }
  243. func (f *fakeAuth) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
  244. return f.authenticateFunc(req)
  245. }
  246. func (f *fakeAuth) GetRequestAttributes(u user.Info, req *http.Request) authorizer.Attributes {
  247. return f.attributesFunc(u, req)
  248. }
  249. func (f *fakeAuth) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
  250. return f.authorizeFunc(a)
  251. }
  252. type serverTestFramework struct {
  253. serverUnderTest *Server
  254. fakeKubelet *fakeKubelet
  255. fakeAuth *fakeAuth
  256. testHTTPServer *httptest.Server
  257. criHandler *utiltesting.FakeHandler
  258. }
  259. func newServerTest() *serverTestFramework {
  260. return newServerTestWithDebug(true, false, nil)
  261. }
  262. func newServerTestWithDebug(enableDebugging, redirectContainerStreaming bool, streamingServer streaming.Server) *serverTestFramework {
  263. fw := &serverTestFramework{}
  264. fw.fakeKubelet = &fakeKubelet{
  265. hostnameFunc: func() string {
  266. return "127.0.0.1"
  267. },
  268. podByNameFunc: func(namespace, name string) (*v1.Pod, bool) {
  269. return &v1.Pod{
  270. ObjectMeta: metav1.ObjectMeta{
  271. Namespace: namespace,
  272. Name: name,
  273. UID: testUID,
  274. },
  275. }, true
  276. },
  277. plegHealth: true,
  278. streamingRuntime: streamingServer,
  279. }
  280. fw.fakeAuth = &fakeAuth{
  281. authenticateFunc: func(req *http.Request) (*authenticator.Response, bool, error) {
  282. return &authenticator.Response{User: &user.DefaultInfo{Name: "test"}}, true, nil
  283. },
  284. attributesFunc: func(u user.Info, req *http.Request) authorizer.Attributes {
  285. return &authorizer.AttributesRecord{User: u}
  286. },
  287. authorizeFunc: func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
  288. return authorizer.DecisionAllow, "", nil
  289. },
  290. }
  291. fw.criHandler = &utiltesting.FakeHandler{
  292. StatusCode: http.StatusOK,
  293. }
  294. server := NewServer(
  295. fw.fakeKubelet,
  296. stats.NewResourceAnalyzer(fw.fakeKubelet, time.Minute),
  297. fw.fakeAuth,
  298. true,
  299. enableDebugging,
  300. false,
  301. redirectContainerStreaming,
  302. fw.criHandler)
  303. fw.serverUnderTest = &server
  304. fw.testHTTPServer = httptest.NewServer(fw.serverUnderTest)
  305. return fw
  306. }
  307. // A helper function to return the correct pod name.
  308. func getPodName(name, namespace string) string {
  309. if namespace == "" {
  310. namespace = metav1.NamespaceDefault
  311. }
  312. return name + "_" + namespace
  313. }
  314. func TestContainerInfo(t *testing.T) {
  315. fw := newServerTest()
  316. defer fw.testHTTPServer.Close()
  317. expectedInfo := &cadvisorapi.ContainerInfo{}
  318. podID := "somepod"
  319. expectedPodID := getPodName(podID, "")
  320. expectedContainerName := "goodcontainer"
  321. fw.fakeKubelet.containerInfoFunc = func(podID string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) {
  322. if podID != expectedPodID || containerName != expectedContainerName {
  323. return nil, fmt.Errorf("bad podID or containerName: podID=%v; containerName=%v", podID, containerName)
  324. }
  325. return expectedInfo, nil
  326. }
  327. resp, err := http.Get(fw.testHTTPServer.URL + fmt.Sprintf("/stats/%v/%v", podID, expectedContainerName))
  328. if err != nil {
  329. t.Fatalf("Got error GETing: %v", err)
  330. }
  331. defer resp.Body.Close()
  332. var receivedInfo cadvisorapi.ContainerInfo
  333. err = json.NewDecoder(resp.Body).Decode(&receivedInfo)
  334. if err != nil {
  335. t.Fatalf("received invalid json data: %v", err)
  336. }
  337. if !receivedInfo.Eq(expectedInfo) {
  338. t.Errorf("received wrong data: %#v", receivedInfo)
  339. }
  340. }
  341. func TestContainerInfoWithUidNamespace(t *testing.T) {
  342. fw := newServerTest()
  343. defer fw.testHTTPServer.Close()
  344. expectedInfo := &cadvisorapi.ContainerInfo{}
  345. podID := "somepod"
  346. expectedNamespace := "custom"
  347. expectedPodID := getPodName(podID, expectedNamespace)
  348. expectedContainerName := "goodcontainer"
  349. fw.fakeKubelet.containerInfoFunc = func(podID string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) {
  350. if podID != expectedPodID || string(uid) != testUID || containerName != expectedContainerName {
  351. return nil, fmt.Errorf("bad podID or uid or containerName: podID=%v; uid=%v; containerName=%v", podID, uid, containerName)
  352. }
  353. return expectedInfo, nil
  354. }
  355. resp, err := http.Get(fw.testHTTPServer.URL + fmt.Sprintf("/stats/%v/%v/%v/%v", expectedNamespace, podID, testUID, expectedContainerName))
  356. if err != nil {
  357. t.Fatalf("Got error GETing: %v", err)
  358. }
  359. defer resp.Body.Close()
  360. var receivedInfo cadvisorapi.ContainerInfo
  361. err = json.NewDecoder(resp.Body).Decode(&receivedInfo)
  362. if err != nil {
  363. t.Fatalf("received invalid json data: %v", err)
  364. }
  365. if !receivedInfo.Eq(expectedInfo) {
  366. t.Errorf("received wrong data: %#v", receivedInfo)
  367. }
  368. }
  369. func TestContainerNotFound(t *testing.T) {
  370. fw := newServerTest()
  371. defer fw.testHTTPServer.Close()
  372. podID := "somepod"
  373. expectedNamespace := "custom"
  374. expectedContainerName := "slowstartcontainer"
  375. fw.fakeKubelet.containerInfoFunc = func(podID string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) {
  376. return nil, kubecontainer.ErrContainerNotFound
  377. }
  378. resp, err := http.Get(fw.testHTTPServer.URL + fmt.Sprintf("/stats/%v/%v/%v/%v", expectedNamespace, podID, testUID, expectedContainerName))
  379. if err != nil {
  380. t.Fatalf("Got error GETing: %v", err)
  381. }
  382. if resp.StatusCode != http.StatusNotFound {
  383. t.Fatalf("Received status %d expecting %d", resp.StatusCode, http.StatusNotFound)
  384. }
  385. defer resp.Body.Close()
  386. }
  387. func TestRootInfo(t *testing.T) {
  388. fw := newServerTest()
  389. defer fw.testHTTPServer.Close()
  390. expectedInfo := &cadvisorapi.ContainerInfo{
  391. ContainerReference: cadvisorapi.ContainerReference{
  392. Name: "/",
  393. },
  394. }
  395. fw.fakeKubelet.rawInfoFunc = func(req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) {
  396. return map[string]*cadvisorapi.ContainerInfo{
  397. expectedInfo.Name: expectedInfo,
  398. }, nil
  399. }
  400. resp, err := http.Get(fw.testHTTPServer.URL + "/stats")
  401. if err != nil {
  402. t.Fatalf("Got error GETing: %v", err)
  403. }
  404. defer resp.Body.Close()
  405. var receivedInfo cadvisorapi.ContainerInfo
  406. err = json.NewDecoder(resp.Body).Decode(&receivedInfo)
  407. if err != nil {
  408. t.Fatalf("received invalid json data: %v", err)
  409. }
  410. if !receivedInfo.Eq(expectedInfo) {
  411. t.Errorf("received wrong data: %#v, expected %#v", receivedInfo, expectedInfo)
  412. }
  413. }
  414. func TestSubcontainerContainerInfo(t *testing.T) {
  415. fw := newServerTest()
  416. defer fw.testHTTPServer.Close()
  417. const kubeletContainer = "/kubelet"
  418. const kubeletSubContainer = "/kubelet/sub"
  419. expectedInfo := map[string]*cadvisorapi.ContainerInfo{
  420. kubeletContainer: {
  421. ContainerReference: cadvisorapi.ContainerReference{
  422. Name: kubeletContainer,
  423. },
  424. },
  425. kubeletSubContainer: {
  426. ContainerReference: cadvisorapi.ContainerReference{
  427. Name: kubeletSubContainer,
  428. },
  429. },
  430. }
  431. fw.fakeKubelet.rawInfoFunc = func(req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) {
  432. return expectedInfo, nil
  433. }
  434. request := fmt.Sprintf("{\"containerName\":%q, \"subcontainers\": true}", kubeletContainer)
  435. resp, err := http.Post(fw.testHTTPServer.URL+"/stats/container", "application/json", bytes.NewBuffer([]byte(request)))
  436. if err != nil {
  437. t.Fatalf("Got error GETing: %v", err)
  438. }
  439. defer resp.Body.Close()
  440. var receivedInfo map[string]*cadvisorapi.ContainerInfo
  441. err = json.NewDecoder(resp.Body).Decode(&receivedInfo)
  442. if err != nil {
  443. t.Fatalf("Received invalid json data: %v", err)
  444. }
  445. if len(receivedInfo) != len(expectedInfo) {
  446. t.Errorf("Received wrong data: %#v, expected %#v", receivedInfo, expectedInfo)
  447. }
  448. for _, containerName := range []string{kubeletContainer, kubeletSubContainer} {
  449. if _, ok := receivedInfo[containerName]; !ok {
  450. t.Errorf("Expected container %q to be present in result: %#v", containerName, receivedInfo)
  451. }
  452. if !receivedInfo[containerName].Eq(expectedInfo[containerName]) {
  453. t.Errorf("Invalid result for %q: Expected %#v, received %#v", containerName, expectedInfo[containerName], receivedInfo[containerName])
  454. }
  455. }
  456. }
  457. func TestMachineInfo(t *testing.T) {
  458. fw := newServerTest()
  459. defer fw.testHTTPServer.Close()
  460. expectedInfo := &cadvisorapi.MachineInfo{
  461. NumCores: 4,
  462. MemoryCapacity: 1024,
  463. }
  464. fw.fakeKubelet.machineInfoFunc = func() (*cadvisorapi.MachineInfo, error) {
  465. return expectedInfo, nil
  466. }
  467. resp, err := http.Get(fw.testHTTPServer.URL + "/spec")
  468. if err != nil {
  469. t.Fatalf("Got error GETing: %v", err)
  470. }
  471. defer resp.Body.Close()
  472. var receivedInfo cadvisorapi.MachineInfo
  473. err = json.NewDecoder(resp.Body).Decode(&receivedInfo)
  474. if err != nil {
  475. t.Fatalf("received invalid json data: %v", err)
  476. }
  477. if !reflect.DeepEqual(&receivedInfo, expectedInfo) {
  478. t.Errorf("received wrong data: %#v", receivedInfo)
  479. }
  480. }
  481. func TestServeLogs(t *testing.T) {
  482. fw := newServerTest()
  483. defer fw.testHTTPServer.Close()
  484. content := string(`<pre><a href="kubelet.log">kubelet.log</a><a href="google.log">google.log</a></pre>`)
  485. fw.fakeKubelet.logFunc = func(w http.ResponseWriter, req *http.Request) {
  486. w.WriteHeader(http.StatusOK)
  487. w.Header().Add("Content-Type", "text/html")
  488. w.Write([]byte(content))
  489. }
  490. resp, err := http.Get(fw.testHTTPServer.URL + "/logs/")
  491. if err != nil {
  492. t.Fatalf("Got error GETing: %v", err)
  493. }
  494. defer resp.Body.Close()
  495. body, err := httputil.DumpResponse(resp, true)
  496. if err != nil {
  497. // copying the response body did not work
  498. t.Errorf("Cannot copy resp: %#v", err)
  499. }
  500. result := string(body)
  501. if !strings.Contains(result, "kubelet.log") || !strings.Contains(result, "google.log") {
  502. t.Errorf("Received wrong data: %s", result)
  503. }
  504. }
  505. func TestServeRunInContainer(t *testing.T) {
  506. fw := newServerTest()
  507. defer fw.testHTTPServer.Close()
  508. output := "foo bar"
  509. podNamespace := "other"
  510. podName := "foo"
  511. expectedPodName := getPodName(podName, podNamespace)
  512. expectedContainerName := "baz"
  513. expectedCommand := "ls -a"
  514. fw.fakeKubelet.runFunc = func(podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error) {
  515. if podFullName != expectedPodName {
  516. t.Errorf("expected %s, got %s", expectedPodName, podFullName)
  517. }
  518. if containerName != expectedContainerName {
  519. t.Errorf("expected %s, got %s", expectedContainerName, containerName)
  520. }
  521. if strings.Join(cmd, " ") != expectedCommand {
  522. t.Errorf("expected: %s, got %v", expectedCommand, cmd)
  523. }
  524. return []byte(output), nil
  525. }
  526. resp, err := http.Post(fw.testHTTPServer.URL+"/run/"+podNamespace+"/"+podName+"/"+expectedContainerName+"?cmd=ls%20-a", "", nil)
  527. if err != nil {
  528. t.Fatalf("Got error POSTing: %v", err)
  529. }
  530. defer resp.Body.Close()
  531. body, err := ioutil.ReadAll(resp.Body)
  532. if err != nil {
  533. // copying the response body did not work
  534. t.Errorf("Cannot copy resp: %#v", err)
  535. }
  536. result := string(body)
  537. if result != output {
  538. t.Errorf("expected %s, got %s", output, result)
  539. }
  540. }
  541. func TestServeRunInContainerWithUID(t *testing.T) {
  542. fw := newServerTest()
  543. defer fw.testHTTPServer.Close()
  544. output := "foo bar"
  545. podNamespace := "other"
  546. podName := "foo"
  547. expectedPodName := getPodName(podName, podNamespace)
  548. expectedContainerName := "baz"
  549. expectedCommand := "ls -a"
  550. fw.fakeKubelet.runFunc = func(podFullName string, uid types.UID, containerName string, cmd []string) ([]byte, error) {
  551. if podFullName != expectedPodName {
  552. t.Errorf("expected %s, got %s", expectedPodName, podFullName)
  553. }
  554. if string(uid) != testUID {
  555. t.Errorf("expected %s, got %s", testUID, uid)
  556. }
  557. if containerName != expectedContainerName {
  558. t.Errorf("expected %s, got %s", expectedContainerName, containerName)
  559. }
  560. if strings.Join(cmd, " ") != expectedCommand {
  561. t.Errorf("expected: %s, got %v", expectedCommand, cmd)
  562. }
  563. return []byte(output), nil
  564. }
  565. resp, err := http.Post(fw.testHTTPServer.URL+"/run/"+podNamespace+"/"+podName+"/"+testUID+"/"+expectedContainerName+"?cmd=ls%20-a", "", nil)
  566. if err != nil {
  567. t.Fatalf("Got error POSTing: %v", err)
  568. }
  569. defer resp.Body.Close()
  570. body, err := ioutil.ReadAll(resp.Body)
  571. if err != nil {
  572. // copying the response body did not work
  573. t.Errorf("Cannot copy resp: %#v", err)
  574. }
  575. result := string(body)
  576. if result != output {
  577. t.Errorf("expected %s, got %s", output, result)
  578. }
  579. }
  580. func TestHealthCheck(t *testing.T) {
  581. fw := newServerTest()
  582. defer fw.testHTTPServer.Close()
  583. fw.fakeKubelet.hostnameFunc = func() string {
  584. return "127.0.0.1"
  585. }
  586. // Test with correct hostname, Docker version
  587. assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz")
  588. // Test with incorrect hostname
  589. fw.fakeKubelet.hostnameFunc = func() string {
  590. return "fake"
  591. }
  592. assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz")
  593. }
  594. func assertHealthFails(t *testing.T, httpURL string, expectedErrorCode int) {
  595. resp, err := http.Get(httpURL)
  596. if err != nil {
  597. t.Fatalf("Got error GETing: %v", err)
  598. }
  599. defer resp.Body.Close()
  600. if resp.StatusCode != expectedErrorCode {
  601. t.Errorf("expected status code %d, got %d", expectedErrorCode, resp.StatusCode)
  602. }
  603. }
  604. // Ensure all registered handlers & services have an associated testcase.
  605. func TestAuthzCoverage(t *testing.T) {
  606. fw := newServerTest()
  607. defer fw.testHTTPServer.Close()
  608. // method:path -> has coverage
  609. expectedCases := map[string]bool{}
  610. // Test all the non-web-service handlers
  611. for _, path := range fw.serverUnderTest.restfulCont.RegisteredHandlePaths() {
  612. expectedCases["GET:"+path] = false
  613. expectedCases["POST:"+path] = false
  614. }
  615. // Test all the generated web-service paths
  616. for _, ws := range fw.serverUnderTest.restfulCont.RegisteredWebServices() {
  617. for _, r := range ws.Routes() {
  618. expectedCases[r.Method+":"+r.Path] = false
  619. }
  620. }
  621. // This is a sanity check that the Handle->HandleWithFilter() delegation is working
  622. // Ideally, these would move to registered web services and this list would get shorter
  623. expectedPaths := []string{"/healthz", "/metrics", "/metrics/cadvisor"}
  624. for _, expectedPath := range expectedPaths {
  625. if _, expected := expectedCases["GET:"+expectedPath]; !expected {
  626. t.Errorf("Expected registered handle path %s was missing", expectedPath)
  627. }
  628. }
  629. for _, tc := range AuthzTestCases() {
  630. expectedCases[tc.Method+":"+tc.Path] = true
  631. }
  632. for tc, found := range expectedCases {
  633. if !found {
  634. t.Errorf("Missing authz test case for %s", tc)
  635. }
  636. }
  637. }
  638. func TestAuthFilters(t *testing.T) {
  639. fw := newServerTest()
  640. defer fw.testHTTPServer.Close()
  641. attributesGetter := NewNodeAuthorizerAttributesGetter(authzTestNodeName)
  642. for _, tc := range AuthzTestCases() {
  643. t.Run(tc.Method+":"+tc.Path, func(t *testing.T) {
  644. var (
  645. expectedUser = AuthzTestUser()
  646. calledAuthenticate = false
  647. calledAuthorize = false
  648. calledAttributes = false
  649. )
  650. fw.fakeAuth.authenticateFunc = func(req *http.Request) (*authenticator.Response, bool, error) {
  651. calledAuthenticate = true
  652. return &authenticator.Response{User: expectedUser}, true, nil
  653. }
  654. fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes {
  655. calledAttributes = true
  656. require.Equal(t, expectedUser, u)
  657. return attributesGetter.GetRequestAttributes(u, req)
  658. }
  659. fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
  660. calledAuthorize = true
  661. tc.AssertAttributes(t, a)
  662. return authorizer.DecisionNoOpinion, "", nil
  663. }
  664. req, err := http.NewRequest(tc.Method, fw.testHTTPServer.URL+tc.Path, nil)
  665. require.NoError(t, err)
  666. resp, err := http.DefaultClient.Do(req)
  667. require.NoError(t, err)
  668. defer resp.Body.Close()
  669. assert.Equal(t, http.StatusForbidden, resp.StatusCode)
  670. assert.True(t, calledAuthenticate, "Authenticate was not called")
  671. assert.True(t, calledAttributes, "Attributes were not called")
  672. assert.True(t, calledAuthorize, "Authorize was not called")
  673. })
  674. }
  675. }
  676. func TestAuthenticationError(t *testing.T) {
  677. var (
  678. expectedUser = &user.DefaultInfo{Name: "test"}
  679. expectedAttributes = &authorizer.AttributesRecord{User: expectedUser}
  680. calledAuthenticate = false
  681. calledAuthorize = false
  682. calledAttributes = false
  683. )
  684. fw := newServerTest()
  685. defer fw.testHTTPServer.Close()
  686. fw.fakeAuth.authenticateFunc = func(req *http.Request) (*authenticator.Response, bool, error) {
  687. calledAuthenticate = true
  688. return &authenticator.Response{User: expectedUser}, true, nil
  689. }
  690. fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes {
  691. calledAttributes = true
  692. return expectedAttributes
  693. }
  694. fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
  695. calledAuthorize = true
  696. return authorizer.DecisionNoOpinion, "", errors.New("Failed")
  697. }
  698. assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusInternalServerError)
  699. if !calledAuthenticate {
  700. t.Fatalf("Authenticate was not called")
  701. }
  702. if !calledAttributes {
  703. t.Fatalf("Attributes was not called")
  704. }
  705. if !calledAuthorize {
  706. t.Fatalf("Authorize was not called")
  707. }
  708. }
  709. func TestAuthenticationFailure(t *testing.T) {
  710. var (
  711. expectedUser = &user.DefaultInfo{Name: "test"}
  712. expectedAttributes = &authorizer.AttributesRecord{User: expectedUser}
  713. calledAuthenticate = false
  714. calledAuthorize = false
  715. calledAttributes = false
  716. )
  717. fw := newServerTest()
  718. defer fw.testHTTPServer.Close()
  719. fw.fakeAuth.authenticateFunc = func(req *http.Request) (*authenticator.Response, bool, error) {
  720. calledAuthenticate = true
  721. return nil, false, nil
  722. }
  723. fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes {
  724. calledAttributes = true
  725. return expectedAttributes
  726. }
  727. fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
  728. calledAuthorize = true
  729. return authorizer.DecisionNoOpinion, "", nil
  730. }
  731. assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusUnauthorized)
  732. if !calledAuthenticate {
  733. t.Fatalf("Authenticate was not called")
  734. }
  735. if calledAttributes {
  736. t.Fatalf("Attributes was called unexpectedly")
  737. }
  738. if calledAuthorize {
  739. t.Fatalf("Authorize was called unexpectedly")
  740. }
  741. }
  742. func TestAuthorizationSuccess(t *testing.T) {
  743. var (
  744. expectedUser = &user.DefaultInfo{Name: "test"}
  745. expectedAttributes = &authorizer.AttributesRecord{User: expectedUser}
  746. calledAuthenticate = false
  747. calledAuthorize = false
  748. calledAttributes = false
  749. )
  750. fw := newServerTest()
  751. defer fw.testHTTPServer.Close()
  752. fw.fakeAuth.authenticateFunc = func(req *http.Request) (*authenticator.Response, bool, error) {
  753. calledAuthenticate = true
  754. return &authenticator.Response{User: expectedUser}, true, nil
  755. }
  756. fw.fakeAuth.attributesFunc = func(u user.Info, req *http.Request) authorizer.Attributes {
  757. calledAttributes = true
  758. return expectedAttributes
  759. }
  760. fw.fakeAuth.authorizeFunc = func(a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
  761. calledAuthorize = true
  762. return authorizer.DecisionAllow, "", nil
  763. }
  764. assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz")
  765. if !calledAuthenticate {
  766. t.Fatalf("Authenticate was not called")
  767. }
  768. if !calledAttributes {
  769. t.Fatalf("Attributes were not called")
  770. }
  771. if !calledAuthorize {
  772. t.Fatalf("Authorize was not called")
  773. }
  774. }
  775. func TestSyncLoopCheck(t *testing.T) {
  776. fw := newServerTest()
  777. defer fw.testHTTPServer.Close()
  778. fw.fakeKubelet.hostnameFunc = func() string {
  779. return "127.0.0.1"
  780. }
  781. fw.fakeKubelet.resyncInterval = time.Minute
  782. fw.fakeKubelet.loopEntryTime = time.Now()
  783. // Test with correct hostname, Docker version
  784. assertHealthIsOk(t, fw.testHTTPServer.URL+"/healthz")
  785. fw.fakeKubelet.loopEntryTime = time.Now().Add(time.Minute * -10)
  786. assertHealthFails(t, fw.testHTTPServer.URL+"/healthz", http.StatusInternalServerError)
  787. }
  788. // returns http response status code from the HTTP GET
  789. func assertHealthIsOk(t *testing.T, httpURL string) {
  790. resp, err := http.Get(httpURL)
  791. if err != nil {
  792. t.Fatalf("Got error GETing: %v", err)
  793. }
  794. defer resp.Body.Close()
  795. if resp.StatusCode != http.StatusOK {
  796. t.Errorf("expected status code %d, got %d", http.StatusOK, resp.StatusCode)
  797. }
  798. body, readErr := ioutil.ReadAll(resp.Body)
  799. if readErr != nil {
  800. // copying the response body did not work
  801. t.Fatalf("Cannot copy resp: %#v", readErr)
  802. }
  803. result := string(body)
  804. if !strings.Contains(result, "ok") {
  805. t.Errorf("expected body contains ok, got %s", result)
  806. }
  807. }
  808. func setPodByNameFunc(fw *serverTestFramework, namespace, pod, container string) {
  809. fw.fakeKubelet.podByNameFunc = func(namespace, name string) (*v1.Pod, bool) {
  810. return &v1.Pod{
  811. ObjectMeta: metav1.ObjectMeta{
  812. Namespace: namespace,
  813. Name: pod,
  814. },
  815. Spec: v1.PodSpec{
  816. Containers: []v1.Container{
  817. {
  818. Name: container,
  819. },
  820. },
  821. },
  822. }, true
  823. }
  824. }
  825. func setGetContainerLogsFunc(fw *serverTestFramework, t *testing.T, expectedPodName, expectedContainerName string, expectedLogOptions *v1.PodLogOptions, output string) {
  826. fw.fakeKubelet.containerLogsFunc = func(_ context.Context, podFullName, containerName string, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) error {
  827. if podFullName != expectedPodName {
  828. t.Errorf("expected %s, got %s", expectedPodName, podFullName)
  829. }
  830. if containerName != expectedContainerName {
  831. t.Errorf("expected %s, got %s", expectedContainerName, containerName)
  832. }
  833. if !reflect.DeepEqual(expectedLogOptions, logOptions) {
  834. t.Errorf("expected %#v, got %#v", expectedLogOptions, logOptions)
  835. }
  836. io.WriteString(stdout, output)
  837. return nil
  838. }
  839. }
  840. func TestContainerLogs(t *testing.T) {
  841. fw := newServerTest()
  842. defer fw.testHTTPServer.Close()
  843. tests := map[string]struct {
  844. query string
  845. podLogOption *v1.PodLogOptions
  846. }{
  847. "without tail": {"", &v1.PodLogOptions{}},
  848. "with tail": {"?tailLines=5", &v1.PodLogOptions{TailLines: pointer.Int64Ptr(5)}},
  849. "with legacy tail": {"?tail=5", &v1.PodLogOptions{TailLines: pointer.Int64Ptr(5)}},
  850. "with tail all": {"?tail=all", &v1.PodLogOptions{}},
  851. "with follow": {"?follow=1", &v1.PodLogOptions{Follow: true}},
  852. }
  853. for desc, test := range tests {
  854. t.Run(desc, func(t *testing.T) {
  855. output := "foo bar"
  856. podNamespace := "other"
  857. podName := "foo"
  858. expectedPodName := getPodName(podName, podNamespace)
  859. expectedContainerName := "baz"
  860. setPodByNameFunc(fw, podNamespace, podName, expectedContainerName)
  861. setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, test.podLogOption, output)
  862. resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + test.query)
  863. if err != nil {
  864. t.Errorf("Got error GETing: %v", err)
  865. }
  866. defer resp.Body.Close()
  867. body, err := ioutil.ReadAll(resp.Body)
  868. if err != nil {
  869. t.Errorf("Error reading container logs: %v", err)
  870. }
  871. result := string(body)
  872. if result != output {
  873. t.Errorf("Expected: '%v', got: '%v'", output, result)
  874. }
  875. })
  876. }
  877. }
  878. func TestContainerLogsWithInvalidTail(t *testing.T) {
  879. fw := newServerTest()
  880. defer fw.testHTTPServer.Close()
  881. output := "foo bar"
  882. podNamespace := "other"
  883. podName := "foo"
  884. expectedPodName := getPodName(podName, podNamespace)
  885. expectedContainerName := "baz"
  886. setPodByNameFunc(fw, podNamespace, podName, expectedContainerName)
  887. setGetContainerLogsFunc(fw, t, expectedPodName, expectedContainerName, &v1.PodLogOptions{}, output)
  888. resp, err := http.Get(fw.testHTTPServer.URL + "/containerLogs/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?tail=-1")
  889. if err != nil {
  890. t.Errorf("Got error GETing: %v", err)
  891. }
  892. defer resp.Body.Close()
  893. if resp.StatusCode != http.StatusUnprocessableEntity {
  894. t.Errorf("Unexpected non-error reading container logs: %#v", resp)
  895. }
  896. }
  897. func makeReq(t *testing.T, method, url, clientProtocol string) *http.Request {
  898. req, err := http.NewRequest(method, url, nil)
  899. if err != nil {
  900. t.Fatalf("error creating request: %v", err)
  901. }
  902. req.Header.Set("Content-Type", "")
  903. req.Header.Add("X-Stream-Protocol-Version", clientProtocol)
  904. return req
  905. }
  906. func TestServeExecInContainerIdleTimeout(t *testing.T) {
  907. ss, err := newTestStreamingServer(100 * time.Millisecond)
  908. require.NoError(t, err)
  909. defer ss.testHTTPServer.Close()
  910. fw := newServerTestWithDebug(true, false, ss)
  911. defer fw.testHTTPServer.Close()
  912. podNamespace := "other"
  913. podName := "foo"
  914. expectedContainerName := "baz"
  915. url := fw.testHTTPServer.URL + "/exec/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?c=ls&c=-a&" + api.ExecStdinParam + "=1"
  916. upgradeRoundTripper := spdy.NewSpdyRoundTripper(nil, true, true)
  917. c := &http.Client{Transport: upgradeRoundTripper}
  918. resp, err := c.Do(makeReq(t, "POST", url, "v4.channel.k8s.io"))
  919. if err != nil {
  920. t.Fatalf("Got error POSTing: %v", err)
  921. }
  922. defer resp.Body.Close()
  923. upgradeRoundTripper.Dialer = &net.Dialer{
  924. Deadline: time.Now().Add(60 * time.Second),
  925. Timeout: 60 * time.Second,
  926. }
  927. conn, err := upgradeRoundTripper.NewConnection(resp)
  928. if err != nil {
  929. t.Fatalf("Unexpected error creating streaming connection: %s", err)
  930. }
  931. if conn == nil {
  932. t.Fatal("Unexpected nil connection")
  933. }
  934. <-conn.CloseChan()
  935. }
  936. func testExecAttach(t *testing.T, verb string) {
  937. tests := map[string]struct {
  938. stdin bool
  939. stdout bool
  940. stderr bool
  941. tty bool
  942. responseStatusCode int
  943. uid bool
  944. redirect bool
  945. }{
  946. "no input or output": {responseStatusCode: http.StatusBadRequest},
  947. "stdin": {stdin: true, responseStatusCode: http.StatusSwitchingProtocols},
  948. "stdout": {stdout: true, responseStatusCode: http.StatusSwitchingProtocols},
  949. "stderr": {stderr: true, responseStatusCode: http.StatusSwitchingProtocols},
  950. "stdout and stderr": {stdout: true, stderr: true, responseStatusCode: http.StatusSwitchingProtocols},
  951. "stdin stdout and stderr": {stdin: true, stdout: true, stderr: true, responseStatusCode: http.StatusSwitchingProtocols},
  952. "stdin stdout stderr with uid": {stdin: true, stdout: true, stderr: true, responseStatusCode: http.StatusSwitchingProtocols, uid: true},
  953. "stdout with redirect": {stdout: true, responseStatusCode: http.StatusFound, redirect: true},
  954. }
  955. for desc := range tests {
  956. test := tests[desc]
  957. t.Run(desc, func(t *testing.T) {
  958. ss, err := newTestStreamingServer(0)
  959. require.NoError(t, err)
  960. defer ss.testHTTPServer.Close()
  961. fw := newServerTestWithDebug(true, test.redirect, ss)
  962. defer fw.testHTTPServer.Close()
  963. fmt.Println(desc)
  964. podNamespace := "other"
  965. podName := "foo"
  966. expectedPodName := getPodName(podName, podNamespace)
  967. expectedContainerName := "baz"
  968. expectedCommand := "ls -a"
  969. expectedStdin := "stdin"
  970. expectedStdout := "stdout"
  971. expectedStderr := "stderr"
  972. done := make(chan struct{})
  973. clientStdoutReadDone := make(chan struct{})
  974. clientStderrReadDone := make(chan struct{})
  975. execInvoked := false
  976. attachInvoked := false
  977. checkStream := func(podFullName string, uid types.UID, containerName string, streamOpts remotecommandserver.Options) {
  978. assert.Equal(t, expectedPodName, podFullName, "podFullName")
  979. if test.uid {
  980. assert.Equal(t, testUID, string(uid), "uid")
  981. }
  982. assert.Equal(t, expectedContainerName, containerName, "containerName")
  983. assert.Equal(t, test.stdin, streamOpts.Stdin, "stdin")
  984. assert.Equal(t, test.stdout, streamOpts.Stdout, "stdout")
  985. assert.Equal(t, test.tty, streamOpts.TTY, "tty")
  986. assert.Equal(t, !test.tty && test.stderr, streamOpts.Stderr, "stderr")
  987. }
  988. fw.fakeKubelet.getExecCheck = func(podFullName string, uid types.UID, containerName string, cmd []string, streamOpts remotecommandserver.Options) {
  989. execInvoked = true
  990. assert.Equal(t, expectedCommand, strings.Join(cmd, " "), "cmd")
  991. checkStream(podFullName, uid, containerName, streamOpts)
  992. }
  993. fw.fakeKubelet.getAttachCheck = func(podFullName string, uid types.UID, containerName string, streamOpts remotecommandserver.Options) {
  994. attachInvoked = true
  995. checkStream(podFullName, uid, containerName, streamOpts)
  996. }
  997. testStream := func(containerID string, in io.Reader, out, stderr io.WriteCloser, tty bool, done chan struct{}) error {
  998. close(done)
  999. assert.Equal(t, testContainerID, containerID, "containerID")
  1000. assert.Equal(t, test.tty, tty, "tty")
  1001. require.Equal(t, test.stdin, in != nil, "in")
  1002. require.Equal(t, test.stdout, out != nil, "out")
  1003. require.Equal(t, !test.tty && test.stderr, stderr != nil, "err")
  1004. if test.stdin {
  1005. b := make([]byte, 10)
  1006. n, err := in.Read(b)
  1007. assert.NoError(t, err, "reading from stdin")
  1008. assert.Equal(t, expectedStdin, string(b[0:n]), "content from stdin")
  1009. }
  1010. if test.stdout {
  1011. _, err := out.Write([]byte(expectedStdout))
  1012. assert.NoError(t, err, "writing to stdout")
  1013. out.Close()
  1014. <-clientStdoutReadDone
  1015. }
  1016. if !test.tty && test.stderr {
  1017. _, err := stderr.Write([]byte(expectedStderr))
  1018. assert.NoError(t, err, "writing to stderr")
  1019. stderr.Close()
  1020. <-clientStderrReadDone
  1021. }
  1022. return nil
  1023. }
  1024. ss.fakeRuntime.execFunc = func(containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
  1025. assert.Equal(t, expectedCommand, strings.Join(cmd, " "), "cmd")
  1026. return testStream(containerID, stdin, stdout, stderr, tty, done)
  1027. }
  1028. ss.fakeRuntime.attachFunc = func(containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
  1029. return testStream(containerID, stdin, stdout, stderr, tty, done)
  1030. }
  1031. var url string
  1032. if test.uid {
  1033. url = fw.testHTTPServer.URL + "/" + verb + "/" + podNamespace + "/" + podName + "/" + testUID + "/" + expectedContainerName + "?ignore=1"
  1034. } else {
  1035. url = fw.testHTTPServer.URL + "/" + verb + "/" + podNamespace + "/" + podName + "/" + expectedContainerName + "?ignore=1"
  1036. }
  1037. if verb == "exec" {
  1038. url += "&command=ls&command=-a"
  1039. }
  1040. if test.stdin {
  1041. url += "&" + api.ExecStdinParam + "=1"
  1042. }
  1043. if test.stdout {
  1044. url += "&" + api.ExecStdoutParam + "=1"
  1045. }
  1046. if test.stderr && !test.tty {
  1047. url += "&" + api.ExecStderrParam + "=1"
  1048. }
  1049. if test.tty {
  1050. url += "&" + api.ExecTTYParam + "=1"
  1051. }
  1052. var (
  1053. resp *http.Response
  1054. upgradeRoundTripper httpstream.UpgradeRoundTripper
  1055. c *http.Client
  1056. )
  1057. if test.redirect {
  1058. c = &http.Client{}
  1059. // Don't follow redirects, since we want to inspect the redirect response.
  1060. c.CheckRedirect = func(*http.Request, []*http.Request) error {
  1061. return http.ErrUseLastResponse
  1062. }
  1063. } else {
  1064. upgradeRoundTripper = spdy.NewRoundTripper(nil, true, true)
  1065. c = &http.Client{Transport: upgradeRoundTripper}
  1066. }
  1067. resp, err = c.Do(makeReq(t, "POST", url, "v4.channel.k8s.io"))
  1068. require.NoError(t, err, "POSTing")
  1069. defer resp.Body.Close()
  1070. _, err = ioutil.ReadAll(resp.Body)
  1071. assert.NoError(t, err, "reading response body")
  1072. require.Equal(t, test.responseStatusCode, resp.StatusCode, "response status")
  1073. if test.responseStatusCode != http.StatusSwitchingProtocols {
  1074. return
  1075. }
  1076. conn, err := upgradeRoundTripper.NewConnection(resp)
  1077. require.NoError(t, err, "creating streaming connection")
  1078. defer conn.Close()
  1079. h := http.Header{}
  1080. h.Set(api.StreamType, api.StreamTypeError)
  1081. _, err = conn.CreateStream(h)
  1082. require.NoError(t, err, "creating error stream")
  1083. if test.stdin {
  1084. h.Set(api.StreamType, api.StreamTypeStdin)
  1085. stream, err := conn.CreateStream(h)
  1086. require.NoError(t, err, "creating stdin stream")
  1087. _, err = stream.Write([]byte(expectedStdin))
  1088. require.NoError(t, err, "writing to stdin stream")
  1089. }
  1090. var stdoutStream httpstream.Stream
  1091. if test.stdout {
  1092. h.Set(api.StreamType, api.StreamTypeStdout)
  1093. stdoutStream, err = conn.CreateStream(h)
  1094. require.NoError(t, err, "creating stdout stream")
  1095. }
  1096. var stderrStream httpstream.Stream
  1097. if test.stderr && !test.tty {
  1098. h.Set(api.StreamType, api.StreamTypeStderr)
  1099. stderrStream, err = conn.CreateStream(h)
  1100. require.NoError(t, err, "creating stderr stream")
  1101. }
  1102. if test.stdout {
  1103. output := make([]byte, 10)
  1104. n, err := stdoutStream.Read(output)
  1105. close(clientStdoutReadDone)
  1106. assert.NoError(t, err, "reading from stdout stream")
  1107. assert.Equal(t, expectedStdout, string(output[0:n]), "stdout")
  1108. }
  1109. if test.stderr && !test.tty {
  1110. output := make([]byte, 10)
  1111. n, err := stderrStream.Read(output)
  1112. close(clientStderrReadDone)
  1113. assert.NoError(t, err, "reading from stderr stream")
  1114. assert.Equal(t, expectedStderr, string(output[0:n]), "stderr")
  1115. }
  1116. // wait for the server to finish before checking if the attach/exec funcs were invoked
  1117. <-done
  1118. if verb == "exec" {
  1119. assert.True(t, execInvoked, "exec should be invoked")
  1120. assert.False(t, attachInvoked, "attach should not be invoked")
  1121. } else {
  1122. assert.True(t, attachInvoked, "attach should be invoked")
  1123. assert.False(t, execInvoked, "exec should not be invoked")
  1124. }
  1125. })
  1126. }
  1127. }
  1128. func TestServeExecInContainer(t *testing.T) {
  1129. testExecAttach(t, "exec")
  1130. }
  1131. func TestServeAttachContainer(t *testing.T) {
  1132. testExecAttach(t, "attach")
  1133. }
  1134. func TestServePortForwardIdleTimeout(t *testing.T) {
  1135. ss, err := newTestStreamingServer(100 * time.Millisecond)
  1136. require.NoError(t, err)
  1137. defer ss.testHTTPServer.Close()
  1138. fw := newServerTestWithDebug(true, false, ss)
  1139. defer fw.testHTTPServer.Close()
  1140. podNamespace := "other"
  1141. podName := "foo"
  1142. url := fw.testHTTPServer.URL + "/portForward/" + podNamespace + "/" + podName
  1143. upgradeRoundTripper := spdy.NewRoundTripper(nil, true, true)
  1144. c := &http.Client{Transport: upgradeRoundTripper}
  1145. req := makeReq(t, "POST", url, "portforward.k8s.io")
  1146. resp, err := c.Do(req)
  1147. if err != nil {
  1148. t.Fatalf("Got error POSTing: %v", err)
  1149. }
  1150. defer resp.Body.Close()
  1151. conn, err := upgradeRoundTripper.NewConnection(resp)
  1152. if err != nil {
  1153. t.Fatalf("Unexpected error creating streaming connection: %s", err)
  1154. }
  1155. if conn == nil {
  1156. t.Fatal("Unexpected nil connection")
  1157. }
  1158. defer conn.Close()
  1159. <-conn.CloseChan()
  1160. }
  1161. func TestServePortForward(t *testing.T) {
  1162. tests := map[string]struct {
  1163. port string
  1164. uid bool
  1165. clientData string
  1166. containerData string
  1167. redirect bool
  1168. shouldError bool
  1169. }{
  1170. "no port": {port: "", shouldError: true},
  1171. "none number port": {port: "abc", shouldError: true},
  1172. "negative port": {port: "-1", shouldError: true},
  1173. "too large port": {port: "65536", shouldError: true},
  1174. "0 port": {port: "0", shouldError: true},
  1175. "min port": {port: "1", shouldError: false},
  1176. "normal port": {port: "8000", shouldError: false},
  1177. "normal port with data forward": {port: "8000", clientData: "client data", containerData: "container data", shouldError: false},
  1178. "max port": {port: "65535", shouldError: false},
  1179. "normal port with uid": {port: "8000", uid: true, shouldError: false},
  1180. "normal port with redirect": {port: "8000", redirect: true, shouldError: false},
  1181. }
  1182. podNamespace := "other"
  1183. podName := "foo"
  1184. for desc := range tests {
  1185. test := tests[desc]
  1186. t.Run(desc, func(t *testing.T) {
  1187. ss, err := newTestStreamingServer(0)
  1188. require.NoError(t, err)
  1189. defer ss.testHTTPServer.Close()
  1190. fw := newServerTestWithDebug(true, test.redirect, ss)
  1191. defer fw.testHTTPServer.Close()
  1192. portForwardFuncDone := make(chan struct{})
  1193. fw.fakeKubelet.getPortForwardCheck = func(name, namespace string, uid types.UID, opts portforward.V4Options) {
  1194. assert.Equal(t, podName, name, "pod name")
  1195. assert.Equal(t, podNamespace, namespace, "pod namespace")
  1196. if test.uid {
  1197. assert.Equal(t, testUID, string(uid), "uid")
  1198. }
  1199. }
  1200. ss.fakeRuntime.portForwardFunc = func(podSandboxID string, port int32, stream io.ReadWriteCloser) error {
  1201. defer close(portForwardFuncDone)
  1202. assert.Equal(t, testPodSandboxID, podSandboxID, "pod sandbox id")
  1203. // The port should be valid if it reaches here.
  1204. testPort, err := strconv.ParseInt(test.port, 10, 32)
  1205. require.NoError(t, err, "parse port")
  1206. assert.Equal(t, int32(testPort), port, "port")
  1207. if test.clientData != "" {
  1208. fromClient := make([]byte, 32)
  1209. n, err := stream.Read(fromClient)
  1210. assert.NoError(t, err, "reading client data")
  1211. assert.Equal(t, test.clientData, string(fromClient[0:n]), "client data")
  1212. }
  1213. if test.containerData != "" {
  1214. _, err := stream.Write([]byte(test.containerData))
  1215. assert.NoError(t, err, "writing container data")
  1216. }
  1217. return nil
  1218. }
  1219. var url string
  1220. if test.uid {
  1221. url = fmt.Sprintf("%s/portForward/%s/%s/%s", fw.testHTTPServer.URL, podNamespace, podName, testUID)
  1222. } else {
  1223. url = fmt.Sprintf("%s/portForward/%s/%s", fw.testHTTPServer.URL, podNamespace, podName)
  1224. }
  1225. var (
  1226. upgradeRoundTripper httpstream.UpgradeRoundTripper
  1227. c *http.Client
  1228. )
  1229. if test.redirect {
  1230. c = &http.Client{}
  1231. // Don't follow redirects, since we want to inspect the redirect response.
  1232. c.CheckRedirect = func(*http.Request, []*http.Request) error {
  1233. return http.ErrUseLastResponse
  1234. }
  1235. } else {
  1236. upgradeRoundTripper = spdy.NewRoundTripper(nil, true, true)
  1237. c = &http.Client{Transport: upgradeRoundTripper}
  1238. }
  1239. req := makeReq(t, "POST", url, "portforward.k8s.io")
  1240. resp, err := c.Do(req)
  1241. require.NoError(t, err, "POSTing")
  1242. defer resp.Body.Close()
  1243. if test.redirect {
  1244. assert.Equal(t, http.StatusFound, resp.StatusCode, "status code")
  1245. return
  1246. }
  1247. assert.Equal(t, http.StatusSwitchingProtocols, resp.StatusCode, "status code")
  1248. conn, err := upgradeRoundTripper.NewConnection(resp)
  1249. require.NoError(t, err, "creating streaming connection")
  1250. defer conn.Close()
  1251. headers := http.Header{}
  1252. headers.Set("streamType", "error")
  1253. headers.Set("port", test.port)
  1254. _, err = conn.CreateStream(headers)
  1255. assert.Equal(t, test.shouldError, err != nil, "expect error")
  1256. if test.shouldError {
  1257. return
  1258. }
  1259. headers.Set("streamType", "data")
  1260. headers.Set("port", test.port)
  1261. dataStream, err := conn.CreateStream(headers)
  1262. require.NoError(t, err, "create stream")
  1263. if test.clientData != "" {
  1264. _, err := dataStream.Write([]byte(test.clientData))
  1265. assert.NoError(t, err, "writing client data")
  1266. }
  1267. if test.containerData != "" {
  1268. fromContainer := make([]byte, 32)
  1269. n, err := dataStream.Read(fromContainer)
  1270. assert.NoError(t, err, "reading container data")
  1271. assert.Equal(t, test.containerData, string(fromContainer[0:n]), "container data")
  1272. }
  1273. <-portForwardFuncDone
  1274. })
  1275. }
  1276. }
  1277. func TestCRIHandler(t *testing.T) {
  1278. fw := newServerTest()
  1279. defer fw.testHTTPServer.Close()
  1280. const (
  1281. path = "/cri/exec/123456abcdef"
  1282. query = "cmd=echo+foo"
  1283. )
  1284. resp, err := http.Get(fw.testHTTPServer.URL + path + "?" + query)
  1285. require.NoError(t, err)
  1286. assert.Equal(t, http.StatusOK, resp.StatusCode)
  1287. assert.Equal(t, "GET", fw.criHandler.RequestReceived.Method)
  1288. assert.Equal(t, path, fw.criHandler.RequestReceived.URL.Path)
  1289. assert.Equal(t, query, fw.criHandler.RequestReceived.URL.RawQuery)
  1290. }
  1291. func TestMetricBuckets(t *testing.T) {
  1292. tests := map[string]struct {
  1293. url string
  1294. bucket string
  1295. }{
  1296. "healthz endpoint": {url: "/healthz", bucket: "healthz"},
  1297. "attach": {url: "/attach/podNamespace/podID/containerName", bucket: "attach"},
  1298. "attach with uid": {url: "/attach/podNamespace/podID/uid/containerName", bucket: "attach"},
  1299. "configz": {url: "/configz", bucket: "configz"},
  1300. "containerLogs": {url: "/containerLogs/podNamespace/podID/containerName", bucket: "containerLogs"},
  1301. "cri": {url: "/cri/", bucket: "cri"},
  1302. "cri with sub": {url: "/cri/foo", bucket: "cri"},
  1303. "debug v flags": {url: "/debug/flags/v", bucket: "debug"},
  1304. "pprof with sub": {url: "/debug/pprof/subpath", bucket: "debug"},
  1305. "exec": {url: "/exec/podNamespace/podID/containerName", bucket: "exec"},
  1306. "exec with uid": {url: "/exec/podNamespace/podID/uid/containerName", bucket: "exec"},
  1307. "healthz": {url: "/healthz/", bucket: "healthz"},
  1308. "healthz log sub": {url: "/healthz/log", bucket: "healthz"},
  1309. "healthz ping": {url: "/healthz/ping", bucket: "healthz"},
  1310. "healthz sync loop": {url: "/healthz/syncloop", bucket: "healthz"},
  1311. "logs": {url: "/logs/", bucket: "logs"},
  1312. "logs with path": {url: "/logs/logpath", bucket: "logs"},
  1313. "metrics": {url: "/metrics", bucket: "metrics"},
  1314. "metrics cadvisor sub": {url: "/metrics/cadvisor", bucket: "metrics/cadvisor"},
  1315. "metrics probes sub": {url: "/metrics/probes", bucket: "metrics/probes"},
  1316. "metrics resource v1alpha1": {url: "/metrics/resource/v1alpha1", bucket: "metrics/resource"},
  1317. "metrics resource sub": {url: "/metrics/resource", bucket: "metrics/resource"},
  1318. "pods": {url: "/pods/", bucket: "pods"},
  1319. "portForward": {url: "/portForward/podNamespace/podID", bucket: "portForward"},
  1320. "portForward with uid": {url: "/portForward/podNamespace/podID/uid", bucket: "portForward"},
  1321. "run": {url: "/run/podNamespace/podID/containerName", bucket: "run"},
  1322. "run with uid": {url: "/run/podNamespace/podID/uid/containerName", bucket: "run"},
  1323. "runningpods": {url: "/runningpods/", bucket: "runningpods"},
  1324. "spec": {url: "/spec/", bucket: "spec"},
  1325. "stats": {url: "/stats/", bucket: "stats"},
  1326. "stats container sub": {url: "/stats/container", bucket: "stats"},
  1327. "stats summary sub": {url: "/stats/summary", bucket: "stats"},
  1328. "stats containerName with uid": {url: "/stats/namespace/podName/uid/containerName", bucket: "stats"},
  1329. "stats containerName": {url: "/stats/podName/containerName", bucket: "stats"},
  1330. "invalid path": {url: "/junk", bucket: "Invalid path"},
  1331. "invalid path starting with good": {url: "/healthzjunk", bucket: "Invalid path"},
  1332. }
  1333. fw := newServerTest()
  1334. defer fw.testHTTPServer.Close()
  1335. for _, test := range tests {
  1336. path := test.url
  1337. bucket := test.bucket
  1338. require.Equal(t, fw.serverUnderTest.getMetricBucket(path), bucket)
  1339. }
  1340. }
  1341. func TestDebuggingDisabledHandlers(t *testing.T) {
  1342. fw := newServerTestWithDebug(false, false, nil)
  1343. defer fw.testHTTPServer.Close()
  1344. paths := []string{
  1345. "/run", "/exec", "/attach", "/portForward", "/containerLogs", "/runningpods",
  1346. "/run/", "/exec/", "/attach/", "/portForward/", "/containerLogs/", "/runningpods/",
  1347. "/run/xxx", "/exec/xxx", "/attach/xxx", "/debug/pprof/profile", "/logs/kubelet.log",
  1348. }
  1349. for _, p := range paths {
  1350. resp, err := http.Get(fw.testHTTPServer.URL + p)
  1351. require.NoError(t, err)
  1352. assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
  1353. body, err := ioutil.ReadAll(resp.Body)
  1354. require.NoError(t, err)
  1355. assert.Equal(t, "Debug endpoints are disabled.\n", string(body))
  1356. resp, err = http.Post(fw.testHTTPServer.URL+p, "", nil)
  1357. require.NoError(t, err)
  1358. assert.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode)
  1359. body, err = ioutil.ReadAll(resp.Body)
  1360. require.NoError(t, err)
  1361. assert.Equal(t, "Debug endpoints are disabled.\n", string(body))
  1362. }
  1363. // test some other paths, make sure they're working
  1364. containerInfo := &cadvisorapi.ContainerInfo{
  1365. ContainerReference: cadvisorapi.ContainerReference{
  1366. Name: "/",
  1367. },
  1368. }
  1369. fw.fakeKubelet.rawInfoFunc = func(req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) {
  1370. return map[string]*cadvisorapi.ContainerInfo{
  1371. containerInfo.Name: containerInfo,
  1372. }, nil
  1373. }
  1374. resp, err := http.Get(fw.testHTTPServer.URL + "/stats")
  1375. require.NoError(t, err)
  1376. assert.Equal(t, http.StatusOK, resp.StatusCode)
  1377. machineInfo := &cadvisorapi.MachineInfo{
  1378. NumCores: 4,
  1379. MemoryCapacity: 1024,
  1380. }
  1381. fw.fakeKubelet.machineInfoFunc = func() (*cadvisorapi.MachineInfo, error) {
  1382. return machineInfo, nil
  1383. }
  1384. resp, err = http.Get(fw.testHTTPServer.URL + "/spec")
  1385. require.NoError(t, err)
  1386. assert.Equal(t, http.StatusOK, resp.StatusCode)
  1387. }
  1388. func TestTrimURLPath(t *testing.T) {
  1389. tests := []struct {
  1390. path, expected string
  1391. }{
  1392. {"", ""},
  1393. {"//", ""},
  1394. {"/pods", "pods"},
  1395. {"pods", "pods"},
  1396. {"pods/", "pods"},
  1397. {"good/", "good"},
  1398. {"pods/probes", "pods"},
  1399. {"metrics", "metrics"},
  1400. {"metrics/resource", "metrics/resource"},
  1401. {"metrics/hello", "metrics/hello"},
  1402. }
  1403. for _, test := range tests {
  1404. assert.Equal(t, test.expected, getURLRootPath(test.path), fmt.Sprintf("path is: %s", test.path))
  1405. }
  1406. }