Support the subresource of node proxy

pull/6/head
feihujiang 2015-11-30 19:48:23 +08:00
parent f7be2cd2a2
commit e85253916f
24 changed files with 4937 additions and 2905 deletions

View File

@ -4748,6 +4748,426 @@
}
]
},
{
"path": "/api/v1/nodes/{name}/proxy",
"description": "API at /api/v1",
"operations": [
{
"type": "string",
"method": "GET",
"summary": "connect GET requests to proxy of Node",
"nickname": "connectGetNamespacedNodeProxy",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "path",
"description": "Path is the URL path to use for the current proxy request to node.",
"required": false,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Node",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "POST",
"summary": "connect POST requests to proxy of Node",
"nickname": "connectPostNamespacedNodeProxy",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "path",
"description": "Path is the URL path to use for the current proxy request to node.",
"required": false,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Node",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "PUT",
"summary": "connect PUT requests to proxy of Node",
"nickname": "connectPutNamespacedNodeProxy",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "path",
"description": "Path is the URL path to use for the current proxy request to node.",
"required": false,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Node",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "DELETE",
"summary": "connect DELETE requests to proxy of Node",
"nickname": "connectDeleteNamespacedNodeProxy",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "path",
"description": "Path is the URL path to use for the current proxy request to node.",
"required": false,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Node",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "HEAD",
"summary": "connect HEAD requests to proxy of Node",
"nickname": "connectHeadNamespacedNodeProxy",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "path",
"description": "Path is the URL path to use for the current proxy request to node.",
"required": false,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Node",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "OPTIONS",
"summary": "connect OPTIONS requests to proxy of Node",
"nickname": "connectOptionsNamespacedNodeProxy",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "path",
"description": "Path is the URL path to use for the current proxy request to node.",
"required": false,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Node",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
}
]
},
{
"path": "/api/v1/nodes/{name}/proxy/{path}",
"description": "API at /api/v1",
"operations": [
{
"type": "string",
"method": "GET",
"summary": "connect GET requests to proxy of Node",
"nickname": "connectGetNamespacedNodeProxy",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "path",
"description": "Path is the URL path to use for the current proxy request to node.",
"required": false,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Node",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "path",
"description": "path to the resource",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "POST",
"summary": "connect POST requests to proxy of Node",
"nickname": "connectPostNamespacedNodeProxy",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "path",
"description": "Path is the URL path to use for the current proxy request to node.",
"required": false,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Node",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "path",
"description": "path to the resource",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "PUT",
"summary": "connect PUT requests to proxy of Node",
"nickname": "connectPutNamespacedNodeProxy",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "path",
"description": "Path is the URL path to use for the current proxy request to node.",
"required": false,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Node",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "path",
"description": "path to the resource",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "DELETE",
"summary": "connect DELETE requests to proxy of Node",
"nickname": "connectDeleteNamespacedNodeProxy",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "path",
"description": "Path is the URL path to use for the current proxy request to node.",
"required": false,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Node",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "path",
"description": "path to the resource",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "HEAD",
"summary": "connect HEAD requests to proxy of Node",
"nickname": "connectHeadNamespacedNodeProxy",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "path",
"description": "Path is the URL path to use for the current proxy request to node.",
"required": false,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Node",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "path",
"description": "path to the resource",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
},
{
"type": "string",
"method": "OPTIONS",
"summary": "connect OPTIONS requests to proxy of Node",
"nickname": "connectOptionsNamespacedNodeProxy",
"parameters": [
{
"type": "string",
"paramType": "query",
"name": "path",
"description": "Path is the URL path to use for the current proxy request to node.",
"required": false,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "name",
"description": "name of the Node",
"required": true,
"allowMultiple": false
},
{
"type": "string",
"paramType": "path",
"name": "path",
"description": "path to the resource",
"required": true,
"allowMultiple": false
}
],
"produces": [
"*/*"
],
"consumes": [
"*/*"
]
}
]
},
{
"path": "/api/v1/nodes/{name}/status",
"description": "API at /api/v1",

File diff suppressed because it is too large Load Diff

View File

@ -104,6 +104,7 @@ func init() {
DeepCopy_api_NodeCondition,
DeepCopy_api_NodeDaemonEndpoints,
DeepCopy_api_NodeList,
DeepCopy_api_NodeProxyOptions,
DeepCopy_api_NodeResources,
DeepCopy_api_NodeSelector,
DeepCopy_api_NodeSelectorRequirement,
@ -1401,6 +1402,14 @@ func DeepCopy_api_NodeList(in NodeList, out *NodeList, c *conversion.Cloner) err
return nil
}
func DeepCopy_api_NodeProxyOptions(in NodeProxyOptions, out *NodeProxyOptions, c *conversion.Cloner) error {
if err := DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
}
out.Path = in.Path
return nil
}
func DeepCopy_api_NodeResources(in NodeResources, out *NodeResources, c *conversion.Cloner) error {
if in.Capacity != nil {
in, out := in.Capacity, &out.Capacity

View File

@ -108,6 +108,7 @@ func newRESTMapper(externalVersions []unversioned.GroupVersion) meta.RESTMapper
"PodExecOptions",
"PodAttachOptions",
"PodProxyOptions",
"NodeProxyOptions",
"ThirdPartyResource",
"ThirdPartyResourceData",
"ThirdPartyResourceList")

View File

@ -68,6 +68,7 @@ func AddToScheme(scheme *runtime.Scheme) {
&Service{},
&NodeList{},
&Node{},
&NodeProxyOptions{},
&Endpoints{},
&EndpointsList{},
&Binding{},
@ -133,6 +134,7 @@ func (obj *EndpointsList) GetObjectKind() unversioned.ObjectKind { r
func (obj *Node) GetObjectMeta() meta.Object { return &obj.ObjectMeta }
func (obj *Node) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *NodeList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *NodeProxyOptions) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *Binding) GetObjectMeta() meta.Object { return &obj.ObjectMeta }
func (obj *Binding) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *Event) GetObjectMeta() meta.Object { return &obj.ObjectMeta }

