Browse code

add clusterquota projection for associated projects

deads2k authored on 2016/06/28 21:26:29
Showing 36 changed files
... ...
@@ -9,6 +9,221 @@
9 9
   },
10 10
   "apis": [
11 11
    {
12
+    "path": "/oapi/v1/namespaces/{namespace}/appliedclusterresourcequotas",
13
+    "description": "OpenShift REST API, version v1",
14
+    "operations": [
15
+     {
16
+      "type": "v1.AppliedClusterResourceQuotaList",
17
+      "method": "GET",
18
+      "summary": "list objects of kind AppliedClusterResourceQuota",
19
+      "nickname": "listNamespacedAppliedClusterResourceQuota",
20
+      "parameters": [
21
+       {
22
+        "type": "string",
23
+        "paramType": "query",
24
+        "name": "pretty",
25
+        "description": "If 'true', then the output is pretty printed.",
26
+        "required": false,
27
+        "allowMultiple": false
28
+       },
29
+       {
30
+        "type": "string",
31
+        "paramType": "query",
32
+        "name": "labelSelector",
33
+        "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.",
34
+        "required": false,
35
+        "allowMultiple": false
36
+       },
37
+       {
38
+        "type": "string",
39
+        "paramType": "query",
40
+        "name": "fieldSelector",
41
+        "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.",
42
+        "required": false,
43
+        "allowMultiple": false
44
+       },
45
+       {
46
+        "type": "boolean",
47
+        "paramType": "query",
48
+        "name": "watch",
49
+        "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.",
50
+        "required": false,
51
+        "allowMultiple": false
52
+       },
53
+       {
54
+        "type": "string",
55
+        "paramType": "query",
56
+        "name": "resourceVersion",
57
+        "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history.",
58
+        "required": false,
59
+        "allowMultiple": false
60
+       },
61
+       {
62
+        "type": "integer",
63
+        "paramType": "query",
64
+        "name": "timeoutSeconds",
65
+        "description": "Timeout for the list/watch call.",
66
+        "required": false,
67
+        "allowMultiple": false
68
+       },
69
+       {
70
+        "type": "string",
71
+        "paramType": "path",
72
+        "name": "namespace",
73
+        "description": "object name and auth scope, such as for teams and projects",
74
+        "required": true,
75
+        "allowMultiple": false
76
+       }
77
+      ],
78
+      "responseMessages": [
79
+       {
80
+        "code": 200,
81
+        "message": "OK",
82
+        "responseModel": "v1.AppliedClusterResourceQuotaList"
83
+       }
84
+      ],
85
+      "produces": [
86
+       "application/json",
87
+       "application/yaml",
88
+       "application/vnd.kubernetes.protobuf"
89
+      ],
90
+      "consumes": [
91
+       "*/*"
92
+      ]
93
+     }
94
+    ]
95
+   },
96
+   {
97
+    "path": "/oapi/v1/namespaces/{namespace}/appliedclusterresourcequotas/{name}",
98
+    "description": "OpenShift REST API, version v1",
99
+    "operations": [
100
+     {
101
+      "type": "v1.AppliedClusterResourceQuota",
102
+      "method": "GET",
103
+      "summary": "read the specified AppliedClusterResourceQuota",
104
+      "nickname": "readNamespacedAppliedClusterResourceQuota",
105
+      "parameters": [
106
+       {
107
+        "type": "string",
108
+        "paramType": "query",
109
+        "name": "pretty",
110
+        "description": "If 'true', then the output is pretty printed.",
111
+        "required": false,
112
+        "allowMultiple": false
113
+       },
114
+       {
115
+        "type": "string",
116
+        "paramType": "path",
117
+        "name": "namespace",
118
+        "description": "object name and auth scope, such as for teams and projects",
119
+        "required": true,
120
+        "allowMultiple": false
121
+       },
122
+       {
123
+        "type": "string",
124
+        "paramType": "path",
125
+        "name": "name",
126
+        "description": "name of the AppliedClusterResourceQuota",
127
+        "required": true,
128
+        "allowMultiple": false
129
+       }
130
+      ],
131
+      "responseMessages": [
132
+       {
133
+        "code": 200,
134
+        "message": "OK",
135
+        "responseModel": "v1.AppliedClusterResourceQuota"
136
+       }
137
+      ],
138
+      "produces": [
139
+       "application/json",
140
+       "application/yaml",
141
+       "application/vnd.kubernetes.protobuf"
142
+      ],
143
+      "consumes": [
144
+       "*/*"
145
+      ]
146
+     }
147
+    ]
148
+   },
149
+   {
150
+    "path": "/oapi/v1/appliedclusterresourcequotas",
151
+    "description": "OpenShift REST API, version v1",
152
+    "operations": [
153
+     {
154
+      "type": "v1.AppliedClusterResourceQuotaList",
155
+      "method": "GET",
156
+      "summary": "list objects of kind AppliedClusterResourceQuota",
157
+      "nickname": "listNamespacedAppliedClusterResourceQuota",
158
+      "parameters": [
159
+       {
160
+        "type": "string",
161
+        "paramType": "query",
162
+        "name": "pretty",
163
+        "description": "If 'true', then the output is pretty printed.",
164
+        "required": false,
165
+        "allowMultiple": false
166
+       },
167
+       {
168
+        "type": "string",
169
+        "paramType": "query",
170
+        "name": "labelSelector",
171
+        "description": "A selector to restrict the list of returned objects by their labels. Defaults to everything.",
172
+        "required": false,
173
+        "allowMultiple": false
174
+       },
175
+       {
176
+        "type": "string",
177
+        "paramType": "query",
178
+        "name": "fieldSelector",
179
+        "description": "A selector to restrict the list of returned objects by their fields. Defaults to everything.",
180
+        "required": false,
181
+        "allowMultiple": false
182
+       },
183
+       {
184
+        "type": "boolean",
185
+        "paramType": "query",
186
+        "name": "watch",
187
+        "description": "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.",
188
+        "required": false,
189
+        "allowMultiple": false
190
+       },
191
+       {
192
+        "type": "string",
193
+        "paramType": "query",
194
+        "name": "resourceVersion",
195
+        "description": "When specified with a watch call, shows changes that occur after that particular version of a resource. Defaults to changes from the beginning of history.",
196
+        "required": false,
197
+        "allowMultiple": false
198
+       },
199
+       {
200
+        "type": "integer",
201
+        "paramType": "query",
202
+        "name": "timeoutSeconds",
203
+        "description": "Timeout for the list/watch call.",
204
+        "required": false,
205
+        "allowMultiple": false
206
+       }
207
+      ],
208
+      "responseMessages": [
209
+       {
210
+        "code": 200,
211
+        "message": "OK",
212
+        "responseModel": "v1.AppliedClusterResourceQuotaList"
213
+       }
214
+      ],
215
+      "produces": [
216
+       "application/json",
217
+       "application/yaml",
218
+       "application/vnd.kubernetes.protobuf"
219
+      ],
220
+      "consumes": [
221
+       "*/*"
222
+      ]
223
+     }
224
+    ]
225
+   },
226
+   {
12 227
     "path": "/oapi/v1/namespaces/{namespace}/buildconfigs",
13 228
     "description": "OpenShift REST API, version v1",
14 229
     "operations": [
... ...
@@ -19217,9 +19432,9 @@
19217 19217
    }
19218 19218
   ],
19219 19219
   "models": {
19220
-   "v1.BuildConfigList": {
19221
-    "id": "v1.BuildConfigList",
19222
-    "description": "BuildConfigList is a collection of BuildConfigs.",
19220
+   "v1.AppliedClusterResourceQuotaList": {
19221
+    "id": "v1.AppliedClusterResourceQuotaList",
19222
+    "description": "AppliedClusterResourceQuotaList is a collection of AppliedClusterResourceQuotas",
19223 19223
     "required": [
19224 19224
      "items"
19225 19225
     ],
... ...
@@ -19234,14 +19449,14 @@
19234 19234
      },
19235 19235
      "metadata": {
19236 19236
       "$ref": "unversioned.ListMeta",
19237
-      "description": "metadata for BuildConfigList."
19237
+      "description": "Standard object's metadata."
19238 19238
      },
19239 19239
      "items": {
19240 19240
       "type": "array",
19241 19241
       "items": {
19242
-       "$ref": "v1.BuildConfig"
19242
+       "$ref": "v1.AppliedClusterResourceQuota"
19243 19243
       },
19244
-      "description": "items is a list of build configs"
19244
+      "description": "Items is a list of AppliedClusterResourceQuota"
19245 19245
      }
19246 19246
     }
19247 19247
    },
... ...
@@ -19259,12 +19474,12 @@
19259 19259
      }
19260 19260
     }
19261 19261
    },
19262
-   "v1.BuildConfig": {
19263
-    "id": "v1.BuildConfig",
19264
-    "description": "BuildConfig is a template which can be used to create new builds.",
19262
+   "v1.AppliedClusterResourceQuota": {
19263
+    "id": "v1.AppliedClusterResourceQuota",
19264
+    "description": "AppliedClusterResourceQuota mirrors ClusterResourceQuota at a namespace scope, for projection into a namespace.  It allows a project-admin to know which ClusterResourceQuotas are applied to his project and their associated usage.",
19265 19265
     "required": [
19266
-     "spec",
19267
-     "status"
19266
+     "metadata",
19267
+     "spec"
19268 19268
     ],
19269 19269
     "properties": {
19270 19270
      "kind": {
... ...
@@ -19277,15 +19492,15 @@
19277 19277
      },
19278 19278
      "metadata": {
19279 19279
       "$ref": "v1.ObjectMeta",
19280
-      "description": "metadata for BuildConfig."
19280
+      "description": "Standard object's metadata."
19281 19281
      },
19282 19282
      "spec": {
19283
-      "$ref": "v1.BuildConfigSpec",
19284
-      "description": "spec holds all the input necessary to produce a new build, and the conditions when to trigger them."
19283
+      "$ref": "v1.ClusterResourceQuotaSpec",
19284
+      "description": "Spec defines the desired quota"
19285 19285
      },
19286 19286
      "status": {
19287
-      "$ref": "v1.BuildConfigStatus",
19288
-      "description": "status holds any relevant information about a build config"
19287
+      "$ref": "v1.ClusterResourceQuotaStatus",
19288
+      "description": "Status defines the actual enforced quota and its current usage"
19289 19289
      }
19290 19290
     }
19291 19291
    },
... ...
@@ -19387,6 +19602,198 @@
19387 19387
      }
19388 19388
     }
19389 19389
    },
