什么是RESTMapper

先来看来什么是RESTMapper。RESTMapper是一个interface,定义在/pkg/meta/interfaces.go中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// RESTMapper allows clients to map resources to kind, and map kind and version
// to interfaces for manipulating those objects. It is primarily intended for
// consumers of Kubernetes compatible REST APIs as defined in docs/devel/api-conventions.md.
//
// The Kubernetes API provides versioned resources and object kinds which are scoped
// to API groups. In other words, kinds and resources should not be assumed to be
// unique across groups.
//
// TODO: split into sub-interfaces
type RESTMapper interface {
// KindFor takes a partial resource and returns the single match. Returns an error if there are multiple matches
KindFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error)
// KindsFor takes a partial resource and returns the list of potential kinds in priority order
KindsFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionKind, error)
// ResourceFor takes a partial resource and returns the single match. Returns an error if there are multiple matches
ResourceFor(input unversioned.GroupVersionResource) (unversioned.GroupVersionResource, error)
// ResourcesFor takes a partial resource and returns the list of potential resource in priority order
ResourcesFor(input unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error)
// RESTMapping identifies a preferred resource mapping for the provided group kind.
RESTMapping(gk unversioned.GroupKind, versions ...string) (*RESTMapping, error)
// RESTMappings returns all resource mappings for the provided group kind.
RESTMappings(gk unversioned.GroupKind) ([]*RESTMapping, error)
AliasesForResource(resource string) ([]string, bool)
ResourceSingularizer(resource string) (singular string, err error)
}

关于RESTMapper的注释非常重要,“RESTMapper allows clients to map resources to kind, and map kind and version to interfaces for manipulating those objects”。也就是说,RESTMapper映射是指GVR(GroupVersionResource)和GVK(GroupVersionKind)的关系,可以通过GVR找到合适的GVK,并可以通过GVK生成一个RESTMapping。

什么是RESTMapping

再来看来RESTMapping,同样定义在/pkg/api/meta/interfaces.go中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// RESTMapping contains the information needed to deal with objects of a specific
// resource and kind in a RESTful manner.
type RESTMapping struct {
// Resource is a string representing the name of this resource as a REST client would see it
//***此处为string,而非GVR***//
Resource string
GroupVersionKind unversioned.GroupVersionKind
// Scope contains the information needed to deal with REST Resources that are in a resource hierarchy
Scope RESTScope
runtime.ObjectConvertor
MetadataAccessor
}

RESTMapping包含Resource名称,及其对应的GVK,还有一个Scope(标明资源是否为root或者namespaced),还有一个Convertor用来转换该GVK对应的Object和一个MetadataAccessor用来提取Object的meta信息。

那么RESTMapping怎么用呢?

比如/pkg/apiserver/api_installer.go中就有使用到RESTMapping中的Scope用来生成合适的URL(RESTScopeNameRoot和RESTScopeNameNamespace处理不同,详见以后对Apiserver的分析)。

再比如/pkg/kubectl/resource_printer.go中的VersionedPrinter中的converter也是来自RESTMapping中的Convertor(以后和Scheme一起分析)。

什么是RESTScope

这里一并把RESTScope介绍掉,因为RESTScope接口也定义在/pkg/api/meta/interfaces.go中:

1
2
3
4
5
6
7
8
9
10
11
12
// RESTScope contains the information needed to deal with REST resources that are in a resource hierarchy
type RESTScope interface {
// Name of the scope
Name() RESTScopeName
// ParamName is the optional name of the parameter that should be inserted in the resource url
// If empty, no param will be inserted
ParamName() string
// ArgumentName is the optional name that should be used for the variable holding the value.
ArgumentName() string
// ParamDescription is the optional description to use to document the parameter in api documentation
ParamDescription() string
}

RESTScope具体由restScope之实现。restScope定义在/pkg/api/meta/restmapper.go中,比较简单,这里就不在详细分析。目前有两种RESTScope:

1
2
3
4
5
6
7
8
9
10
var RESTScopeNamespace = &restScope{
name: RESTScopeNameNamespace,
paramName: "namespaces",
argumentName: "namespace",
paramDescription: "object name and auth scope, such as for teams and projects",
}
var RESTScopeRoot = &restScope{
name: RESTScopeNameRoot,
}

这里的RESTScopeNameNamespace和RESTScopeNameRoot定义在/pkg/api/meta/interfaces.go中:

1
2
3
4
const (
RESTScopeNameNamespace RESTScopeName = "namespace"
RESTScopeNameRoot RESTScopeName = "root"
)

这里只要记住,RESTScopeNamespace表明该资源是在Namespace下的,如pods,rc等;RESTScopeRoot标明资源是全局的,如nodes, pv等。

DefaultRESTMapper

DefaultRESTMapper实现了RESTMapper interface。为什么称为DefaultRESTMapper呢,因为DefaultRESTMapper定义了defaultGroupVersions。DefaultRESTMapper定义在/pkg/api/metamapper.go中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//***DefaultRESTMapper中的resource是指GVR,kind是指GVK***//
//***singular和Plural都是GVR***//
type DefaultRESTMapper struct {
defaultGroupVersions []unversioned.GroupVersion
resourceToKind map[unversioned.GroupVersionResource]unversioned.GroupVersionKind
kindToPluralResource map[unversioned.GroupVersionKind]unversioned.GroupVersionResource
kindToScope map[unversioned.GroupVersionKind]RESTScope
singularToPlural map[unversioned.GroupVersionResource]unversioned.GroupVersionResource
pluralToSingular map[unversioned.GroupVersionResource]unversioned.GroupVersionResource
interfacesFunc VersionInterfacesFunc
// aliasToResource is used for mapping aliases to resources
aliasToResource map[string][]string
}

现在来详细分析DefaultRESTMapper的字段的涵义。

  • defaultGroupVersions: 默认的GroupVersion,如v1,apps/v1beta1等,一般一个DefaultRESTMapper只设一个默认的GroupVersion;
  • resourceToKind:GVR(单数,复数)到GVK的map;
  • kindToPluralResource:GVK到GVR(复数)的map;
  • kindToScope:GVK到Scope的map;
  • singularToPlural:GVR(单数)到GVR(复数)的map;
  • interfacesFunc:用来产生Convertor和MetadataAccessor,具体实现为/pkg/api/install/install.go中的interfacesFor()函数。
  • aliasToResource:管理别名,但貌似系统中没怎么用这功能。
    这里要说明白的是,DefaultRESTMapper中的Resource有单数和复数的区别。但为什么要有单复数的区别,现在还不知道。

下面来分析DefaultRESTMapper的重要方法的实现。

NewDefaultRESTMapper()

NewDefaultRESTMapper()生成一个新的DefaultRESTMapper。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func NewDefaultRESTMapper(defaultGroupVersions []unversioned.GroupVersion, f VersionInterfacesFunc) *DefaultRESTMapper {
resourceToKind := make(map[unversioned.GroupVersionResource]unversioned.GroupVersionKind)
kindToPluralResource := make(map[unversioned.GroupVersionKind]unversioned.GroupVersionResource)
kindToScope := make(map[unversioned.GroupVersionKind]RESTScope)
singularToPlural := make(map[unversioned.GroupVersionResource]unversioned.GroupVersionResource)
pluralToSingular := make(map[unversioned.GroupVersionResource]unversioned.GroupVersionResource)
aliasToResource := make(map[string][]string)
// TODO: verify name mappings work correctly when versions differ
return &DefaultRESTMapper{
resourceToKind: resourceToKind,
kindToPluralResource: kindToPluralResource,
kindToScope: kindToScope,
defaultGroupVersions: defaultGroupVersions,
singularToPlural: singularToPlural,
pluralToSingular: pluralToSingular,
aliasToResource: aliasToResource,
interfacesFunc: f,
}
}

