Skip to main content
Glama
by BenedatLLC
test_k8s_tools.py20.8 kB
"""Tests for our k8s tools. we mock out the connection to kubernetes (k8s_tools.K8S). """ import datetime from types import SimpleNamespace from k8stools import k8s_tools from unittest.mock import patch import pytest class MockK8S: def list_namespace(self): now = datetime.datetime.now(datetime.timezone.utc) ns1 = SimpleNamespace( metadata=SimpleNamespace(name="default", creation_timestamp=now - datetime.timedelta(days=5)), status=SimpleNamespace(phase="Active") ) ns2 = SimpleNamespace( metadata=SimpleNamespace(name="test", creation_timestamp=now - datetime.timedelta(days=2)), status=SimpleNamespace(phase="Active") ) return SimpleNamespace(items=[ns1, ns2]) def list_node(self): return self._mock_nodes() def _mock_nodes(self): now = datetime.datetime.now(datetime.timezone.utc) # Create mock node 1 - control-plane node condition1 = SimpleNamespace(type="Ready", status="True") address1 = SimpleNamespace(type="InternalIP", address="192.168.1.10") address2 = SimpleNamespace(type="ExternalIP", address="203.0.113.10") node_info1 = SimpleNamespace( kubelet_version="v1.28.2", os_image="Ubuntu 22.04.3 LTS", kernel_version="5.15.0-78-generic", container_runtime_version="containerd://1.7.2" ) node1 = SimpleNamespace( metadata=SimpleNamespace( name="control-plane-1", creation_timestamp=now - datetime.timedelta(days=30), labels={ "node-role.kubernetes.io/control-plane": "", "node-role.kubernetes.io/master": "", "kubernetes.io/hostname": "control-plane-1" } ), status=SimpleNamespace( conditions=[condition1], addresses=[address1, address2], node_info=node_info1 ) ) # Create mock node 2 - worker node condition2 = SimpleNamespace(type="Ready", status="True") address3 = SimpleNamespace(type="InternalIP", address="192.168.1.20") node_info2 = SimpleNamespace( kubelet_version="v1.28.2", os_image="Ubuntu 22.04.3 LTS", kernel_version="5.15.0-78-generic", container_runtime_version="containerd://1.7.2" ) node2 = SimpleNamespace( metadata=SimpleNamespace( name="worker-1", creation_timestamp=now - datetime.timedelta(days=25), labels={ "kubernetes.io/hostname": "worker-1" } ), status=SimpleNamespace( conditions=[condition2], addresses=[address3], node_info=node_info2 ) ) # Create mock node 3 - NotReady worker node condition3 = SimpleNamespace(type="Ready", status="False") address4 = SimpleNamespace(type="InternalIP", address="192.168.1.30") node_info3 = SimpleNamespace( kubelet_version="v1.28.1", os_image="Ubuntu 20.04.6 LTS", kernel_version="5.4.0-150-generic", container_runtime_version="docker://24.0.5" ) node3 = SimpleNamespace( metadata=SimpleNamespace( name="worker-2", creation_timestamp=now - datetime.timedelta(days=20), labels={ "node-role.kubernetes.io/worker": "", "kubernetes.io/hostname": "worker-2" } ), status=SimpleNamespace( conditions=[condition3], addresses=[address4], node_info=node_info3 ) ) return SimpleNamespace(items=[node1, node2, node3]) def list_pod_for_all_namespaces(self): return self._mock_pods() def list_namespaced_pod(self, namespace): pods = [pod for pod in self._mock_pods().items if pod.metadata.namespace == namespace] return SimpleNamespace(items=pods) def read_namespaced_pod(self, name, namespace): for pod in self._mock_pods().items: if pod.metadata.name == name and pod.metadata.namespace == namespace: return pod return None def list_namespaced_event(self, namespace, field_selector=None): now = datetime.datetime.now(datetime.timezone.utc) event1 = SimpleNamespace( last_timestamp=now - datetime.timedelta(hours=1), type="Normal", reason="Started", involved_object=SimpleNamespace(name="pod-1"), message="Pod started successfully." ) event2 = SimpleNamespace( last_timestamp=now - datetime.timedelta(minutes=30), type="Warning", reason="Failed", involved_object=SimpleNamespace(name="pod-1"), message="Pod failed to start." ) return SimpleNamespace(items=[event1, event2]) def read_namespaced_pod_log(self, name, namespace, container=None, follow=False, _preload_content=True, timestamps=True, tail_lines=None, limit_bytes=None): # Return a sample log string for testing if name == "pod-1" and namespace == "default" and container == "container-1": return "2025-07-12T00:00:00Z container-1 log line 1\n2025-07-12T00:01:00Z container-1 log line 2" return "" def list_service_for_all_namespaces(self): return self._mock_services() def list_namespaced_service(self, namespace): services = [svc for svc in self._mock_services().items if svc.metadata.namespace == namespace] return SimpleNamespace(items=services) def _mock_pods(self): now = datetime.datetime.now(datetime.timezone.utc) def spec_to_dict(self): return { "containers": [{"name": "container-1", "image": "nginx:latest"}], # Add other fields as needed for your tests } container_status = SimpleNamespace( ready=True, restart_count=1, last_state=SimpleNamespace(terminated=SimpleNamespace(started_at=now - datetime.timedelta(hours=3), finished_at=now - datetime.timedelta(hours=2), exit_code=137, reason='OOMKilled', message=None), running=None, waiting=None), state=SimpleNamespace(running=SimpleNamespace(started_at=now - datetime.timedelta(days=1)), waiting=None, terminated=None), name="container-1", image="nginx:latest", started=True, stop_signal=None, volume_mounts=[], resources=None, resource_requests={}, resource_limits={}, allocated_resources={} ) container1 = SimpleNamespace(name="container-1", image="nginx:latest") container2 = SimpleNamespace(name="container-2", image="busybox:latest") spec1 = SimpleNamespace(containers=[container1], node_name="node-1") spec1.to_dict = spec_to_dict.__get__(spec1) pod1 = SimpleNamespace( metadata=SimpleNamespace(name="pod-1", namespace="default", creation_timestamp=now - datetime.timedelta(days=1)), spec=spec1, status=SimpleNamespace(container_statuses=[container_status], pod_ip="10.244.1.10"), to_dict=lambda self: dict(self) ) spec2 = SimpleNamespace(containers=[container1, container2], node_name="node-2") spec2.to_dict = spec_to_dict.__get__(spec2) pod2 = SimpleNamespace( metadata=SimpleNamespace(name="pod-2", namespace="test", creation_timestamp=now - datetime.timedelta(hours=12)), spec=spec2, status=SimpleNamespace(container_statuses=[container_status, container_status], pod_ip="10.244.2.20"), to_dict=lambda self: dict(self) ) return SimpleNamespace(items=[pod1, pod2]) def _mock_services(self): now = datetime.datetime.now(datetime.timezone.utc) # Create mock service 1 - ClusterIP in default namespace port1 = SimpleNamespace(port=80, protocol="TCP") port2 = SimpleNamespace(port=443, protocol="TCP") service1 = SimpleNamespace( metadata=SimpleNamespace( name="nginx-service", namespace="default", creation_timestamp=now - datetime.timedelta(days=3) ), spec=SimpleNamespace( type="ClusterIP", cluster_ip="10.96.1.100", ports=[port1, port2] ), status=SimpleNamespace(load_balancer=None) ) # Create mock service 2 - LoadBalancer in test namespace port3 = SimpleNamespace(port=8080, protocol="TCP") ingress = SimpleNamespace(ip="192.168.1.100", hostname=None) service2 = SimpleNamespace( metadata=SimpleNamespace( name="app-service", namespace="test", creation_timestamp=now - datetime.timedelta(hours=8) ), spec=SimpleNamespace( type="LoadBalancer", cluster_ip="10.96.2.200", ports=[port3] ), status=SimpleNamespace( load_balancer=SimpleNamespace(ingress=[ingress]) ) ) # Create mock service 3 - ExternalName in default namespace service3 = SimpleNamespace( metadata=SimpleNamespace( name="external-service", namespace="default", creation_timestamp=now - datetime.timedelta(hours=2) ), spec=SimpleNamespace( type="ExternalName", cluster_ip="None", ports=[] ), status=SimpleNamespace(load_balancer=None) ) return SimpleNamespace(items=[service1, service2, service3]) class MockAppsV1Api: def list_namespaced_deployment(self, namespace): deployments = [dep for dep in self._mock_deployments().items if dep.metadata.namespace == namespace] return SimpleNamespace(items=deployments) def list_deployment_for_all_namespaces(self): return self._mock_deployments() def _mock_deployments(self): now = datetime.datetime.now(datetime.timezone.utc) # Create mock deployment 1 in default namespace deployment1 = SimpleNamespace( metadata=SimpleNamespace( name="nginx-deployment", namespace="default", creation_timestamp=now - datetime.timedelta(days=2) ), spec=SimpleNamespace(replicas=3), status=SimpleNamespace( ready_replicas=2, updated_replicas=3, available_replicas=2 ) ) # Create mock deployment 2 in test namespace deployment2 = SimpleNamespace( metadata=SimpleNamespace( name="app-deployment", namespace="test", creation_timestamp=now - datetime.timedelta(hours=6) ), spec=SimpleNamespace(replicas=1), status=SimpleNamespace( ready_replicas=1, updated_replicas=1, available_replicas=1 ) ) return SimpleNamespace(items=[deployment1, deployment2]) @pytest.fixture(scope="module", autouse=True) def setup_and_teardown_mock(): """Set up mock K8S client before tests and clean up after.""" # Store original values original_k8s = k8s_tools.K8S original_apps_v1 = k8s_tools.APPS_V1_API # Set up mocks k8s_tools.K8S = MockK8S() k8s_tools.APPS_V1_API = MockAppsV1Api() yield # Clean up - reset to original values k8s_tools.K8S = original_k8s k8s_tools.APPS_V1_API = original_apps_v1 # Note: We no longer set the global state at module level # k8s_tools.K8S = MockK8S() # Removed this line def test_get_namespaces(): namespaces = k8s_tools.get_namespaces() assert len(namespaces) == 2 assert namespaces[0].name == "default" assert namespaces[1].name == "test" assert namespaces[0].status == "Active" assert isinstance(namespaces[0].age, datetime.timedelta) def test_get_node_summaries(): nodes = k8s_tools.get_node_summaries() assert len(nodes) == 3 # Check first node (control-plane-1) node1 = nodes[0] assert isinstance(node1, k8s_tools.NodeSummary) assert node1.name == "control-plane-1" assert node1.status == "Ready" assert set(node1.roles) == {"control-plane", "master"} assert node1.version == "v1.28.2" assert node1.internal_ip == "192.168.1.10" assert node1.external_ip == "203.0.113.10" assert node1.os_image == "Ubuntu 22.04.3 LTS" assert node1.kernel_version == "5.15.0-78-generic" assert node1.container_runtime == "containerd://1.7.2" assert isinstance(node1.age, datetime.timedelta) # Check second node (worker-1) - should have <none> roles node2 = nodes[1] assert isinstance(node2, k8s_tools.NodeSummary) assert node2.name == "worker-1" assert node2.status == "Ready" assert node2.roles == ["<none>"] assert node2.version == "v1.28.2" assert node2.internal_ip == "192.168.1.20" assert node2.external_ip is None assert node2.os_image == "Ubuntu 22.04.3 LTS" assert node2.kernel_version == "5.15.0-78-generic" assert node2.container_runtime == "containerd://1.7.2" assert isinstance(node2.age, datetime.timedelta) # Check third node (worker-2) - NotReady status node3 = nodes[2] assert isinstance(node3, k8s_tools.NodeSummary) assert node3.name == "worker-2" assert node3.status == "NotReady" assert node3.roles == ["worker"] assert node3.version == "v1.28.1" assert node3.internal_ip == "192.168.1.30" assert node3.external_ip is None assert node3.os_image == "Ubuntu 20.04.6 LTS" assert node3.kernel_version == "5.4.0-150-generic" assert node3.container_runtime == "docker://24.0.5" assert isinstance(node3.age, datetime.timedelta) def test_get_pod_summaries(): pods = k8s_tools.get_pod_summaries() assert len(pods) == 2 assert pods[0].name == "pod-1" assert pods[1].name == "pod-2" assert pods[0].namespace == "default" assert pods[1].namespace == "test" assert pods[0].total_containers == 1 assert pods[1].total_containers == 2 assert pods[0].ready_containers == 1 assert pods[1].ready_containers == 2 assert isinstance(pods[0].age, datetime.timedelta) # Test the new fields assert pods[0].ip == "10.244.1.10" assert pods[1].ip == "10.244.2.20" assert pods[0].node == "node-1" assert pods[1].node == "node-2" def test_get_pod_container_statuses(): # Adjusted for new return type: should be instance of k8s_tools.ContainerStatus with patch.object(k8s_tools.client, "V1Pod", SimpleNamespace): statuses = k8s_tools.get_pod_container_statuses("pod-1", "default") assert isinstance(statuses, list) assert len(statuses) == 1 cs = statuses[0] assert isinstance(cs, k8s_tools.ContainerStatus) assert cs.container_name == "container-1" assert cs.ready is True assert cs.restart_count == 1 assert hasattr(cs, "last_state") def test_get_pod_events(): events = k8s_tools.get_pod_events("pod-1", "default") assert len(events) == 2 assert events[0].type == "Normal" assert events[1].type == "Warning" assert events[0].reason == "Started" assert events[1].reason == "Failed" assert events[0].object == "pod-1" assert isinstance(events[0].last_seen, datetime.timedelta) def test_get_pod_spec(): with patch.object(k8s_tools.client, "V1Pod", SimpleNamespace): spec = k8s_tools.get_pod_spec("pod-1", "default") assert isinstance(spec, dict) assert "containers" in spec assert spec["containers"] is not None assert isinstance(spec["containers"], list) assert len(spec["containers"]) == 1 assert spec["containers"][0]["name"] == "container-1" assert spec["containers"][0]["image"] == "nginx:latest" def test_retrieve_logs_from_pod_and_container(): logs = k8s_tools.get_logs_for_pod_and_container("pod-1", "default", "container-1") assert isinstance(logs, str) assert "container-1 log line 1" in logs assert "container-1 log line 2" in logs def test_deployment_summaries(): deployments = k8s_tools.get_deployment_summaries() assert isinstance(deployments, list) assert len(deployments) == 2 # Check first deployment (nginx-deployment in default namespace) deployment1 = deployments[0] assert isinstance(deployment1, k8s_tools.DeploymentSummary) assert deployment1.name == "nginx-deployment" assert deployment1.namespace == "default" assert deployment1.total_replicas == 3 assert deployment1.ready_replicas == 2 assert deployment1.up_to_date_relicas == 3 assert deployment1.available_replicas == 2 assert isinstance(deployment1.age, datetime.timedelta) # Check second deployment (app-deployment in test namespace) deployment2 = deployments[1] assert isinstance(deployment2, k8s_tools.DeploymentSummary) assert deployment2.name == "app-deployment" assert deployment2.namespace == "test" assert deployment2.total_replicas == 1 assert deployment2.ready_replicas == 1 assert deployment2.up_to_date_relicas == 1 assert deployment2.available_replicas == 1 assert isinstance(deployment2.age, datetime.timedelta) # Test namespace-specific deployments default_deployments = k8s_tools.get_deployment_summaries("default") assert isinstance(default_deployments, list) assert len(default_deployments) == 1 assert default_deployments[0].name == "nginx-deployment" assert default_deployments[0].namespace == "default" def test_service_summaries(): services = k8s_tools.get_service_summaries() assert isinstance(services, list) assert len(services) == 3 # Check first service (nginx-service ClusterIP in default namespace) service1 = services[0] assert isinstance(service1, k8s_tools.ServiceSummary) assert service1.name == "nginx-service" assert service1.namespace == "default" assert service1.type == "ClusterIP" assert service1.cluster_ip == "10.96.1.100" assert service1.external_ip is None assert len(service1.ports) == 2 assert service1.ports[0].port == 80 assert service1.ports[0].protocol == "TCP" assert service1.ports[1].port == 443 assert service1.ports[1].protocol == "TCP" assert isinstance(service1.age, datetime.timedelta) # Check second service (app-service LoadBalancer in test namespace) service2 = services[1] assert isinstance(service2, k8s_tools.ServiceSummary) assert service2.name == "app-service" assert service2.namespace == "test" assert service2.type == "LoadBalancer" assert service2.cluster_ip == "10.96.2.200" assert service2.external_ip == "192.168.1.100" assert len(service2.ports) == 1 assert service2.ports[0].port == 8080 assert service2.ports[0].protocol == "TCP" assert isinstance(service2.age, datetime.timedelta) # Check third service (external-service ExternalName in default namespace) service3 = services[2] assert isinstance(service3, k8s_tools.ServiceSummary) assert service3.name == "external-service" assert service3.namespace == "default" assert service3.type == "ExternalName" assert service3.cluster_ip is None # Should be None for ExternalName services assert service3.external_ip is None assert len(service3.ports) == 0 assert isinstance(service3.age, datetime.timedelta) # Test namespace-specific services default_services = k8s_tools.get_service_summaries("default") assert isinstance(default_services, list) assert len(default_services) == 2 assert default_services[0].name == "nginx-service" assert default_services[0].namespace == "default" assert default_services[1].name == "external-service" assert default_services[1].namespace == "default"

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/BenedatLLC/k8stools'

If you have feedback or need assistance with the MCP directory API, please join our Discord server