mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-10 08:20:59 +00:00
Precompute IsNamespaceScoped to avoid expensive schema reads (#4152)
* Precompute IsNamespaceScoped to avoid expensive schema reads See https://github.com/GoogleContainerTools/kpt/issues/2469 For the `gcr.io/kpt-fn/set-namespace:v0.1` function, over 50% of CPU time is spent on IsNamespaceScoped. Instead of unmarshalling 100k lines of JSON to determine this, instead just precompute it. We can ensure this never is inaccurate as the test verifies the precomputed result is up to date. In real world kpt pipelines this cuts execution of set-namespace (and similar functions, just an example of a trivial function) from 2.0s to 1.0s. Because these functions are run in long pipelines over many resources, this adds up a lot. * Add documentation
This commit is contained in:
@@ -39,6 +39,12 @@ The above command will update the [OpenAPI schema] and the [Kustomization schema
|
||||
create a directory kubernetesapi/v1212 and store the resulting
|
||||
swagger.json and swagger.go files there.
|
||||
|
||||
#### Precomputations
|
||||
|
||||
To avoid expensive schema lookups, some functions have precomputed results based on the schema. Unit tests
|
||||
ensure these are kept in sync with the schema; if these tests fail you will need to follow the suggested diff
|
||||
to update the precomputed results.
|
||||
|
||||
### Run all tests
|
||||
|
||||
At the top of the repository, run the tests.
|
||||
|
||||
@@ -50,6 +50,95 @@ type openapiData struct {
|
||||
schemaInit bool
|
||||
}
|
||||
|
||||
// precomputedIsNamespaceScoped precomputes IsNamespaceScoped for known types. This avoids Schema creation,
|
||||
// which is expensive
|
||||
// The test output from TestIsNamespaceScopedPrecompute shows the expected map in go syntax,and can be copy and pasted
|
||||
// from the failure if it changes.
|
||||
var precomputedIsNamespaceScoped = map[yaml.TypeMeta]bool{
|
||||
{APIVersion: "admissionregistration.k8s.io/v1", Kind: "MutatingWebhookConfiguration"}: false,
|
||||
{APIVersion: "admissionregistration.k8s.io/v1", Kind: "ValidatingWebhookConfiguration"}: false,
|
||||
{APIVersion: "admissionregistration.k8s.io/v1beta1", Kind: "MutatingWebhookConfiguration"}: false,
|
||||
{APIVersion: "admissionregistration.k8s.io/v1beta1", Kind: "ValidatingWebhookConfiguration"}: false,
|
||||
{APIVersion: "apiextensions.k8s.io/v1", Kind: "CustomResourceDefinition"}: false,
|
||||
{APIVersion: "apiextensions.k8s.io/v1beta1", Kind: "CustomResourceDefinition"}: false,
|
||||
{APIVersion: "apiregistration.k8s.io/v1", Kind: "APIService"}: false,
|
||||
{APIVersion: "apiregistration.k8s.io/v1beta1", Kind: "APIService"}: false,
|
||||
{APIVersion: "apps/v1", Kind: "ControllerRevision"}: true,
|
||||
{APIVersion: "apps/v1", Kind: "DaemonSet"}: true,
|
||||
{APIVersion: "apps/v1", Kind: "Deployment"}: true,
|
||||
{APIVersion: "apps/v1", Kind: "ReplicaSet"}: true,
|
||||
{APIVersion: "apps/v1", Kind: "StatefulSet"}: true,
|
||||
{APIVersion: "autoscaling/v1", Kind: "HorizontalPodAutoscaler"}: true,
|
||||
{APIVersion: "autoscaling/v1", Kind: "Scale"}: true,
|
||||
{APIVersion: "autoscaling/v2beta1", Kind: "HorizontalPodAutoscaler"}: true,
|
||||
{APIVersion: "autoscaling/v2beta2", Kind: "HorizontalPodAutoscaler"}: true,
|
||||
{APIVersion: "batch/v1", Kind: "CronJob"}: true,
|
||||
{APIVersion: "batch/v1", Kind: "Job"}: true,
|
||||
{APIVersion: "batch/v1beta1", Kind: "CronJob"}: true,
|
||||
{APIVersion: "certificates.k8s.io/v1", Kind: "CertificateSigningRequest"}: false,
|
||||
{APIVersion: "certificates.k8s.io/v1beta1", Kind: "CertificateSigningRequest"}: false,
|
||||
{APIVersion: "coordination.k8s.io/v1", Kind: "Lease"}: true,
|
||||
{APIVersion: "coordination.k8s.io/v1beta1", Kind: "Lease"}: true,
|
||||
{APIVersion: "discovery.k8s.io/v1", Kind: "EndpointSlice"}: true,
|
||||
{APIVersion: "discovery.k8s.io/v1beta1", Kind: "EndpointSlice"}: true,
|
||||
{APIVersion: "events.k8s.io/v1", Kind: "Event"}: true,
|
||||
{APIVersion: "events.k8s.io/v1beta1", Kind: "Event"}: true,
|
||||
{APIVersion: "extensions/v1beta1", Kind: "Ingress"}: true,
|
||||
{APIVersion: "flowcontrol.apiserver.k8s.io/v1beta1", Kind: "FlowSchema"}: false,
|
||||
{APIVersion: "flowcontrol.apiserver.k8s.io/v1beta1", Kind: "PriorityLevelConfiguration"}: false,
|
||||
{APIVersion: "networking.k8s.io/v1", Kind: "Ingress"}: true,
|
||||
{APIVersion: "networking.k8s.io/v1", Kind: "IngressClass"}: false,
|
||||
{APIVersion: "networking.k8s.io/v1", Kind: "NetworkPolicy"}: true,
|
||||
{APIVersion: "networking.k8s.io/v1beta1", Kind: "Ingress"}: true,
|
||||
{APIVersion: "networking.k8s.io/v1beta1", Kind: "IngressClass"}: false,
|
||||
{APIVersion: "node.k8s.io/v1", Kind: "RuntimeClass"}: false,
|
||||
{APIVersion: "node.k8s.io/v1beta1", Kind: "RuntimeClass"}: false,
|
||||
{APIVersion: "policy/v1", Kind: "PodDisruptionBudget"}: true,
|
||||
{APIVersion: "policy/v1beta1", Kind: "PodDisruptionBudget"}: true,
|
||||
{APIVersion: "policy/v1beta1", Kind: "PodSecurityPolicy"}: false,
|
||||
{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole"}: false,
|
||||
{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRoleBinding"}: false,
|
||||
{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"}: true,
|
||||
{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "RoleBinding"}: true,
|
||||
{APIVersion: "rbac.authorization.k8s.io/v1beta1", Kind: "ClusterRole"}: false,
|
||||
{APIVersion: "rbac.authorization.k8s.io/v1beta1", Kind: "ClusterRoleBinding"}: false,
|
||||
{APIVersion: "rbac.authorization.k8s.io/v1beta1", Kind: "Role"}: true,
|
||||
{APIVersion: "rbac.authorization.k8s.io/v1beta1", Kind: "RoleBinding"}: true,
|
||||
{APIVersion: "scheduling.k8s.io/v1", Kind: "PriorityClass"}: false,
|
||||
{APIVersion: "scheduling.k8s.io/v1beta1", Kind: "PriorityClass"}: false,
|
||||
{APIVersion: "storage.k8s.io/v1", Kind: "CSIDriver"}: false,
|
||||
{APIVersion: "storage.k8s.io/v1", Kind: "CSINode"}: false,
|
||||
{APIVersion: "storage.k8s.io/v1", Kind: "StorageClass"}: false,
|
||||
{APIVersion: "storage.k8s.io/v1", Kind: "VolumeAttachment"}: false,
|
||||
{APIVersion: "storage.k8s.io/v1beta1", Kind: "CSIDriver"}: false,
|
||||
{APIVersion: "storage.k8s.io/v1beta1", Kind: "CSINode"}: false,
|
||||
{APIVersion: "storage.k8s.io/v1beta1", Kind: "CSIStorageCapacity"}: true,
|
||||
{APIVersion: "storage.k8s.io/v1beta1", Kind: "StorageClass"}: false,
|
||||
{APIVersion: "storage.k8s.io/v1beta1", Kind: "VolumeAttachment"}: false,
|
||||
{APIVersion: "v1", Kind: "ComponentStatus"}: false,
|
||||
{APIVersion: "v1", Kind: "ConfigMap"}: true,
|
||||
{APIVersion: "v1", Kind: "Endpoints"}: true,
|
||||
{APIVersion: "v1", Kind: "Event"}: true,
|
||||
{APIVersion: "v1", Kind: "LimitRange"}: true,
|
||||
{APIVersion: "v1", Kind: "Namespace"}: false,
|
||||
{APIVersion: "v1", Kind: "Node"}: false,
|
||||
{APIVersion: "v1", Kind: "NodeProxyOptions"}: false,
|
||||
{APIVersion: "v1", Kind: "PersistentVolume"}: false,
|
||||
{APIVersion: "v1", Kind: "PersistentVolumeClaim"}: true,
|
||||
{APIVersion: "v1", Kind: "Pod"}: true,
|
||||
{APIVersion: "v1", Kind: "PodAttachOptions"}: true,
|
||||
{APIVersion: "v1", Kind: "PodExecOptions"}: true,
|
||||
{APIVersion: "v1", Kind: "PodPortForwardOptions"}: true,
|
||||
{APIVersion: "v1", Kind: "PodProxyOptions"}: true,
|
||||
{APIVersion: "v1", Kind: "PodTemplate"}: true,
|
||||
{APIVersion: "v1", Kind: "ReplicationController"}: true,
|
||||
{APIVersion: "v1", Kind: "ResourceQuota"}: true,
|
||||
{APIVersion: "v1", Kind: "Secret"}: true,
|
||||
{APIVersion: "v1", Kind: "Service"}: true,
|
||||
{APIVersion: "v1", Kind: "ServiceAccount"}: true,
|
||||
{APIVersion: "v1", Kind: "ServiceProxyOptions"}: true,
|
||||
}
|
||||
|
||||
// ResourceSchema wraps the OpenAPI Schema.
|
||||
type ResourceSchema struct {
|
||||
// Schema is the OpenAPI schema for a Resource or field
|
||||
@@ -265,10 +354,17 @@ func GetSchema(s string, schema *spec.Schema) (*ResourceSchema, error) {
|
||||
// cluster-scoped by looking at the information in the openapi schema.
|
||||
// The second return value tells whether the provided type could be found
|
||||
// in the openapi schema. If the value is false here, the scope of the
|
||||
// resource is not known. If the type if found, the first return value will
|
||||
// resource is not known. If the type is found, the first return value will
|
||||
// be true if the resource is namespace-scoped, and false if the type is
|
||||
// cluster-scoped.
|
||||
func IsNamespaceScoped(typeMeta yaml.TypeMeta) (bool, bool) {
|
||||
if res, f := precomputedIsNamespaceScoped[typeMeta]; f {
|
||||
return res, true
|
||||
}
|
||||
return isNamespaceScopedFromSchema(typeMeta)
|
||||
}
|
||||
|
||||
func isNamespaceScopedFromSchema(typeMeta yaml.TypeMeta) (bool, bool) {
|
||||
initSchema()
|
||||
isNamespaceScoped, found := globalSchema.namespaceabilityByResourceType[typeMeta]
|
||||
return isNamespaceScoped, found
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
@@ -271,6 +272,14 @@ func TestIsNamespaceScoped_builtin(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestIsNamespaceScopedPrecompute checks that the precomputed result meets the actual result
|
||||
func TestIsNamespaceScopedPrecompute(t *testing.T) {
|
||||
initSchema()
|
||||
if diff := cmp.Diff(globalSchema.namespaceabilityByResourceType, precomputedIsNamespaceScoped); diff != "" {
|
||||
t.Fatalf(diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsNamespaceScoped_custom(t *testing.T) {
|
||||
SuppressBuiltInSchemaUse()
|
||||
err := AddSchema([]byte(`
|
||||
|
||||
Reference in New Issue
Block a user