19390
+   "v1.ClusterResourceQuotaSpec": {
19391
+    "id": "v1.ClusterResourceQuotaSpec",
19392
+    "description": "ClusterResourceQuotaSpec defines the desired quota restrictions",
19393
+    "required": [
19394
+     "selector",
19395
+     "quota"
19396
+    ],
19397
+    "properties": {
19398
+     "selector": {
19399
+      "$ref": "unversioned.LabelSelector",
19400
+      "description": "Selector is the label selector used to match projects.  It is not allowed to be empty and should only select active projects on the scale of dozens (though it can select many more less active projects).  These projects will contend on object creation through this resource."
19401
+     },
19402
+     "quota": {
19403
+      "$ref": "v1.ResourceQuotaSpec",
19404
+      "description": "Quota defines the desired quota"
19405
+     }
19406
+    }
19407
+   },
19408
+   "unversioned.LabelSelector": {
19409
+    "id": "unversioned.LabelSelector",
19410
+    "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.",
19411
+    "properties": {
19412
+     "matchLabels": {
19413
+      "type": "object",
19414
+      "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed."
19415
+     },
19416
+     "matchExpressions": {
19417
+      "type": "array",
19418
+      "items": {
19419
+       "$ref": "unversioned.LabelSelectorRequirement"
19420
+      },
19421
+      "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed."
19422
+     }
19423
+    }
19424
+   },
19425
+   "unversioned.LabelSelectorRequirement": {
19426
+    "id": "unversioned.LabelSelectorRequirement",
19427
+    "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.",
19428
+    "required": [
19429
+     "key",
19430
+     "operator"
19431
+    ],
19432
+    "properties": {
19433
+     "key": {
19434
+      "type": "string",
19435
+      "description": "key is the label key that the selector applies to."
19436
+     },
19437
+     "operator": {
19438
+      "type": "string",
19439
+      "description": "operator represents a key's relationship to a set of values. Valid operators ard In, NotIn, Exists and DoesNotExist."
19440
+     },
19441
+     "values": {
19442
+      "type": "array",
19443
+      "items": {
19444
+       "type": "string"
19445
+      },
19446
+      "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch."
19447
+     }
19448
+    }
19449
+   },
19450
+   "v1.ResourceQuotaSpec": {
19451
+    "id": "v1.ResourceQuotaSpec",
19452
+    "description": "ResourceQuotaSpec defines the desired hard limits to enforce for Quota.",
19453
+    "properties": {
19454
+     "hard": {
19455
+      "type": "object",
19456
+      "description": "Hard is the set of desired hard limits for each named resource. More info: http://releases.k8s.io/HEAD/docs/design/admission_control_resource_quota.md#admissioncontrol-plugin-resourcequota"
19457
+     },
19458
+     "scopes": {
19459
+      "type": "array",
19460
+      "items": {
19461
+       "$ref": "v1.ResourceQuotaScope"
19462
+      },
19463
+      "description": "A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects."
19464
+     }
19465
+    }
19466
+   },
19467
+   "v1.ResourceQuotaScope": {
19468
+    "id": "v1.ResourceQuotaScope",
19469
+    "properties": {}
19470
+   },
19471
+   "v1.ClusterResourceQuotaStatus": {
19472
+    "id": "v1.ClusterResourceQuotaStatus",
19473
+    "description": "ClusterResourceQuotaStatus defines the actual enforced quota and its current usage",
19474
+    "required": [
19475
+     "total",
19476
+     "namespaces"
19477
+    ],
19478
+    "properties": {
19479
+     "total": {
19480
+      "$ref": "v1.ResourceQuotaStatus",
19481
+      "description": "Total defines the actual enforced quota and its current usage across all namespaces"
19482
+     },
19483
+     "namespaces": {
19484
+      "type": "array",
19485
+      "items": {
19486
+       "$ref": "v1.ResourceQuotaStatusByNamespace"
19487
+      },
19488
+      "description": "Namespaces slices the usage by namespace.  This division allows for quick resolution of deletion reconcilation inside of a single namespace without requiring a recalculation across all namespaces.  This can be used to pull the deltas for a given namespace."
19489
+     }
19490
+    }
19491
+   },
19492
+   "v1.ResourceQuotaStatus": {
19493
+    "id": "v1.ResourceQuotaStatus",
19494
+    "description": "ResourceQuotaStatus defines the enforced hard limits and observed use.",
19495
+    "properties": {
19496
+     "hard": {
19497
+      "type": "object",
19498
+      "description": "Hard is the set of enforced hard limits for each named resource. More info: http://releases.k8s.io/HEAD/docs/design/admission_control_resource_quota.md#admissioncontrol-plugin-resourcequota"
19499
+     },
19500
+     "used": {
19501
+      "type": "object",
19502
+      "description": "Used is the current observed total usage of the resource in the namespace."
19503
+     }
19504
+    }
19505
+   },
19506
+   "v1.ResourceQuotaStatusByNamespace": {
19507
+    "id": "v1.ResourceQuotaStatusByNamespace",
19508
+    "description": "ResourceQuotaStatusByNamespace gives status for a particular namespace",
19509
+    "required": [
19510
+     "namespace",
19511
+     "status"
19512
+    ],
19513
+    "properties": {
19514
+     "namespace": {
19515
+      "type": "string",
19516
+      "description": "Namespace the namespace this status applies to"
19517
+     },
19518
+     "status": {
19519
+      "$ref": "v1.ResourceQuotaStatus",
19520
+      "description": "Status indicates how many resources have been consumed by this namespace"
19521
+     }
19522
+    }
19523
+   },
19524
+   "v1.BuildConfigList": {
19525
+    "id": "v1.BuildConfigList",
19526
+    "description": "BuildConfigList is a collection of BuildConfigs.",
19527
+    "required": [
19528
+     "items"
19529
+    ],
19530
+    "properties": {
19531
+     "kind": {
19532
+      "type": "string",
19533
+      "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds"
19534
+     },
19535
+     "apiVersion": {
19536
+      "type": "string",
19537
+      "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources"
19538
+     },
19539
+     "metadata": {
19540
+      "$ref": "unversioned.ListMeta",
19541
+      "description": "metadata for BuildConfigList."
19542
+     },
19543
+     "items": {
19544
+      "type": "array",
19545
+      "items": {
19546
+       "$ref": "v1.BuildConfig"
19547
+      },
19548
+      "description": "items is a list of build configs"
19549
+     }
19550
+    }
19551
+   },
19552
+   "v1.BuildConfig": {
19553
+    "id": "v1.BuildConfig",
19554
+    "description": "BuildConfig is a template which can be used to create new builds.",
19555
+    "required": [
19556
+     "spec",
19557
+     "status"
19558
+    ],
19559
+    "properties": {
19560
+     "kind": {
19561
+      "type": "string",
19562
+      "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds"
19563
+     },
19564
+     "apiVersion": {
19565
+      "type": "string",
19566
+      "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources"
19567
+     },
19568
+     "metadata": {
19569
+      "$ref": "v1.ObjectMeta",
19570
+      "description": "metadata for BuildConfig."
19571
+     },
19572
+     "spec": {
19573
+      "$ref": "v1.BuildConfigSpec",
19574
+      "description": "spec holds all the input necessary to produce a new build, and the conditions when to trigger them."
19575
+     },
19576
+     "status": {
19577
+      "$ref": "v1.BuildConfigStatus",
19578
+      "description": "status holds any relevant information about a build config"
19579
+     }
19580
+    }
19581
+   },
19390 19582
    "v1.BuildConfigSpec": {
19391 19583
     "id": "v1.BuildConfigSpec",
19392 19584
     "description": "BuildConfigSpec describes when and how builds are created",
... ...
@@ -20899,140 +21306,6 @@
20899 20899
      }
20900 20900
     }
20901 20901
    },