View File

@ -39935,6 +39935,264 @@ func (x *PodProxyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder)
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
}
func (x *NodeProxyOptions) CodecEncodeSelf(e *codec1978.Encoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e)
_, _, _ = h, z, r
if x == nil {
r.EncodeNil()
} else {
yym1 := z.EncBinary()
_ = yym1
if false {
} else if z.HasExtensions() && z.EncExt(x) {
} else {
yysep2 := !z.EncBinary()
yy2arr2 := z.EncBasicHandle().StructToArray
var yyq2 [3]bool
_, _, _ = yysep2, yyq2, yy2arr2
const yyr2 bool = false
yyq2[1] = x.Kind != ""
yyq2[2] = x.APIVersion != ""
var yynn2 int
if yyr2 || yy2arr2 {
r.EncodeArrayStart(3)
} else {
yynn2 = 1
for _, b := range yyq2 {
if b {
yynn2++
}
}
r.EncodeMapStart(yynn2)
yynn2 = 0
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
yym4 := z.EncBinary()
_ = yym4
if false {
} else {
r.EncodeString(codecSelferC_UTF81234, string(x.Path))
}
} else {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("Path"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
yym5 := z.EncBinary()
_ = yym5
if false {
} else {
r.EncodeString(codecSelferC_UTF81234, string(x.Path))
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[1] {
yym7 := z.EncBinary()
_ = yym7
if false {
} else {
r.EncodeString(codecSelferC_UTF81234, string(x.Kind))
}
} else {
r.EncodeString(codecSelferC_UTF81234, "")
}
} else {
if yyq2[1] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("kind"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
yym8 := z.EncBinary()
_ = yym8
if false {
} else {
r.EncodeString(codecSelferC_UTF81234, string(x.Kind))
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[2] {
yym10 := z.EncBinary()
_ = yym10
if false {
} else {
r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion))
}
} else {
r.EncodeString(codecSelferC_UTF81234, "")
}
} else {
if yyq2[2] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("apiVersion"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
yym11 := z.EncBinary()
_ = yym11
if false {
} else {
r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion))
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
} else {
z.EncSendContainerState(codecSelfer_containerMapEnd1234)
}
}
}
}
func (x *NodeProxyOptions) CodecDecodeSelf(d *codec1978.Decoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
yym1 := z.DecBinary()
_ = yym1
if false {
} else if z.HasExtensions() && z.DecExt(x) {
} else {
yyct2 := r.ContainerType()
if yyct2 == codecSelferValueTypeMap1234 {
yyl2 := r.ReadMapStart()
if yyl2 == 0 {
z.DecSendContainerState(codecSelfer_containerMapEnd1234)
} else {
x.codecDecodeSelfFromMap(yyl2, d)
}
} else if yyct2 == codecSelferValueTypeArray1234 {
yyl2 := r.ReadArrayStart()
if yyl2 == 0 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
} else {
x.codecDecodeSelfFromArray(yyl2, d)
}
} else {
panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234)
}
}
}
func (x *NodeProxyOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
var yys3Slc = z.DecScratchBuffer() // default slice to decode into
_ = yys3Slc
var yyhl3 bool = l >= 0
for yyj3 := 0; ; yyj3++ {
if yyhl3 {
if yyj3 >= l {
break
}
} else {
if r.CheckBreak() {
break
}
}
z.DecSendContainerState(codecSelfer_containerMapKey1234)
yys3Slc = r.DecodeBytes(yys3Slc, true, true)
yys3 := string(yys3Slc)
z.DecSendContainerState(codecSelfer_containerMapValue1234)
switch yys3 {
case "Path":
if r.TryDecodeAsNil() {
x.Path = ""
} else {
x.Path = string(r.DecodeString())
}
case "kind":
if r.TryDecodeAsNil() {
x.Kind = ""
} else {
x.Kind = string(r.DecodeString())
}
case "apiVersion":
if r.TryDecodeAsNil() {
x.APIVersion = ""
} else {
x.APIVersion = string(r.DecodeString())
}
default:
z.DecStructFieldNotFound(-1, yys3)
} // end switch yys3
} // end for yyj3
z.DecSendContainerState(codecSelfer_containerMapEnd1234)
}
func (x *NodeProxyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
var yyj7 int
var yyb7 bool
var yyhl7 bool = l >= 0
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
} else {
yyb7 = r.CheckBreak()
}
if yyb7 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.Path = ""
} else {
x.Path = string(r.DecodeString())
}
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
} else {
yyb7 = r.CheckBreak()
}
if yyb7 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.Kind = ""
} else {
x.Kind = string(r.DecodeString())
}
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
} else {
yyb7 = r.CheckBreak()
}
if yyb7 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.APIVersion = ""
} else {
x.APIVersion = string(r.DecodeString())
}
for {
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
} else {
yyb7 = r.CheckBreak()
}
if yyb7 {
break
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
z.DecStructFieldNotFound(yyj7-1, "")
}
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
}
func (x *ObjectReference) CodecEncodeSelf(e *codec1978.Encoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e)