NewDefaultRESTMapper()会被/pkg/api/mapper.go的NewDefaultRESTMapperFromScheme()函数调用。NewDefaultRESTMapperFromScheme()是生成DefaultRESTMapper的入口。这就很方便我们看系统中有多少个DefaultRESTMapper了:
[v1], [apps/v1beta1], [authentication.k8s.io/v1beta1], [authorization.k8s.io/v1beta1], [autoscaling/v1], [batch/v1 batch/v2alpha1], [certificates.k8s.io/v1alpha1], [componentconfig/v1alpha1], [extensions/v1beta1], [policy/v1beta1], [rbac.authorization.k8s.io/v1alpha1], [storage.k8s.io/v1beta1], [imagepolicy.k8s.io/v1alpha1]

Add()

Add()方法主要是把GVK和GVK对应的scope加入到DefaultRESTMapper对应的字段中。其中KindToResource()返回GVK对应的GVR(复数)和GVR(单数)。

1
2
3
4
5
6
7
8
9
10
11
12
13
//***添加GVK***//
func (m *DefaultRESTMapper) Add(kind unversioned.GroupVersionKind, scope RESTScope) {
plural, singular := KindToResource(kind)
m.singularToPlural[singular] = plural
m.pluralToSingular[plural] = singular
m.resourceToKind[singular] = kind
m.resourceToKind[plural] = kind
m.kindToPluralResource[kind] = plural
m.kindToScope[kind] = scope
}

ResourceFor()

ResourceFor()通过GVR(信息不一定要全)找到一个最合适的注册的GVR。规则如下:

  • 如果参数GVR没有有Resource,则返回错误。
  • 如果参数GVR限定Group,Version和Resource,则匹配Group,Version和Resource;
  • 如果参数GVR限定Group和Resource,则匹配Group和Resource;
  • 如果参数GVR限定Version和Resource,则匹配Version和Resource;
  • 如果参数GVR只有Resource,则匹配Resource。
  • 如果系统中存在多个匹配,则返回错误(系统现在还不支持在不同的Group中定义相同的type)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//***找到最匹配的注册GVR***//
func (m *DefaultRESTMapper) ResourceFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionResource, error) {
resources, err := m.ResourcesFor(resource)
if err != nil {
return unversioned.GroupVersionResource{}, err
}
if len(resources) == 1 {
return resources[0], nil
}
return unversioned.GroupVersionResource{}, &AmbiguousResourceError{PartialResource: resource, MatchingResources: resources}
}
//***寻找匹配input GVR的注册GVR***//
func (m *DefaultRESTMapper) ResourcesFor(input unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) {
//***获取GVR***//
resource := coerceResourceForMatching(input)
hasResource := len(resource.Resource) > 0
hasGroup := len(resource.Group) > 0
hasVersion := len(resource.Version) > 0
if !hasResource {
return nil, fmt.Errorf("a resource must be present, got: %v", resource)
}
ret := []unversioned.GroupVersionResource{}
switch {
//***限定group和version,则比较GVR***//
case hasGroup && hasVersion:
// fully qualified. Find the exact match
for plural, singular := range m.pluralToSingular {
if singular == resource {
ret = append(ret, plural)
break
}
if plural == resource {
ret = append(ret, plural)
break
}
}
//***只限定group,则比较GR***//
case hasGroup:
// given a group, prefer an exact match. If you don't find one, resort to a prefix match on group
foundExactMatch := false
requestedGroupResource := resource.GroupResource()
for plural, singular := range m.pluralToSingular {
if singular.GroupResource() == requestedGroupResource {
foundExactMatch = true
ret = append(ret, plural)
}
if plural.GroupResource() == requestedGroupResource {
foundExactMatch = true
ret = append(ret, plural)
}
}
// if you didn't find an exact match, match on group prefixing. This allows storageclass.storage to match
// storageclass.storage.k8s.io
if !foundExactMatch {
for plural, singular := range m.pluralToSingular {
if !strings.HasPrefix(plural.Group, requestedGroupResource.Group) {
continue
}
if singular.Resource == requestedGroupResource.Resource {
ret = append(ret, plural)
}
if plural.Resource == requestedGroupResource.Resource {
ret = append(ret, plural)
}
}
}
//***限定version,则比较version和resource***//
case hasVersion:
for plural, singular := range m.pluralToSingular {
if singular.Version == resource.Version && singular.Resource == resource.Resource {
ret = append(ret, plural)
}
if plural.Version == resource.Version && plural.Resource == resource.Resource {
ret = append(ret, plural)
}
}
//***只比较Resource***//
default:
for plural, singular := range m.pluralToSingular {
if singular.Resource == resource.Resource {
ret = append(ret, plural)
}
if plural.Resource == resource.Resource {
ret = append(ret, plural)
}
}
}
if len(ret) == 0 {
return nil, &NoResourceMatchError{PartialResource: resource}
}
sort.Sort(resourceByPreferredGroupVersion{ret, m.defaultGroupVersions})
return ret, nil
}

