func describerMap(c *client.Client, kclient kclient.Interface, host string) map[unversioned.GroupKind]kctl.Describer { m := map[unversioned.GroupKind]kctl.Describer{ buildapi.Kind("Build"): &BuildDescriber{c, kclient}, buildapi.Kind("BuildConfig"): &BuildConfigDescriber{c, host}, deployapi.Kind("DeploymentConfig"): &DeploymentConfigDescriber{c, kclient, nil}, authorizationapi.Kind("Identity"): &IdentityDescriber{c}, imageapi.Kind("Image"): &ImageDescriber{c}, imageapi.Kind("ImageStream"): &ImageStreamDescriber{c}, imageapi.Kind("ImageStreamTag"): &ImageStreamTagDescriber{c}, imageapi.Kind("ImageStreamImage"): &ImageStreamImageDescriber{c}, routeapi.Kind("Route"): &RouteDescriber{c, kclient}, projectapi.Kind("Project"): &ProjectDescriber{c, kclient}, templateapi.Kind("Template"): &TemplateDescriber{c, meta.NewAccessor(), kapi.Scheme, nil}, authorizationapi.Kind("Policy"): &PolicyDescriber{c}, authorizationapi.Kind("PolicyBinding"): &PolicyBindingDescriber{c}, authorizationapi.Kind("RoleBinding"): &RoleBindingDescriber{c}, authorizationapi.Kind("Role"): &RoleDescriber{c}, authorizationapi.Kind("ClusterPolicy"): &ClusterPolicyDescriber{c}, authorizationapi.Kind("ClusterPolicyBinding"): &ClusterPolicyBindingDescriber{c}, authorizationapi.Kind("ClusterRoleBinding"): &ClusterRoleBindingDescriber{c}, authorizationapi.Kind("ClusterRole"): &ClusterRoleDescriber{c}, oauthapi.Kind("OAuthAccessToken"): &OAuthAccessTokenDescriber{c}, userapi.Kind("User"): &UserDescriber{c}, userapi.Kind("Group"): &GroupDescriber{c.Groups()}, userapi.Kind("UserIdentityMapping"): &UserIdentityMappingDescriber{c}, quotaapi.Kind("ClusterResourceQuota"): &ClusterQuotaDescriber{c}, quotaapi.Kind("AppliedClusterResourceQuota"): &AppliedClusterQuotaDescriber{c}, } return m }
func (r *replenishmentControllerFactory) NewController(options *kresourcequota.ReplenishmentControllerOptions) (*framework.Controller, error) { var result *framework.Controller switch options.GroupKind { case imageapi.Kind("ImageStream"): _, result = framework.NewInformer( &cache.ListWatch{ ListFunc: func(options api.ListOptions) (runtime.Object, error) { return r.osClient.ImageStreams(api.NamespaceAll).List(options) }, WatchFunc: func(options api.ListOptions) (watch.Interface, error) { return r.osClient.ImageStreams(api.NamespaceAll).Watch(options) }, }, &imageapi.ImageStream{}, options.ResyncPeriod(), framework.ResourceEventHandlerFuncs{ UpdateFunc: ImageStreamReplenishmentUpdateFunc(options), DeleteFunc: kresourcequota.ObjectReplenishmentDeleteFunc(options), }, ) default: return nil, fmt.Errorf("no replenishment controller available for %s", options.GroupKind) } return result, nil }
// TestOriginQuotaAdmissionIsErrorQuotaExceeded verifies that if a resource exceeds allowed usage, the // admission will return error we can recognize. func TestOriginQuotaAdmissionIsErrorQuotaExceeded(t *testing.T) { resourceQuota := &kapi.ResourceQuota{ ObjectMeta: kapi.ObjectMeta{Name: "quota", Namespace: "test", ResourceVersion: "124"}, Status: kapi.ResourceQuotaStatus{ Hard: kapi.ResourceList{ imageapi.ResourceImageStreams: resource.MustParse("0"), }, Used: kapi.ResourceList{ imageapi.ResourceImageStreams: resource.MustParse("0"), }, }, } kubeClient := kfake.NewSimpleClientset(resourceQuota) osClient := testclient.NewSimpleFake(&imageapi.ImageStream{}) plugin := NewOriginResourceQuota(kubeClient).(*originQuotaAdmission) plugin.SetOriginQuotaRegistry(quota.NewOriginQuotaRegistry(osClient)) if err := plugin.Validate(); err != nil { t.Fatalf("unexpected error: %v", err) } newIS := &imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Namespace: "test", Name: "is", }, } err := plugin.Admit(admission.NewAttributesRecord(newIS, nil, imageapi.Kind("ImageStream").WithVersion("version"), newIS.Namespace, newIS.Name, kapi.Resource("imageStreams").WithVersion("version"), "", admission.Create, nil)) if err == nil { t.Fatalf("Expected an error exceeding quota") } if !quotautil.IsErrorQuotaExceeded(err) { t.Fatalf("Expected error %q to be matched by IsErrorQuotaExceeded()", err.Error()) } }
// SupportsAttributes is a helper that returns true if the resource is supported by the plugin. // Implements the LimitRangerActions interface. func (a *imageLimitRangerPlugin) SupportsAttributes(attr kadmission.Attributes) bool { if attr.GetSubresource() != "" { return false } return attr.GetKind().GroupKind() == imageapi.Kind("ImageStreamMapping") }
func TestAdmitImageStreamMapping(t *testing.T) { tests := map[string]struct { imageStreamMapping *imageapi.ImageStreamMapping limitRange *kapi.LimitRange shouldAdmit bool operation kadmission.Operation }{ "new ism, no limit range": { imageStreamMapping: getImageStreamMapping(), operation: kadmission.Create, shouldAdmit: true, }, "new ism, under limit range": { imageStreamMapping: getImageStreamMapping(), limitRange: getLimitRange("1Ki"), operation: kadmission.Create, shouldAdmit: true, }, "new ism, over limit range": { imageStreamMapping: getImageStreamMapping(), limitRange: getLimitRange("0Ki"), operation: kadmission.Create, shouldAdmit: false, }, } for k, v := range tests { var fakeKubeClient clientset.Interface if v.limitRange != nil { fakeKubeClient = clientsetfake.NewSimpleClientset(v.limitRange) } else { fakeKubeClient = clientsetfake.NewSimpleClientset() } plugin, err := NewImageLimitRangerPlugin(fakeKubeClient, nil) if err != nil { t.Errorf("%s failed creating plugin %v", k, err) continue } attrs := kadmission.NewAttributesRecord(v.imageStreamMapping, imageapi.Kind("ImageStreamMapping").WithVersion("version"), v.imageStreamMapping.Namespace, v.imageStreamMapping.Name, imageapi.Resource("imagestreammappings").WithVersion("version"), "", v.operation, nil) err = plugin.Admit(attrs) if v.shouldAdmit && err != nil { t.Errorf("%s expected to be admitted but received error %v", k, err) } if !v.shouldAdmit && err == nil { t.Errorf("%s expected to be rejected but received no error", k) } } }
func imageImportStatus(err error, kind, position string) unversioned.Status { switch t := err.(type) { case kapierrors.APIStatus: return t.Status() case *field.Error: return kapierrors.NewInvalid(api.Kind(kind), position, field.ErrorList{t}).ErrStatus default: return kapierrors.NewInternalError(err).ErrStatus } }
// Accept accepts BuildConfigs and ImageStreams. func (a *acceptBuildConfigs) Accept(from interface{}) bool { obj, _, err := objectMetaData(from) if err != nil { return false } gvk, err := a.typer.ObjectKind(obj) if err != nil { return false } return gvk.GroupKind() == build.Kind("BuildConfig") || gvk.GroupKind() == image.Kind("ImageStream") }
// NewImageStreamTagEvaluator computes resource usage of ImageStreamsTags. Its sole purpose is to handle // UPDATE admission operations on imageStreamTags resource. func NewImageStreamTagEvaluator(istNamespacer osclient.ImageStreamTagsNamespacer, isNamespacer osclient.ImageStreamsNamespacer) kquota.Evaluator { computeResources := []kapi.ResourceName{ imageapi.ResourceImageStreams, } matchesScopeFunc := func(kapi.ResourceQuotaScope, runtime.Object) bool { return true } getFuncByNamespace := func(namespace, id string) (runtime.Object, error) { isName, tag, err := imageapi.ParseImageStreamTagName(id) if err != nil { return nil, err } obj, err := istNamespacer.ImageStreamTags(namespace).Get(isName, tag) if err != nil { if !kerrors.IsNotFound(err) { return nil, err } obj = &imageapi.ImageStreamTag{ ObjectMeta: kapi.ObjectMeta{ Namespace: namespace, Name: id, }, } } return obj, nil } return &generic.GenericEvaluator{ Name: imageStreamTagEvaluatorName, InternalGroupKind: imageapi.Kind("ImageStreamTag"), InternalOperationResources: map[admission.Operation][]kapi.ResourceName{ admission.Update: computeResources, admission.Create: computeResources, }, MatchedResourceNames: computeResources, MatchesScopeFunc: matchesScopeFunc, UsageFunc: makeImageStreamTagAdmissionUsageFunc(isNamespacer), GetFuncByNamespace: getFuncByNamespace, ListFuncByNamespace: func(namespace string, options kapi.ListOptions) (runtime.Object, error) { return &imageapi.ImageStreamTagList{}, nil }, ConstraintsFunc: imageStreamTagConstraintsFunc, } }
// NewImageStreamImportEvaluator computes resource usage for ImageStreamImport objects. This particular kind // is a virtual resource. It depends on ImageStream usage evaluator to compute image numbers before the // the admission can work. func NewImageStreamImportEvaluator(isNamespacer osclient.ImageStreamsNamespacer) kquota.Evaluator { computeResources := []kapi.ResourceName{ imageapi.ResourceImageStreams, } matchesScopeFunc := func(kapi.ResourceQuotaScope, runtime.Object) bool { return true } return &generic.GenericEvaluator{ Name: imageStreamImportName, InternalGroupKind: imageapi.Kind("ImageStreamImport"), InternalOperationResources: map[admission.Operation][]kapi.ResourceName{admission.Create: computeResources}, MatchedResourceNames: computeResources, MatchesScopeFunc: matchesScopeFunc, UsageFunc: makeImageStreamImportAdmissionUsageFunc(isNamespacer), ListFuncByNamespace: func(namespace string, options kapi.ListOptions) (runtime.Object, error) { return &kapi.List{}, nil }, ConstraintsFunc: imageStreamImportConstraintsFunc, } }
// findStreamForMapping retrieves an ImageStream whose DockerImageRepository matches dockerRepo. func (s *REST) findStreamForMapping(ctx kapi.Context, mapping *api.ImageStreamMapping) (*api.ImageStream, error) { if len(mapping.Name) > 0 { return s.imageStreamRegistry.GetImageStream(ctx, mapping.Name) } if len(mapping.DockerImageRepository) != 0 { list, err := s.imageStreamRegistry.ListImageStreams(ctx, &kapi.ListOptions{}) if err != nil { return nil, err } for i := range list.Items { if mapping.DockerImageRepository == list.Items[i].Spec.DockerImageRepository { return &list.Items[i], nil } } return nil, errors.NewInvalid(api.Kind("ImageStreamMapping"), "", field.ErrorList{ field.NotFound(field.NewPath("dockerImageStream"), mapping.DockerImageRepository), }) } return nil, errors.NewNotFound(api.Resource("imagestream"), "") }
// NewImageStreamEvaluator computes resource usage of ImageStreams. Instantiating this is necessary for // resource quota admission controller to properly work on image stream related objects. func NewImageStreamEvaluator(isNamespacer osclient.ImageStreamsNamespacer) kquota.Evaluator { allResources := []kapi.ResourceName{ imageapi.ResourceImageStreams, } return &generic.GenericEvaluator{ Name: imageStreamEvaluatorName, InternalGroupKind: imageapi.Kind("ImageStream"), InternalOperationResources: map[admission.Operation][]kapi.ResourceName{ admission.Create: allResources, }, MatchedResourceNames: allResources, MatchesScopeFunc: generic.MatchesNoScopeFunc, ConstraintsFunc: generic.ObjectCountConstraintsFunc(imageapi.ResourceImageStreams), UsageFunc: generic.ObjectCountUsageFunc(imageapi.ResourceImageStreams), ListFuncByNamespace: func(namespace string, options kapi.ListOptions) (runtime.Object, error) { return isNamespacer.ImageStreams(namespace).List(options) }, } }
// RunResourceQuotaManager starts resource quota controller for OpenShift resources func (c *MasterConfig) RunResourceQuotaManager(cm *cmapp.CMServer) { concurrentResourceQuotaSyncs := defaultConcurrentResourceQuotaSyncs resourceQuotaSyncPeriod := defaultResourceQuotaSyncPeriod replenishmentSyncPeriodFunc := controller.StaticResyncPeriodFunc(defaultReplenishmentSyncPeriod) if cm != nil { // TODO: should these be part of os master config? concurrentResourceQuotaSyncs = cm.ConcurrentResourceQuotaSyncs resourceQuotaSyncPeriod = cm.ResourceQuotaSyncPeriod.Duration replenishmentSyncPeriodFunc = kctrlmgr.ResyncPeriod(cm) } osClient, kClient := c.ResourceQuotaManagerClients() resourceQuotaRegistry := quota.NewRegistry(osClient, false) resourceQuotaControllerOptions := &kresourcequota.ResourceQuotaControllerOptions{ KubeClient: kClient, ResyncPeriod: controller.StaticResyncPeriodFunc(resourceQuotaSyncPeriod), Registry: resourceQuotaRegistry, GroupKindsToReplenish: []unversioned.GroupKind{imageapi.Kind("ImageStream")}, ControllerFactory: quotacontroller.NewReplenishmentControllerFactory(osClient), ReplenishmentResyncPeriod: replenishmentSyncPeriodFunc, } go kresourcequota.NewResourceQuotaController(resourceQuotaControllerOptions).Run(concurrentResourceQuotaSyncs, utilwait.NeverStop) }
kquota "k8s.io/kubernetes/pkg/quota" "k8s.io/kubernetes/pkg/quota/install" osclient "github.com/openshift/origin/pkg/client" imageapi "github.com/openshift/origin/pkg/image/api" "github.com/openshift/origin/pkg/quota/image" ) // NewOriginQuotaRegistry returns a registry object that knows how to evaluate quota usage of OpenShift // resources. func NewOriginQuotaRegistry(osClient osclient.Interface) kquota.Registry { return image.NewImageQuotaRegistry(osClient) } // NewAllResourceQuotaRegistry returns a registry object that knows how to evaluate all resources func NewAllResourceQuotaRegistry(osClient osclient.Interface, kubeClientSet clientset.Interface) kquota.Registry { return kquota.UnionRegistry{install.NewRegistry(kubeClientSet), NewOriginQuotaRegistry(osClient)} } // AllEvaluatedGroupKinds is the list of all group kinds that we evaluate for quotas in openshift and kube var AllEvaluatedGroupKinds = []unversioned.GroupKind{ kapi.Kind("Pod"), kapi.Kind("Service"), kapi.Kind("ReplicationController"), kapi.Kind("PersistentVolumeClaim"), kapi.Kind("Secret"), kapi.Kind("ConfigMap"), imageapi.Kind("ImageStream"), }
func invalidStatus(position string, errs ...*field.Error) unversioned.Status { return kapierrors.NewInvalid(api.Kind(""), position, errs).ErrStatus }
// TestIsErrorQuotaExceeded verifies that if a resource exceedes allowed usage, the admission will return // error we can recognize. func TestIsErrorQuotaExceeded(t *testing.T) { for _, tc := range []struct { name string err error shouldMatch bool }{ { name: "unrelated error", err: errors.New("unrelated"), }, { name: "wrong type", err: errors.New(errQuotaMessageString), }, { name: "wrong kapi type", err: kerrors.NewUnauthorized(errQuotaMessageString), }, { name: "unrelated forbidden error", err: kerrors.NewForbidden(kapi.Resource("imageStreams"), "is", errors.New("unrelated")), }, { name: "unrelated invalid error", err: kerrors.NewInvalid(imageapi.Kind("imageStreams"), "is", field.ErrorList{ field.Required(field.NewPath("imageStream").Child("Spec"), "detail"), }), }, { name: "quota error not recognized with invalid reason", err: kerrors.NewInvalid(imageapi.Kind("imageStreams"), "is", field.ErrorList{ field.Forbidden(field.NewPath("imageStreams"), errQuotaMessageString), }), }, { name: "quota unknown error not recognized with invalid reason", err: kerrors.NewInvalid(imageapi.Kind("imageStreams"), "is", field.ErrorList{ field.Forbidden(field.NewPath("imageStreams"), errQuotaUnknownMessageString), }), }, { name: "quota exceeded error", err: kerrors.NewForbidden(kapi.Resource("imageStream"), "is", errors.New(errQuotaMessageString)), shouldMatch: true, }, { name: "quota unknown error", err: kerrors.NewForbidden(kapi.Resource("imageStream"), "is", errors.New(errQuotaUnknownMessageString)), shouldMatch: true, }, { name: "limits exceeded error with forbidden reason", err: kerrors.NewForbidden(imageapi.Resource("imageStream"), "is", errors.New(errLimitsMessageString)), shouldMatch: true, }, { name: "limits exceeded error with invalid reason", err: kerrors.NewInvalid(imageapi.Kind("imageStreams"), "is", field.ErrorList{ field.Forbidden(field.NewPath("imageStream"), errLimitsMessageString), }), shouldMatch: true, }, } { match := IsErrorQuotaExceeded(tc.err) if !match && tc.shouldMatch { t.Errorf("[%s] expected to match error [%T]: %v", tc.name, tc.err, tc.err) } if match && !tc.shouldMatch { t.Errorf("[%s] expected not to match error [%T]: %v", tc.name, tc.err, tc.err) } } }
func invalidStatus(kind, position string, errs ...*field.Error) unversioned.Status { return kapierrors.NewInvalid(api.Kind(kind), position, errs).(kapierrors.APIStatus).Status() }