View File

@ -2002,6 +2002,14 @@ type PodProxyOptions struct {
Path string
}
// NodeProxyOptions is the query options to a Node's proxy call
type NodeProxyOptions struct {
unversioned.TypeMeta
// Path is the URL path to use for the current proxy request
Path string
}
// ObjectReference contains enough information to let you inspect or modify the referred object.
type ObjectReference struct {
Kind string `json:"kind,omitempty"`

View File

@ -1564,6 +1564,18 @@ func Convert_api_NodeList_To_v1_NodeList(in *api.NodeList, out *NodeList, s conv
return autoConvert_api_NodeList_To_v1_NodeList(in, out, s)
}
func autoConvert_api_NodeProxyOptions_To_v1_NodeProxyOptions(in *api.NodeProxyOptions, out *NodeProxyOptions, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*api.NodeProxyOptions))(in)
}
out.Path = in.Path
return nil
}
func Convert_api_NodeProxyOptions_To_v1_NodeProxyOptions(in *api.NodeProxyOptions, out *NodeProxyOptions, s conversion.Scope) error {
return autoConvert_api_NodeProxyOptions_To_v1_NodeProxyOptions(in, out, s)
}
func autoConvert_api_NodeSpec_To_v1_NodeSpec(in *api.NodeSpec, out *NodeSpec, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*api.NodeSpec))(in)
@ -4804,6 +4816,18 @@ func Convert_v1_NodeList_To_api_NodeList(in *NodeList, out *api.NodeList, s conv
return autoConvert_v1_NodeList_To_api_NodeList(in, out, s)
}
func autoConvert_v1_NodeProxyOptions_To_api_NodeProxyOptions(in *NodeProxyOptions, out *api.NodeProxyOptions, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*NodeProxyOptions))(in)
}
out.Path = in.Path
return nil
}
func Convert_v1_NodeProxyOptions_To_api_NodeProxyOptions(in *NodeProxyOptions, out *api.NodeProxyOptions, s conversion.Scope) error {
return autoConvert_v1_NodeProxyOptions_To_api_NodeProxyOptions(in, out, s)
}
func autoConvert_v1_NodeSpec_To_api_NodeSpec(in *NodeSpec, out *api.NodeSpec, s conversion.Scope) error {
if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found {
defaulting.(func(*NodeSpec))(in)
@ -6519,6 +6543,7 @@ func init() {
autoConvert_api_NodeCondition_To_v1_NodeCondition,
autoConvert_api_NodeDaemonEndpoints_To_v1_NodeDaemonEndpoints,
autoConvert_api_NodeList_To_v1_NodeList,
autoConvert_api_NodeProxyOptions_To_v1_NodeProxyOptions,
autoConvert_api_NodeSpec_To_v1_NodeSpec,
autoConvert_api_NodeStatus_To_v1_NodeStatus,
autoConvert_api_NodeSystemInfo_To_v1_NodeSystemInfo,
@ -6649,6 +6674,7 @@ func init() {
autoConvert_v1_NodeCondition_To_api_NodeCondition,
autoConvert_v1_NodeDaemonEndpoints_To_api_NodeDaemonEndpoints,
autoConvert_v1_NodeList_To_api_NodeList,
autoConvert_v1_NodeProxyOptions_To_api_NodeProxyOptions,
autoConvert_v1_NodeSpec_To_api_NodeSpec,
autoConvert_v1_NodeStatus_To_api_NodeStatus,
autoConvert_v1_NodeSystemInfo_To_api_NodeSystemInfo,

View File

@ -1158,6 +1158,14 @@ func deepCopy_v1_NodeList(in NodeList, out *NodeList, c *conversion.Cloner) erro
return nil
}
func deepCopy_v1_NodeProxyOptions(in NodeProxyOptions, out *NodeProxyOptions, c *conversion.Cloner) error {
if err := deepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
return err
}
out.Path = in.Path
return nil
}
func deepCopy_v1_NodeSpec(in NodeSpec, out *NodeSpec, c *conversion.Cloner) error {
out.PodCIDR = in.PodCIDR
out.ExternalID = in.ExternalID
@ -2679,6 +2687,7 @@ func init() {
deepCopy_v1_NodeCondition,
deepCopy_v1_NodeDaemonEndpoints,
deepCopy_v1_NodeList,
deepCopy_v1_NodeProxyOptions,
deepCopy_v1_NodeSpec,
deepCopy_v1_NodeStatus,
deepCopy_v1_NodeSystemInfo,

View File

@ -50,6 +50,7 @@ func addKnownTypes(scheme *runtime.Scheme) {
&EndpointsList{},
&Node{},
&NodeList{},
&NodeProxyOptions{},
&Binding{},
&Event{},
&EventList{},
@ -100,6 +101,7 @@ func (obj *Endpoints) GetObjectKind() unversioned.ObjectKind { r
func (obj *EndpointsList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *Node) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *NodeList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *NodeProxyOptions) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *Binding) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *Event) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
func (obj *EventList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }

View File

@ -39740,6 +39740,271 @@ func (x *PodProxyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder)
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
}
func (x *NodeProxyOptions) CodecEncodeSelf(e *codec1978.Encoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e)
_, _, _ = h, z, r
if x == nil {
r.EncodeNil()
} else {
yym1 := z.EncBinary()
_ = yym1
if false {
} else if z.HasExtensions() && z.EncExt(x) {
} else {
yysep2 := !z.EncBinary()
yy2arr2 := z.EncBasicHandle().StructToArray
var yyq2 [3]bool
_, _, _ = yysep2, yyq2, yy2arr2
const yyr2 bool = false
yyq2[0] = x.Path != ""
yyq2[1] = x.Kind != ""
yyq2[2] = x.APIVersion != ""
var yynn2 int
if yyr2 || yy2arr2 {
r.EncodeArrayStart(3)
} else {
yynn2 = 0
for _, b := range yyq2 {
if b {
yynn2++
}
}
r.EncodeMapStart(yynn2)
yynn2 = 0
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[0] {
yym4 := z.EncBinary()
_ = yym4
if false {
} else {
r.EncodeString(codecSelferC_UTF81234, string(x.Path))
}
} else {
r.EncodeString(codecSelferC_UTF81234, "")
}
} else {
if yyq2[0] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("path"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
yym5 := z.EncBinary()
_ = yym5
if false {
} else {
r.EncodeString(codecSelferC_UTF81234, string(x.Path))
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[1] {
yym7 := z.EncBinary()
_ = yym7
if false {
} else {
r.EncodeString(codecSelferC_UTF81234, string(x.Kind))
}
} else {
r.EncodeString(codecSelferC_UTF81234, "")
}
} else {
if yyq2[1] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("kind"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
yym8 := z.EncBinary()
_ = yym8
if false {
} else {
r.EncodeString(codecSelferC_UTF81234, string(x.Kind))
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[2] {
yym10 := z.EncBinary()
_ = yym10
if false {
} else {
r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion))
}
} else {
r.EncodeString(codecSelferC_UTF81234, "")
}
} else {
if yyq2[2] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("apiVersion"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
yym11 := z.EncBinary()
_ = yym11
if false {
} else {
r.EncodeString(codecSelferC_UTF81234, string(x.APIVersion))
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
} else {
z.EncSendContainerState(codecSelfer_containerMapEnd1234)
}
}
}
}
func (x *NodeProxyOptions) CodecDecodeSelf(d *codec1978.Decoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
yym1 := z.DecBinary()
_ = yym1
if false {
} else if z.HasExtensions() && z.DecExt(x) {
} else {
yyct2 := r.ContainerType()
if yyct2 == codecSelferValueTypeMap1234 {
yyl2 := r.ReadMapStart()
if yyl2 == 0 {
z.DecSendContainerState(codecSelfer_containerMapEnd1234)
} else {
x.codecDecodeSelfFromMap(yyl2, d)
}
} else if yyct2 == codecSelferValueTypeArray1234 {
yyl2 := r.ReadArrayStart()
if yyl2 == 0 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
} else {
x.codecDecodeSelfFromArray(yyl2, d)
}
} else {
panic(codecSelferOnlyMapOrArrayEncodeToStructErr1234)
}
}
}
func (x *NodeProxyOptions) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
var yys3Slc = z.DecScratchBuffer() // default slice to decode into
_ = yys3Slc
var yyhl3 bool = l >= 0
for yyj3 := 0; ; yyj3++ {
if yyhl3 {
if yyj3 >= l {
break
}
} else {
if r.CheckBreak() {
break
}
}
z.DecSendContainerState(codecSelfer_containerMapKey1234)
yys3Slc = r.DecodeBytes(yys3Slc, true, true)
yys3 := string(yys3Slc)
z.DecSendContainerState(codecSelfer_containerMapValue1234)
switch yys3 {
case "path":
if r.TryDecodeAsNil() {
x.Path = ""
} else {
x.Path = string(r.DecodeString())
}
case "kind":
if r.TryDecodeAsNil() {
x.Kind = ""
} else {
x.Kind = string(r.DecodeString())
}
case "apiVersion":
if r.TryDecodeAsNil() {
x.APIVersion = ""
} else {
x.APIVersion = string(r.DecodeString())
}
default:
z.DecStructFieldNotFound(-1, yys3)
} // end switch yys3
} // end for yyj3
z.DecSendContainerState(codecSelfer_containerMapEnd1234)
}
func (x *NodeProxyOptions) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
var yyj7 int
var yyb7 bool
var yyhl7 bool = l >= 0
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
} else {
yyb7 = r.CheckBreak()
}
if yyb7 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.Path = ""
} else {
x.Path = string(r.DecodeString())
}
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
} else {
yyb7 = r.CheckBreak()
}
if yyb7 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.Kind = ""
} else {
x.Kind = string(r.DecodeString())
}
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
} else {
yyb7 = r.CheckBreak()
}
if yyb7 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.APIVersion = ""
} else {
x.APIVersion = string(r.DecodeString())
}
for {
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
} else {
yyb7 = r.CheckBreak()
}
if yyb7 {
break
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
z.DecStructFieldNotFound(yyj7-1, "")
}
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
}
func (x *ObjectReference) CodecEncodeSelf(e *codec1978.Encoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e)

