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
+
Keys |
@@ -225,6 +226,140 @@ {{ node.name }}
+Kubernetes Pod Information
+
+
+
+ Keys |
+ Values |
+
+
+
+
+ Name |
+ {{ pod.name }} |
+
+
+ Namespace |
+ {{ pod.namespace }} |
+
+
+ Node |
+ {{ pod.node_name }} |
+
+ {% if pod.node_selector %}
+
+ Node Selector |
+ {{ pod.node_selector }} |
+
+ {% endif %}
+
+ 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 |
+
+
+
+
+ Name |
+ Size |
+ Storage Class |
+ Storage Phase |
+
+
+
+ {% for volume in pod.volume_details %}
+
+ {{ volume.pvc_name }} |
+ {{ volume.size }} |
+ {{ volume.class }} |
+ {{ volume.phase }} |
+
+ {% endfor %}
+
+
+ |
+
+
+ Potential Ready |
+ {{ pod.potential_ready }} |
+
+ {% if pod.affinity %}
+
+ Affinity |
+
+
+
+
+ Type |
+ Policy |
+ Details |
+
+
+
+ {% for key, value in pod.affinity.items() %}
+
+ {{ key }} |
+ {{ value.policy }} |
+ {{ value.details }} |
+
+ {% endfor %}
+
+
+ |
+
+ {% endif %}
+
+ Node Details |
+
+
+
+
+ Key |
+ Value |
+
+
+
+ {% for key, value in pod.node_details.items() %}
+
+ {{ key }} |
+ {{ value }} |
+
+ {% endfor %}
+
+
+ |
+
+
+