# Copyright Least Authority Enterprises. # See LICENSE for details. """ Tests for ``_textrenderer``. """ from __future__ import unicode_literals from io import StringIO as TextIO from hypothesis import given from hypothesis.strategies import integers, text from twisted.trial.unittest import TestCase from bitmath import Byte import attr from .._textrenderer import ( _render_container, _render_containers, _render_pods, _render_pod, _render_nodes, _render_limited_width, _Memory, Size, Sink, ) from txkube import v1 class RenderLimitedWidthTests(TestCase): @given(integers(min_value=3), text()) def test_width(self, limit, long_text): rendered = _render_limited_width(long_text, limit) self.assertEqual(min(limit, len(long_text)), len(rendered)) @given(integers(max_value=2), text()) def test_too_narrow(self, limit, long_text): with self.assertRaises(ValueError): _render_limited_width(long_text, limit) class RenderMemoryTests(TestCase): def test_bytes(self): self.assertEqual( " 123.00 Byte", _Memory(Byte(123)).render("8.2"), ) def test_kibibytes(self): self.assertEqual( " 12.50 KiB", _Memory(Byte(1024 * 12 + 512)).render("8.2"), ) def test_mebibytes(self): self.assertEqual( " 123.25 MiB", _Memory(Byte(2 ** 20 * 123 + 2 ** 20 / 4)).render("8.2"), ) def test_gibibytes(self): self.assertEqual( " 1.05 GiB", _Memory(Byte(2 ** 30 + 2 ** 30 / 20)).render("8.2"), ) def test_tebibytes(self): self.assertEqual( " 100.00 TiB", _Memory(Byte(2 ** 40 * 100)).render("8.2"), ) def test_pebibytes(self): self.assertEqual( " 100.00 PiB", _Memory(Byte(2 ** 50 * 100)).render("8.2"), ) def test_exbibytes(self): self.assertEqual( " 100.00 EiB", _Memory(Byte(2 ** 60 * 100)).render("8.2"), ) class ContainersTests(TestCase): def test_render_one(self): container = { "name": "foo", "usage": { "cpu": "100m", "memory": "200Mi", }, } self.assertEqual( " " " (foo)" " 10.0" " 200.00 MiB" " " "\n", _render_container(container), ) def test_render_several(self): containers = [ { "name": "foo", "usage": { "cpu": "100m", "memory": "200Mi", }, }, { "name": "bar", "usage": { "cpu": "200m", "memory": "100Mi", }, }, ] lines = _render_containers(containers).splitlines() self.assertEqual( ["(bar)", "(foo)"], list(line.split()[0].strip() for line in lines), ) class PodTests(TestCase): def test_render_several(self): name = "alpha" nodes = [ v1.Node( metadata=v1.ObjectMeta( name=name, ), status=v1.NodeStatus( allocatable={ "memory": "100MiB", }, ), ), ] pods = [ v1.Pod( metadata=v1.ObjectMeta( name="foo", ), ), v1.Pod( metadata=v1.ObjectMeta( name="bar", ), ), ] pod_usage = [ { "metadata": { "name": "foo", "namespace": "default", "creationTimestamp": "2017-04-07T15:21:22Z" }, "timestamp": "2017-04-07T15:21:00Z", "window": "1m0s", "containers": [ { "name": "foo-a", "usage": { "cpu": "100m", "memory": "50Ki" } }, { "name": "foo-b", "usage": { "cpu": "200m", "memory": "150Ki" } } ] }, { "metadata": { "name": "bar", "namespace": "default", "creationTimestamp": "2017-04-07T15:21:22Z" }, "timestamp": "2017-04-07T15:21:00Z", "window": "1m0s", "containers": [ { "name": "bar-a", "usage": { "cpu": "100m", "memory": "50Ki" } }, { "name": "bar-b", "usage": { "cpu": "500m", "memory": "10Ki" } } ] }, ] lines = list( line for line in _render_pods(pods, pod_usage, nodes).splitlines() if not line.strip().startswith("(") ) self.assertEqual( ["bar", "foo"], list(line.split()[0].strip() for line in lines), ) def test_render_pod(self): pod_usage = { "metadata": { "name": "foo", "namespace": "default", "creationTimestamp": "2017-04-07T15:21:22Z" }, "timestamp": "2017-04-07T15:21:00Z", "window": "1m0s", "containers": [ { "name": "foo-a", "usage": { "cpu": "100m", "memory": "128Ki" } }, ] } fields = _render_pod(pod_usage, _Memory(Byte(1024 * 1024))).split() self.assertEqual( [u'foo', u'10.0', u'128.00', u'KiB', u'12.50'], fields, ) class NodeTests(TestCase): def test_render_several(self): node = { "kind":"Node", "apiVersion":"v1", "metadata":{ "name":"ip-172-20-86-0.ec2.internal", "selfLink":"/api/v1/nodesip-172-20-86-0.ec2.internal", "uid":"e35442ce-0e45-11e7-acfa-1272db66581c", "resourceVersion":"17592039", "creationTimestamp":"2017-03-21T14:51:38Z", "labels":{ "beta.kubernetes.io/arch":"amd64", "beta.kubernetes.io/instance-type":"m3.medium", "beta.kubernetes.io/os":"linux", "failure-domain.beta.kubernetes.io/region":"us-east-1", "failure-domain.beta.kubernetes.io/zone":"us-east-1b", "kubernetes.io/hostname":"ip-172-20-86-0.ec2.internal", }, "annotations":{ "volumes.kubernetes.io/controller-managed-attach-detach":"true", }, }, "spec":{ "podCIDR":"100.96.1.0/24", "externalID":"i-0dd2f62f32659dcb2", "providerID":"aws:///us-east-1b/i-0dd2f62f32659dcb2", }, "status":{ "capacity":{ "alpha.kubernetes.io/nvidia-gpu":"0", "cpu":"1", "memory":"3857324Ki", "pods":"110", }, "allocatable":{ "alpha.kubernetes.io/nvidia-gpu":"0", "cpu":"1", "memory":"200Ki", "pods":"110", }, "conditions":[ { "type":"OutOfDisk", "status":"False", "lastHeartbeatTime":"2017-04-07T22:56:28Z", "lastTransitionTime":"2017-03-21T14:51:38Z", "reason":"KubeletHasSufficientDisk", "message":"kubelet has sufficient disk space available", }, { "type":"MemoryPressure", "status":"False", "lastHeartbeatTime":"2017-04-07T22:56:28Z", "lastTransitionTime":"2017-03-21T14:51:38Z", "reason":"KubeletHasSufficientMemory", "message":"kubelet has sufficient memory available", }, { "type":"DiskPressure", "status":"False", "lastHeartbeatTime":"2017-04-07T22:56:28Z", "lastTransitionTime":"2017-03-21T14:51:38Z", "reason":"KubeletHasNoDiskPressure", "message":"kubelet has no disk pressure", }, { "type":"Ready", "status":"True", "lastHeartbeatTime":"2017-04-07T22:56:28Z", "lastTransitionTime":"2017-03-21T14:52:08Z", "reason":"KubeletReady", "message":"kubelet is posting ready status", }, { "type":"NetworkUnavailable", "status":"False", "lastHeartbeatTime":"2017-03-21T14:51:43Z", "lastTransitionTime":"2017-03-21T14:51:43Z", "reason":"RouteCreated", "message":"RouteController created a route", }, ], "addresses":[ {"type":"InternalIP","address":"172.20.86.0"}, {"type":"LegacyHostIP","address":"172.20.86.0"}, {"type":"ExternalIP","address":"54.82.248.124"}, {"type":"Hostname","address":"ip-172-20-86-0.ec2.internal"}, ], "daemonEndpoints":{ "kubeletEndpoint":{"Port":10250}, }, "nodeInfo":{ "machineID":"1af1476dd91d40c0952ff71f54297123", "systemUUID":"EC28F27B-6B13-F91A-79BF-DFC0EA0B0BBD", "bootID":"a4d005b1-3117-4989-8a56-37d831d66d9d", "kernelVersion":"4.4.26-k8s", "osImage":"Debian GNU/Linux 8 (jessie)", "containerRuntimeVersion":"docker://1.12.3", "kubeletVersion":"v1.5.2", "kubeProxyVersion":"v1.5.2", "operatingSystem":"linux", "architecture":"amd64", }, "images":[ { "names":[ "example.invalid/foo@sha256:1685a4543dc70cb29e5a9df4b47a09ed7d6e54c00fb50296afff65683c67e0ff", "example.invalid/foo:1941d38", ], "sizeBytes":752076313, }, ], "volumesInUse":[ "kubernetes.io/aws-ebs/vol-01b01d11a6b17e2de", "kubernetes.io/aws-ebs/aws://us-east-1b/vol-0b977509f3c44d901", "kubernetes.io/aws-ebs/aws://us-east-1b/vol-091a8106ddd94357b", "kubernetes.io/aws-ebs/vol-0e80ac26be3edd63f", ], "volumesAttached":[ {"name":"kubernetes.io/aws-ebs/vol-01b01d11a6b17e2de","devicePath":"/dev/xvdbc"}, {"name":"kubernetes.io/aws-ebs/aws://us-east-1b/vol-091a8106ddd94357b","devicePath":"/dev/xvdba"}, {"name":"kubernetes.io/aws-ebs/vol-0e80ac26be3edd63f","devicePath":"/dev/xvdbb"}, {"name":"kubernetes.io/aws-ebs/aws://us-east-1b/vol-0b977509f3c44d901","devicePath":"/dev/xvdbd"}, ], }, } usage = { "metadata": { "name": "ip-172-20-86-0.ec2.internal", "creationTimestamp": "2017-04-11T14:41:47Z" }, "timestamp": "2017-04-11T14:41:00Z", "window": "1m0s", "usage": { "cpu": "68m", "memory": "100Ki" } } pods = [ v1.Pod( status=v1.PodStatus( hostIP=node["status"]["addresses"][0]["address"], ), ), ] self.assertEqual( "Node 0 " "CPU% 6.80 " "MEM% 50.00 ( 100 KiB/ 200 KiB) " "POD% 0.91 ( 1/110) " "Ready\n", _render_nodes([node], [usage], pods), ) @attr.s class StubTerminal(object): _size = attr.ib() def size(self): return self._size class SinkTests(TestCase): def test_maximum_rows(self): """ Any single string passed to ``Sink.write`` gets limited to the number of rows available on the sink. """ size = Size(rows=3, columns=80, xpixels=3, ypixels=7) outfile = TextIO() sink = Sink( terminal=StubTerminal(size=size), outfile=outfile, ) lines = list( "Hello {}\n".format(n) for n in range(size.rows + 1) ) sink.write("".join(lines)) self.assertEqual( lines[:size.rows], list( line + "\n" for line in outfile.getvalue().splitlines() ), )