Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
K
k3s
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
k3s
Commits
c17f3ee3
Unverified
Commit
c17f3ee3
authored
Dec 13, 2016
by
Tim St. Clair
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Don't include user data in CRI streaming redirect URLs
parent
330c9227
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
482 additions
and
94 deletions
+482
-94
server.go
pkg/kubelet/server/server.go
+3
-3
BUILD
pkg/kubelet/server/streaming/BUILD
+7
-2
errors.go
pkg/kubelet/server/streaming/errors.go
+20
-4
request_cache.go
pkg/kubelet/server/streaming/request_cache.go
+146
-0
request_cache_test.go
pkg/kubelet/server/streaming/request_cache_test.go
+221
-0
server.go
pkg/kubelet/server/streaming/server.go
+77
-85
server_test.go
pkg/kubelet/server/streaming/server_test.go
+0
-0
kubectl.go
test/e2e/kubectl.go
+8
-0
No files found.
pkg/kubelet/server/server.go
View file @
c17f3ee3
...
@@ -606,7 +606,7 @@ func (s *Server) getAttach(request *restful.Request, response *restful.Response)
...
@@ -606,7 +606,7 @@ func (s *Server) getAttach(request *restful.Request, response *restful.Response)
podFullName
:=
kubecontainer
.
GetPodFullName
(
pod
)
podFullName
:=
kubecontainer
.
GetPodFullName
(
pod
)
redirect
,
err
:=
s
.
host
.
GetAttach
(
podFullName
,
params
.
podUID
,
params
.
containerName
,
*
streamOpts
)
redirect
,
err
:=
s
.
host
.
GetAttach
(
podFullName
,
params
.
podUID
,
params
.
containerName
,
*
streamOpts
)
if
err
!=
nil
{
if
err
!=
nil
{
response
.
WriteError
(
streaming
.
HTTPStatus
(
err
),
er
r
)
streaming
.
WriteError
(
err
,
response
.
ResponseWrite
r
)
return
return
}
}
if
redirect
!=
nil
{
if
redirect
!=
nil
{
...
@@ -644,7 +644,7 @@ func (s *Server) getExec(request *restful.Request, response *restful.Response) {
...
@@ -644,7 +644,7 @@ func (s *Server) getExec(request *restful.Request, response *restful.Response) {
podFullName
:=
kubecontainer
.
GetPodFullName
(
pod
)
podFullName
:=
kubecontainer
.
GetPodFullName
(
pod
)
redirect
,
err
:=
s
.
host
.
GetExec
(
podFullName
,
params
.
podUID
,
params
.
containerName
,
params
.
cmd
,
*
streamOpts
)
redirect
,
err
:=
s
.
host
.
GetExec
(
podFullName
,
params
.
podUID
,
params
.
containerName
,
params
.
cmd
,
*
streamOpts
)
if
err
!=
nil
{
if
err
!=
nil
{
response
.
WriteError
(
streaming
.
HTTPStatus
(
err
),
er
r
)
streaming
.
WriteError
(
err
,
response
.
ResponseWrite
r
)
return
return
}
}
if
redirect
!=
nil
{
if
redirect
!=
nil
{
...
@@ -714,7 +714,7 @@ func (s *Server) getPortForward(request *restful.Request, response *restful.Resp
...
@@ -714,7 +714,7 @@ func (s *Server) getPortForward(request *restful.Request, response *restful.Resp
redirect
,
err
:=
s
.
host
.
GetPortForward
(
pod
.
Name
,
pod
.
Namespace
,
pod
.
UID
)
redirect
,
err
:=
s
.
host
.
GetPortForward
(
pod
.
Name
,
pod
.
Namespace
,
pod
.
UID
)
if
err
!=
nil
{
if
err
!=
nil
{
response
.
WriteError
(
streaming
.
HTTPStatus
(
err
),
er
r
)
streaming
.
WriteError
(
err
,
response
.
ResponseWrite
r
)
return
return
}
}
if
redirect
!=
nil
{
if
redirect
!=
nil
{
...
...
pkg/kubelet/server/streaming/BUILD
View file @
c17f3ee3
...
@@ -12,14 +12,15 @@ go_library(
...
@@ -12,14 +12,15 @@ go_library(
name = "go_default_library",
name = "go_default_library",
srcs = [
srcs = [
"errors.go",
"errors.go",
"request_cache.go",
"server.go",
"server.go",
],
],
tags = ["automanaged"],
tags = ["automanaged"],
deps = [
deps = [
"//pkg/api:go_default_library",
"//pkg/kubelet/api/v1alpha1/runtime:go_default_library",
"//pkg/kubelet/api/v1alpha1/runtime:go_default_library",
"//pkg/kubelet/server/portforward:go_default_library",
"//pkg/kubelet/server/portforward:go_default_library",
"//pkg/kubelet/server/remotecommand:go_default_library",
"//pkg/kubelet/server/remotecommand:go_default_library",
"//pkg/util/clock:go_default_library",
"//pkg/util/term:go_default_library",
"//pkg/util/term:go_default_library",
"//vendor:github.com/emicklei/go-restful",
"//vendor:github.com/emicklei/go-restful",
"//vendor:google.golang.org/grpc",
"//vendor:google.golang.org/grpc",
...
@@ -30,7 +31,10 @@ go_library(
...
@@ -30,7 +31,10 @@ go_library(
go_test(
go_test(
name = "go_default_test",
name = "go_default_test",
srcs = ["server_test.go"],
srcs = [
"request_cache_test.go",
"server_test.go",
],
library = ":go_default_library",
library = ":go_default_library",
tags = ["automanaged"],
tags = ["automanaged"],
deps = [
deps = [
...
@@ -43,6 +47,7 @@ go_test(
...
@@ -43,6 +47,7 @@ go_test(
"//vendor:github.com/stretchr/testify/assert",
"//vendor:github.com/stretchr/testify/assert",
"//vendor:github.com/stretchr/testify/require",
"//vendor:github.com/stretchr/testify/require",
"//vendor:k8s.io/client-go/pkg/api",
"//vendor:k8s.io/client-go/pkg/api",
"//vendor:k8s.io/client-go/pkg/util/clock",
],
],
)
)
...
...
pkg/kubelet/server/streaming/errors.go
View file @
c17f3ee3
...
@@ -19,6 +19,7 @@ package streaming
...
@@ -19,6 +19,7 @@ package streaming
import
(
import
(
"fmt"
"fmt"
"net/http"
"net/http"
"strconv"
"time"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc"
...
@@ -33,12 +34,27 @@ func ErrorTimeout(op string, timeout time.Duration) error {
...
@@ -33,12 +34,27 @@ func ErrorTimeout(op string, timeout time.Duration) error {
return
grpc
.
Errorf
(
codes
.
DeadlineExceeded
,
fmt
.
Sprintf
(
"%s timed out after %s"
,
op
,
timeout
.
String
()))
return
grpc
.
Errorf
(
codes
.
DeadlineExceeded
,
fmt
.
Sprintf
(
"%s timed out after %s"
,
op
,
timeout
.
String
()))
}
}
// Translates a CRI streaming error into an HTTP status code.
// The error returned when the maximum number of in-flight requests is exceeded.
func
HTTPStatus
(
err
error
)
int
{
func
ErrorTooManyInFlight
()
error
{
return
grpc
.
Errorf
(
codes
.
ResourceExhausted
,
"maximum number of in-flight requests exceeded"
)
}
// Translates a CRI streaming error into an appropriate HTTP response.
func
WriteError
(
err
error
,
w
http
.
ResponseWriter
)
error
{
var
status
int
switch
grpc
.
Code
(
err
)
{
switch
grpc
.
Code
(
err
)
{
case
codes
.
NotFound
:
case
codes
.
NotFound
:
return
http
.
StatusNotFound
status
=
http
.
StatusNotFound
case
codes
.
ResourceExhausted
:
// We only expect to hit this if there is a DoS, so we just wait the full TTL.
// If this is ever hit in steady-state operations, consider increasing the MaxInFlight requests,
// or plumbing through the time to next expiration.
w
.
Header
()
.
Set
(
"Retry-After"
,
strconv
.
Itoa
(
int
(
CacheTTL
.
Seconds
())))
status
=
http
.
StatusTooManyRequests
default
:
default
:
return
http
.
StatusInternalServerError
status
=
http
.
StatusInternalServerError
}
}
w
.
WriteHeader
(
status
)
_
,
writeErr
:=
w
.
Write
([]
byte
(
err
.
Error
()))
return
writeErr
}
}
pkg/kubelet/server/streaming/request_cache.go
0 → 100644
View file @
c17f3ee3
/*
Copyright 2016 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
streaming
import
(
"container/list"
"crypto/rand"
"encoding/base64"
"fmt"
"math"
"sync"
"time"
"k8s.io/kubernetes/pkg/util/clock"
)
var
(
// Timeout after which tokens become invalid.
CacheTTL
=
1
*
time
.
Minute
// The maximum number of in-flight requests to allow.
MaxInFlight
=
1000
// Length of the random base64 encoded token identifying the request.
TokenLen
=
8
)
// requestCache caches streaming (exec/attach/port-forward) requests and generates a single-use
// random token for their retrieval. The requestCache is used for building streaming URLs without
// the need to encode every request parameter in the URL.
type
requestCache
struct
{
// clock is used to obtain the current time
clock
clock
.
Clock
// tokens maps the generate token to the request for fast retrieval.
tokens
map
[
string
]
*
list
.
Element
// ll maintains an age-ordered request list for faster garbage collection of expired requests.
ll
*
list
.
List
lock
sync
.
Mutex
}
// Type representing an *ExecRequest, *AttachRequest, or *PortForwardRequest.
type
request
interface
{}
type
cacheEntry
struct
{
token
string
req
request
expireTime
time
.
Time
}
func
newRequestCache
()
*
requestCache
{
return
&
requestCache
{
clock
:
clock
.
RealClock
{},
ll
:
list
.
New
(),
tokens
:
make
(
map
[
string
]
*
list
.
Element
),
}
}
// Insert the given request into the cache and returns the token used for fetching it out.
func
(
c
*
requestCache
)
Insert
(
req
request
)
(
token
string
,
err
error
)
{
c
.
lock
.
Lock
()
defer
c
.
lock
.
Unlock
()
// Remove expired entries.
c
.
gc
()
// If the cache is full, reject the request.
if
c
.
ll
.
Len
()
==
MaxInFlight
{
return
""
,
ErrorTooManyInFlight
()
}
token
,
err
=
c
.
uniqueToken
()
if
err
!=
nil
{
return
""
,
err
}
ele
:=
c
.
ll
.
PushFront
(
&
cacheEntry
{
token
,
req
,
c
.
clock
.
Now
()
.
Add
(
CacheTTL
)})
c
.
tokens
[
token
]
=
ele
return
token
,
nil
}
// Consume the token (remove it from the cache) and return the cached request, if found.
func
(
c
*
requestCache
)
Consume
(
token
string
)
(
req
request
,
found
bool
)
{
c
.
lock
.
Lock
()
defer
c
.
lock
.
Unlock
()
ele
,
ok
:=
c
.
tokens
[
token
]
if
!
ok
{
return
nil
,
false
}
c
.
ll
.
Remove
(
ele
)
delete
(
c
.
tokens
,
token
)
entry
:=
ele
.
Value
.
(
*
cacheEntry
)
if
c
.
clock
.
Now
()
.
After
(
entry
.
expireTime
)
{
// Entry already expired.
return
nil
,
false
}
return
entry
.
req
,
true
}
// uniqueToken generates a random URL-safe token and ensures uniqueness.
func
(
c
*
requestCache
)
uniqueToken
()
(
string
,
error
)
{
const
maxTries
=
10
// Number of bytes to be TokenLen when base64 encoded.
tokenSize
:=
math
.
Ceil
(
float64
(
TokenLen
)
*
6
/
8
)
rawToken
:=
make
([]
byte
,
int
(
tokenSize
))
for
i
:=
0
;
i
<
maxTries
;
i
++
{
if
_
,
err
:=
rand
.
Read
(
rawToken
);
err
!=
nil
{
return
""
,
err
}
encoded
:=
base64
.
RawURLEncoding
.
EncodeToString
(
rawToken
)
token
:=
encoded
[
:
TokenLen
]
// If it's unique, return it. Otherwise retry.
if
_
,
exists
:=
c
.
tokens
[
encoded
];
!
exists
{
return
token
,
nil
}
}
return
""
,
fmt
.
Errorf
(
"failed to generate unique token"
)
}
// Must be write-locked prior to calling.
func
(
c
*
requestCache
)
gc
()
{
now
:=
c
.
clock
.
Now
()
for
c
.
ll
.
Len
()
>
0
{
oldest
:=
c
.
ll
.
Back
()
entry
:=
oldest
.
Value
.
(
*
cacheEntry
)
if
!
now
.
After
(
entry
.
expireTime
)
{
return
}
// Oldest value is expired; remove it.
c
.
ll
.
Remove
(
oldest
)
delete
(
c
.
tokens
,
entry
.
token
)
}
}
pkg/kubelet/server/streaming/request_cache_test.go
0 → 100644
View file @
c17f3ee3
/*
Copyright 2016 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
streaming
import
(
"net/http"
"net/http/httptest"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/client-go/pkg/util/clock"
)
func
TestInsert
(
t
*
testing
.
T
)
{
c
,
_
:=
newTestCache
()
// Insert normal
oldestTok
,
err
:=
c
.
Insert
(
nextRequest
())
require
.
NoError
(
t
,
err
)
assert
.
Len
(
t
,
oldestTok
,
TokenLen
)
assertCacheSize
(
t
,
c
,
1
)
// Insert until full
for
i
:=
0
;
i
<
MaxInFlight
-
2
;
i
++
{
tok
,
err
:=
c
.
Insert
(
nextRequest
())
require
.
NoError
(
t
,
err
)
assert
.
Len
(
t
,
tok
,
TokenLen
)
}
assertCacheSize
(
t
,
c
,
MaxInFlight
-
1
)
newestReq
:=
nextRequest
()
newestTok
,
err
:=
c
.
Insert
(
newestReq
)
require
.
NoError
(
t
,
err
)
assert
.
Len
(
t
,
newestTok
,
TokenLen
)
assertCacheSize
(
t
,
c
,
MaxInFlight
)
require
.
Contains
(
t
,
c
.
tokens
,
oldestTok
,
"oldest request should still be cached"
)
// Consume newest token.
req
,
ok
:=
c
.
Consume
(
newestTok
)
assert
.
True
(
t
,
ok
,
"newest request should still be cached"
)
assert
.
Equal
(
t
,
newestReq
,
req
)
require
.
Contains
(
t
,
c
.
tokens
,
oldestTok
,
"oldest request should still be cached"
)
// Insert again (still full)
tok
,
err
:=
c
.
Insert
(
nextRequest
())
require
.
NoError
(
t
,
err
)
assert
.
Len
(
t
,
tok
,
TokenLen
)
assertCacheSize
(
t
,
c
,
MaxInFlight
)
// Insert again (should evict)
_
,
err
=
c
.
Insert
(
nextRequest
())
assert
.
Error
(
t
,
err
,
"should reject further requests"
)
errResponse
:=
httptest
.
NewRecorder
()
require
.
NoError
(
t
,
WriteError
(
err
,
errResponse
))
assert
.
Equal
(
t
,
errResponse
.
Code
,
http
.
StatusTooManyRequests
)
assert
.
Equal
(
t
,
strconv
.
Itoa
(
int
(
CacheTTL
.
Seconds
())),
errResponse
.
HeaderMap
.
Get
(
"Retry-After"
))
assertCacheSize
(
t
,
c
,
MaxInFlight
)
_
,
ok
=
c
.
Consume
(
oldestTok
)
assert
.
True
(
t
,
ok
,
"oldest request should be valid"
)
}
func
TestConsume
(
t
*
testing
.
T
)
{
c
,
clock
:=
newTestCache
()
{
// Insert & consume.
req
:=
nextRequest
()
tok
,
err
:=
c
.
Insert
(
req
)
require
.
NoError
(
t
,
err
)
assertCacheSize
(
t
,
c
,
1
)
cachedReq
,
ok
:=
c
.
Consume
(
tok
)
assert
.
True
(
t
,
ok
)
assert
.
Equal
(
t
,
req
,
cachedReq
)
assertCacheSize
(
t
,
c
,
0
)
}
{
// Insert & consume out of order
req1
:=
nextRequest
()
tok1
,
err
:=
c
.
Insert
(
req1
)
require
.
NoError
(
t
,
err
)
assertCacheSize
(
t
,
c
,
1
)
req2
:=
nextRequest
()
tok2
,
err
:=
c
.
Insert
(
req2
)
require
.
NoError
(
t
,
err
)
assertCacheSize
(
t
,
c
,
2
)
cachedReq2
,
ok
:=
c
.
Consume
(
tok2
)
assert
.
True
(
t
,
ok
)
assert
.
Equal
(
t
,
req2
,
cachedReq2
)
assertCacheSize
(
t
,
c
,
1
)
cachedReq1
,
ok
:=
c
.
Consume
(
tok1
)
assert
.
True
(
t
,
ok
)
assert
.
Equal
(
t
,
req1
,
cachedReq1
)
assertCacheSize
(
t
,
c
,
0
)
}
{
// Consume a second time
req
:=
nextRequest
()
tok
,
err
:=
c
.
Insert
(
req
)
require
.
NoError
(
t
,
err
)
assertCacheSize
(
t
,
c
,
1
)
cachedReq
,
ok
:=
c
.
Consume
(
tok
)
assert
.
True
(
t
,
ok
)
assert
.
Equal
(
t
,
req
,
cachedReq
)
assertCacheSize
(
t
,
c
,
0
)
_
,
ok
=
c
.
Consume
(
tok
)
assert
.
False
(
t
,
ok
)
assertCacheSize
(
t
,
c
,
0
)
}
{
// Consume without insert
_
,
ok
:=
c
.
Consume
(
"fooBAR"
)
assert
.
False
(
t
,
ok
)
assertCacheSize
(
t
,
c
,
0
)
}
{
// Consume expired
tok
,
err
:=
c
.
Insert
(
nextRequest
())
require
.
NoError
(
t
,
err
)
assertCacheSize
(
t
,
c
,
1
)
clock
.
Step
(
2
*
CacheTTL
)
_
,
ok
:=
c
.
Consume
(
tok
)
assert
.
False
(
t
,
ok
)
assertCacheSize
(
t
,
c
,
0
)
}
}
func
TestGC
(
t
*
testing
.
T
)
{
c
,
clock
:=
newTestCache
()
// When empty
c
.
gc
()
assertCacheSize
(
t
,
c
,
0
)
tok1
,
err
:=
c
.
Insert
(
nextRequest
())
require
.
NoError
(
t
,
err
)
assertCacheSize
(
t
,
c
,
1
)
clock
.
Step
(
10
*
time
.
Second
)
tok2
,
err
:=
c
.
Insert
(
nextRequest
())
require
.
NoError
(
t
,
err
)
assertCacheSize
(
t
,
c
,
2
)
// expired: tok1, tok2
// non-expired: tok3, tok4
clock
.
Step
(
2
*
CacheTTL
)
tok3
,
err
:=
c
.
Insert
(
nextRequest
())
require
.
NoError
(
t
,
err
)
assertCacheSize
(
t
,
c
,
1
)
clock
.
Step
(
10
*
time
.
Second
)
tok4
,
err
:=
c
.
Insert
(
nextRequest
())
require
.
NoError
(
t
,
err
)
assertCacheSize
(
t
,
c
,
2
)
_
,
ok
:=
c
.
Consume
(
tok1
)
assert
.
False
(
t
,
ok
)
_
,
ok
=
c
.
Consume
(
tok2
)
assert
.
False
(
t
,
ok
)
_
,
ok
=
c
.
Consume
(
tok3
)
assert
.
True
(
t
,
ok
)
_
,
ok
=
c
.
Consume
(
tok4
)
assert
.
True
(
t
,
ok
)
// When full, nothing is expired.
for
i
:=
0
;
i
<
MaxInFlight
;
i
++
{
_
,
err
:=
c
.
Insert
(
nextRequest
())
require
.
NoError
(
t
,
err
)
}
assertCacheSize
(
t
,
c
,
MaxInFlight
)
// When everything is expired
clock
.
Step
(
2
*
CacheTTL
)
_
,
err
=
c
.
Insert
(
nextRequest
())
require
.
NoError
(
t
,
err
)
assertCacheSize
(
t
,
c
,
1
)
}
func
newTestCache
()
(
*
requestCache
,
*
clock
.
FakeClock
)
{
c
:=
newRequestCache
()
fakeClock
:=
clock
.
NewFakeClock
(
time
.
Now
())
c
.
clock
=
fakeClock
return
c
,
fakeClock
}
func
assertCacheSize
(
t
*
testing
.
T
,
cache
*
requestCache
,
expectedSize
int
)
{
tokenLen
:=
len
(
cache
.
tokens
)
llLen
:=
cache
.
ll
.
Len
()
assert
.
Equal
(
t
,
tokenLen
,
llLen
,
"inconsistent cache size! len(tokens)=%d; len(ll)=%d"
,
tokenLen
,
llLen
)
assert
.
Equal
(
t
,
expectedSize
,
tokenLen
,
"unexpected cache size!"
)
}
var
requestUID
=
0
func
nextRequest
()
interface
{}
{
requestUID
++
return
requestUID
}
pkg/kubelet/server/streaming/server.go
View file @
c17f3ee3
...
@@ -25,10 +25,12 @@ import (
...
@@ -25,10 +25,12 @@ import (
"path"
"path"
"time"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
restful
"github.com/emicklei/go-restful"
restful
"github.com/emicklei/go-restful"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/api"
runtimeapi
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
runtimeapi
"k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
"k8s.io/kubernetes/pkg/kubelet/server/portforward"
"k8s.io/kubernetes/pkg/kubelet/server/portforward"
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
...
@@ -97,6 +99,7 @@ func NewServer(config Config, runtime Runtime) (Server, error) {
...
@@ -97,6 +99,7 @@ func NewServer(config Config, runtime Runtime) (Server, error) {
s
:=
&
server
{
s
:=
&
server
{
config
:
config
,
config
:
config
,
runtime
:
&
criAdapter
{
runtime
},
runtime
:
&
criAdapter
{
runtime
},
cache
:
newRequestCache
(),
}
}
if
s
.
config
.
BaseURL
==
nil
{
if
s
.
config
.
BaseURL
==
nil
{
...
@@ -114,9 +117,9 @@ func NewServer(config Config, runtime Runtime) (Server, error) {
...
@@ -114,9 +117,9 @@ func NewServer(config Config, runtime Runtime) (Server, error) {
path
string
path
string
handler
restful
.
RouteFunction
handler
restful
.
RouteFunction
}{
}{
{
"/exec/{
containerID
}"
,
s
.
serveExec
},
{
"/exec/{
token
}"
,
s
.
serveExec
},
{
"/attach/{
containerID
}"
,
s
.
serveAttach
},
{
"/attach/{
token
}"
,
s
.
serveAttach
},
{
"/portforward/{
podSandboxID
}"
,
s
.
servePortForward
},
{
"/portforward/{
token
}"
,
s
.
servePortForward
},
}
}
// If serving relative to a base path, set that here.
// If serving relative to a base path, set that here.
pathPrefix
:=
path
.
Dir
(
s
.
config
.
BaseURL
.
Path
)
pathPrefix
:=
path
.
Dir
(
s
.
config
.
BaseURL
.
Path
)
...
@@ -139,37 +142,45 @@ type server struct {
...
@@ -139,37 +142,45 @@ type server struct {
config
Config
config
Config
runtime
*
criAdapter
runtime
*
criAdapter
handler
http
.
Handler
handler
http
.
Handler
cache
*
requestCache
}
}
func
(
s
*
server
)
GetExec
(
req
*
runtimeapi
.
ExecRequest
)
(
*
runtimeapi
.
ExecResponse
,
error
)
{
func
(
s
*
server
)
GetExec
(
req
*
runtimeapi
.
ExecRequest
)
(
*
runtimeapi
.
ExecResponse
,
error
)
{
url
:=
s
.
buildURL
(
"exec"
,
req
.
GetContainerId
(),
streamOpts
{
if
req
.
GetContainerId
()
==
""
{
stdin
:
req
.
GetStdin
(),
return
nil
,
grpc
.
Errorf
(
codes
.
InvalidArgument
,
"missing required container_id"
)
stdout
:
true
,
}
stderr
:
!
req
.
GetTty
(),
// For TTY connections, both stderr is combined with stdout.
token
,
err
:=
s
.
cache
.
Insert
(
req
)
tty
:
req
.
GetTty
(),
if
err
!=
nil
{
command
:
req
.
GetCmd
(),
return
nil
,
err
}
)
}
return
&
runtimeapi
.
ExecResponse
{
return
&
runtimeapi
.
ExecResponse
{
Url
:
&
url
,
Url
:
s
.
buildURL
(
"exec"
,
token
)
,
},
nil
},
nil
}
}
func
(
s
*
server
)
GetAttach
(
req
*
runtimeapi
.
AttachRequest
)
(
*
runtimeapi
.
AttachResponse
,
error
)
{
func
(
s
*
server
)
GetAttach
(
req
*
runtimeapi
.
AttachRequest
)
(
*
runtimeapi
.
AttachResponse
,
error
)
{
url
:=
s
.
buildURL
(
"attach"
,
req
.
GetContainerId
(),
streamOpts
{
if
req
.
GetContainerId
()
==
""
{
stdin
:
req
.
GetStdin
(),
return
nil
,
grpc
.
Errorf
(
codes
.
InvalidArgument
,
"missing required container_id"
)
stdout
:
true
,
}
stderr
:
!
req
.
GetTty
(),
// For TTY connections, both stderr is combined with stdout.
token
,
err
:=
s
.
cache
.
Insert
(
req
)
tty
:
req
.
GetTty
(),
if
err
!=
nil
{
})
return
nil
,
err
}
return
&
runtimeapi
.
AttachResponse
{
return
&
runtimeapi
.
AttachResponse
{
Url
:
&
url
,
Url
:
s
.
buildURL
(
"attach"
,
token
)
,
},
nil
},
nil
}
}
func
(
s
*
server
)
GetPortForward
(
req
*
runtimeapi
.
PortForwardRequest
)
(
*
runtimeapi
.
PortForwardResponse
,
error
)
{
func
(
s
*
server
)
GetPortForward
(
req
*
runtimeapi
.
PortForwardRequest
)
(
*
runtimeapi
.
PortForwardResponse
,
error
)
{
url
:=
s
.
buildURL
(
"portforward"
,
req
.
GetPodSandboxId
(),
streamOpts
{})
if
req
.
GetPodSandboxId
()
==
""
{
return
nil
,
grpc
.
Errorf
(
codes
.
InvalidArgument
,
"missing required pod_sandbox_id"
)
}
token
,
err
:=
s
.
cache
.
Insert
(
req
)
if
err
!=
nil
{
return
nil
,
err
}
return
&
runtimeapi
.
PortForwardResponse
{
return
&
runtimeapi
.
PortForwardResponse
{
Url
:
&
url
,
Url
:
s
.
buildURL
(
"portforward"
,
token
)
,
},
nil
},
nil
}
}
...
@@ -200,63 +211,32 @@ func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
...
@@ -200,63 +211,32 @@ func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s
.
handler
.
ServeHTTP
(
w
,
r
)
s
.
handler
.
ServeHTTP
(
w
,
r
)
}
}
type
streamOpts
struct
{
func
(
s
*
server
)
buildURL
(
method
,
token
string
)
*
string
{
stdin
bool
loc
:=
s
.
config
.
BaseURL
.
ResolveReference
(
&
url
.
URL
{
stdout
bool
Path
:
path
.
Join
(
method
,
token
),
stderr
bool
})
.
String
()
tty
bool
return
&
loc
command
[]
string
port
[]
int32
}
const
(
urlParamStdin
=
api
.
ExecStdinParam
urlParamStdout
=
api
.
ExecStdoutParam
urlParamStderr
=
api
.
ExecStderrParam
urlParamTTY
=
api
.
ExecTTYParam
urlParamCommand
=
api
.
ExecCommandParamm
)
func
(
s
*
server
)
buildURL
(
method
,
id
string
,
opts
streamOpts
)
string
{
loc
:=
&
url
.
URL
{
Path
:
path
.
Join
(
method
,
id
),
}
query
:=
url
.
Values
{}
if
opts
.
stdin
{
query
.
Add
(
urlParamStdin
,
"1"
)
}
if
opts
.
stdout
{
query
.
Add
(
urlParamStdout
,
"1"
)
}
if
opts
.
stderr
{
query
.
Add
(
urlParamStderr
,
"1"
)
}
if
opts
.
tty
{
query
.
Add
(
urlParamTTY
,
"1"
)
}
for
_
,
c
:=
range
opts
.
command
{
query
.
Add
(
urlParamCommand
,
c
)
}
loc
.
RawQuery
=
query
.
Encode
()
return
s
.
config
.
BaseURL
.
ResolveReference
(
loc
)
.
String
()
}
}
func
(
s
*
server
)
serveExec
(
req
*
restful
.
Request
,
resp
*
restful
.
Response
)
{
func
(
s
*
server
)
serveExec
(
req
*
restful
.
Request
,
resp
*
restful
.
Response
)
{
containerID
:=
req
.
PathParameter
(
"containerID"
)
token
:=
req
.
PathParameter
(
"token"
)
if
containerID
==
""
{
cachedRequest
,
ok
:=
s
.
cache
.
Consume
(
token
)
resp
.
WriteError
(
http
.
StatusBadRequest
,
errors
.
New
(
"missing required containerID path parameter"
))
if
!
ok
{
http
.
NotFound
(
resp
.
ResponseWriter
,
req
.
Request
)
return
return
}
}
exec
,
ok
:=
cachedRequest
.
(
*
runtimeapi
.
ExecRequest
)
streamOpts
,
err
:=
remotecommand
.
NewOptions
(
req
.
Request
)
if
!
ok
{
if
err
!=
nil
{
http
.
NotFound
(
resp
.
ResponseWriter
,
req
.
Request
)
resp
.
WriteError
(
http
.
StatusBadRequest
,
err
)
return
return
}
}
cmd
:=
req
.
Request
.
URL
.
Query
()[
api
.
ExecCommandParamm
]
streamOpts
:=
&
remotecommand
.
Options
{
Stdin
:
exec
.
GetStdin
(),
Stdout
:
true
,
Stderr
:
!
exec
.
GetTty
(),
TTY
:
exec
.
GetTty
(),
}
remotecommand
.
ServeExec
(
remotecommand
.
ServeExec
(
resp
.
ResponseWriter
,
resp
.
ResponseWriter
,
...
@@ -264,8 +244,8 @@ func (s *server) serveExec(req *restful.Request, resp *restful.Response) {
...
@@ -264,8 +244,8 @@ func (s *server) serveExec(req *restful.Request, resp *restful.Response) {
s
.
runtime
,
s
.
runtime
,
""
,
// unused: podName
""
,
// unused: podName
""
,
// unusued: podUID
""
,
// unusued: podUID
containerID
,
exec
.
GetContainerId
()
,
cmd
,
exec
.
GetCmd
()
,
streamOpts
,
streamOpts
,
s
.
config
.
StreamIdleTimeout
,
s
.
config
.
StreamIdleTimeout
,
s
.
config
.
StreamCreationTimeout
,
s
.
config
.
StreamCreationTimeout
,
...
@@ -273,25 +253,31 @@ func (s *server) serveExec(req *restful.Request, resp *restful.Response) {
...
@@ -273,25 +253,31 @@ func (s *server) serveExec(req *restful.Request, resp *restful.Response) {
}
}
func
(
s
*
server
)
serveAttach
(
req
*
restful
.
Request
,
resp
*
restful
.
Response
)
{
func
(
s
*
server
)
serveAttach
(
req
*
restful
.
Request
,
resp
*
restful
.
Response
)
{
containerID
:=
req
.
PathParameter
(
"containerID"
)
token
:=
req
.
PathParameter
(
"token"
)
if
containerID
==
""
{
cachedRequest
,
ok
:=
s
.
cache
.
Consume
(
token
)
resp
.
WriteError
(
http
.
StatusBadRequest
,
errors
.
New
(
"missing required containerID path parameter"
))
if
!
ok
{
http
.
NotFound
(
resp
.
ResponseWriter
,
req
.
Request
)
return
return
}
}
attach
,
ok
:=
cachedRequest
.
(
*
runtimeapi
.
AttachRequest
)
streamOpts
,
err
:=
remotecommand
.
NewOptions
(
req
.
Request
)
if
!
ok
{
if
err
!=
nil
{
http
.
NotFound
(
resp
.
ResponseWriter
,
req
.
Request
)
resp
.
WriteError
(
http
.
StatusBadRequest
,
err
)
return
return
}
}
streamOpts
:=
&
remotecommand
.
Options
{
Stdin
:
attach
.
GetStdin
(),
Stdout
:
true
,
Stderr
:
!
attach
.
GetTty
(),
TTY
:
attach
.
GetTty
(),
}
remotecommand
.
ServeAttach
(
remotecommand
.
ServeAttach
(
resp
.
ResponseWriter
,
resp
.
ResponseWriter
,
req
.
Request
,
req
.
Request
,
s
.
runtime
,
s
.
runtime
,
""
,
// unused: podName
""
,
// unused: podName
""
,
// unusued: podUID
""
,
// unusued: podUID
containerID
,
attach
.
GetContainerId
()
,
streamOpts
,
streamOpts
,
s
.
config
.
StreamIdleTimeout
,
s
.
config
.
StreamIdleTimeout
,
s
.
config
.
StreamCreationTimeout
,
s
.
config
.
StreamCreationTimeout
,
...
@@ -299,9 +285,15 @@ func (s *server) serveAttach(req *restful.Request, resp *restful.Response) {
...
@@ -299,9 +285,15 @@ func (s *server) serveAttach(req *restful.Request, resp *restful.Response) {
}
}
func
(
s
*
server
)
servePortForward
(
req
*
restful
.
Request
,
resp
*
restful
.
Response
)
{
func
(
s
*
server
)
servePortForward
(
req
*
restful
.
Request
,
resp
*
restful
.
Response
)
{
podSandboxID
:=
req
.
PathParameter
(
"podSandboxID"
)
token
:=
req
.
PathParameter
(
"token"
)
if
podSandboxID
==
""
{
cachedRequest
,
ok
:=
s
.
cache
.
Consume
(
token
)
resp
.
WriteError
(
http
.
StatusBadRequest
,
errors
.
New
(
"missing required podSandboxID path parameter"
))
if
!
ok
{
http
.
NotFound
(
resp
.
ResponseWriter
,
req
.
Request
)
return
}
pf
,
ok
:=
cachedRequest
.
(
*
runtimeapi
.
PortForwardRequest
)
if
!
ok
{
http
.
NotFound
(
resp
.
ResponseWriter
,
req
.
Request
)
return
return
}
}
...
@@ -309,7 +301,7 @@ func (s *server) servePortForward(req *restful.Request, resp *restful.Response)
...
@@ -309,7 +301,7 @@ func (s *server) servePortForward(req *restful.Request, resp *restful.Response)
resp
.
ResponseWriter
,
resp
.
ResponseWriter
,
req
.
Request
,
req
.
Request
,
s
.
runtime
,
s
.
runtime
,
p
odSandboxID
,
p
f
.
GetPodSandboxId
()
,
""
,
// unused: podUID
""
,
// unused: podUID
s
.
config
.
StreamIdleTimeout
,
s
.
config
.
StreamIdleTimeout
,
s
.
config
.
StreamCreationTimeout
)
s
.
config
.
StreamCreationTimeout
)
...
...
pkg/kubelet/server/streaming/server_test.go
View file @
c17f3ee3
This diff is collapsed.
Click to expand it.
test/e2e/kubectl.go
View file @
c17f3ee3
...
@@ -391,6 +391,14 @@ var _ = framework.KubeDescribe("Kubectl client", func() {
...
@@ -391,6 +391,14 @@ var _ = framework.KubeDescribe("Kubectl client", func() {
framework
.
Failf
(
"Unexpected kubectl exec output. Wanted %q, got %q"
,
e
,
a
)
framework
.
Failf
(
"Unexpected kubectl exec output. Wanted %q, got %q"
,
e
,
a
)
}
}
By
(
"executing a very long command in the container"
)
veryLongData
:=
make
([]
rune
,
20000
)
for
i
:=
0
;
i
<
len
(
veryLongData
);
i
++
{
veryLongData
[
i
]
=
'a'
}
execOutput
=
framework
.
RunKubectlOrDie
(
"exec"
,
fmt
.
Sprintf
(
"--namespace=%v"
,
ns
),
simplePodName
,
"echo"
,
string
(
veryLongData
))
Expect
(
string
(
veryLongData
))
.
To
(
Equal
(
strings
.
TrimSpace
(
execOutput
)),
"Unexpected kubectl exec output"
)
By
(
"executing a command in the container with noninteractive stdin"
)
By
(
"executing a command in the container with noninteractive stdin"
)
execOutput
=
framework
.
NewKubectlCommand
(
"exec"
,
fmt
.
Sprintf
(
"--namespace=%v"
,
ns
),
"-i"
,
simplePodName
,
"cat"
)
.
execOutput
=
framework
.
NewKubectlCommand
(
"exec"
,
fmt
.
Sprintf
(
"--namespace=%v"
,
ns
),
"-i"
,
simplePodName
,
"cat"
)
.
WithStdinData
(
"abcd1234"
)
.
WithStdinData
(
"abcd1234"
)
.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment