Add HasOpenAPISchemaChanged to DiscoveryInterface

This commit is contained in:
Kevin Delgado 2021-07-16 22:53:39 +00:00
parent 7b9757faa4
commit 235a57a29e
4 changed files with 43 additions and 32 deletions

View File

@ -1,8 +1,6 @@
package v1 package v1
import ( import (
"crypto/sha512"
"encoding/json"
"fmt" "fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -32,40 +30,25 @@ type objectTypeCache interface {
type nonCachingObjectTypeCache struct { type nonCachingObjectTypeCache struct {
// TODO: lock this? // TODO: lock this?
discoveryClient discovery.DiscoveryInterface discoveryClient discovery.DiscoveryInterface
docHash [sha512.Size]uint8
gvkParser *fieldmanager.GvkParser gvkParser *fieldmanager.GvkParser
typeForGVK map[schema.GroupVersionKind]*typed.ParseableType
} }
// objectTypeForGVK retrieves the typed.ParseableType for a given gvk from the cache // objectTypeForGVK retrieves the typed.ParseableType for a given gvk from the cache
func (c *nonCachingObjectTypeCache) objectTypeForGVK(gvk schema.GroupVersionKind) (*typed.ParseableType, error) { func (c *nonCachingObjectTypeCache) objectTypeForGVK(gvk schema.GroupVersionKind) (*typed.ParseableType, error) {
doc, err := c.discoveryClient.OpenAPISchema()
if err != nil {
return nil, err
}
docBytes, err := json.Marshal(doc) if !c.discoveryClient.HasOpenAPISchemaChanged() && c.gvkParser != nil {
if err != nil { // cache hit
return nil, err
}
docHash := sha512.Sum512(docBytes)
// cache hit
if c.docHash == docHash {
fmt.Println("cache hit") fmt.Println("cache hit")
fmt.Printf("docHash = %+v\n", docHash) fmt.Printf("gvk = %+v\n", gvk)
objType, ok := c.typeForGVK[gvk] return c.gvkParser.Type(gvk), nil
if ok {
fmt.Println("gvk recognized")
fmt.Printf("gvk = %+v\n", gvk)
return objType, nil
}
objType = c.gvkParser.Type(gvk)
c.typeForGVK[gvk] = objType
return objType, nil
} else { } else {
fmt.Println("cache miss")
// cache miss // cache miss
fmt.Println("cache miss")
fmt.Printf("gvk = %+v\n", gvk)
doc, err := c.discoveryClient.OpenAPISchema()
if err != nil {
return nil, err
}
models, err := proto.NewOpenAPIData(doc) models, err := proto.NewOpenAPIData(doc)
if err != nil { if err != nil {
return nil, err return nil, err
@ -77,11 +60,7 @@ func (c *nonCachingObjectTypeCache) objectTypeForGVK(gvk schema.GroupVersionKind
} }
objType := gvkParser.Type(gvk) objType := gvkParser.Type(gvk)
c.docHash = docHash
c.gvkParser = gvkParser c.gvkParser = gvkParser
c.typeForGVK = map[schema.GroupVersionKind]*typed.ParseableType{
gvk: objType,
}
return objType, nil return objType, nil
} }

View File

@ -240,6 +240,10 @@ func (d *CachedDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
return d.delegate.OpenAPISchema() return d.delegate.OpenAPISchema()
} }
func (d *CachedDiscoveryClient) HasOpenAPISchemaChanged() bool {
return d.delegate.HasOpenAPISchemaChanged()
}
// Fresh is supposed to tell the caller whether or not to retry if the cache // Fresh is supposed to tell the caller whether or not to retry if the cache
// fails to find something (false = retry, true = no need to retry). // fails to find something (false = retry, true = no need to retry).
func (d *CachedDiscoveryClient) Fresh() bool { func (d *CachedDiscoveryClient) Fresh() bool {

View File

@ -20,6 +20,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http"
"net/url" "net/url"
"sort" "sort"
"strings" "strings"
@ -125,6 +126,8 @@ type ServerVersionInterface interface {
type OpenAPISchemaInterface interface { type OpenAPISchemaInterface interface {
// OpenAPISchema retrieves and parses the swagger API schema the server supports. // OpenAPISchema retrieves and parses the swagger API schema the server supports.
OpenAPISchema() (*openapi_v2.Document, error) OpenAPISchema() (*openapi_v2.Document, error)
HasOpenAPISchemaChanged() bool
} }
// DiscoveryClient implements the functions that discover server-supported API groups, // DiscoveryClient implements the functions that discover server-supported API groups,
@ -133,6 +136,7 @@ type DiscoveryClient struct {
restClient restclient.Interface restClient restclient.Interface
LegacyPrefix string LegacyPrefix string
etag string
} }
// Convert metav1.APIVersions to metav1.APIGroup. APIVersions is used by legacy v1, so // Convert metav1.APIVersions to metav1.APIGroup. APIVersions is used by legacy v1, so
@ -419,9 +423,21 @@ func (d *DiscoveryClient) ServerVersion() (*version.Info, error) {
return &info, nil return &info, nil
} }
func (d *DiscoveryClient) HasOpenAPISchemaChanged() bool {
result := d.restClient.Verb("HEAD").AbsPath("/openapi/v2").SetHeader("If-None-Match", d.etag).SetHeader("Accept", mimePb).Do(context.TODO())
var status int
result.StatusCode(&status)
if status == http.StatusNotModified {
return false
}
return true
}
// OpenAPISchema fetches the open api schema using a rest client and parses the proto. // OpenAPISchema fetches the open api schema using a rest client and parses the proto.
func (d *DiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) { func (d *DiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
data, err := d.restClient.Get().AbsPath("/openapi/v2").SetHeader("Accept", mimePb).Do(context.TODO()).Raw() result := d.restClient.Get().AbsPath("/openapi/v2").SetHeader("Accept", mimePb).Do(context.TODO())
d.etag = result.Etag()
data, err := result.Raw()
if err != nil { if err != nil {
if errors.IsForbidden(err) || errors.IsNotFound(err) || errors.IsNotAcceptable(err) { if errors.IsForbidden(err) || errors.IsNotFound(err) || errors.IsNotAcceptable(err) {
// single endpoint not found/registered in old server, try to fetch old endpoint // single endpoint not found/registered in old server, try to fetch old endpoint

View File

@ -1062,6 +1062,12 @@ func (r *Request) DoRaw(ctx context.Context) ([]byte, error) {
// transformResponse converts an API response into a structured API object // transformResponse converts an API response into a structured API object
func (r *Request) transformResponse(resp *http.Response, req *http.Request) Result { func (r *Request) transformResponse(resp *http.Response, req *http.Request) Result {
var etag string
etagHeader, ok := resp.Header["Etag"]
if ok && len(etagHeader) == 1 {
etag = etagHeader[0]
}
var body []byte var body []byte
if resp.Body != nil { if resp.Body != nil {
data, err := ioutil.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
@ -1146,6 +1152,7 @@ func (r *Request) transformResponse(resp *http.Response, req *http.Request) Resu
statusCode: resp.StatusCode, statusCode: resp.StatusCode,
decoder: decoder, decoder: decoder,
warnings: handleWarnings(resp.Header, r.warningHandler), warnings: handleWarnings(resp.Header, r.warningHandler),
etag: etag,
} }
} }
@ -1272,6 +1279,7 @@ type Result struct {
contentType string contentType string
err error err error
statusCode int statusCode int
etag string
decoder runtime.Decoder decoder runtime.Decoder
} }
@ -1308,6 +1316,10 @@ func (r Result) Get() (runtime.Object, error) {
return out, nil return out, nil
} }
func (r Result) Etag() string {
return r.etag
}
// StatusCode returns the HTTP status code of the request. (Only valid if no // StatusCode returns the HTTP status code of the request. (Only valid if no
// error was returned.) // error was returned.)
func (r Result) StatusCode(statusCode *int) Result { func (r Result) StatusCode(statusCode *int) Result {