View File

@ -2431,6 +2431,14 @@ type PodProxyOptions struct {
Path string `json:"path,omitempty"`
}
// NodeProxyOptions is the query options to a Node's proxy call.
type NodeProxyOptions struct {
unversioned.TypeMeta `json:",inline"`
// Path is the URL path to use for the current proxy request to node.
Path string `json:"path,omitempty"`
}
// ObjectReference contains enough information to let you inspect or modify the referred object.
type ObjectReference struct {
// Kind of the referent.

View File

@ -813,6 +813,15 @@ func (NodeList) SwaggerDoc() map[string]string {
return map_NodeList
}
var map_NodeProxyOptions = map[string]string{
"": "NodeProxyOptions is the query options to a Node's proxy call.",
"path": "Path is the URL path to use for the current proxy request to node.",
}
func (NodeProxyOptions) SwaggerDoc() map[string]string {
return map_NodeProxyOptions
}
var map_NodeSelector = map[string]string{
"": "A node selector represents the union of the results of one or more label queries over a set of nodes; that is, it represents the OR of the selectors represented by the node selector terms.",
"nodeSelectorTerms": "Required. A list of node selector terms. The terms are ORed.",

View File

@ -277,13 +277,13 @@ func (m *Master) initV1ResourcesStorage(c *Config) {
endpointsStorage := endpointsetcd.NewREST(dbClient("endpoints"), storageDecorator)
m.endpointRegistry = endpoint.NewRegistry(endpointsStorage)
nodeStorage, nodeStatusStorage := nodeetcd.NewREST(dbClient("nodes"), storageDecorator, c.KubeletClient, m.ProxyTransport)
m.nodeRegistry = node.NewRegistry(nodeStorage)
nodeStorage := nodeetcd.NewStorage(dbClient("nodes"), storageDecorator, c.KubeletClient, m.ProxyTransport)
m.nodeRegistry = node.NewRegistry(nodeStorage.Node)
podStorage := podetcd.NewStorage(
dbClient("pods"),
storageDecorator,
kubeletclient.ConnectionInfoGetter(nodeStorage),
kubeletclient.ConnectionInfoGetter(nodeStorage.Node),
m.ProxyTransport,
)
@ -333,8 +333,9 @@ func (m *Master) initV1ResourcesStorage(c *Config) {
"services": service.NewStorage(m.serviceRegistry, m.endpointRegistry, serviceClusterIPAllocator, serviceNodePortAllocator, m.ProxyTransport),
"services/status": serviceStatusStorage,
"endpoints": endpointsStorage,
"nodes": nodeStorage,
"nodes/status": nodeStatusStorage,
"nodes": nodeStorage.Node,
"nodes/status": nodeStorage.Status,
"nodes/proxy": nodeStorage.Proxy,
"events": eventStorage,
"limitRanges": limitRangeStorage,

View File

@ -28,10 +28,18 @@ import (
"k8s.io/kubernetes/pkg/registry/generic"
etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd"
"k8s.io/kubernetes/pkg/registry/node"
noderest "k8s.io/kubernetes/pkg/registry/node/rest"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage"
)
// NodeStorage includes storage for nodes and all sub resources
type NodeStorage struct {
Node *REST
Status *StatusREST
Proxy *noderest.ProxyREST
}
type REST struct {
*etcdgeneric.Etcd
connection client.ConnectionInfoGetter
@ -53,7 +61,7 @@ func (r *StatusREST) Update(ctx api.Context, obj runtime.Object) (runtime.Object
}
// NewREST returns a RESTStorage object that will work against nodes.
func NewREST(s storage.Interface, storageDecorator generic.StorageDecorator, connection client.ConnectionInfoGetter, proxyTransport http.RoundTripper) (*REST, *StatusREST) {
func NewStorage(s storage.Interface, storageDecorator generic.StorageDecorator, connection client.ConnectionInfoGetter, proxyTransport http.RoundTripper) NodeStorage {
prefix := "/minions"
newListFunc := func() runtime.Object { return &api.NodeList{} }
@ -85,7 +93,13 @@ func NewREST(s storage.Interface, storageDecorator generic.StorageDecorator, con
statusStore := *store
statusStore.UpdateStrategy = node.StatusStrategy
return &REST{store, connection, proxyTransport}, &StatusREST{store: &statusStore}
nodeREST := &REST{store, connection, proxyTransport}
return NodeStorage{
Node: nodeREST,
Status: &StatusREST{store: &statusStore},
Proxy: &noderest.ProxyREST{Store: store, Connection: client.ConnectionInfoGetter(nodeREST), ProxyTransport: proxyTransport},
}
}
// Implement Redirector.

View File

@ -39,8 +39,8 @@ func (fakeConnectionInfoGetter) GetConnectionInfo(ctx api.Context, nodeName stri
func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) {
etcdStorage, server := registrytest.NewEtcdStorage(t, "")
storage, _ := NewREST(etcdStorage, generic.UndecoratedStorage, fakeConnectionInfoGetter{}, nil)
return storage, server
storage := NewStorage(etcdStorage, generic.UndecoratedStorage, fakeConnectionInfoGetter{}, nil)
return storage.Node, server
}
func validNewNode() *api.Node {

View File

@ -0,0 +1,81 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package node
import (
"fmt"
"net/http"
"net/url"
"path"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/capabilities"
"k8s.io/kubernetes/pkg/kubelet/client"
etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd"
genericrest "k8s.io/kubernetes/pkg/registry/generic/rest"
"k8s.io/kubernetes/pkg/registry/node"
"k8s.io/kubernetes/pkg/runtime"
)
// ProxyREST implements the proxy subresource for a Node
type ProxyREST struct {
Store *etcdgeneric.Etcd
Connection client.ConnectionInfoGetter
ProxyTransport http.RoundTripper
}
// Implement Connecter
var _ = rest.Connecter(&ProxyREST{})
var proxyMethods = []string{"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"}
// New returns an empty service resource
func (r *ProxyREST) New() runtime.Object {
return &api.Node{}
}
// ConnectMethods returns the list of HTTP methods that can be proxied
func (r *ProxyREST) ConnectMethods() []string {
return proxyMethods
}
// NewConnectOptions returns versioned resource that represents proxy parameters
func (r *ProxyREST) NewConnectOptions() (runtime.Object, bool, string) {
return &api.NodeProxyOptions{}, true, "path"
}
// Connect returns a handler for the node proxy
func (r *ProxyREST) Connect(ctx api.Context, id string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
proxyOpts, ok := opts.(*api.NodeProxyOptions)
if !ok {
return nil, fmt.Errorf("Invalid options object: %#v", opts)
}
location, transport, err := node.ResourceLocation(r.Store, r.Connection, r.ProxyTransport, ctx, id)
if err != nil {
return nil, err
}
location.Path = path.Join(location.Path, proxyOpts.Path)
// Return a proxy handler that uses the desired transport, wrapped with additional proxy handling (to get URL rewriting, X-Forwarded-* headers, etc)
return newThrottledUpgradeAwareProxyHandler(location, transport, true, false, responder), nil
}
func newThrottledUpgradeAwareProxyHandler(location *url.URL, transport http.RoundTripper, wrapTransport, upgradeRequired bool, responder rest.Responder) *genericrest.UpgradeAwareProxyHandler {
handler := genericrest.NewUpgradeAwareProxyHandler(location, transport, wrapTransport, upgradeRequired, responder)
handler.MaxBytesPerSec = capabilities.Get().PerConnectionBandwidthLimitBytesPerSec
return handler
}

View File

@ -130,7 +130,7 @@ func assertFilesExist(fileNames []string, fileDir string, pod *api.Pod, client *
expectNoError(wait.Poll(time.Second*2, time.Second*60, func() (bool, error) {
failed = []string{}
subResourceProxyAvailable, err := serverVersionGTE(subResourceProxyVersion, client)
subResourceProxyAvailable, err := serverVersionGTE(subResourcePodProxyVersion, client)
if err != nil {
return false, err
}

View File

@ -282,7 +282,7 @@ var _ = Describe("Kubectl client", func() {
Failf("unable to create streaming upload. Error: %s", err)
}
subResourceProxyAvailable, err := serverVersionGTE(subResourceProxyVersion, c)
subResourceProxyAvailable, err := serverVersionGTE(subResourcePodProxyVersion, c)
if err != nil {
Failf("Unable to determine server version. Error: %s", err)
}
@ -1284,7 +1284,7 @@ func getUDData(jpgExpected string, ns string) func(*client.Client, string) error
// getUDData validates data.json in the update-demo (returns nil if data is ok).
return func(c *client.Client, podID string) error {
Logf("validating pod %s", podID)
subResourceProxyAvailable, err := serverVersionGTE(subResourceProxyVersion, c)
subResourceProxyAvailable, err := serverVersionGTE(subResourcePodProxyVersion, c)
if err != nil {
return err
}

View File

@ -153,14 +153,35 @@ func getContainerInfo(c *client.Client, nodeName string, req *stats.StatsRequest
if err != nil {
return nil, err
}
data, err := c.Post().
Prefix("proxy").
Resource("nodes").
Name(fmt.Sprintf("%v:%v", nodeName, ports.KubeletPort)).
Suffix("stats/container").
SetHeader("Content-Type", "application/json").
Body(reqBody).
Do().Raw()
subResourceProxyAvailable, err := serverVersionGTE(subResourceServiceAndNodeProxyVersion, c)
if err != nil {
return nil, err
}
var data []byte
if subResourceProxyAvailable {
data, err = c.Post().
Resource("nodes").
SubResource("proxy").
Name(fmt.Sprintf("%v:%v", nodeName, ports.KubeletPort)).
Suffix("stats/container").
SetHeader("Content-Type", "application/json").
Body(reqBody).
Do().Raw()
} else {
data, err = c.Post().
Prefix("proxy").
Resource("nodes").
Name(fmt.Sprintf("%v:%v", nodeName, ports.KubeletPort)).
Suffix("stats/container").
SetHeader("Content-Type", "application/json").
Body(reqBody).
Do().Raw()
}
if err != nil {
return nil, err
}
var containers map[string]cadvisorapi.ContainerInfo
err = json.Unmarshal(data, &containers)
@ -316,21 +337,41 @@ type usageDataPerContainer struct {
}
// Performs a get on a node proxy endpoint given the nodename and rest client.
func nodeProxyRequest(c *client.Client, node, endpoint string) client.Result {
return c.Get().
Prefix("proxy").
Resource("nodes").
Name(fmt.Sprintf("%v:%v", node, ports.KubeletPort)).
Suffix(endpoint).
Do()
func nodeProxyRequest(c *client.Client, node, endpoint string) (client.Result, error) {
subResourceProxyAvailable, err := serverVersionGTE(subResourceServiceAndNodeProxyVersion, c)
if err != nil {
return client.Result{}, err
}
var result client.Result
if subResourceProxyAvailable {
result = c.Get().
Resource("nodes").
SubResource("proxy").
Name(fmt.Sprintf("%v:%v", node, ports.KubeletPort)).
Suffix(endpoint).
Do()
} else {
result = c.Get().
Prefix("proxy").
Resource("nodes").
Name(fmt.Sprintf("%v:%v", node, ports.KubeletPort)).
Suffix(endpoint).
Do()
}
return result, nil
}
// Retrieve metrics from the kubelet server of the given node.
func getKubeletMetricsThroughProxy(c *client.Client, node string) (string, error) {
metric, err := nodeProxyRequest(c, node, "metrics").Raw()
client, err := nodeProxyRequest(c, node, "metrics")
if err != nil {
return "", err
}
metric, errRaw := client.Raw()
if errRaw != nil {
return "", err
}
return string(metric), nil
}
@ -354,7 +395,11 @@ func getKubeletMetricsThroughNode(nodeName string) (string, error) {
// pods/containers), but do not contain the full spec.
func GetKubeletPods(c *client.Client, node string) (*api.PodList, error) {
result := &api.PodList{}
if err := nodeProxyRequest(c, node, "runningpods").Into(result); err != nil {
client, err := nodeProxyRequest(c, node, "runningpods")
if err != nil {
return &api.PodList{}, err
}
if err = client.Into(result); err != nil {
return &api.PodList{}, err
}
return result, nil

View File

@ -117,7 +117,7 @@ func testPreStop(c *client.Client, ns string) {
// Validate that the server received the web poke.
err = wait.Poll(time.Second*5, time.Second*60, func() (bool, error) {
subResourceProxyAvailable, err := serverVersionGTE(subResourceProxyVersion, c)
subResourceProxyAvailable, err := serverVersionGTE(subResourcePodProxyVersion, c)
if err != nil {
return false, err
}

View File

@ -52,11 +52,13 @@ func proxyContext(version string) {
prefix := "/api/" + version
// Port here has to be kept in sync with default kubelet port.
It("should proxy logs on node with explicit kubelet port [Conformance]", func() { nodeProxyTest(f, version, ":10250/logs/") })
It("should proxy logs on node with explicit kubelet port [Conformance]", func() { nodeProxyTest(f, prefix+"/proxy/nodes/", ":10250/logs/") })
It("should proxy logs on node [Conformance]", func() { nodeProxyTest(f, prefix+"/proxy/nodes/", "/logs/") })
It("should proxy to cadvisor [Conformance]", func() { nodeProxyTest(f, prefix+"/proxy/nodes/", ":4194/containers/") })
It("should proxy logs on node [Conformance]", func() { nodeProxyTest(f, version, "/logs/") })
It("should proxy to cadvisor [Conformance]", func() { nodeProxyTest(f, version, ":4194/containers/") })
It("should proxy logs on node with explicit kubelet port using proxy subresource [Conformance]", func() { nodeProxyTest(f, prefix+"/nodes/", ":10250/proxy/logs/") })
It("should proxy logs on node using proxy subresource [Conformance]", func() { nodeProxyTest(f, prefix+"/nodes/", "/proxy/logs/") })
It("should proxy to cadvisor using proxy subresource [Conformance]", func() { nodeProxyTest(f, prefix+"/nodes/", ":4194/proxy/containers/") })
It("should proxy through a service and a pod [Conformance]", func() {
labels := map[string]string{"proxy-service-target": "true"}
@ -258,15 +260,14 @@ func pickNode(c *client.Client) (string, error) {
return nodes.Items[0].Name, nil
}
func nodeProxyTest(f *Framework, version, nodeDest string) {
prefix := "/api/" + version
func nodeProxyTest(f *Framework, prefix, nodeDest string) {
node, err := pickNode(f.Client)
Expect(err).NotTo(HaveOccurred())
// TODO: Change it to test whether all requests succeeded when requests
// not reaching Kubelet issue is debugged.
serviceUnavailableErrors := 0
for i := 0; i < proxyAttempts; i++ {
_, status, d, err := doProxy(f, prefix+"/proxy/nodes/"+node+nodeDest)
_, status, d, err := doProxy(f, prefix+node+nodeDest)
if status == http.StatusServiceUnavailable {
Logf("Failed proxying node logs due to service unavailable: %v", err)
time.Sleep(time.Second)

View File

@ -118,7 +118,8 @@ const (
//
// TODO(ihmccreery): remove once we don't care about v1.0 anymore, (tentatively
// in v1.3).
var subResourceProxyVersion = version.MustParse("v1.1.0")
var subResourcePodProxyVersion = version.MustParse("v1.1.0")
var subResourceServiceAndNodeProxyVersion = version.MustParse("v1.2.0")
type CloudConfig struct {
ProjectID string
@ -995,7 +996,7 @@ func (r podProxyResponseChecker) checkAllResponses() (done bool, err error) {
if !isElementOf(pod.UID, currentPods) {
return false, fmt.Errorf("pod with UID %s is no longer a member of the replica set. Must have been restarted for some reason. Current replica set: %v", pod.UID, currentPods)
}
subResourceProxyAvailable, err := serverVersionGTE(subResourceProxyVersion, r.c)
subResourceProxyAvailable, err := serverVersionGTE(subResourcePodProxyVersion, r.c)
if err != nil {
return false, err
}

View File

@ -238,7 +238,7 @@ func testVolumeClient(client *client.Client, config VolumeTestConfig, volume api
expectNoError(waitForPodRunningInNamespace(client, clientPod.Name, config.namespace))
By("reading a web page from the client")
subResourceProxyAvailable, err := serverVersionGTE(subResourceProxyVersion, client)
subResourceProxyAvailable, err := serverVersionGTE(subResourcePodProxyVersion, client)
if err != nil {
Failf("Failed to get server version: %v", err)
}