... | ... |
@@ -8,6 +8,7 @@ import ( |
8 | 8 |
|
9 | 9 |
kapi "k8s.io/kubernetes/pkg/api" |
10 | 10 |
"k8s.io/kubernetes/pkg/api/errors" |
11 |
+ kclient "k8s.io/kubernetes/pkg/client/unversioned" |
|
11 | 12 |
"k8s.io/kubernetes/pkg/fields" |
12 | 13 |
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" |
13 | 14 |
"k8s.io/kubernetes/pkg/watch" |
... | ... |
@@ -70,6 +71,7 @@ type ImportImageOptions struct { |
70 | 70 |
// helpers |
71 | 71 |
out io.Writer |
72 | 72 |
osClient client.Interface |
73 |
+ kClient kclient.Interface |
|
73 | 74 |
isClient client.ImageStreamInterface |
74 | 75 |
} |
75 | 76 |
|
... | ... |
@@ -86,11 +88,12 @@ func (o *ImportImageOptions) Complete(f *clientcmd.Factory, args []string, out i |
86 | 86 |
} |
87 | 87 |
o.Namespace = namespace |
88 | 88 |
|
89 |
- osClient, _, err := f.Clients() |
|
89 |
+ osClient, kClient, err := f.Clients() |
|
90 | 90 |
if err != nil { |
91 | 91 |
return err |
92 | 92 |
} |
93 | 93 |
o.osClient = osClient |
94 |
+ o.kClient = kClient |
|
94 | 95 |
o.isClient = osClient.ImageStreams(namespace) |
95 | 96 |
o.out = out |
96 | 97 |
|
... | ... |
@@ -140,7 +143,7 @@ func (o *ImportImageOptions) Run() error { |
140 | 140 |
fmt.Fprint(o.out, "The import completed successfully.\n\n") |
141 | 141 |
|
142 | 142 |
// optimization, use the image stream returned by the call |
143 |
- d := describe.ImageStreamDescriber{Interface: o.osClient} |
|
143 |
+ d := describe.ImageStreamDescriber{OSClient: o.osClient, KubeClient: o.kClient} |
|
144 | 144 |
info, err := d.Describe(o.Namespace, stream.Name) |
145 | 145 |
if err != nil { |
146 | 146 |
return err |
... | ... |
@@ -185,7 +188,7 @@ func (o *ImportImageOptions) Run() error { |
185 | 185 |
|
186 | 186 |
fmt.Fprint(o.out, "The import completed successfully.\n\n") |
187 | 187 |
|
188 |
- d := describe.ImageStreamDescriber{Interface: o.osClient} |
|
188 |
+ d := describe.ImageStreamDescriber{OSClient: o.osClient, KubeClient: o.kClient} |
|
189 | 189 |
info, err := d.Describe(updatedStream.Namespace, updatedStream.Name) |
190 | 190 |
if err != nil { |
191 | 191 |
return err |
... | ... |
@@ -29,6 +29,7 @@ import ( |
29 | 29 |
deployapi "github.com/openshift/origin/pkg/deploy/api" |
30 | 30 |
imageapi "github.com/openshift/origin/pkg/image/api" |
31 | 31 |
projectapi "github.com/openshift/origin/pkg/project/api" |
32 |
+ |
|
32 | 33 |
routeapi "github.com/openshift/origin/pkg/route/api" |
33 | 34 |
templateapi "github.com/openshift/origin/pkg/template/api" |
34 | 35 |
userapi "github.com/openshift/origin/pkg/user/api" |
... | ... |
@@ -41,7 +42,7 @@ func describerMap(c *client.Client, kclient kclient.Interface, host string) map[ |
41 | 41 |
deployapi.Kind("DeploymentConfig"): NewDeploymentConfigDescriber(c, kclient), |
42 | 42 |
authorizationapi.Kind("Identity"): &IdentityDescriber{c}, |
43 | 43 |
imageapi.Kind("Image"): &ImageDescriber{c}, |
44 |
- imageapi.Kind("ImageStream"): &ImageStreamDescriber{c}, |
|
44 |
+ imageapi.Kind("ImageStream"): &ImageStreamDescriber{c, kclient}, |
|
45 | 45 |
imageapi.Kind("ImageStreamTag"): &ImageStreamTagDescriber{c}, |
46 | 46 |
imageapi.Kind("ImageStreamImage"): &ImageStreamImageDescriber{c}, |
47 | 47 |
routeapi.Kind("Route"): &RouteDescriber{c, kclient}, |
... | ... |
@@ -586,12 +587,13 @@ func (d *ImageStreamImageDescriber) Describe(namespace, name string) (string, er |
586 | 586 |
|
587 | 587 |
// ImageStreamDescriber generates information about a ImageStream |
588 | 588 |
type ImageStreamDescriber struct { |
589 |
- client.Interface |
|
589 |
+ OSClient client.Interface |
|
590 |
+ KubeClient kclient.Interface |
|
590 | 591 |
} |
591 | 592 |
|
592 | 593 |
// Describe returns the description of an imageStream |
593 | 594 |
func (d *ImageStreamDescriber) Describe(namespace, name string) (string, error) { |
594 |
- c := d.ImageStreams(namespace) |
|
595 |
+ c := d.OSClient.ImageStreams(namespace) |
|
595 | 596 |
imageStream, err := c.Get(name) |
596 | 597 |
if err != nil { |
597 | 598 |
return "", err |
... | ... |
@@ -600,6 +602,7 @@ func (d *ImageStreamDescriber) Describe(namespace, name string) (string, error) |
600 | 600 |
return tabbedString(func(out *tabwriter.Writer) error { |
601 | 601 |
formatMeta(out, imageStream.ObjectMeta) |
602 | 602 |
formatString(out, "Docker Pull Spec", imageStream.Status.DockerImageRepository) |
603 |
+ formatImageStreamQuota(out, d.OSClient, d.KubeClient, imageStream) |
|
603 | 604 |
formatImageStreamTags(out, imageStream) |
604 | 605 |
return nil |
605 | 606 |
}) |
... | ... |
@@ -121,7 +121,7 @@ func TestDescribers(t *testing.T) { |
121 | 121 |
&BuildDescriber{c, fakeKube}, |
122 | 122 |
&BuildConfigDescriber{c, ""}, |
123 | 123 |
&ImageDescriber{c}, |
124 |
- &ImageStreamDescriber{c}, |
|
124 |
+ &ImageStreamDescriber{c, fakeKube}, |
|
125 | 125 |
&ImageStreamTagDescriber{c}, |
126 | 126 |
&ImageStreamImageDescriber{c}, |
127 | 127 |
&RouteDescriber{c, fakeKube}, |
... | ... |
@@ -11,12 +11,15 @@ import ( |
11 | 11 |
"github.com/docker/docker/pkg/units" |
12 | 12 |
|
13 | 13 |
"k8s.io/kubernetes/pkg/api" |
14 |
+ "k8s.io/kubernetes/pkg/api/resource" |
|
15 |
+ kclient "k8s.io/kubernetes/pkg/client/unversioned" |
|
14 | 16 |
"k8s.io/kubernetes/pkg/labels" |
15 | 17 |
"k8s.io/kubernetes/pkg/util/sets" |
16 | 18 |
|
17 | 19 |
buildapi "github.com/openshift/origin/pkg/build/api" |
18 | 20 |
"github.com/openshift/origin/pkg/client" |
19 | 21 |
imageapi "github.com/openshift/origin/pkg/image/api" |
22 |
+ imagequota "github.com/openshift/origin/pkg/quota/image" |
|
20 | 23 |
) |
21 | 24 |
|
22 | 25 |
const emptyString = "<none>" |
... | ... |
@@ -301,3 +304,53 @@ func formatImageStreamTags(out *tabwriter.Writer, stream *imageapi.ImageStream) |
301 | 301 |
} |
302 | 302 |
} |
303 | 303 |
} |
304 |
+ |
|
305 |
+func formatImageStreamQuota(out *tabwriter.Writer, c client.Interface, kc kclient.Interface, stream *imageapi.ImageStream) { |
|
306 |
+ quotas, err := kc.ResourceQuotas(stream.Namespace).List(api.ListOptions{}) |
|
307 |
+ if err != nil { |
|
308 |
+ return |
|
309 |
+ } |
|
310 |
+ |
|
311 |
+ var limit *resource.Quantity |
|
312 |
+ for _, item := range quotas.Items { |
|
313 |
+ // search for smallest ImageStream quota |
|
314 |
+ if value, ok := item.Spec.Hard[imageapi.ResourceImageStreamSize]; ok { |
|
315 |
+ if limit == nil || limit.Cmp(value) > 0 { |
|
316 |
+ limit = &value |
|
317 |
+ } |
|
318 |
+ } |
|
319 |
+ } |
|
320 |
+ if limit != nil { |
|
321 |
+ quantity := imagequota.GetImageStreamSize(c, stream, make(map[string]*imageapi.Image)) |
|
322 |
+ scale := mega |
|
323 |
+ if quantity.Value() >= (1<<giga.scale) || limit.Value() >= (1<<giga.scale) { |
|
324 |
+ scale = giga |
|
325 |
+ } |
|
326 |
+ formatString(out, "Quota Usage", fmt.Sprintf("%s / %s", |
|
327 |
+ formatQuantity(quantity, scale), formatQuantity(limit, scale))) |
|
328 |
+ } |
|
329 |
+} |
|
330 |
+ |
|
331 |
+type scale struct { |
|
332 |
+ scale uint64 |
|
333 |
+ unit string |
|
334 |
+} |
|
335 |
+ |
|
336 |
+var ( |
|
337 |
+ mega = scale{20, "MiB"} |
|
338 |
+ giga = scale{30, "GiB"} |
|
339 |
+) |
|
340 |
+ |
|
341 |
+// formatQuantity prints quantity according to passed scale. Manual scaling was |
|
342 |
+// done here to make sure we print correct binary values for quantity. |
|
343 |
+func formatQuantity(quantity *resource.Quantity, scale scale) string { |
|
344 |
+ integer := quantity.Value() >> scale.scale |
|
345 |
+ // fraction is the reminder of a division shifted by one order of magnitude |
|
346 |
+ fraction := (quantity.Value() % (1 << scale.scale)) >> (scale.scale - 10) |
|
347 |
+ // additionally we present only 2 digits after dot, so divide by 10 |
|
348 |
+ fraction = fraction / 10 |
|
349 |
+ if fraction > 0 { |
|
350 |
+ return fmt.Sprintf("%d.%02d%s", integer, fraction, scale.unit) |
|
351 |
+ } |
|
352 |
+ return fmt.Sprintf("%d%s", integer, scale.unit) |
|
353 |
+} |
... | ... |
@@ -6,9 +6,11 @@ import ( |
6 | 6 |
"text/tabwriter" |
7 | 7 |
"time" |
8 | 8 |
|
9 |
- imageapi "github.com/openshift/origin/pkg/image/api" |
|
10 | 9 |
kapi "k8s.io/kubernetes/pkg/api" |
10 |
+ "k8s.io/kubernetes/pkg/api/resource" |
|
11 | 11 |
"k8s.io/kubernetes/pkg/api/unversioned" |
12 |
+ |
|
13 |
+ imageapi "github.com/openshift/origin/pkg/image/api" |
|
12 | 14 |
) |
13 | 15 |
|
14 | 16 |
func TestFormatImageStreamTags(t *testing.T) { |
... | ... |
@@ -79,3 +81,45 @@ func TestFormatImageStreamTags(t *testing.T) { |
79 | 79 |
actual := string(buf.String()) |
80 | 80 |
t.Logf("\n%s", actual) |
81 | 81 |
} |
82 |
+ |
|
83 |
+func TestFormatQuantity(t *testing.T) { |
|
84 |
+ testCases := []struct { |
|
85 |
+ value int64 |
|
86 |
+ scale scale |
|
87 |
+ expected string |
|
88 |
+ }{ |
|
89 |
+ { |
|
90 |
+ value: 1 << 30, |
|
91 |
+ scale: giga, |
|
92 |
+ expected: "1GiB", |
|
93 |
+ }, |
|
94 |
+ { |
|
95 |
+ value: 1 << 20, |
|
96 |
+ scale: mega, |
|
97 |
+ expected: "1MiB", |
|
98 |
+ }, |
|
99 |
+ { |
|
100 |
+ value: 10 * (1 << 20), |
|
101 |
+ scale: giga, |
|
102 |
+ expected: "0.01GiB", |
|
103 |
+ }, |
|
104 |
+ { |
|
105 |
+ value: (1 << 30) + (1 << 20), |
|
106 |
+ scale: giga, |
|
107 |
+ expected: "1GiB", |
|
108 |
+ }, |
|
109 |
+ { |
|
110 |
+ value: (1 << 30) + 10*(1<<20), |
|
111 |
+ scale: giga, |
|
112 |
+ expected: "1.01GiB", |
|
113 |
+ }, |
|
114 |
+ } |
|
115 |
+ |
|
116 |
+ for idx, test := range testCases { |
|
117 |
+ quantity := resource.NewQuantity(test.value, resource.BinarySI) |
|
118 |
+ actual := formatQuantity(quantity, test.scale) |
|
119 |
+ if actual != test.expected { |
|
120 |
+ t.Errorf("(%d) expected '%s', got '%s'", idx, test.expected, actual) |
|
121 |
+ } |
|
122 |
+ } |
|
123 |
+} |