diff --git a/app/lib/kubernetes_client.py b/app/lib/kubernetes_client.py index 5b9d449..062fb14 100644 --- a/app/lib/kubernetes_client.py +++ b/app/lib/kubernetes_client.py @@ -5,6 +5,108 @@ namespace = get_namespace() +def get_pod(pod_name): + pod = kubernetes_client.CoreV1Api().read_namespaced_pod(namespace=namespace, name=pod_name) + return pod + + +def get_pod_details(pod_name): + pod = get_pod(pod_name) + volume_details = [] + + # Get the specs of each volume + for volume in pod.spec.volumes: + if volume.persistent_volume_claim: + pvc_name = volume.persistent_volume_claim.claim_name + volume_specs = get_pod_pvc_details(namespace, pvc_name) + volume_details.append({ + 'pvc_name': pvc_name, + 'size': volume_specs['size'], + 'class': volume_specs['class'], + 'phase': volume_specs['phase'] + }) + + + affinity = pod.spec.affinity + if affinity: + affinity_details = get_affinity_details(affinity) + else: + affinity_details = {} + + # Get the node name where the pod is scheduled + pod_node_location = pod.spec.node_name + node_details = get_pod_node_details(pod_node_location) + + potential_ready = pod.status.phase == "Running" + container_resources = pod.spec.containers[0].resources + + pod_details = { + 'affinity': affinity_details, + 'limit_cpu': container_resources.limits.get('cpu', '') if container_resources.limits else 'None', + 'limit_memory': container_resources.limits.get('memory', '') if container_resources.limits else 'None', + 'name': pod.metadata.name, + 'namespace': pod.metadata.namespace, + 'node_details': node_details, + 'node_name': pod.spec.node_name, + 'node_selector': pod.spec.node_selector or {}, + 'potential_ready': potential_ready, + 'request_cpu': container_resources.requests.get('cpu', '') if container_resources.requests else 'None', + 'request_memory': container_resources.requests.get('memory', '') if container_resources.requests else 'None', + 'restart_count': pod.status.container_statuses[0].restart_count, + 'tolerations': pod.spec.tolerations or [], + 'volume_details': volume_details + } + return pod_details + + +def get_pod_pvc_details(namespace, pvc_name): + pvc = kubernetes_client.CoreV1Api().read_namespaced_persistent_volume_claim(namespace=namespace, name=pvc_name) + pvc_details = { + 'size': pvc.spec.resources.requests['storage'], + 'class': pvc.spec.storage_class_name, + 'phase': pvc.status.phase + } + return pvc_details + + +def get_pod_node_details(pod_name): + node = kubernetes_client.CoreV1Api().read_node(name=pod_name) + node_details = {} + + for label in ["kubernetes.io/arch", "node.kubernetes.io/instance-type", "kubernetes.io/os"]: + if label in node.metadata.labels: + node_details[label] = node.metadata.labels[label] + + if node.status.node_info.kernel_version: + node_details['kernel-version'] = node.status.node_info.kernel_version + + return node_details + + +def get_affinity_details(affinity): + affinity_types = [("node_affinity", affinity.node_affinity), + ("pod_affinity", affinity.pod_affinity), + ("pod_anti_affinity", affinity.pod_anti_affinity)] + affinity_details = {} + + for affinity_type, affinity_obj in affinity_types: + if affinity_obj: + required_during_scheduling = affinity_obj.required_during_scheduling_ignored_during_execution + if required_during_scheduling: + affinity_details[affinity_type] = { + "policy": "required_during_scheduling", + "details": required_during_scheduling + } + + preferred_during_scheduling = affinity_obj.preferred_during_scheduling_ignored_during_execution + if preferred_during_scheduling: + affinity_details[affinity_type] = { + "policy": "preferred_during_scheduling", + "details": preferred_during_scheduling + } + return affinity_details + + def list_stateful_sets(): return kubernetes_client.CustomObjectsApi().list_namespaced_custom_object(group="apps", version="v1", plural="statefulsets", @@ -28,11 +130,6 @@ def list_parachain_collator_stateful_sets(para_id): return list(map(lambda sts: sts['metadata']['name'], collator_stateful_sets)) -def get_pod(pod_name): - pod = kubernetes_client.CoreV1Api().read_namespaced_pod(namespace=namespace, name=pod_name) - return pod - - def list_substrate_node_pods(role_label=''): pods = kubernetes_client.CoreV1Api().list_namespaced_pod(namespace=namespace).items # Keep only pods which are substrate nodes diff --git a/app/routers/views.py b/app/routers/views.py index 2ee6dd6..9f3f03a 100644 --- a/app/routers/views.py +++ b/app/routers/views.py @@ -4,7 +4,7 @@ from app import __version__ from app.config.network_configuration import get_node_logs_link, get_network -from app.lib.kubernetes_client import list_validator_stateful_sets, list_parachain_collator_stateful_sets +from app.lib.kubernetes_client import get_pod_details, list_validator_stateful_sets, list_parachain_collator_stateful_sets from app.lib.network_utils import list_substrate_nodes, list_validators, get_session_queued_keys, list_parachains, \ list_parachain_collators, get_substrate_node from app.lib.runtime_utils import get_relay_runtime, get_relay_active_configuration, get_parachain_runtime @@ -43,7 +43,7 @@ async def get_nodes( request: Request, node_name: str = Path(description="Name of the node") ): - return templates.TemplateResponse('node_info.html', dict(request=request, network=network, node=get_substrate_node(node_name))) + return templates.TemplateResponse('node_info.html', dict(request=request, network=network, node=get_substrate_node(node_name), pod=get_pod_details(node_name))) @router.get("/validators", response_class=HTMLResponse, include_in_schema=False) diff --git a/app/templates/node_info.html b/app/templates/node_info.html index 595be68..812767a 100644 --- a/app/templates/node_info.html +++ b/app/templates/node_info.html @@ -25,7 +25,8 @@

{{ node.name }}

{% endif %} - +

Substrate Node Information

+
@@ -225,6 +226,140 @@

{{ node.name }}

Keys
+

Kubernetes Pod Information

+ + + + + + + + + + + + + + + + + + + + + {% if pod.node_selector %} + + + + + {% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% if pod.affinity %} + + + + + {% endif %} + + + + + +
KeysValues
Name{{ pod.name }}
Namespace{{ pod.namespace }}
Node{{ pod.node_name }}
Node Selector{{ pod.node_selector }}
Node Tolerations +
    + {% for toleration in pod.tolerations %} +
  • {{ toleration.key }}={{ toleration.operator }}:{{ toleration.value }}
  • + {% endfor %} +
+
Request CPU{{ pod.request_cpu }}
Request Memory{{ pod.request_memory }}
Limit CPU{{ pod.limit_cpu }}
Limit Memory{{ pod.limit_memory }}
Restart Count{{ pod.restart_count }}
Volume Details + + + + + + + + + + + {% for volume in pod.volume_details %} + + + + + + + {% endfor %} + +
NameSizeStorage ClassStorage Phase
{{ volume.pvc_name }}{{ volume.size }}{{ volume.class }}{{ volume.phase }}
+
Potential Ready{{ pod.potential_ready }}
Affinity + + + + + + + + + + {% for key, value in pod.affinity.items() %} + + + + + + {% endfor %} + +
TypePolicyDetails
{{ key }}{{ value.policy }}{{ value.details }}
+
Node Details + + + + + + + + + {% for key, value in pod.node_details.items() %} + + + + + {% endfor %} + +
KeyValue
{{ key }}{{ value }}
+