KindFor()

KindFor()通过GVR(信息不一定要全)找到一个最合适的注册的GVK。规则和ResourceFor()一样:

  • 如果参数GVR没有有Resource,则返回错误。
  • 如果参数GVR限定Group,Version和Resource,则匹配Group,Version和Resource;
  • 如果参数GVR限定Group和Resource,则匹配Group和Resource;
  • 如果参数GVR限定Version和Resource,则匹配Version和Resource;
  • 如果参数GVR只有Resource,则匹配Resource。
  • 如果系统中存在多个匹配,则返回错误(系统现在还不支持在不同的Group中定义相同的type)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
//***获取与GVR最匹配的GVK***//
func (m *DefaultRESTMapper) KindFor(resource unversioned.GroupVersionResource) (unversioned.GroupVersionKind, error) {
kinds, err := m.KindsFor(resource)
if err != nil {
return unversioned.GroupVersionKind{}, err
}
if len(kinds) == 1 {
return kinds[0], nil
}
return unversioned.GroupVersionKind{}, &AmbiguousResourceError{PartialResource: resource, MatchingKinds: kinds}
}
//***通过GVR获取GVK***//
func (m *DefaultRESTMapper) KindsFor(input unversioned.GroupVersionResource) ([]unversioned.GroupVersionKind, error) {
resource := coerceResourceForMatching(input)
hasResource := len(resource.Resource) > 0
hasGroup := len(resource.Group) > 0
hasVersion := len(resource.Version) > 0
if !hasResource {
return nil, fmt.Errorf("a resource must be present, got: %v", resource)
}
ret := []unversioned.GroupVersionKind{}
switch {
// fully qualified. Find the exact match
case hasGroup && hasVersion:
kind, exists := m.resourceToKind[resource]
if exists {
ret = append(ret, kind)
}
case hasGroup:
foundExactMatch := false
requestedGroupResource := resource.GroupResource()
for currResource, currKind := range m.resourceToKind {
if currResource.GroupResource() == requestedGroupResource {
foundExactMatch = true
ret = append(ret, currKind)
}
}
// if you didn't find an exact match, match on group prefixing. This allows storageclass.storage to match
// storageclass.storage.k8s.io
if !foundExactMatch {
for currResource, currKind := range m.resourceToKind {
if !strings.HasPrefix(currResource.Group, requestedGroupResource.Group) {
continue
}
if currResource.Resource == requestedGroupResource.Resource {
ret = append(ret, currKind)
}
}
}
case hasVersion:
for currResource, currKind := range m.resourceToKind {
if currResource.Version == resource.Version && currResource.Resource == resource.Resource {
ret = append(ret, currKind)
}
}
default:
for currResource, currKind := range m.resourceToKind {
if currResource.Resource == resource.Resource {
ret = append(ret, currKind)
}
}
}
if len(ret) == 0 {
return nil, &NoResourceMatchError{PartialResource: input}
}
sort.Sort(kindByPreferredGroupVersion{ret, m.defaultGroupVersions})
return ret, nil
}

