storage_factory.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. /*
  2. Copyright 2016 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 genericapiserver
  14. import (
  15. "fmt"
  16. "mime"
  17. "strings"
  18. "k8s.io/kubernetes/pkg/api/unversioned"
  19. "k8s.io/kubernetes/pkg/runtime"
  20. "k8s.io/kubernetes/pkg/runtime/serializer/recognizer"
  21. "k8s.io/kubernetes/pkg/storage/storagebackend"
  22. "k8s.io/kubernetes/pkg/util/sets"
  23. "github.com/golang/glog"
  24. )
  25. // StorageFactory is the interface to locate the storage for a given GroupResource
  26. type StorageFactory interface {
  27. // New finds the storage destination for the given group and resource. It will
  28. // return an error if the group has no storage destination configured.
  29. NewConfig(groupResource unversioned.GroupResource) (*storagebackend.Config, error)
  30. // ResourcePrefix returns the overridden resource prefix for the GroupResource
  31. // This allows for cohabitation of resources with different native types and provides
  32. // centralized control over the shape of etcd directories
  33. ResourcePrefix(groupResource unversioned.GroupResource) string
  34. // Backends gets all backends for all registered storage destinations.
  35. // Used for getting all instances for health validations.
  36. Backends() []string
  37. }
  38. // DefaultStorageFactory takes a GroupResource and returns back its storage interface. This result includes:
  39. // 1. Merged etcd config, including: auth, server locations, prefixes
  40. // 2. Resource encodings for storage: group,version,kind to store as
  41. // 3. Cohabitating default: some resources like hpa are exposed through multiple APIs. They must agree on 1 and 2
  42. type DefaultStorageFactory struct {
  43. // StorageConfig describes how to create a storage backend in general.
  44. // Its authentication information will be used for every storage.Interface returned.
  45. StorageConfig storagebackend.Config
  46. Overrides map[unversioned.GroupResource]groupResourceOverrides
  47. // DefaultMediaType is the media type used to store resources. If it is not set, "application/json" is used.
  48. DefaultMediaType string
  49. // DefaultSerializer is used to create encoders and decoders for the storage.Interface.
  50. DefaultSerializer runtime.StorageSerializer
  51. // ResourceEncodingConfig describes how to encode a particular GroupVersionResource
  52. ResourceEncodingConfig ResourceEncodingConfig
  53. // APIResourceConfigSource indicates whether the *storage* is enabled, NOT the API
  54. // This is discrete from resource enablement because those are separate concerns. How this source is configured
  55. // is left to the caller.
  56. APIResourceConfigSource APIResourceConfigSource
  57. // newStorageCodecFn exists to be overwritten for unit testing.
  58. newStorageCodecFn func(storageMediaType string, ns runtime.StorageSerializer, storageVersion, memoryVersion unversioned.GroupVersion, config storagebackend.Config) (codec runtime.Codec, err error)
  59. }
  60. type groupResourceOverrides struct {
  61. // etcdLocation contains the list of "special" locations that are used for particular GroupResources
  62. // These are merged on top of the StorageConfig when requesting the storage.Interface for a given GroupResource
  63. etcdLocation []string
  64. // etcdPrefix is the base location for a GroupResource.
  65. etcdPrefix string
  66. // etcdResourcePrefix is the location to use to store a particular type under the `etcdPrefix` location
  67. // If empty, the default mapping is used. If the default mapping doesn't contain an entry, it will use
  68. // the ToLowered name of the resource, not including the group.
  69. etcdResourcePrefix string
  70. // mediaType is the desired serializer to choose. If empty, the default is chosen.
  71. mediaType string
  72. // serializer contains the list of "special" serializers for a GroupResource. Resource=* means for the entire group
  73. serializer runtime.StorageSerializer
  74. // cohabitatingResources keeps track of which resources must be stored together. This happens when we have multiple ways
  75. // of exposing one set of concepts. autoscaling.HPA and extensions.HPA as a for instance
  76. // The order of the slice matters! It is the priority order of lookup for finding a storage location
  77. cohabitatingResources []unversioned.GroupResource
  78. }
  79. var _ StorageFactory = &DefaultStorageFactory{}
  80. const AllResources = "*"
  81. func NewDefaultStorageFactory(config storagebackend.Config, defaultMediaType string, defaultSerializer runtime.StorageSerializer, resourceEncodingConfig ResourceEncodingConfig, resourceConfig APIResourceConfigSource) *DefaultStorageFactory {
  82. if len(defaultMediaType) == 0 {
  83. defaultMediaType = runtime.ContentTypeJSON
  84. }
  85. return &DefaultStorageFactory{
  86. StorageConfig: config,
  87. Overrides: map[unversioned.GroupResource]groupResourceOverrides{},
  88. DefaultMediaType: defaultMediaType,
  89. DefaultSerializer: defaultSerializer,
  90. ResourceEncodingConfig: resourceEncodingConfig,
  91. APIResourceConfigSource: resourceConfig,
  92. newStorageCodecFn: NewStorageCodec,
  93. }
  94. }
  95. func (s *DefaultStorageFactory) SetEtcdLocation(groupResource unversioned.GroupResource, location []string) {
  96. overrides := s.Overrides[groupResource]
  97. overrides.etcdLocation = location
  98. s.Overrides[groupResource] = overrides
  99. }
  100. func (s *DefaultStorageFactory) SetEtcdPrefix(groupResource unversioned.GroupResource, prefix string) {
  101. overrides := s.Overrides[groupResource]
  102. overrides.etcdPrefix = prefix
  103. s.Overrides[groupResource] = overrides
  104. }
  105. // SetResourceEtcdPrefix sets the prefix for a resource, but not the base-dir. You'll end up in `etcdPrefix/resourceEtcdPrefix`.
  106. func (s *DefaultStorageFactory) SetResourceEtcdPrefix(groupResource unversioned.GroupResource, prefix string) {
  107. overrides := s.Overrides[groupResource]
  108. overrides.etcdResourcePrefix = prefix
  109. s.Overrides[groupResource] = overrides
  110. }
  111. func (s *DefaultStorageFactory) SetSerializer(groupResource unversioned.GroupResource, mediaType string, serializer runtime.StorageSerializer) {
  112. overrides := s.Overrides[groupResource]
  113. overrides.mediaType = mediaType
  114. overrides.serializer = serializer
  115. s.Overrides[groupResource] = overrides
  116. }
  117. // AddCohabitatingResources links resources together the order of the slice matters! its the priority order of lookup for finding a storage location
  118. func (s *DefaultStorageFactory) AddCohabitatingResources(groupResources ...unversioned.GroupResource) {
  119. for _, groupResource := range groupResources {
  120. overrides := s.Overrides[groupResource]
  121. overrides.cohabitatingResources = groupResources
  122. s.Overrides[groupResource] = overrides
  123. }
  124. }
  125. func getAllResourcesAlias(resource unversioned.GroupResource) unversioned.GroupResource {
  126. return unversioned.GroupResource{Group: resource.Group, Resource: AllResources}
  127. }
  128. func (s *DefaultStorageFactory) getStorageGroupResource(groupResource unversioned.GroupResource) unversioned.GroupResource {
  129. for _, potentialStorageResource := range s.Overrides[groupResource].cohabitatingResources {
  130. if s.APIResourceConfigSource.AnyVersionOfResourceEnabled(potentialStorageResource) {
  131. return potentialStorageResource
  132. }
  133. }
  134. return groupResource
  135. }
  136. // New finds the storage destination for the given group and resource. It will
  137. // return an error if the group has no storage destination configured.
  138. func (s *DefaultStorageFactory) NewConfig(groupResource unversioned.GroupResource) (*storagebackend.Config, error) {
  139. chosenStorageResource := s.getStorageGroupResource(groupResource)
  140. groupOverride := s.Overrides[getAllResourcesAlias(chosenStorageResource)]
  141. exactResourceOverride := s.Overrides[chosenStorageResource]
  142. overriddenEtcdLocations := []string{}
  143. if len(groupOverride.etcdLocation) > 0 {
  144. overriddenEtcdLocations = groupOverride.etcdLocation
  145. }
  146. if len(exactResourceOverride.etcdLocation) > 0 {
  147. overriddenEtcdLocations = exactResourceOverride.etcdLocation
  148. }
  149. etcdPrefix := s.StorageConfig.Prefix
  150. if len(groupOverride.etcdPrefix) > 0 {
  151. etcdPrefix = groupOverride.etcdPrefix
  152. }
  153. if len(exactResourceOverride.etcdPrefix) > 0 {
  154. etcdPrefix = exactResourceOverride.etcdPrefix
  155. }
  156. etcdMediaType := s.DefaultMediaType
  157. if len(groupOverride.mediaType) != 0 {
  158. etcdMediaType = groupOverride.mediaType
  159. }
  160. if len(exactResourceOverride.mediaType) != 0 {
  161. etcdMediaType = exactResourceOverride.mediaType
  162. }
  163. etcdSerializer := s.DefaultSerializer
  164. if groupOverride.serializer != nil {
  165. etcdSerializer = groupOverride.serializer
  166. }
  167. if exactResourceOverride.serializer != nil {
  168. etcdSerializer = exactResourceOverride.serializer
  169. }
  170. // operate on copy
  171. config := s.StorageConfig
  172. config.Prefix = etcdPrefix
  173. if len(overriddenEtcdLocations) > 0 {
  174. config.ServerList = overriddenEtcdLocations
  175. }
  176. storageEncodingVersion, err := s.ResourceEncodingConfig.StorageEncodingFor(chosenStorageResource)
  177. if err != nil {
  178. return nil, err
  179. }
  180. internalVersion, err := s.ResourceEncodingConfig.InMemoryEncodingFor(groupResource)
  181. if err != nil {
  182. return nil, err
  183. }
  184. codec, err := s.newStorageCodecFn(etcdMediaType, etcdSerializer, storageEncodingVersion, internalVersion, config)
  185. if err != nil {
  186. return nil, err
  187. }
  188. glog.V(3).Infof("storing %v in %v, reading as %v from %v", groupResource, storageEncodingVersion, internalVersion, config)
  189. config.Codec = codec
  190. return &config, nil
  191. }
  192. // Get all backends for all registered storage destinations.
  193. // Used for getting all instances for health validations.
  194. func (s *DefaultStorageFactory) Backends() []string {
  195. backends := sets.NewString(s.StorageConfig.ServerList...)
  196. for _, overrides := range s.Overrides {
  197. backends.Insert(overrides.etcdLocation...)
  198. }
  199. return backends.List()
  200. }
  201. // NewStorageCodec assembles a storage codec for the provided storage media type, the provided serializer, and the requested
  202. // storage and memory versions.
  203. func NewStorageCodec(storageMediaType string, ns runtime.StorageSerializer, storageVersion, memoryVersion unversioned.GroupVersion, config storagebackend.Config) (runtime.Codec, error) {
  204. mediaType, options, err := mime.ParseMediaType(storageMediaType)
  205. if err != nil {
  206. return nil, fmt.Errorf("%q is not a valid mime-type", storageMediaType)
  207. }
  208. serializer, ok := ns.SerializerForMediaType(mediaType, options)
  209. if !ok {
  210. return nil, fmt.Errorf("unable to find serializer for %q", storageMediaType)
  211. }
  212. s := serializer.Serializer
  213. // etcd2 only supports string data - we must wrap any result before returning
  214. // TODO: storagebackend should return a boolean indicating whether it supports binary data
  215. if !serializer.EncodesAsText && (config.Type == storagebackend.StorageTypeUnset || config.Type == storagebackend.StorageTypeETCD2) {
  216. glog.V(4).Infof("Wrapping the underlying binary storage serializer with a base64 encoding for etcd2")
  217. s = runtime.NewBase64Serializer(s)
  218. }
  219. encoder := ns.EncoderForVersion(
  220. s,
  221. runtime.NewMultiGroupVersioner(
  222. storageVersion,
  223. unversioned.GroupKind{Group: storageVersion.Group},
  224. unversioned.GroupKind{Group: memoryVersion.Group},
  225. ),
  226. )
  227. ds := recognizer.NewDecoder(s, ns.UniversalDeserializer())
  228. decoder := ns.DecoderToVersion(
  229. ds,
  230. runtime.NewMultiGroupVersioner(
  231. memoryVersion,
  232. unversioned.GroupKind{Group: memoryVersion.Group},
  233. unversioned.GroupKind{Group: storageVersion.Group},
  234. ),
  235. )
  236. return runtime.NewCodec(encoder, decoder), nil
  237. }
  238. var specialDefaultResourcePrefixes = map[unversioned.GroupResource]string{
  239. unversioned.GroupResource{Group: "", Resource: "replicationControllers"}: "controllers",
  240. unversioned.GroupResource{Group: "", Resource: "replicationcontrollers"}: "controllers",
  241. unversioned.GroupResource{Group: "", Resource: "endpoints"}: "services/endpoints",
  242. unversioned.GroupResource{Group: "", Resource: "nodes"}: "minions",
  243. unversioned.GroupResource{Group: "", Resource: "services"}: "services/specs",
  244. }
  245. func (s *DefaultStorageFactory) ResourcePrefix(groupResource unversioned.GroupResource) string {
  246. chosenStorageResource := s.getStorageGroupResource(groupResource)
  247. groupOverride := s.Overrides[getAllResourcesAlias(chosenStorageResource)]
  248. exactResourceOverride := s.Overrides[chosenStorageResource]
  249. etcdResourcePrefix := specialDefaultResourcePrefixes[chosenStorageResource]
  250. if len(groupOverride.etcdResourcePrefix) > 0 {
  251. etcdResourcePrefix = groupOverride.etcdResourcePrefix
  252. }
  253. if len(exactResourceOverride.etcdResourcePrefix) > 0 {
  254. etcdResourcePrefix = exactResourceOverride.etcdResourcePrefix
  255. }
  256. if len(etcdResourcePrefix) == 0 {
  257. etcdResourcePrefix = strings.ToLower(chosenStorageResource.Resource)
  258. }
  259. return etcdResourcePrefix
  260. }