20902
-   "v1.ClusterResourceQuotaSpec": {
20903
-    "id": "v1.ClusterResourceQuotaSpec",
20904
-    "description": "ClusterResourceQuotaSpec defines the desired quota restrictions",
20905
-    "required": [
20906
-     "selector",
20907
-     "quota"
20908
-    ],
20909
-    "properties": {
20910
-     "selector": {
20911
-      "$ref": "unversioned.LabelSelector",
20912
-      "description": "Selector is the label selector used to match projects.  It is not allowed to be empty and should only select active projects on the scale of dozens (though it can select many more less active projects).  These projects will contend on object creation through this resource."
20913
-     },
20914
-     "quota": {
20915
-      "$ref": "v1.ResourceQuotaSpec",
20916
-      "description": "Quota defines the desired quota"
20917
-     }
20918
-    }
20919
-   },
20920
-   "unversioned.LabelSelector": {
20921
-    "id": "unversioned.LabelSelector",
20922
-    "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.",
20923
-    "properties": {
20924
-     "matchLabels": {
20925
-      "type": "object",
20926
-      "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed."
20927
-     },
20928
-     "matchExpressions": {
20929
-      "type": "array",
20930
-      "items": {
20931
-       "$ref": "unversioned.LabelSelectorRequirement"
20932
-      },
20933
-      "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed."
20934
-     }
20935
-    }
20936
-   },
20937
-   "unversioned.LabelSelectorRequirement": {
20938
-    "id": "unversioned.LabelSelectorRequirement",
20939
-    "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.",
20940
-    "required": [
20941
-     "key",
20942
-     "operator"
20943
-    ],
20944
-    "properties": {
20945
-     "key": {
20946
-      "type": "string",
20947
-      "description": "key is the label key that the selector applies to."
20948
-     },
20949
-     "operator": {
20950
-      "type": "string",
20951
-      "description": "operator represents a key's relationship to a set of values. Valid operators ard In, NotIn, Exists and DoesNotExist."
20952
-     },
20953
-     "values": {
20954
-      "type": "array",
20955
-      "items": {
20956
-       "type": "string"
20957
-      },
20958
-      "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch."
20959
-     }
20960
-    }
20961
-   },
20962
-   "v1.ResourceQuotaSpec": {
20963
-    "id": "v1.ResourceQuotaSpec",
20964
-    "description": "ResourceQuotaSpec defines the desired hard limits to enforce for Quota.",
20965
-    "properties": {
20966
-     "hard": {
20967
-      "type": "object",
20968
-      "description": "Hard is the set of desired hard limits for each named resource. More info: http://releases.k8s.io/HEAD/docs/design/admission_control_resource_quota.md#admissioncontrol-plugin-resourcequota"
20969
-     },
20970
-     "scopes": {
20971
-      "type": "array",
20972
-      "items": {
20973
-       "$ref": "v1.ResourceQuotaScope"
20974
-      },
20975
-      "description": "A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects."
20976
-     }
20977
-    }
20978
-   },
20979
-   "v1.ResourceQuotaScope": {
20980
-    "id": "v1.ResourceQuotaScope",
20981
-    "properties": {}
20982
-   },
20983
-   "v1.ClusterResourceQuotaStatus": {
20984
-    "id": "v1.ClusterResourceQuotaStatus",
20985
-    "description": "ClusterResourceQuotaStatus defines the actual enforced quota and its current usage",
20986
-    "required": [
20987
-     "total",
20988
-     "namespaces"
20989
-    ],
20990
-    "properties": {
20991
-     "total": {
20992
-      "$ref": "v1.ResourceQuotaStatus",
20993
-      "description": "Total defines the actual enforced quota and its current usage across all namespaces"
20994
-     },
20995
-     "namespaces": {
20996
-      "type": "array",
20997
-      "items": {
20998
-       "$ref": "v1.ResourceQuotaStatusByNamespace"
20999
-      },
21000
-      "description": "Namespaces slices the usage by namespace.  This division allows for quick resolution of deletion reconcilation inside of a single namespace without requiring a recalculation across all namespaces.  This can be used to pull the deltas for a given namespace."
21001
-     }
21002
-    }
21003
-   },
21004
-   "v1.ResourceQuotaStatus": {
21005
-    "id": "v1.ResourceQuotaStatus",
21006
-    "description": "ResourceQuotaStatus defines the enforced hard limits and observed use.",
21007
-    "properties": {
21008
-     "hard": {
21009
-      "type": "object",
21010
-      "description": "Hard is the set of enforced hard limits for each named resource. More info: http://releases.k8s.io/HEAD/docs/design/admission_control_resource_quota.md#admissioncontrol-plugin-resourcequota"
21011
-     },
21012
-     "used": {
21013
-      "type": "object",
21014
-      "description": "Used is the current observed total usage of the resource in the namespace."
21015
-     }
21016
-    }
21017
-   },
21018
-   "v1.ResourceQuotaStatusByNamespace": {
21019
-    "id": "v1.ResourceQuotaStatusByNamespace",
21020
-    "description": "ResourceQuotaStatusByNamespace gives status for a particular namespace",
21021
-    "required": [
21022
-     "namespace",
21023
-     "status"
21024
-    ],
21025
-    "properties": {
21026
-     "namespace": {
21027
-      "type": "string",
21028
-      "description": "Namespace the namespace this status applies to"
21029
-     },
21030
-     "status": {
21031
-      "$ref": "v1.ResourceQuotaStatus",
21032
-      "description": "Status indicates how many resources have been consumed by this namespace"
21033
-     }
21034
-    }
21035
-   },
21036 20902
    "v1.ClusterRoleBindingList": {
21037 20903
     "id": "v1.ClusterRoleBindingList",
21038 20904
     "description": "ClusterRoleBindingList is a collection of ClusterRoleBindings",
... ...
@@ -1312,6 +1312,7 @@ _oc_get()
1312 1312
 
1313 1313
     must_have_one_flag=()
1314 1314
     must_have_one_noun=()
1315
+    must_have_one_noun+=("appliedclusterresourcequota")
1315 1316
     must_have_one_noun+=("build")
1316 1317
     must_have_one_noun+=("buildconfig")
1317 1318
     must_have_one_noun+=("clusternetwork")
... ...
@@ -1371,6 +1372,7 @@ _oc_get()
1371 1371
     must_have_one_noun+=("user")
1372 1372
     must_have_one_noun+=("useridentitymapping")
1373 1373
     noun_aliases=()
1374
+    noun_aliases+=("appliedclusterresourcequotas")
1374 1375
     noun_aliases+=("buildconfigs")
1375 1376
     noun_aliases+=("builds")
1376 1377
     noun_aliases+=("clusternetworks")
... ...
@@ -1499,6 +1501,7 @@ _oc_describe()
1499 1499
 
1500 1500
     must_have_one_flag=()
1501 1501
     must_have_one_noun=()
1502
+    must_have_one_noun+=("appliedclusterresourcequota")
1502 1503
     must_have_one_noun+=("build")
1503 1504
     must_have_one_noun+=("buildconfig")
1504 1505
     must_have_one_noun+=("clusterpolicy")
... ...
@@ -1641,6 +1644,7 @@ _oc_edit()
1641 1641
 
1642 1642
     must_have_one_flag=()
1643 1643
     must_have_one_noun=()
1644
+    must_have_one_noun+=("appliedclusterresourcequota")
1644 1645
     must_have_one_noun+=("build")
1645 1646
     must_have_one_noun+=("buildconfig")
1646 1647
     must_have_one_noun+=("clusternetwork")
... ...
@@ -1700,6 +1704,7 @@ _oc_edit()
1700 1700
     must_have_one_noun+=("user")
1701 1701
     must_have_one_noun+=("useridentitymapping")
1702 1702
     noun_aliases=()
1703
+    noun_aliases+=("appliedclusterresourcequotas")
1703 1704
     noun_aliases+=("buildconfigs")
1704 1705
     noun_aliases+=("builds")
1705 1706
     noun_aliases+=("clusternetworks")
... ...
@@ -2311,6 +2316,7 @@ _oc_label()
2311 2311
 
2312 2312
     must_have_one_flag=()
2313 2313
     must_have_one_noun=()
2314
+    must_have_one_noun+=("appliedclusterresourcequota")
2314 2315
     must_have_one_noun+=("build")
2315 2316
     must_have_one_noun+=("buildconfig")
2316 2317
     must_have_one_noun+=("clusternetwork")
... ...
@@ -2370,6 +2376,7 @@ _oc_label()
2370 2370
     must_have_one_noun+=("user")
2371 2371
     must_have_one_noun+=("useridentitymapping")
2372 2372
     noun_aliases=()
2373
+    noun_aliases+=("appliedclusterresourcequotas")
2373 2374
     noun_aliases+=("buildconfigs")
2374 2375
     noun_aliases+=("builds")
2375 2376
     noun_aliases+=("clusternetworks")
... ...
@@ -2703,6 +2710,7 @@ _oc_delete()
2703 2703
 
2704 2704
     must_have_one_flag=()
2705 2705
     must_have_one_noun=()
2706
+    must_have_one_noun+=("appliedclusterresourcequota")
2706 2707
     must_have_one_noun+=("build")
2707 2708
     must_have_one_noun+=("buildconfig")
2708 2709
     must_have_one_noun+=("clusternetwork")
... ...
@@ -2762,6 +2770,7 @@ _oc_delete()
2762 2762
     must_have_one_noun+=("user")
2763 2763
     must_have_one_noun+=("useridentitymapping")
2764 2764
     noun_aliases=()
2765
+    noun_aliases+=("appliedclusterresourcequotas")
2765 2766
     noun_aliases+=("buildconfigs")
2766 2767
     noun_aliases+=("builds")
2767 2768
     noun_aliases+=("clusternetworks")
... ...
@@ -8418,6 +8427,7 @@ _oc_patch()
8418 8418
     must_have_one_flag+=("--patch=")
8419 8419
     must_have_one_flag+=("-p")
8420 8420
     must_have_one_noun=()
8421
+    must_have_one_noun+=("appliedclusterresourcequota")
8421 8422
     must_have_one_noun+=("build")
8422 8423
     must_have_one_noun+=("buildconfig")
8423 8424
     must_have_one_noun+=("clusternetwork")
... ...
@@ -8477,6 +8487,7 @@ _oc_patch()
8477 8477
     must_have_one_noun+=("user")
8478 8478
     must_have_one_noun+=("useridentitymapping")
8479 8479
     noun_aliases=()
8480
+    noun_aliases+=("appliedclusterresourcequotas")
8480 8481
     noun_aliases+=("buildconfigs")
8481 8482
     noun_aliases+=("builds")
8482 8483
     noun_aliases+=("clusternetworks")
... ...
@@ -5388,6 +5388,7 @@ _openshift_cli_get()
5388 5388
 
5389 5389
     must_have_one_flag=()
5390 5390
     must_have_one_noun=()
5391
+    must_have_one_noun+=("appliedclusterresourcequota")
5391 5392
     must_have_one_noun+=("build")
5392 5393
     must_have_one_noun+=("buildconfig")
5393 5394
     must_have_one_noun+=("clusternetwork")
... ...
@@ -5447,6 +5448,7 @@ _openshift_cli_get()
5447 5447
     must_have_one_noun+=("user")
5448 5448
     must_have_one_noun+=("useridentitymapping")
5449 5449
     noun_aliases=()
5450
+    noun_aliases+=("appliedclusterresourcequotas")
5450 5451
     noun_aliases+=("buildconfigs")
5451 5452
     noun_aliases+=("builds")
5452 5453
     noun_aliases+=("clusternetworks")
... ...
@@ -5576,6 +5578,7 @@ _openshift_cli_describe()
5576 5576
 
5577 5577
     must_have_one_flag=()
5578 5578
     must_have_one_noun=()
5579
+    must_have_one_noun+=("appliedclusterresourcequota")
5579 5580
     must_have_one_noun+=("build")
5580 5581
     must_have_one_noun+=("buildconfig")
5581 5582
     must_have_one_noun+=("clusterpolicy")
... ...
@@ -5719,6 +5722,7 @@ _openshift_cli_edit()
5719 5719
 
5720 5720
     must_have_one_flag=()
5721 5721
     must_have_one_noun=()
5722
+    must_have_one_noun+=("appliedclusterresourcequota")
5722 5723
     must_have_one_noun+=("build")
5723 5724
     must_have_one_noun+=("buildconfig")
5724 5725
     must_have_one_noun+=("clusternetwork")
... ...
@@ -5778,6 +5782,7 @@ _openshift_cli_edit()
5778 5778
     must_have_one_noun+=("user")
5779 5779
     must_have_one_noun+=("useridentitymapping")
5780 5780
     noun_aliases=()
5781
+    noun_aliases+=("appliedclusterresourcequotas")
5781 5782
     noun_aliases+=("buildconfigs")
5782 5783
     noun_aliases+=("builds")
5783 5784
     noun_aliases+=("clusternetworks")
... ...
@@ -6397,6 +6402,7 @@ _openshift_cli_label()
6397 6397
 
6398 6398
     must_have_one_flag=()
6399 6399
     must_have_one_noun=()
6400
+    must_have_one_noun+=("appliedclusterresourcequota")
6400 6401
     must_have_one_noun+=("build")
6401 6402
     must_have_one_noun+=("buildconfig")
6402 6403
     must_have_one_noun+=("clusternetwork")
... ...
@@ -6456,6 +6462,7 @@ _openshift_cli_label()
6456 6456
     must_have_one_noun+=("user")
6457 6457
     must_have_one_noun+=("useridentitymapping")
6458 6458
     noun_aliases=()
6459
+    noun_aliases+=("appliedclusterresourcequotas")
6459 6460
     noun_aliases+=("buildconfigs")
6460 6461
     noun_aliases+=("builds")
6461 6462
     noun_aliases+=("clusternetworks")
... ...
@@ -6792,6 +6799,7 @@ _openshift_cli_delete()
6792 6792
 
6793 6793
     must_have_one_flag=()
6794 6794
     must_have_one_noun=()
6795
+    must_have_one_noun+=("appliedclusterresourcequota")
6795 6796
     must_have_one_noun+=("build")
6796 6797
     must_have_one_noun+=("buildconfig")
6797 6798
     must_have_one_noun+=("clusternetwork")
... ...
@@ -6851,6 +6859,7 @@ _openshift_cli_delete()
6851 6851
     must_have_one_noun+=("user")
6852 6852
     must_have_one_noun+=("useridentitymapping")
6853 6853
     noun_aliases=()
6854
+    noun_aliases+=("appliedclusterresourcequotas")
6854 6855
     noun_aliases+=("buildconfigs")
6855 6856
     noun_aliases+=("builds")
6856 6857
     noun_aliases+=("clusternetworks")
... ...
@@ -12616,6 +12625,7 @@ _openshift_cli_patch()
12616 12616
     must_have_one_flag+=("--patch=")
12617 12617
     must_have_one_flag+=("-p")
12618 12618
     must_have_one_noun=()
12619
+    must_have_one_noun+=("appliedclusterresourcequota")
12619 12620
     must_have_one_noun+=("build")
12620 12621
     must_have_one_noun+=("buildconfig")
12621 12622
     must_have_one_noun+=("clusternetwork")
... ...
@@ -12675,6 +12685,7 @@ _openshift_cli_patch()
12675 12675
     must_have_one_noun+=("user")
12676 12676
     must_have_one_noun+=("useridentitymapping")
12677 12677
     noun_aliases=()
12678
+    noun_aliases+=("appliedclusterresourcequotas")
12678 12679
     noun_aliases+=("buildconfigs")
12679 12680
     noun_aliases+=("builds")
12680 12681
     noun_aliases+=("clusternetworks")
... ...
@@ -14524,6 +14535,7 @@ _openshift_kube_get()
14524 14524
 
14525 14525
     must_have_one_flag=()
14526 14526
     must_have_one_noun=()
14527
+    must_have_one_noun+=("appliedclusterresourcequota")
14527 14528
     must_have_one_noun+=("build")
14528 14529
     must_have_one_noun+=("buildconfig")
14529 14530
     must_have_one_noun+=("clusternetwork")
... ...
@@ -14583,6 +14595,7 @@ _openshift_kube_get()
14583 14583
     must_have_one_noun+=("user")
14584 14584
     must_have_one_noun+=("useridentitymapping")
14585 14585
     noun_aliases=()
14586
+    noun_aliases+=("appliedclusterresourcequotas")
14586 14587
     noun_aliases+=("buildconfigs")
14587 14588
     noun_aliases+=("builds")
14588 14589
     noun_aliases+=("clusternetworks")
... ...
@@ -15719,6 +15732,7 @@ _openshift_kube_patch()
15719 15719
     must_have_one_flag+=("--patch=")
15720 15720
     must_have_one_flag+=("-p")
15721 15721
     must_have_one_noun=()
15722
+    must_have_one_noun+=("appliedclusterresourcequota")
15722 15723
     must_have_one_noun+=("build")
15723 15724
     must_have_one_noun+=("buildconfig")
15724 15725
     must_have_one_noun+=("clusternetwork")
... ...
@@ -15778,6 +15792,7 @@ _openshift_kube_patch()
15778 15778
     must_have_one_noun+=("user")
15779 15779
     must_have_one_noun+=("useridentitymapping")
15780 15780
     noun_aliases=()
15781
+    noun_aliases+=("appliedclusterresourcequotas")
15781 15782
     noun_aliases+=("buildconfigs")
15782 15783
     noun_aliases+=("builds")
15783 15784
     noun_aliases+=("clusternetworks")
... ...
@@ -15953,6 +15968,7 @@ _openshift_kube_delete()
15953 15953
 
15954 15954
     must_have_one_flag=()
15955 15955
     must_have_one_noun=()
15956
+    must_have_one_noun+=("appliedclusterresourcequota")
15956 15957
     must_have_one_noun+=("build")
15957 15958
     must_have_one_noun+=("buildconfig")
15958 15959
     must_have_one_noun+=("clusternetwork")
... ...
@@ -16012,6 +16028,7 @@ _openshift_kube_delete()
16012 16012
     must_have_one_noun+=("user")
16013 16013
     must_have_one_noun+=("useridentitymapping")
16014 16014
     noun_aliases=()
16015
+    noun_aliases+=("appliedclusterresourcequotas")
16015 16016
     noun_aliases+=("buildconfigs")
16016 16017
     noun_aliases+=("builds")
16017 16018
     noun_aliases+=("clusternetworks")
... ...
@@ -16183,6 +16200,7 @@ _openshift_kube_edit()
16183 16183
 
16184 16184
     must_have_one_flag=()
16185 16185
     must_have_one_noun=()
16186
+    must_have_one_noun+=("appliedclusterresourcequota")
16186 16187
     must_have_one_noun+=("build")
16187 16188
     must_have_one_noun+=("buildconfig")
16188 16189
     must_have_one_noun+=("clusternetwork")
... ...
@@ -16242,6 +16260,7 @@ _openshift_kube_edit()
16242 16242
     must_have_one_noun+=("user")
16243 16243
     must_have_one_noun+=("useridentitymapping")
16244 16244
     noun_aliases=()
16245
+    noun_aliases+=("appliedclusterresourcequotas")
16245 16246
     noun_aliases+=("buildconfigs")
16246 16247
     noun_aliases+=("builds")
16247 16248
     noun_aliases+=("clusternetworks")
... ...
@@ -18339,6 +18358,7 @@ _openshift_kube_label()
18339 18339
 
18340 18340
     must_have_one_flag=()
18341 18341
     must_have_one_noun=()
18342
+    must_have_one_noun+=("appliedclusterresourcequota")
18342 18343
     must_have_one_noun+=("build")
18343 18344
     must_have_one_noun+=("buildconfig")
18344 18345
     must_have_one_noun+=("clusternetwork")
... ...
@@ -18398,6 +18418,7 @@ _openshift_kube_label()
18398 18398
     must_have_one_noun+=("user")
18399 18399
     must_have_one_noun+=("useridentitymapping")
18400 18400
     noun_aliases=()
18401
+    noun_aliases+=("appliedclusterresourcequotas")
18401 18402
     noun_aliases+=("buildconfigs")
18402 18403
     noun_aliases+=("builds")
18403 18404
     noun_aliases+=("clusternetworks")
... ...
@@ -1473,6 +1473,7 @@ _oc_get()
1473 1473
 
1474 1474
     must_have_one_flag=()
1475 1475
     must_have_one_noun=()
1476
+    must_have_one_noun+=("appliedclusterresourcequota")
1476 1477
     must_have_one_noun+=("build")
1477 1478
     must_have_one_noun+=("buildconfig")
1478 1479
     must_have_one_noun+=("clusternetwork")
... ...
@@ -1532,6 +1533,7 @@ _oc_get()
1532 1532
     must_have_one_noun+=("user")
1533 1533
     must_have_one_noun+=("useridentitymapping")
1534 1534
     noun_aliases=()
1535
+    noun_aliases+=("appliedclusterresourcequotas")
1535 1536
     noun_aliases+=("buildconfigs")
1536 1537
     noun_aliases+=("builds")
1537 1538
     noun_aliases+=("clusternetworks")
... ...
@@ -1660,6 +1662,7 @@ _oc_describe()
1660 1660
 
1661 1661
     must_have_one_flag=()
1662 1662
     must_have_one_noun=()
1663
+    must_have_one_noun+=("appliedclusterresourcequota")
1663 1664
     must_have_one_noun+=("build")
1664 1665
     must_have_one_noun+=("buildconfig")
1665 1666
     must_have_one_noun+=("clusterpolicy")
... ...
@@ -1802,6 +1805,7 @@ _oc_edit()
1802 1802
 
1803 1803
     must_have_one_flag=()
1804 1804
     must_have_one_noun=()
1805
+    must_have_one_noun+=("appliedclusterresourcequota")
1805 1806
     must_have_one_noun+=("build")
1806 1807
     must_have_one_noun+=("buildconfig")
1807 1808
     must_have_one_noun+=("clusternetwork")
... ...
@@ -1861,6 +1865,7 @@ _oc_edit()
1861 1861
     must_have_one_noun+=("user")
1862 1862
     must_have_one_noun+=("useridentitymapping")
1863 1863
     noun_aliases=()
1864
+    noun_aliases+=("appliedclusterresourcequotas")
1864 1865
     noun_aliases+=("buildconfigs")
1865 1866
     noun_aliases+=("builds")
1866 1867
     noun_aliases+=("clusternetworks")
... ...
@@ -2472,6 +2477,7 @@ _oc_label()
2472 2472
 
2473 2473
     must_have_one_flag=()
2474 2474
     must_have_one_noun=()
2475
+    must_have_one_noun+=("appliedclusterresourcequota")
2475 2476
     must_have_one_noun+=("build")
2476 2477
     must_have_one_noun+=("buildconfig")
2477 2478
     must_have_one_noun+=("clusternetwork")
... ...
@@ -2531,6 +2537,7 @@ _oc_label()
2531 2531
     must_have_one_noun+=("user")
2532 2532
     must_have_one_noun+=("useridentitymapping")
2533 2533
     noun_aliases=()
2534
+    noun_aliases+=("appliedclusterresourcequotas")
2534 2535
     noun_aliases+=("buildconfigs")
2535 2536
     noun_aliases+=("builds")
2536 2537
     noun_aliases+=("clusternetworks")
... ...
@@ -2864,6 +2871,7 @@ _oc_delete()
2864 2864
 
2865 2865
     must_have_one_flag=()
2866 2866
     must_have_one_noun=()
2867
+    must_have_one_noun+=("appliedclusterresourcequota")
2867 2868
     must_have_one_noun+=("build")
2868 2869
     must_have_one_noun+=("buildconfig")
2869 2870
     must_have_one_noun+=("clusternetwork")
... ...
@@ -2923,6 +2931,7 @@ _oc_delete()
2923 2923
     must_have_one_noun+=("user")
2924 2924
     must_have_one_noun+=("useridentitymapping")
2925 2925
     noun_aliases=()
2926
+    noun_aliases+=("appliedclusterresourcequotas")
2926 2927
     noun_aliases+=("buildconfigs")
2927 2928
     noun_aliases+=("builds")
2928 2929
     noun_aliases+=("clusternetworks")
... ...
@@ -8579,6 +8588,7 @@ _oc_patch()
8579 8579
     must_have_one_flag+=("--patch=")
8580 8580
     must_have_one_flag+=("-p")
8581 8581
     must_have_one_noun=()
8582
+    must_have_one_noun+=("appliedclusterresourcequota")
8582 8583
     must_have_one_noun+=("build")
8583 8584
     must_have_one_noun+=("buildconfig")
8584 8585
     must_have_one_noun+=("clusternetwork")
... ...
@@ -8638,6 +8648,7 @@ _oc_patch()
8638 8638
     must_have_one_noun+=("user")
8639 8639
     must_have_one_noun+=("useridentitymapping")
8640 8640
     noun_aliases=()
8641
+    noun_aliases+=("appliedclusterresourcequotas")
8641 8642
     noun_aliases+=("buildconfigs")
8642 8643
     noun_aliases+=("builds")
8643 8644
     noun_aliases+=("clusternetworks")
... ...
@@ -5549,6 +5549,7 @@ _openshift_cli_get()
5549 5549
 
5550 5550
     must_have_one_flag=()
5551 5551
     must_have_one_noun=()
5552
+    must_have_one_noun+=("appliedclusterresourcequota")
5552 5553
     must_have_one_noun+=("build")
5553 5554
     must_have_one_noun+=("buildconfig")
5554 5555
     must_have_one_noun+=("clusternetwork")
... ...
@@ -5608,6 +5609,7 @@ _openshift_cli_get()
5608 5608
     must_have_one_noun+=("user")
5609 5609
     must_have_one_noun+=("useridentitymapping")
5610 5610
     noun_aliases=()
5611
+    noun_aliases+=("appliedclusterresourcequotas")
5611 5612
     noun_aliases+=("buildconfigs")
5612 5613
     noun_aliases+=("builds")
5613 5614
     noun_aliases+=("clusternetworks")
... ...
@@ -5737,6 +5739,7 @@ _openshift_cli_describe()
5737 5737
 
5738 5738
     must_have_one_flag=()
5739 5739
     must_have_one_noun=()
5740
+    must_have_one_noun+=("appliedclusterresourcequota")
5740 5741
     must_have_one_noun+=("build")
5741 5742
     must_have_one_noun+=("buildconfig")
5742 5743
     must_have_one_noun+=("clusterpolicy")
... ...
@@ -5880,6 +5883,7 @@ _openshift_cli_edit()
5880 5880
 
5881 5881
     must_have_one_flag=()
5882 5882
     must_have_one_noun=()
5883
+    must_have_one_noun+=("appliedclusterresourcequota")
5883 5884
     must_have_one_noun+=("build")
5884 5885
     must_have_one_noun+=("buildconfig")
5885 5886
     must_have_one_noun+=("clusternetwork")
... ...
@@ -5939,6 +5943,7 @@ _openshift_cli_edit()
5939 5939
     must_have_one_noun+=("user")
5940 5940
     must_have_one_noun+=("useridentitymapping")
5941 5941
     noun_aliases=()
5942
+    noun_aliases+=("appliedclusterresourcequotas")
5942 5943
     noun_aliases+=("buildconfigs")
5943 5944
     noun_aliases+=("builds")
5944 5945
     noun_aliases+=("clusternetworks")
... ...
@@ -6558,6 +6563,7 @@ _openshift_cli_label()
6558 6558
 
6559 6559
     must_have_one_flag=()
6560 6560
     must_have_one_noun=()
6561
+    must_have_one_noun+=("appliedclusterresourcequota")
6561 6562
     must_have_one_noun+=("build")
6562 6563
     must_have_one_noun+=("buildconfig")
6563 6564
     must_have_one_noun+=("clusternetwork")
... ...
@@ -6617,6 +6623,7 @@ _openshift_cli_label()
6617 6617
     must_have_one_noun+=("user")
6618 6618
     must_have_one_noun+=("useridentitymapping")
6619 6619
     noun_aliases=()
6620
+    noun_aliases+=("appliedclusterresourcequotas")
6620 6621
     noun_aliases+=("buildconfigs")
6621 6622
     noun_aliases+=("builds")
6622 6623
     noun_aliases+=("clusternetworks")
... ...
@@ -6953,6 +6960,7 @@ _openshift_cli_delete()
6953 6953
 
6954 6954
     must_have_one_flag=()
6955 6955
     must_have_one_noun=()
6956
+    must_have_one_noun+=("appliedclusterresourcequota")
6956 6957
     must_have_one_noun+=("build")
6957 6958
     must_have_one_noun+=("buildconfig")
6958 6959
     must_have_one_noun+=("clusternetwork")
... ...
@@ -7012,6 +7020,7 @@ _openshift_cli_delete()
7012 7012
     must_have_one_noun+=("user")
7013 7013
     must_have_one_noun+=("useridentitymapping")
7014 7014
     noun_aliases=()
7015
+    noun_aliases+=("appliedclusterresourcequotas")
7015 7016
     noun_aliases+=("buildconfigs")
7016 7017
     noun_aliases+=("builds")
7017 7018
     noun_aliases+=("clusternetworks")
... ...
@@ -12777,6 +12786,7 @@ _openshift_cli_patch()
12777 12777
     must_have_one_flag+=("--patch=")
12778 12778
     must_have_one_flag+=("-p")
12779 12779
     must_have_one_noun=()
12780
+    must_have_one_noun+=("appliedclusterresourcequota")
12780 12781
     must_have_one_noun+=("build")
12781 12782
     must_have_one_noun+=("buildconfig")
12782 12783
     must_have_one_noun+=("clusternetwork")
... ...
@@ -12836,6 +12846,7 @@ _openshift_cli_patch()
12836 12836
     must_have_one_noun+=("user")
12837 12837
     must_have_one_noun+=("useridentitymapping")
12838 12838
     noun_aliases=()
12839
+    noun_aliases+=("appliedclusterresourcequotas")
12839 12840
     noun_aliases+=("buildconfigs")
12840 12841
     noun_aliases+=("builds")
12841 12842
     noun_aliases+=("clusternetworks")
... ...
@@ -14685,6 +14696,7 @@ _openshift_kube_get()
14685 14685
 
14686 14686
     must_have_one_flag=()
14687 14687
     must_have_one_noun=()
14688
+    must_have_one_noun+=("appliedclusterresourcequota")
14688 14689
     must_have_one_noun+=("build")
14689 14690
     must_have_one_noun+=("buildconfig")
14690 14691
     must_have_one_noun+=("clusternetwork")
... ...
@@ -14744,6 +14756,7 @@ _openshift_kube_get()
14744 14744
     must_have_one_noun+=("user")
14745 14745
     must_have_one_noun+=("useridentitymapping")
14746 14746
     noun_aliases=()
14747
+    noun_aliases+=("appliedclusterresourcequotas")
14747 14748
     noun_aliases+=("buildconfigs")
14748 14749
     noun_aliases+=("builds")
14749 14750
     noun_aliases+=("clusternetworks")
... ...
@@ -15880,6 +15893,7 @@ _openshift_kube_patch()
15880 15880
     must_have_one_flag+=("--patch=")
15881 15881
     must_have_one_flag+=("-p")
15882 15882
     must_have_one_noun=()
15883
+    must_have_one_noun+=("appliedclusterresourcequota")
15883 15884
     must_have_one_noun+=("build")
15884 15885
     must_have_one_noun+=("buildconfig")
15885 15886
     must_have_one_noun+=("clusternetwork")
... ...
@@ -15939,6 +15953,7 @@ _openshift_kube_patch()
15939 15939
     must_have_one_noun+=("user")
15940 15940
     must_have_one_noun+=("useridentitymapping")
15941 15941
     noun_aliases=()
15942
+    noun_aliases+=("appliedclusterresourcequotas")
15942 15943
     noun_aliases+=("buildconfigs")
15943 15944
     noun_aliases+=("builds")
15944 15945
     noun_aliases+=("clusternetworks")
... ...
@@ -16114,6 +16129,7 @@ _openshift_kube_delete()
16114 16114
 
16115 16115
     must_have_one_flag=()
16116 16116
     must_have_one_noun=()
16117
+    must_have_one_noun+=("appliedclusterresourcequota")
16117 16118
     must_have_one_noun+=("build")
16118 16119
     must_have_one_noun+=("buildconfig")
16119 16120
     must_have_one_noun+=("clusternetwork")
... ...
@@ -16173,6 +16189,7 @@ _openshift_kube_delete()
16173 16173
     must_have_one_noun+=("user")
16174 16174
     must_have_one_noun+=("useridentitymapping")
16175 16175
     noun_aliases=()
16176
+    noun_aliases+=("appliedclusterresourcequotas")
16176 16177
     noun_aliases+=("buildconfigs")
16177 16178
     noun_aliases+=("builds")
16178 16179
     noun_aliases+=("clusternetworks")
... ...
@@ -16344,6 +16361,7 @@ _openshift_kube_edit()
16344 16344
 
16345 16345
     must_have_one_flag=()
16346 16346
     must_have_one_noun=()
16347
+    must_have_one_noun+=("appliedclusterresourcequota")
16347 16348
     must_have_one_noun+=("build")
16348 16349
     must_have_one_noun+=("buildconfig")
16349 16350
     must_have_one_noun+=("clusternetwork")
... ...
@@ -16403,6 +16421,7 @@ _openshift_kube_edit()
16403 16403
     must_have_one_noun+=("user")
16404 16404
     must_have_one_noun+=("useridentitymapping")
16405 16405
     noun_aliases=()
16406
+    noun_aliases+=("appliedclusterresourcequotas")
16406 16407
     noun_aliases+=("buildconfigs")
16407 16408
     noun_aliases+=("builds")
16408 16409
     noun_aliases+=("clusternetworks")
... ...
@@ -18500,6 +18519,7 @@ _openshift_kube_label()
18500 18500
 
18501 18501
     must_have_one_flag=()
18502 18502
     must_have_one_noun=()
18503
+    must_have_one_noun+=("appliedclusterresourcequota")
18503 18504
     must_have_one_noun+=("build")
18504 18505
     must_have_one_noun+=("buildconfig")
18505 18506
     must_have_one_noun+=("clusternetwork")
... ...
@@ -18559,6 +18579,7 @@ _openshift_kube_label()
18559 18559
     must_have_one_noun+=("user")
18560 18560
     must_have_one_noun+=("useridentitymapping")
18561 18561
     noun_aliases=()
18562
+    noun_aliases+=("appliedclusterresourcequotas")
18562 18563
     noun_aliases+=("buildconfigs")
18563 18564
     noun_aliases+=("builds")
18564 18565
     noun_aliases+=("clusternetworks")
... ...
@@ -48,6 +48,7 @@ DIR_BLACKLIST='./hack
48 48
 ./pkg/image
49 49
 ./pkg/oauth
50 50
 ./pkg/project
51
+./pkg/quota
51 52
 ./pkg/router
52 53
 ./pkg/security
53 54
 ./pkg/serviceaccounts
... ...
@@ -12,6 +12,7 @@ import (
12 12
 	buildapi "github.com/openshift/origin/pkg/build/api"
13 13
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
14 14
 	imageapi "github.com/openshift/origin/pkg/image/api"
15
+	quotaapi "github.com/openshift/origin/pkg/quota/api"
15 16
 )
16 17
 
17 18
 // KnownValidationExceptions is the list of API types that do NOT have corresponding validation
... ...
@@ -25,6 +26,7 @@ var KnownValidationExceptions = []reflect.Type{
25 25
 	reflect.TypeOf(&authorizationapi.IsPersonalSubjectAccessReview{}), // only an api type for runtime.EmbeddedObject, never accepted
26 26
 	reflect.TypeOf(&authorizationapi.SubjectAccessReviewResponse{}),   // this object is only returned, never accepted
27 27
 	reflect.TypeOf(&authorizationapi.ResourceAccessReviewResponse{}),  // this object is only returned, never accepted
28
+	reflect.TypeOf(&quotaapi.AppliedClusterResourceQuota{}),           // this object is only returned, never accepted
28 29
 }
29 30
 
30 31
 // MissingValidationExceptions is the list of types that were missing validation methods when I started
... ...
@@ -98,5 +98,4 @@ func registerAll() {
98 98
 	Validator.MustRegister(&securityapi.PodSecurityPolicyReview{}, securityvalidation.ValidatePodSecurityPolicyReview, nil)
99 99
 
100 100
 	Validator.MustRegister(&quotaapi.ClusterResourceQuota{}, quotavalidation.ValidateClusterResourceQuota, quotavalidation.ValidateClusterResourceQuotaUpdate)
101
-
102 101
 }
103 102
new file mode 100644
... ...
@@ -0,0 +1,46 @@
0
+package client
1
+
2
+import (
3
+	kapi "k8s.io/kubernetes/pkg/api"
4
+
5
+	quotaapi "github.com/openshift/origin/pkg/quota/api"
6
+)
7
+
8
+// AppliedClusterResourceQuotasNamespacer has methods to work with AppliedClusterResourceQuota resources in a namespace
9
+type AppliedClusterResourceQuotasNamespacer interface {
10
+	AppliedClusterResourceQuotas(namespace string) AppliedClusterResourceQuotaInterface
11
+}
12
+
13
+// AppliedClusterResourceQuotaInterface exposes methods on AppliedClusterResourceQuota resources.
14
+type AppliedClusterResourceQuotaInterface interface {
15
+	List(opts kapi.ListOptions) (*quotaapi.AppliedClusterResourceQuotaList, error)
16
+	Get(name string) (*quotaapi.AppliedClusterResourceQuota, error)
17
+}
18
+
19
+// appliedClusterResourceQuotas implements AppliedClusterResourceQuotasNamespacer interface
20
+type appliedClusterResourceQuotas struct {
21
+	r  *Client
22
+	ns string
23
+}
24
+
25
+// newAppliedClusterResourceQuotas returns a appliedClusterResourceQuotas
26
+func newAppliedClusterResourceQuotas(c *Client, namespace string) *appliedClusterResourceQuotas {
27
+	return &appliedClusterResourceQuotas{
28
+		r:  c,
29
+		ns: namespace,
30
+	}
31
+}
32
+
33
+// List returns a list of appliedClusterResourceQuotas that match the label and field selectors.
34
+func (c *appliedClusterResourceQuotas) List(opts kapi.ListOptions) (result *quotaapi.AppliedClusterResourceQuotaList, err error) {
35
+	result = &quotaapi.AppliedClusterResourceQuotaList{}
36
+	err = c.r.Get().Namespace(c.ns).Resource("appliedclusterresourcequotas").VersionedParams(&opts, kapi.ParameterCodec).Do().Into(result)
37
+	return
38
+}
39
+
40
+// Get returns information about a particular appliedClusterResourceQuota and error if one occurs.
41
+func (c *appliedClusterResourceQuotas) Get(name string) (result *quotaapi.AppliedClusterResourceQuota, err error) {
42
+	result = &quotaapi.AppliedClusterResourceQuota{}
43
+	err = c.r.Get().Namespace(c.ns).Resource("appliedclusterresourcequotas").Name(name).Do().Into(result)
44
+	return
45
+}
... ...
@@ -61,6 +61,7 @@ type Interface interface {
61 61
 	ClusterRolesInterface
62 62
 	ClusterRoleBindingsInterface
63 63
 	ClusterResourceQuotasInterface
64
+	AppliedClusterResourceQuotasNamespacer
64 65
 }
65 66
 
66 67
 // Builds provides a REST client for Builds
... ...
@@ -268,6 +269,10 @@ func (c *Client) ClusterResourceQuotas() ClusterResourceQuotaInterface {
268 268
 	return newClusterResourceQuotas(c)
269 269
 }
270 270
 
271
+func (c *Client) AppliedClusterResourceQuotas(namespace string) AppliedClusterResourceQuotaInterface {
272
+	return newAppliedClusterResourceQuotas(c, namespace)
273
+}
274
+
271 275
 // Client is an OpenShift client object
272 276
 type Client struct {
273 277
 	*restclient.RESTClient
... ...
@@ -329,3 +329,7 @@ func (c *Fake) ClusterRoleBindings() client.ClusterRoleBindingInterface {
329 329
 func (c *Fake) ClusterResourceQuotas() client.ClusterResourceQuotaInterface {
330 330
 	return &FakeClusterResourceQuotas{Fake: c}
331 331
 }
332
+
333
+func (c *Fake) AppliedClusterResourceQuotas(namespace string) client.AppliedClusterResourceQuotaInterface {
334
+	return &FakeAppliedClusterResourceQuotas{Fake: c, Namespace: namespace}
335
+}
332 336
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+package testclient
1
+
2
+import (
3
+	kapi "k8s.io/kubernetes/pkg/api"
4
+	ktestclient "k8s.io/kubernetes/pkg/client/unversioned/testclient"
5
+
6
+	quotaapi "github.com/openshift/origin/pkg/quota/api"
7
+)
8
+
9
+type FakeAppliedClusterResourceQuotas struct {
10
+	Fake      *Fake
11
+	Namespace string
12
+}
13
+
14
+func (c *FakeAppliedClusterResourceQuotas) Get(name string) (*quotaapi.AppliedClusterResourceQuota, error) {
15
+	obj, err := c.Fake.Invokes(ktestclient.NewGetAction("appliedclusterresourcequotas", c.Namespace, name), &quotaapi.AppliedClusterResourceQuota{})
16
+	if obj == nil {
17
+		return nil, err
18
+	}
19
+
20
+	return obj.(*quotaapi.AppliedClusterResourceQuota), err
21
+}
22
+
23
+func (c *FakeAppliedClusterResourceQuotas) List(opts kapi.ListOptions) (*quotaapi.AppliedClusterResourceQuotaList, error) {
24
+	obj, err := c.Fake.Invokes(ktestclient.NewListAction("appliedclusterresourcequotas", c.Namespace, opts), &quotaapi.AppliedClusterResourceQuotaList{})
25
+	if obj == nil {
26
+		return nil, err
27
+	}
28
+
29
+	return obj.(*quotaapi.AppliedClusterResourceQuotaList), err
30
+}
... ...
@@ -61,6 +61,7 @@ func describerMap(c *client.Client, kclient kclient.Interface, host string) map[
61 61
 		userapi.Kind("Group"):                         &GroupDescriber{c.Groups()},
62 62
 		userapi.Kind("UserIdentityMapping"):           &UserIdentityMappingDescriber{c},
63 63
 		quotaapi.Kind("ClusterResourceQuota"):         &ClusterQuotaDescriber{c},
64
+		quotaapi.Kind("AppliedClusterResourceQuota"):  &AppliedClusterQuotaDescriber{c},
64 65
 	}
65 66
 	return m
66 67
 }
... ...
@@ -1415,7 +1416,10 @@ func (d *ClusterQuotaDescriber) Describe(namespace, name string, settings kctl.D
1415 1415
 	if err != nil {
1416 1416
 		return "", err
1417 1417
 	}
1418
+	return DescribeClusterQuota(quota)
1419
+}
1418 1420
 
1421
+func DescribeClusterQuota(quota *quotaapi.ClusterResourceQuota) (string, error) {
1419 1422
 	selector, err := unversioned.LabelSelectorAsSelector(quota.Spec.Selector)
1420 1423
 	if err != nil {
1421 1424
 		return "", err
... ...
@@ -1451,3 +1455,15 @@ func (d *ClusterQuotaDescriber) Describe(namespace, name string, settings kctl.D
1451 1451
 		return nil
1452 1452
 	})
1453 1453
 }
1454
+
1455
+type AppliedClusterQuotaDescriber struct {
1456
+	client.Interface
1457
+}
1458
+
1459
+func (d *AppliedClusterQuotaDescriber) Describe(namespace, name string, settings kctl.DescriberSettings) (string, error) {
1460
+	quota, err := d.AppliedClusterResourceQuotas(namespace).Get(name)
1461
+	if err != nil {
1462
+		return "", err
1463
+	}
1464
+	return DescribeClusterQuota(quotaapi.ConvertAppliedClusterResourceQuotaToClusterResourceQuota(quota))
1465
+}
... ...
@@ -134,6 +134,8 @@ func NewHumanReadablePrinter(noHeaders, withNamespace, wide bool, showAll bool,
134 134
 
135 135
 	p.Handler(clusterResourceQuotaColumns, printClusterResourceQuota)
136 136
 	p.Handler(clusterResourceQuotaColumns, printClusterResourceQuotaList)
137
+	p.Handler(clusterResourceQuotaColumns, printAppliedClusterResourceQuota)
138
+	p.Handler(clusterResourceQuotaColumns, printAppliedClusterResourceQuotaList)
137 139
 
138 140
 	return p
139 141
 }
... ...
@@ -942,3 +944,16 @@ func printClusterResourceQuotaList(list *quotaapi.ClusterResourceQuotaList, w io
942 942
 	}
943 943
 	return nil
944 944
 }
945
+
946
+func printAppliedClusterResourceQuota(resourceQuota *quotaapi.AppliedClusterResourceQuota, w io.Writer, options kctl.PrintOptions) error {
947
+	return printClusterResourceQuota(quotaapi.ConvertAppliedClusterResourceQuotaToClusterResourceQuota(resourceQuota), w, options)
948
+}
949
+
950
+func printAppliedClusterResourceQuotaList(list *quotaapi.AppliedClusterResourceQuotaList, w io.Writer, options kctl.PrintOptions) error {
951
+	for i := range list.Items {
952
+		if err := printClusterResourceQuota(quotaapi.ConvertAppliedClusterResourceQuotaToClusterResourceQuota(&list.Items[i]), w, options); err != nil {
953
+			return err
954
+		}
955
+	}
956
+	return nil
957
+}
... ...
@@ -139,6 +139,8 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole {
139 139
 
140 140
 				authorizationapi.NewRule(read...).Groups(projectGroup).Resources("projectrequests", "projects").RuleOrDie(),
141 141
 
142
+				authorizationapi.NewRule(read...).Groups(quotaGroup).Resources("appliedclusterresourcequotas").RuleOrDie(),
143
+
142 144
 				authorizationapi.NewRule(read...).Groups(quotaGroup).Resources("clusterresourcequotas").RuleOrDie(),
143 145
 
144 146
 				authorizationapi.NewRule(read...).Groups(routeGroup).Resources("routes", "routes/status").RuleOrDie(),
... ...
@@ -239,6 +241,8 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole {
239 239
 
240 240
 				authorizationapi.NewRule("get", "patch", "update", "delete").Groups(projectGroup).Resources("projects").RuleOrDie(),
241 241
 
242
+				authorizationapi.NewRule(read...).Groups(quotaGroup).Resources("appliedclusterresourcequotas").RuleOrDie(),
243
+
242 244
 				authorizationapi.NewRule(readWrite...).Groups(routeGroup).Resources("routes").RuleOrDie(),
243 245
 				authorizationapi.NewRule(read...).Groups(routeGroup).Resources("routes/status").RuleOrDie(),
244 246
 				// an admin can run routers that write back conditions to the route
... ...
@@ -286,6 +290,8 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole {
286 286
 
287 287
 				authorizationapi.NewRule("get").Groups(projectGroup).Resources("projects").RuleOrDie(),
288 288
 
289
+				authorizationapi.NewRule(read...).Groups(quotaGroup).Resources("appliedclusterresourcequotas").RuleOrDie(),
290
+
289 291
 				authorizationapi.NewRule(readWrite...).Groups(routeGroup).Resources("routes").RuleOrDie(),
290 292
 				authorizationapi.NewRule(read...).Groups(routeGroup).Resources("routes/status").RuleOrDie(),
291 293
 
... ...
@@ -328,6 +334,8 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole {
328 328
 
329 329
 				authorizationapi.NewRule("get").Groups(projectGroup).Resources("projects").RuleOrDie(),
330 330
 
331
+				authorizationapi.NewRule(read...).Groups(quotaGroup).Resources("appliedclusterresourcequotas").RuleOrDie(),
332
+
331 333
 				authorizationapi.NewRule(read...).Groups(routeGroup).Resources("routes").RuleOrDie(),
332 334
 				authorizationapi.NewRule(read...).Groups(routeGroup).Resources("routes/status").RuleOrDie(),
333 335
 
... ...
@@ -91,6 +91,7 @@ import (
91 91
 	"github.com/openshift/origin/pkg/build/registry/buildclone"
92 92
 	"github.com/openshift/origin/pkg/build/registry/buildconfiginstantiate"
93 93
 
94
+	appliedclusterresourcequotaregistry "github.com/openshift/origin/pkg/quota/registry/appliedclusterresourcequota"
94 95
 	clusterresourcequotaregistry "github.com/openshift/origin/pkg/quota/registry/clusterresourcequota"
95 96
 
96 97
 	clusterpolicyregistry "github.com/openshift/origin/pkg/authorization/registry/clusterpolicy"
... ...
@@ -611,6 +612,8 @@ func (c *MasterConfig) GetRestStorage() map[string]rest.Storage {
611 611
 		"clusterRoles":          clusterRoleStorage,
612 612
 
613 613
 		"clusterResourceQuotas": restInPeace(clusterresourcequotaregistry.NewStorage(c.RESTOptionsGetter)),
614
+		"appliedClusterResourceQuotas": appliedclusterresourcequotaregistry.NewREST(
615
+			c.ClusterQuotaMappingController.GetClusterQuotaMapper(), c.Informers.ClusterResourceQuotas().Lister(), c.Informers.Namespaces().Lister()),
614 616
 	}
615 617
 
616 618
 	if configapi.IsBuildEnabled(&c.Options) {
... ...
@@ -17,6 +17,7 @@ import (
17 17
 	"k8s.io/kubernetes/pkg/api/unversioned"
18 18
 	"k8s.io/kubernetes/pkg/apiserver"
19 19
 	"k8s.io/kubernetes/pkg/client/cache"
20
+	"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
20 21
 	"k8s.io/kubernetes/pkg/client/restclient"
21 22
 	kclient "k8s.io/kubernetes/pkg/client/unversioned"
22 23
 	clientadapter "k8s.io/kubernetes/pkg/client/unversioned/adapters/internalclientset"
... ...
@@ -68,6 +69,7 @@ import (
68 68
 	projectcache "github.com/openshift/origin/pkg/project/cache"
69 69
 	"github.com/openshift/origin/pkg/quota"
70 70
 	quotaadmission "github.com/openshift/origin/pkg/quota/admission/resourcequota"
71
+	"github.com/openshift/origin/pkg/quota/controller/clusterquotamapping"
71 72
 	"github.com/openshift/origin/pkg/serviceaccounts"
72 73
 	usercache "github.com/openshift/origin/pkg/user/cache"
73 74
 	groupregistry "github.com/openshift/origin/pkg/user/registry/group"
... ...
@@ -76,7 +78,6 @@ import (
76 76
 	useretcd "github.com/openshift/origin/pkg/user/registry/user/etcd"
77 77
 	"github.com/openshift/origin/pkg/util/leaderlease"
78 78
 	"github.com/openshift/origin/pkg/util/restoptions"
79
-	"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
80 79
 )
81 80
 
82 81
 // MasterConfig defines the required parameters for starting the OpenShift master
... ...
@@ -91,9 +92,10 @@ type MasterConfig struct {
91 91
 	Authorizer                    authorizer.Authorizer
92 92
 	AuthorizationAttributeBuilder authorizer.AuthorizationAttributeBuilder
93 93
 
94
-	GroupCache                *usercache.GroupCache
95
-	ProjectAuthorizationCache *projectauth.AuthorizationCache
96
-	ProjectCache              *projectcache.ProjectCache
94
+	GroupCache                    *usercache.GroupCache
95
+	ProjectAuthorizationCache     *projectauth.AuthorizationCache
96
+	ProjectCache                  *projectcache.ProjectCache
97
+	ClusterQuotaMappingController *clusterquotamapping.ClusterQuotaMappingController
97 98
 
98 99
 	// RequestContextMapper maps requests to contexts
99 100
 	RequestContextMapper kapi.RequestContextMapper
... ...
@@ -197,6 +199,7 @@ func BuildMasterConfig(options configapi.MasterConfig) (*MasterConfig, error) {
197 197
 	}
198 198
 	groupCache := usercache.NewGroupCache(groupregistry.NewRegistry(groupStorage))
199 199
 	projectCache := projectcache.NewProjectCache(privilegedLoopbackKubeClient.Namespaces(), options.ProjectConfig.DefaultNodeSelector)
200
+	clusterQuotaMappingController := clusterquotamapping.NewClusterQuotaMappingController(informerFactory.Namespaces(), informerFactory.ClusterResourceQuotas())
200 201
 
201 202
 	kubeletClientConfig := configapi.GetKubeletClientConfig(options)
202 203
 
... ...
@@ -274,9 +277,10 @@ func BuildMasterConfig(options configapi.MasterConfig) (*MasterConfig, error) {
274 274
 		Authorizer:                    authorizer,
275 275
 		AuthorizationAttributeBuilder: newAuthorizationAttributeBuilder(requestContextMapper),
276 276
 
277
-		GroupCache:                groupCache,
278
-		ProjectAuthorizationCache: newProjectAuthorizationCache(authorizer, privilegedLoopbackKubeClient, informerFactory),
279
-		ProjectCache:              projectCache,
277
+		GroupCache:                    groupCache,
278
+		ProjectAuthorizationCache:     newProjectAuthorizationCache(authorizer, privilegedLoopbackKubeClient, informerFactory),
279
+		ProjectCache:                  projectCache,
280
+		ClusterQuotaMappingController: clusterQuotaMappingController,
280 281
 
281 282
 		RequestContextMapper: requestContextMapper,
282 283
 
... ...
@@ -17,13 +17,16 @@ import (
17 17
 	"github.com/openshift/origin/pkg/api/validation"
18 18
 	otestclient "github.com/openshift/origin/pkg/client/testclient"
19 19
 	"github.com/openshift/origin/pkg/controller/shared"
20
+	quotaapi "github.com/openshift/origin/pkg/quota/api"
21
+	"github.com/openshift/origin/pkg/quota/controller/clusterquotamapping"
20 22
 	"github.com/openshift/origin/pkg/util/restoptions"
21 23
 )
22 24
 
23 25
 // KnownUpdateValidationExceptions is the list of types that are known to not have an update validation function registered
24 26
 // If you add something to this list, explain why it doesn't need update validation.
25 27
 var KnownUpdateValidationExceptions = []reflect.Type{
26
-	reflect.TypeOf(&extapi.Scale{}), // scale operation uses the ValidateScale() function for both create and update
28
+	reflect.TypeOf(&extapi.Scale{}),                         // scale operation uses the ValidateScale() function for both create and update
29
+	reflect.TypeOf(&quotaapi.AppliedClusterResourceQuota{}), // this only retrieved, never created.  its a virtual projection of ClusterResourceQuota
27 30
 }
28 31
 
29 32
 // TestValidationRegistration makes sure that any RESTStorage that allows create or update has the correct validation register.
... ...
@@ -71,10 +74,12 @@ func TestValidationRegistration(t *testing.T) {
71 71
 func fakeMasterConfig() *MasterConfig {
72 72
 	etcdHelper := etcdstorage.NewEtcdStorage(nil, api.Codecs.LegacyCodec(), "", false, genericapiserver.DefaultDeserializationCacheSize)
73 73
 
74
+	informerFactory := shared.NewInformerFactory(testclient.NewSimpleFake(), otestclient.NewSimpleFake(), shared.DefaultListerWatcherOverrides{}, 1*time.Second)
74 75
 	return &MasterConfig{
75
-		KubeletClientConfig: &kubeletclient.KubeletClientConfig{},
76
-		RESTOptionsGetter:   restoptions.NewSimpleGetter(etcdHelper),
77
-		EtcdHelper:          etcdHelper,
78
-		Informers:           shared.NewInformerFactory(testclient.NewSimpleFake(), otestclient.NewSimpleFake(), shared.DefaultListerWatcherOverrides{}, 1*time.Second),
76
+		KubeletClientConfig:           &kubeletclient.KubeletClientConfig{},
77
+		RESTOptionsGetter:             restoptions.NewSimpleGetter(etcdHelper),
78
+		EtcdHelper:                    etcdHelper,
79
+		Informers:                     informerFactory,
80
+		ClusterQuotaMappingController: clusterquotamapping.NewClusterQuotaMappingController(informerFactory.Namespaces(), informerFactory.ClusterResourceQuotas()),
79 81
 	}
80 82
 }
... ...
@@ -4,6 +4,7 @@ import (
4 4
 	"io/ioutil"
5 5
 	"net"
6 6
 	"path"
7
+	"sync"
7 8
 	"time"
8 9
 
9 10
 	"github.com/golang/glog"
... ...
@@ -52,7 +53,6 @@ import (
52 52
 	imageapi "github.com/openshift/origin/pkg/image/api"
53 53
 	quota "github.com/openshift/origin/pkg/quota"
54 54
 	quotacontroller "github.com/openshift/origin/pkg/quota/controller"
55
-	"github.com/openshift/origin/pkg/quota/controller/clusterquotamapping"
56 55
 	serviceaccountcontrollers "github.com/openshift/origin/pkg/serviceaccounts/controllers"
57 56
 )
58 57
 
... ...
@@ -502,7 +502,10 @@ func (c *MasterConfig) RunResourceQuotaManager(cm *cmapp.CMServer) {
502 502
 	go kresourcequota.NewResourceQuotaController(resourceQuotaControllerOptions).Run(concurrentResourceQuotaSyncs, utilwait.NeverStop)
503 503
 }
504 504
 
505
+var initClusterQuotaMapping sync.Once
506
+
505 507
 func (c *MasterConfig) RunClusterQuotaMappingController() {
506
-	controller := clusterquotamapping.NewClusterQuotaMappingController(c.Informers.Namespaces(), c.Informers.ClusterResourceQuotas())
507
-	go controller.Run(5, utilwait.NeverStop)
508
+	initClusterQuotaMapping.Do(func() {
509
+		go c.ClusterQuotaMappingController.Run(5, utilwait.NeverStop)
510
+	})
508 511
 }
... ...
@@ -446,6 +446,7 @@ func StartAPI(oc *origin.MasterConfig, kc *kubernetes.MasterConfig) error {
446 446
 
447 447
 	// Must start policy caching immediately
448 448
 	oc.Informers.StartCore(utilwait.NeverStop)
449
+	oc.RunClusterQuotaMappingController()
449 450
 	oc.RunGroupCache()
450 451
 	oc.RunProjectCache()
451 452
 
... ...
@@ -23,6 +23,7 @@ import (
23 23
 	"github.com/openshift/origin/pkg/cmd/server/origin"
24 24
 	"github.com/openshift/origin/pkg/controller/shared"
25 25
 	projectcache "github.com/openshift/origin/pkg/project/cache"
26
+	"github.com/openshift/origin/pkg/quota/controller/clusterquotamapping"
26 27
 	"github.com/openshift/origin/pkg/util/restoptions"
27 28
 
28 29
 	// install all APIs
... ...
@@ -166,11 +167,13 @@ func TestAdmissionLifecycle(t *testing.T) {
166 166
 func TestCreatesAllowedDuringNamespaceDeletion(t *testing.T) {
167 167
 	etcdHelper := etcdstorage.NewEtcdStorage(nil, kapi.Codecs.LegacyCodec(), "", false, genericapiserver.DefaultDeserializationCacheSize)
168 168
 
169
+	informerFactory := shared.NewInformerFactory(testclient.NewSimpleFake(), otestclient.NewSimpleFake(), shared.DefaultListerWatcherOverrides{}, 1*time.Second)
169 170
 	config := &origin.MasterConfig{
170
-		KubeletClientConfig: &kubeletclient.KubeletClientConfig{},
171
-		RESTOptionsGetter:   restoptions.NewSimpleGetter(etcdHelper),
172
-		EtcdHelper:          etcdHelper,
173
-		Informers:           shared.NewInformerFactory(testclient.NewSimpleFake(), otestclient.NewSimpleFake(), shared.DefaultListerWatcherOverrides{}, 1*time.Second),
171
+		KubeletClientConfig:           &kubeletclient.KubeletClientConfig{},
172
+		RESTOptionsGetter:             restoptions.NewSimpleGetter(etcdHelper),
173
+		EtcdHelper:                    etcdHelper,
174
+		Informers:                     informerFactory,
175
+		ClusterQuotaMappingController: clusterquotamapping.NewClusterQuotaMappingController(informerFactory.Namespaces(), informerFactory.ClusterResourceQuotas()),
174 176
 	}
175 177
 	storageMap := config.GetRestStorage()
176 178
 	resources := sets.String{}
177 179
new file mode 100644
... ...
@@ -0,0 +1,19 @@
0
+package api
1
+
2
+// ConvertClusterResourceQuotaToAppliedClusterQuota returns back a converted AppliedClusterResourceQuota which is NOT a deep copy.
3
+func ConvertClusterResourceQuotaToAppliedClusterResourceQuota(in *ClusterResourceQuota) *AppliedClusterResourceQuota {
4
+	return &AppliedClusterResourceQuota{
5
+		ObjectMeta: in.ObjectMeta,
6
+		Spec:       in.Spec,
7
+		Status:     in.Status,
8
+	}
9
+}
10
+
11
+// ConvertClusterResourceQuotaToAppliedClusterQuota returns back a converted AppliedClusterResourceQuota which is NOT a deep copy.
12
+func ConvertAppliedClusterResourceQuotaToClusterResourceQuota(in *AppliedClusterResourceQuota) *ClusterResourceQuota {
13
+	return &ClusterResourceQuota{
14
+		ObjectMeta: in.ObjectMeta,
15
+		Spec:       in.Spec,
16
+		Status:     in.Status,
17
+	}
18
+}
... ...
@@ -12,6 +12,8 @@ import (
12 12
 
13 13
 func init() {
14 14
 	if err := api.Scheme.AddGeneratedDeepCopyFuncs(
15
+		DeepCopy_api_AppliedClusterResourceQuota,
16
+		DeepCopy_api_AppliedClusterResourceQuotaList,
15 17
 		DeepCopy_api_ClusterResourceQuota,
16 18
 		DeepCopy_api_ClusterResourceQuotaList,
17 19
 		DeepCopy_api_ClusterResourceQuotaSpec,
... ...
@@ -23,6 +25,43 @@ func init() {
23 23
 	}
24 24
 }
25 25
 
26
+func DeepCopy_api_AppliedClusterResourceQuota(in AppliedClusterResourceQuota, out *AppliedClusterResourceQuota, c *conversion.Cloner) error {
27
+	if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
28
+		return err
29
+	}
30
+	if err := api.DeepCopy_api_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil {
31
+		return err
32
+	}
33
+	if err := DeepCopy_api_ClusterResourceQuotaSpec(in.Spec, &out.Spec, c); err != nil {
34
+		return err
35
+	}
36
+	if err := DeepCopy_api_ClusterResourceQuotaStatus(in.Status, &out.Status, c); err != nil {
37
+		return err
38
+	}
39
+	return nil
40
+}
41
+
42
+func DeepCopy_api_AppliedClusterResourceQuotaList(in AppliedClusterResourceQuotaList, out *AppliedClusterResourceQuotaList, c *conversion.Cloner) error {
43
+	if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
44
+		return err
45
+	}
46
+	if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil {
47
+		return err
48
+	}
49
+	if in.Items != nil {
50
+		in, out := in.Items, &out.Items
51
+		*out = make([]AppliedClusterResourceQuota, len(in))
52
+		for i := range in {
53
+			if err := DeepCopy_api_AppliedClusterResourceQuota(in[i], &(*out)[i], c); err != nil {
54
+				return err
55
+			}
56
+		}
57
+	} else {
58
+		out.Items = nil
59
+	}
60
+	return nil
61
+}
62
+
26 63
 func DeepCopy_api_ClusterResourceQuota(in ClusterResourceQuota, out *ClusterResourceQuota, c *conversion.Cloner) error {
27 64
 	if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
28 65
 		return err
... ...
@@ -1,7 +1,6 @@
1 1
 package api
2 2
 
3 3
 import (
4
-	kapi "k8s.io/kubernetes/pkg/api"
5 4
 	"k8s.io/kubernetes/pkg/api/meta"
6 5
 	"k8s.io/kubernetes/pkg/api/unversioned"
7 6
 	"k8s.io/kubernetes/pkg/runtime"
... ...
@@ -28,15 +27,20 @@ func AddToScheme(scheme *runtime.Scheme) {
28 28
 // Adds the list of known types to api.Scheme.
29 29
 func addKnownTypes(scheme *runtime.Scheme) {
30 30
 	scheme.AddKnownTypes(SchemeGroupVersion,
31
-		&kapi.ListOptions{},
32 31
 		&ClusterResourceQuota{},
33 32
 		&ClusterResourceQuotaList{},
34
-
35
-		&kapi.DeleteOptions{},
36
-		&kapi.ListOptions{},
33
+		&AppliedClusterResourceQuota{},
34
+		&AppliedClusterResourceQuotaList{},
37 35
 	)
38 36
 }
39 37
 
38
+func (obj *AppliedClusterResourceQuotaList) GetObjectKind() unversioned.ObjectKind {
39
+	return &obj.TypeMeta
40
+}
41
+func (obj *AppliedClusterResourceQuota) GetObjectKind() unversioned.ObjectKind {
42
+	return &obj.TypeMeta
43
+}
44
+
40 45
 func (obj *ClusterResourceQuotaList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
41 46
 func (obj *ClusterResourceQuota) GetObjectKind() unversioned.ObjectKind     { return &obj.TypeMeta }
42 47
 func (obj *ClusterResourceQuota) GetObjectMeta() meta.Object                { return &obj.ObjectMeta }
... ...
@@ -54,6 +54,31 @@ type ClusterResourceQuotaList struct {
54 54
 	Items []ClusterResourceQuota
55 55
 }
56 56
 
57
+// AppliedClusterResourceQuota mirrors ClusterResourceQuota at a namespace scope, for projection
58
+// into a namespace.  It allows a project-admin to know which ClusterResourceQuotas are applied to
59
+// his project and their associated usage.
60
+type AppliedClusterResourceQuota struct {
61
+	unversioned.TypeMeta
62
+	// Standard object's metadata.
63
+	kapi.ObjectMeta
64
+
65
+	// Spec defines the desired quota
66
+	Spec ClusterResourceQuotaSpec
67
+
68
+	// Status defines the actual enforced quota and its current usage
69
+	Status ClusterResourceQuotaStatus
70
+}
71
+
72
+// AppliedClusterResourceQuotaList is a collection of AppliedClusterResourceQuotas
73
+type AppliedClusterResourceQuotaList struct {
74
+	unversioned.TypeMeta
75
+	// Standard object's metadata.
76
+	unversioned.ListMeta
77
+
78
+	// Items is a list of AppliedClusterResourceQuota
79
+	Items []AppliedClusterResourceQuota
80
+}
81
+
57 82
 // ResourceQuotasStatusByNamespace provides type correct methods
58 83
 type ResourceQuotasStatusByNamespace struct {
59 84
 	orderedMap orderedMap
... ...
@@ -13,6 +13,10 @@ import (
13 13
 
14 14
 func init() {
15 15
 	if err := api.Scheme.AddGeneratedConversionFuncs(
16
+		Convert_v1_AppliedClusterResourceQuota_To_api_AppliedClusterResourceQuota,
17
+		Convert_api_AppliedClusterResourceQuota_To_v1_AppliedClusterResourceQuota,
18
+		Convert_v1_AppliedClusterResourceQuotaList_To_api_AppliedClusterResourceQuotaList,
19
+		Convert_api_AppliedClusterResourceQuotaList_To_v1_AppliedClusterResourceQuotaList,
16 20
 		Convert_v1_ClusterResourceQuota_To_api_ClusterResourceQuota,
17 21
 		Convert_api_ClusterResourceQuota_To_v1_ClusterResourceQuota,
18 22
 		Convert_v1_ClusterResourceQuotaList_To_api_ClusterResourceQuotaList,
... ...
@@ -27,6 +31,98 @@ func init() {
27 27
 	}
28 28
 }
29 29
 
30
+func autoConvert_v1_AppliedClusterResourceQuota_To_api_AppliedClusterResourceQuota(in *AppliedClusterResourceQuota, out *quota_api.AppliedClusterResourceQuota, s conversion.Scope) error {
31
+	if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
32
+		return err
33
+	}
34
+	// TODO: Inefficient conversion - can we improve it?
35
+	if err := s.Convert(&in.ObjectMeta, &out.ObjectMeta, 0); err != nil {
36
+		return err
37
+	}
38
+	if err := Convert_v1_ClusterResourceQuotaSpec_To_api_ClusterResourceQuotaSpec(&in.Spec, &out.Spec, s); err != nil {
39
+		return err
40
+	}
41
+	if err := Convert_v1_ClusterResourceQuotaStatus_To_api_ClusterResourceQuotaStatus(&in.Status, &out.Status, s); err != nil {
42
+		return err
43
+	}
44
+	return nil
45
+}
46
+
47
+func Convert_v1_AppliedClusterResourceQuota_To_api_AppliedClusterResourceQuota(in *AppliedClusterResourceQuota, out *quota_api.AppliedClusterResourceQuota, s conversion.Scope) error {
48
+	return autoConvert_v1_AppliedClusterResourceQuota_To_api_AppliedClusterResourceQuota(in, out, s)
49
+}
50
+
51
+func autoConvert_api_AppliedClusterResourceQuota_To_v1_AppliedClusterResourceQuota(in *quota_api.AppliedClusterResourceQuota, out *AppliedClusterResourceQuota, s conversion.Scope) error {
52
+	if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
53
+		return err
54
+	}
55
+	// TODO: Inefficient conversion - can we improve it?
56
+	if err := s.Convert(&in.ObjectMeta, &out.ObjectMeta, 0); err != nil {
57
+		return err
58
+	}
59
+	if err := Convert_api_ClusterResourceQuotaSpec_To_v1_ClusterResourceQuotaSpec(&in.Spec, &out.Spec, s); err != nil {
60
+		return err
61
+	}
62
+	if err := Convert_api_ClusterResourceQuotaStatus_To_v1_ClusterResourceQuotaStatus(&in.Status, &out.Status, s); err != nil {
63
+		return err
64
+	}
65
+	return nil
66
+}
67
+
68
+func Convert_api_AppliedClusterResourceQuota_To_v1_AppliedClusterResourceQuota(in *quota_api.AppliedClusterResourceQuota, out *AppliedClusterResourceQuota, s conversion.Scope) error {
69
+	return autoConvert_api_AppliedClusterResourceQuota_To_v1_AppliedClusterResourceQuota(in, out, s)
70
+}
71
+
72
+func autoConvert_v1_AppliedClusterResourceQuotaList_To_api_AppliedClusterResourceQuotaList(in *AppliedClusterResourceQuotaList, out *quota_api.AppliedClusterResourceQuotaList, s conversion.Scope) error {
73
+	if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
74
+		return err
75
+	}
76
+	if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil {
77
+		return err
78
+	}
79
+	if in.Items != nil {
80
+		in, out := &in.Items, &out.Items
81
+		*out = make([]quota_api.AppliedClusterResourceQuota, len(*in))
82
+		for i := range *in {
83
+			if err := Convert_v1_AppliedClusterResourceQuota_To_api_AppliedClusterResourceQuota(&(*in)[i], &(*out)[i], s); err != nil {
84
+				return err
85
+			}
86
+		}
87
+	} else {
88
+		out.Items = nil
89
+	}
90
+	return nil
91
+}
92
+
93
+func Convert_v1_AppliedClusterResourceQuotaList_To_api_AppliedClusterResourceQuotaList(in *AppliedClusterResourceQuotaList, out *quota_api.AppliedClusterResourceQuotaList, s conversion.Scope) error {
94
+	return autoConvert_v1_AppliedClusterResourceQuotaList_To_api_AppliedClusterResourceQuotaList(in, out, s)
95
+}
96
+
97
+func autoConvert_api_AppliedClusterResourceQuotaList_To_v1_AppliedClusterResourceQuotaList(in *quota_api.AppliedClusterResourceQuotaList, out *AppliedClusterResourceQuotaList, s conversion.Scope) error {
98
+	if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
99
+		return err
100
+	}
101
+	if err := api.Convert_unversioned_ListMeta_To_unversioned_ListMeta(&in.ListMeta, &out.ListMeta, s); err != nil {
102
+		return err
103
+	}
104
+	if in.Items != nil {
105
+		in, out := &in.Items, &out.Items
106
+		*out = make([]AppliedClusterResourceQuota, len(*in))
107
+		for i := range *in {
108
+			if err := Convert_api_AppliedClusterResourceQuota_To_v1_AppliedClusterResourceQuota(&(*in)[i], &(*out)[i], s); err != nil {
109
+				return err
110
+			}
111
+		}
112
+	} else {
113
+		out.Items = nil
114
+	}
115
+	return nil
116
+}
117
+
118
+func Convert_api_AppliedClusterResourceQuotaList_To_v1_AppliedClusterResourceQuotaList(in *quota_api.AppliedClusterResourceQuotaList, out *AppliedClusterResourceQuotaList, s conversion.Scope) error {
119
+	return autoConvert_api_AppliedClusterResourceQuotaList_To_v1_AppliedClusterResourceQuotaList(in, out, s)
120
+}
121
+
30 122
 func autoConvert_v1_ClusterResourceQuota_To_api_ClusterResourceQuota(in *ClusterResourceQuota, out *quota_api.ClusterResourceQuota, s conversion.Scope) error {
31 123
 	if err := api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
32 124
 		return err
... ...
@@ -13,6 +13,8 @@ import (
13 13
 
14 14
 func init() {
15 15
 	if err := api.Scheme.AddGeneratedDeepCopyFuncs(
16
+		DeepCopy_v1_AppliedClusterResourceQuota,
17
+		DeepCopy_v1_AppliedClusterResourceQuotaList,
16 18
 		DeepCopy_v1_ClusterResourceQuota,
17 19
 		DeepCopy_v1_ClusterResourceQuotaList,
18 20
 		DeepCopy_v1_ClusterResourceQuotaSpec,
... ...
@@ -24,6 +26,43 @@ func init() {
24 24
 	}
25 25
 }
26 26
 
27
+func DeepCopy_v1_AppliedClusterResourceQuota(in AppliedClusterResourceQuota, out *AppliedClusterResourceQuota, c *conversion.Cloner) error {
28
+	if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
29
+		return err
30
+	}
31
+	if err := api_v1.DeepCopy_v1_ObjectMeta(in.ObjectMeta, &out.ObjectMeta, c); err != nil {
32
+		return err
33
+	}
34
+	if err := DeepCopy_v1_ClusterResourceQuotaSpec(in.Spec, &out.Spec, c); err != nil {
35
+		return err
36
+	}
37
+	if err := DeepCopy_v1_ClusterResourceQuotaStatus(in.Status, &out.Status, c); err != nil {
38
+		return err
39
+	}
40
+	return nil
41
+}
42
+
43
+func DeepCopy_v1_AppliedClusterResourceQuotaList(in AppliedClusterResourceQuotaList, out *AppliedClusterResourceQuotaList, c *conversion.Cloner) error {
44
+	if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
45
+		return err
46
+	}
47
+	if err := unversioned.DeepCopy_unversioned_ListMeta(in.ListMeta, &out.ListMeta, c); err != nil {
48
+		return err
49
+	}
50
+	if in.Items != nil {
51
+		in, out := in.Items, &out.Items
52
+		*out = make([]AppliedClusterResourceQuota, len(in))
53
+		for i := range in {
54
+			if err := DeepCopy_v1_AppliedClusterResourceQuota(in[i], &(*out)[i], c); err != nil {
55
+				return err
56
+			}
57
+		}
58
+	} else {
59
+		out.Items = nil
60
+	}
61
+	return nil
62
+}
63
+
27 64
 func DeepCopy_v1_ClusterResourceQuota(in ClusterResourceQuota, out *ClusterResourceQuota, c *conversion.Cloner) error {
28 65
 	if err := unversioned.DeepCopy_unversioned_TypeMeta(in.TypeMeta, &out.TypeMeta, c); err != nil {
29 66
 		return err
... ...
@@ -28,8 +28,17 @@ func addKnownTypes(scheme *runtime.Scheme) {
28 28
 	scheme.AddKnownTypes(SchemeGroupVersion,
29 29
 		&ClusterResourceQuota{},
30 30
 		&ClusterResourceQuotaList{},
31
+		&AppliedClusterResourceQuota{},
32
+		&AppliedClusterResourceQuotaList{},
31 33
 	)
32 34
 }
33 35
 
36
+func (obj *AppliedClusterResourceQuotaList) GetObjectKind() unversioned.ObjectKind {
37
+	return &obj.TypeMeta
38
+}
39
+func (obj *AppliedClusterResourceQuota) GetObjectKind() unversioned.ObjectKind {
40
+	return &obj.TypeMeta
41
+}
42
+
34 43
 func (obj *ClusterResourceQuotaList) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
35 44
 func (obj *ClusterResourceQuota) GetObjectKind() unversioned.ObjectKind     { return &obj.TypeMeta }
... ...
@@ -5,6 +5,27 @@ package v1
5 5
 // by hack/update-generated-swagger-descriptions.sh and should be run after a full build of OpenShift.
6 6
 // ==== DO NOT EDIT THIS FILE MANUALLY ====
7 7
 
8
+var map_AppliedClusterResourceQuota = map[string]string{
9
+	"":         "AppliedClusterResourceQuota mirrors ClusterResourceQuota at a namespace scope, for projection into a namespace.  It allows a project-admin to know which ClusterResourceQuotas are applied to his project and their associated usage.",
10
+	"metadata": "Standard object's metadata.",
11
+	"spec":     "Spec defines the desired quota",
12
+	"status":   "Status defines the actual enforced quota and its current usage",
13
+}
14
+
15
+func (AppliedClusterResourceQuota) SwaggerDoc() map[string]string {
16
+	return map_AppliedClusterResourceQuota
17
+}
18
+
19
+var map_AppliedClusterResourceQuotaList = map[string]string{
20
+	"":         "AppliedClusterResourceQuotaList is a collection of AppliedClusterResourceQuotas",
21
+	"metadata": "Standard object's metadata.",
22
+	"items":    "Items is a list of AppliedClusterResourceQuota",
23
+}
24
+
25
+func (AppliedClusterResourceQuotaList) SwaggerDoc() map[string]string {
26
+	return map_AppliedClusterResourceQuotaList
27
+}
28
+
8 29
 var map_ClusterResourceQuota = map[string]string{
9 30
 	"":         "ClusterResourceQuota mirrors ResourceQuota at a cluster scope.  This object is easily convertible to synthetic ResourceQuota object to allow quota evaluation re-use.",
10 31
 	"metadata": "Standard object's metadata.",
... ...
@@ -63,3 +63,28 @@ type ResourceQuotaStatusByNamespace struct {
63 63
 	// Status indicates how many resources have been consumed by this namespace
64 64
 	Status kapi.ResourceQuotaStatus `json:"status"`
65 65
 }
66
+
67
+// AppliedClusterResourceQuota mirrors ClusterResourceQuota at a namespace scope, for projection
68
+// into a namespace.  It allows a project-admin to know which ClusterResourceQuotas are applied to
69
+// his project and their associated usage.
70
+type AppliedClusterResourceQuota struct {
71
+	unversioned.TypeMeta `json:",inline"`
72
+	// Standard object's metadata.
73
+	kapi.ObjectMeta `json:"metadata"`
74
+
75
+	// Spec defines the desired quota
76
+	Spec ClusterResourceQuotaSpec `json:"spec"`
77
+
78
+	// Status defines the actual enforced quota and its current usage
79
+	Status ClusterResourceQuotaStatus `json:"status,omitempty"`
80
+}
81
+
82
+// AppliedClusterResourceQuotaList is a collection of AppliedClusterResourceQuotas
83
+type AppliedClusterResourceQuotaList struct {
84
+	unversioned.TypeMeta `json:",inline"`
85
+	// Standard object's metadata.
86
+	unversioned.ListMeta `json:"metadata,omitempty"`
87
+
88
+	// Items is a list of AppliedClusterResourceQuota
89
+	Items []AppliedClusterResourceQuota `json:"items"`
90
+}
... ...
@@ -38,9 +38,19 @@ func ValidateClusterResourceQuota(clusterquota *quotaapi.ClusterResourceQuota) f
38 38
 	return allErrs
39 39
 }
40 40
 
41
-func ValidateClusterResourceQuotaUpdate(clusterquota *quotaapi.ClusterResourceQuota, oldClusterResourceQuota *quotaapi.ClusterResourceQuota) field.ErrorList {
41
+func ValidateClusterResourceQuotaUpdate(clusterquota, oldClusterResourceQuota *quotaapi.ClusterResourceQuota) field.ErrorList {
42 42
 	allErrs := validation.ValidateObjectMetaUpdate(&clusterquota.ObjectMeta, &oldClusterResourceQuota.ObjectMeta, field.NewPath("metadata"))
43 43
 	allErrs = append(allErrs, ValidateClusterResourceQuota(clusterquota)...)
44 44
 
45 45
 	return allErrs
46 46
 }
47
+
48
+func ValidateAppliedClusterResourceQuota(clusterquota *quotaapi.AppliedClusterResourceQuota) field.ErrorList {
49
+	return ValidateClusterResourceQuota(quotaapi.ConvertAppliedClusterResourceQuotaToClusterResourceQuota(clusterquota))
50
+}
51
+
52
+func ValidateAppliedClusterResourceQuotaUpdate(clusterquota, oldClusterResourceQuota *quotaapi.AppliedClusterResourceQuota) field.ErrorList {
53
+	return ValidateClusterResourceQuotaUpdate(
54
+		quotaapi.ConvertAppliedClusterResourceQuotaToClusterResourceQuota(clusterquota),
55
+		quotaapi.ConvertAppliedClusterResourceQuotaToClusterResourceQuota(oldClusterResourceQuota))
56
+}
... ...
@@ -1,9 +1,105 @@
1 1
 package validation
2 2
 
3 3
 import (
4
+	"k8s.io/kubernetes/pkg/api"
5
+	"k8s.io/kubernetes/pkg/api/resource"
6
+	"k8s.io/kubernetes/pkg/api/unversioned"
7
+	"k8s.io/kubernetes/pkg/util/validation/field"
4 8
 	"testing"
9
+
10
+	quotaapi "github.com/openshift/origin/pkg/quota/api"
5 11
 )
6 12
 
7
-func TestClusterQuota(t *testing.T) {
13
+func TestValidationClusterQuota(t *testing.T) {
14
+	spec := api.ResourceQuotaSpec{
15
+		Hard: api.ResourceList{
16
+			api.ResourceCPU:                    resource.MustParse("100"),
17
+			api.ResourceMemory:                 resource.MustParse("10000"),
18
+			api.ResourceRequestsCPU:            resource.MustParse("100"),
19
+			api.ResourceRequestsMemory:         resource.MustParse("10000"),
20
+			api.ResourceLimitsCPU:              resource.MustParse("100"),
21
+			api.ResourceLimitsMemory:           resource.MustParse("10000"),
22
+			api.ResourcePods:                   resource.MustParse("10"),
23
+			api.ResourceServices:               resource.MustParse("0"),
24
+			api.ResourceReplicationControllers: resource.MustParse("10"),
25
+			api.ResourceQuotas:                 resource.MustParse("10"),
26
+			api.ResourceConfigMaps:             resource.MustParse("10"),
27
+			api.ResourceSecrets:                resource.MustParse("10"),
28
+		},
29
+	}
30
+
31
+	// storage is not yet supported as a quota tracked resource
32
+	invalidQuotaResourceSpec := api.ResourceQuotaSpec{
33
+		Hard: api.ResourceList{
34
+			api.ResourceStorage: resource.MustParse("10"),
35
+		},
36
+	}
37
+	validLabels := map[string]string{"a": "b"}
38
+
39
+	errs := ValidateClusterResourceQuota(
40
+		&quotaapi.ClusterResourceQuota{
41
+			ObjectMeta: api.ObjectMeta{Name: "good"},
42
+			Spec: quotaapi.ClusterResourceQuotaSpec{
43
+				Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
44
+				Quota:    spec,
45
+			},
46
+		},
47
+	)
48
+	if len(errs) != 0 {
49
+		t.Errorf("expected success: %v", errs)
50
+	}
8 51
 
52
+	errorCases := map[string]struct {
53
+		A quotaapi.ClusterResourceQuota
54
+		T field.ErrorType
55
+		F string
56
+	}{
57
+		"non-zero-length namespace": {
58
+			A: quotaapi.ClusterResourceQuota{
59
+				ObjectMeta: api.ObjectMeta{Namespace: "bad", Name: "good"},
60
+				Spec: quotaapi.ClusterResourceQuotaSpec{
61
+					Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
62
+					Quota:    spec,
63
+				},
64
+			},
65
+			T: field.ErrorTypeForbidden,
66
+			F: "metadata.namespace",
67
+		},
68
+		"missing label selector": {
69
+			A: quotaapi.ClusterResourceQuota{
70
+				ObjectMeta: api.ObjectMeta{Name: "good"},
71
+				Spec: quotaapi.ClusterResourceQuotaSpec{
72
+					Quota: spec,
73
+				},
74
+			},
75
+			T: field.ErrorTypeRequired,
76
+			F: "spec.selector",
77
+		},
78
+		"bad quota spec": {
79
+			A: quotaapi.ClusterResourceQuota{
80
+				ObjectMeta: api.ObjectMeta{Name: "good"},
81
+				Spec: quotaapi.ClusterResourceQuotaSpec{
82
+					Selector: &unversioned.LabelSelector{MatchLabels: validLabels},
83
+					Quota:    invalidQuotaResourceSpec,
84
+				},
85
+			},
86
+			T: field.ErrorTypeInvalid,
87
+			F: "spec.quota.hard[storage]",
88
+		},
89
+	}
90
+	for k, v := range errorCases {
91
+		errs := ValidateClusterResourceQuota(&v.A)
92
+		if len(errs) == 0 {
93
+			t.Errorf("expected failure %s for %v", k, v.A)
94
+			continue
95
+		}
96
+		for i := range errs {
97
+			if errs[i].Type != v.T {
98
+				t.Errorf("%s: expected errors to have type %s: %v", k, v.T, errs[i])
99
+			}
100
+			if errs[i].Field != v.F {
101
+				t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i])
102
+			}
103
+		}
104
+	}
9 105
 }
... ...
@@ -305,6 +305,10 @@ func (m *clusterQuotaMapper) setMapping(quota *quotaapi.ClusterResourceQuota, na
305 305
 
306 306
 }
307 307
 
308
+func (c *ClusterQuotaMappingController) GetClusterQuotaMapper() ClusterQuotaMapper {
309
+	return c.clusterQuotaMapper
310
+}
311
+
308 312
 func (c *ClusterQuotaMappingController) Run(workers int, stopCh <-chan struct{}) {
309 313
 	defer utilruntime.HandleCrash()
310 314
 
311 315
new file mode 100644
... ...
@@ -0,0 +1,81 @@
0
+package appliedclusterresourcequota
1
+
2
+import (
3
+	kapi "k8s.io/kubernetes/pkg/api"
4
+	kapierrors "k8s.io/kubernetes/pkg/api/errors"
5
+	"k8s.io/kubernetes/pkg/runtime"
6
+	"k8s.io/kubernetes/pkg/util/sets"
7
+
8
+	oapi "github.com/openshift/origin/pkg/api"
9
+	ocache "github.com/openshift/origin/pkg/client/cache"
10
+	quotaapi "github.com/openshift/origin/pkg/quota/api"
11
+	"github.com/openshift/origin/pkg/quota/controller/clusterquotamapping"
12
+	clusterresourcequotaregistry "github.com/openshift/origin/pkg/quota/registry/clusterresourcequota"
13
+)
14
+
15
+type AppliedClusterResourceQuotaREST struct {
16
+	quotaMapper     clusterquotamapping.ClusterQuotaMapper
17
+	quotaLister     *ocache.IndexerToClusterResourceQuotaLister
18
+	namespaceLister *ocache.IndexerToNamespaceLister
19
+}
20
+
21
+func NewREST(quotaMapper clusterquotamapping.ClusterQuotaMapper, quotaLister *ocache.IndexerToClusterResourceQuotaLister, namespaceLister *ocache.IndexerToNamespaceLister) *AppliedClusterResourceQuotaREST {
22
+	return &AppliedClusterResourceQuotaREST{
23
+		quotaMapper:     quotaMapper,
24
+		quotaLister:     quotaLister,
25
+		namespaceLister: namespaceLister,
26
+	}
27
+}
28
+
29
+func (r *AppliedClusterResourceQuotaREST) New() runtime.Object {
30
+	return &quotaapi.AppliedClusterResourceQuota{}
31
+}
32
+
33
+func (r *AppliedClusterResourceQuotaREST) Get(ctx kapi.Context, name string) (runtime.Object, error) {
34
+	namespace, ok := kapi.NamespaceFrom(ctx)
35
+	if !ok {
36
+		return nil, kapierrors.NewBadRequest("namespace is required")
37
+	}
38
+
39
+	quotaNames, _ := r.quotaMapper.GetClusterQuotasFor(namespace)
40
+	quotaNamesSet := sets.NewString(quotaNames...)
41
+	if !quotaNamesSet.Has(name) {
42
+		return nil, kapierrors.NewNotFound(quotaapi.Resource("appliedclusterresourcequota"), name)
43
+	}
44
+
45
+	clusterQuota, err := r.quotaLister.Get(name)
46
+	if err != nil {
47
+		return nil, err
48
+	}
49
+
50
+	return quotaapi.ConvertClusterResourceQuotaToAppliedClusterResourceQuota(clusterQuota), nil
51
+}
52
+
53
+func (r *AppliedClusterResourceQuotaREST) NewList() runtime.Object {
54
+	return &quotaapi.AppliedClusterResourceQuotaList{}
55
+}
56
+
57
+func (r *AppliedClusterResourceQuotaREST) List(ctx kapi.Context, options *kapi.ListOptions) (runtime.Object, error) {
58
+	namespace, ok := kapi.NamespaceFrom(ctx)
59
+	if !ok {
60
+		return nil, kapierrors.NewBadRequest("namespace is required")
61
+	}
62
+
63
+	// TODO max resource version?  watch?
64
+	list := &quotaapi.AppliedClusterResourceQuotaList{}
65
+	matcher := clusterresourcequotaregistry.Matcher(oapi.ListOptionsToSelectors(options))
66
+	quotaNames, _ := r.quotaMapper.GetClusterQuotasFor(namespace)
67
+
68
+	for _, name := range quotaNames {
69
+		quota, err := r.quotaLister.Get(name)
70
+		if err != nil {
71
+			continue
72
+		}
73
+		if matches, err := matcher.Matches(quota); err != nil || !matches {
74
+			continue
75
+		}
76
+		list.Items = append(list.Items, *quotaapi.ConvertClusterResourceQuotaToAppliedClusterResourceQuota(quota))
77
+	}
78
+
79
+	return list, nil
80
+}
0 81
new file mode 100755
... ...
@@ -0,0 +1,21 @@
0
+#!/bin/bash
1
+
2
+set -o errexit
3
+set -o nounset
4
+set -o pipefail
5
+
6
+OS_ROOT=$(dirname "${BASH_SOURCE}")/../..
7
+source "${OS_ROOT}/hack/lib/init.sh"
8
+os::log::stacktrace::install
9
+trap os::test::junit::reconcile_output EXIT
10
+
11
+os::test::junit::declare_suite_start "cmd/quota"
12
+
13
+os::cmd::expect_success 'oc new-project foo --as=deads'
14
+os::cmd::expect_success 'oc label namespace/foo owner=deads'
15
+os::cmd::expect_success 'oc create clusterquota for-deads --project-selector=owner=deads --hard=pods=10'
16
+os::cmd::try_until_text 'oc get appliedclusterresourcequota -n foo --as deads -o name' "for-deads"
17
+os::cmd::expect_success 'oc delete project foo'
18
+
19
+echo "quota: ok"
20
+os::test::junit::declare_suite_end
... ...
@@ -217,6 +217,15 @@ items:
217 217
     - ""
218 218
     attributeRestrictions: null
219 219
     resources:
220
+    - appliedclusterresourcequotas
221
+    verbs:
222
+    - get
223
+    - list
224
+    - watch
225
+  - apiGroups:
226
+    - ""
227
+    attributeRestrictions: null
228
+    resources:
220 229
     - clusterresourcequotas
221 230
     verbs:
222 231
     - get
... ...
@@ -644,6 +653,15 @@ items:
644 644
     - ""
645 645
     attributeRestrictions: null
646 646
     resources:
647
+    - appliedclusterresourcequotas
648
+    verbs:
649
+    - get
650
+    - list
651
+    - watch
652
+  - apiGroups:
653
+    - ""
654
+    attributeRestrictions: null
655
+    resources:
647 656
     - routes
648 657
     verbs:
649 658
     - create
... ...
@@ -956,6 +974,15 @@ items:
956 956
     - ""
957 957
     attributeRestrictions: null
958 958
     resources:
959
+    - appliedclusterresourcequotas
960
+    verbs:
961
+    - get
962
+    - list
963
+    - watch
964
+  - apiGroups:
965
+    - ""
966
+    attributeRestrictions: null
967
+    resources:
959 968
     - routes
960 969
     verbs:
961 970
     - create
... ...
@@ -1162,6 +1189,15 @@ items:
1162 1162
     - ""
1163 1163
     attributeRestrictions: null
1164 1164
     resources:
1165
+    - appliedclusterresourcequotas
1166
+    verbs:
1167
+    - get
1168
+    - list
1169
+    - watch
1170
+  - apiGroups:
1171
+    - ""
1172
+    attributeRestrictions: null
1173
+    resources:
1165 1174
     - routes
1166 1175
     verbs:
1167 1176
     - get