Coverage for src / server_list / spec / webapi / vm.py: 88%
52 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-31 11:45 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-31 11:45 +0000
1#!/usr/bin/env python3
2"""
3Web API for VM information.
4Endpoint: /server-list/api/vm
5"""
7import dataclasses
9import flask
11import server_list.spec.data_collector as data_collector
12import server_list.spec.models as models
13import server_list.spec.webapi as webapi
15vm_api = flask.Blueprint("vm_api", __name__)
18def _vm_to_response(vm: models.VMInfo, host_reachable: bool) -> dict:
19 """VMInfo をAPIレスポンス用dictに変換.
21 Args:
22 vm: VMInfo dataclass
23 host_reachable: ESXi ホストへの到達可否
25 Returns:
26 API レスポンス用 dict (cached_power_state を含む)
27 """
28 result = dataclasses.asdict(vm)
29 result["cached_power_state"] = result.get("power_state")
30 if not host_reachable:
31 result["power_state"] = "unknown"
32 return result
35def apply_unknown_power_state_if_unreachable(vm_info: models.VMInfo) -> dict:
36 """Apply 'unknown' power_state if the ESXi host is unreachable.
38 When ESXi host cannot be contacted, we use cached data but
39 set power_state to 'unknown' to indicate the current state
40 cannot be determined. The original cached value is preserved
41 in cached_power_state for resource calculations.
43 Converts VMInfo to dict for JSON serialization.
45 Note: 呼び出し元で vm_info の None チェックを行うこと。
46 """
47 host_reachable = data_collector.is_host_reachable(vm_info.esxi_host)
48 return _vm_to_response(vm_info, host_reachable)
51@vm_api.route("/vm/info", methods=["GET"])
52def get_vm_info_api():
53 """
54 Get VM information.
56 Query parameters:
57 vm_name: VM name to look up (required)
58 esxi_host: ESXi host (optional, for disambiguation)
60 Returns:
61 JSON with VM info (cpu_count, ram_mb, storage_gb, power_state, collected_at)
62 """
63 vm_name = flask.request.args.get("vm_name")
65 if not vm_name:
66 return webapi.error_response("vm_name is required", 400)
68 esxi_host = flask.request.args.get("esxi_host")
70 result = data_collector.get_vm_info(vm_name, esxi_host)
72 if result:
73 # Apply unknown power_state if host is unreachable
74 result_dict = apply_unknown_power_state_if_unreachable(result)
75 return webapi.success_response(result_dict)
77 return webapi.error_response(f"VM not found: {vm_name}")
80@vm_api.route("/vm/info/batch", methods=["POST"])
81def get_vm_info_batch():
82 """
83 Get VM information for multiple VMs.
85 Request body (JSON):
86 vms: List of VM names to look up
87 esxi_host: ESXi host (optional)
89 Returns:
90 JSON with results for each VM
91 """
92 data = flask.request.get_json()
94 if not data or "vms" not in data:
95 return webapi.error_response("VM list is required", 400)
97 vm_list = data["vms"]
98 esxi_host = data.get("esxi_host")
99 results = {}
101 for vm_name in vm_list:
102 result = data_collector.get_vm_info(vm_name, esxi_host)
103 if result: 103 ↛ 111line 103 didn't jump to line 111 because the condition on line 103 was always true
104 # Apply unknown power_state if host is unreachable
105 result_dict = apply_unknown_power_state_if_unreachable(result)
106 results[vm_name] = {
107 "success": True,
108 "data": result_dict
109 }
110 else:
111 results[vm_name] = {
112 "success": False,
113 "data": None
114 }
116 return flask.jsonify({
117 "success": True,
118 "results": results
119 })
122@vm_api.route("/vm/host/<esxi_host>", methods=["GET"])
123def get_vms_for_host(esxi_host: str):
124 """
125 Get all VMs for a specific ESXi host.
127 Returns:
128 JSON with list of VMs and their info
129 """
130 vms = data_collector.get_all_vm_info_for_host(esxi_host)
131 host_reachable = data_collector.is_host_reachable(esxi_host)
133 return webapi.success_response({
134 "esxi_host": esxi_host,
135 "vms": [_vm_to_response(vm, host_reachable) for vm in vms],
136 })
139@vm_api.route("/vm/refresh/<esxi_host>", methods=["POST"])
140def refresh_host_data(esxi_host: str):
141 """
142 Trigger immediate data collection from an ESXi host.
144 This endpoint triggers the data collector to fetch fresh data
145 from the specified ESXi host. After collection is complete,
146 an SSE event is sent to notify connected clients.
148 Returns:
149 JSON with success status
150 """
151 success = data_collector.collect_host_data(esxi_host)
153 if success:
154 return flask.jsonify({
155 "success": True,
156 "message": f"Data collection completed for {esxi_host}",
157 })
159 return webapi.error_response(f"Failed to collect data from {esxi_host}", 500)