RESTMapping()

RESTMapping()的参数是GK和versions,通常的做法是把一个GVK直接拆成GK和Version,然后获取mapping。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// RESTMapping returns a struct representing the resource path and conversion interfaces a
// RESTClient should use to operate on the provided group/kind in order of versions. If a version search
// order is not provided, the search order provided to DefaultRESTMapper will be used to resolve which
// version should be used to access the named group/kind.
// TODO: consider refactoring to use RESTMappings in a way that preserves version ordering and preference
//***返回RESTMapping***//
func (m *DefaultRESTMapper) RESTMapping(gk unversioned.GroupKind, versions ...string) (*RESTMapping, error) {
// Pick an appropriate version
var gvk *unversioned.GroupVersionKind
hadVersion := false
//***构造GVK***//
for _, version := range versions {
if len(version) == 0 || version == runtime.APIVersionInternal {
continue
}
currGVK := gk.WithVersion(version)
hadVersion = true
//***一旦找到合适的version,则break***//
if _, ok := m.kindToPluralResource[currGVK]; ok {
gvk = &currGVK
break
}
}
// Use the default preferred versions
if !hadVersion && (gvk == nil) {
for _, gv := range m.defaultGroupVersions {
if gv.Group != gk.Group {
continue
}
currGVK := gk.WithVersion(gv.Version)
if _, ok := m.kindToPluralResource[currGVK]; ok {
gvk = &currGVK
break
}
}
}
if gvk == nil {
return nil, &NoKindMatchError{PartialKind: gk.WithVersion("")}
}
// Ensure we have a REST mapping
//***按照GVK获取resource***//
//***从原注释看,GVK和GVR的关系叫做REST mapping***//
resource, ok := m.kindToPluralResource[*gvk]
if !ok {
found := []unversioned.GroupVersion{}
for _, gv := range m.defaultGroupVersions {
if _, ok := m.kindToPluralResource[*gvk]; ok {
found = append(found, gv)
}
}
if len(found) > 0 {
return nil, fmt.Errorf("object with kind %q exists in versions %v, not %v", gvk.Kind, found, gvk.GroupVersion().String())
}
return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported object", gvk.GroupVersion().String(), gvk.Kind)
}
// Ensure we have a REST scope
//***获取scope***//
scope, ok := m.kindToScope[*gvk]
if !ok {
return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", gvk.GroupVersion().String(), gvk.Kind)
}
interfaces, err := m.interfacesFunc(gvk.GroupVersion())
if err != nil {
return nil, fmt.Errorf("the provided version %q has no relevant versions: %v", gvk.GroupVersion().String(), err)
}
//***构造RESTMapping***//
//***RESTMapping中有resource(名称), GVK, scope, convertor, accessor***//
retVal := &RESTMapping{
Resource: resource.Resource,
GroupVersionKind: *gvk,
Scope: scope,
ObjectConvertor: interfaces.ObjectConvertor,
MetadataAccessor: interfaces.MetadataAccessor,
}
return retVal, nil
}

RESTMapping()的流程如下:

  • 构造GVK:使用GK和Versions,或GK和DefaultGroupVersions,构造GVK;
  • 获取GVR:从kindToPluralResource中获取GVR;
  • 获取scope:从kindToScope中获取scope;
  • 使用interfacesFunc()获取Convertor和MetadataAccessor;
  • 组装成RESTMapping并返回。

总结

RESTMapper可以从GVR获取GVK,并生成一个RESTMapping来处理该GVR。RESTMapping中有Resource名称,GVK,Scope,Convertor,Accessor等和GVR有关的信息。