cloudpods_storages_tool.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package tools
  15. import (
  16. "context"
  17. "encoding/json"
  18. "fmt"
  19. "strconv"
  20. "strings"
  21. "github.com/mark3labs/mcp-go/mcp"
  22. "yunion.io/x/log"
  23. "yunion.io/x/onecloud/pkg/mcp-server/adapters"
  24. "yunion.io/x/onecloud/pkg/mcp-server/models"
  25. )
  26. // CloudpodsStoragesTool 用于查询Cloudpods块存储列表的工具
  27. type CloudpodsStoragesTool struct {
  28. // adapter 用于与Cloudpods API进行交互
  29. adapter *adapters.CloudpodsAdapter
  30. }
  31. // NewCloudpodsStoragesTool 创建一个新的CloudpodsStoragesTool实例
  32. //
  33. // 参数:
  34. // - adapter: 用于与Cloudpods API交互的适配器
  35. //
  36. // 返回值:
  37. // - *CloudpodsStoragesTool: CloudpodsStoragesTool实例指针
  38. func NewCloudpodsStoragesTool(adapter *adapters.CloudpodsAdapter) *CloudpodsStoragesTool {
  39. return &CloudpodsStoragesTool{
  40. adapter: adapter,
  41. }
  42. }
  43. // GetTool 定义并返回查询块存储列表工具的元数据
  44. //
  45. // 工具用途:
  46. //
  47. // 查询Cloudpods块存储列表,获取存储资源信息
  48. //
  49. // 参数说明:
  50. // - limit: 返回结果数量限制,默认为20
  51. // - offset: 返回结果偏移量,默认为0
  52. // - search: 搜索关键词,可以按存储名称搜索
  53. // - cloudregion_ids: 云区域ID,多个用逗号分隔
  54. // - zone_ids: 可用区ID,多个用逗号分隔
  55. // - providers: 云平台提供商,多个用逗号分隔,如:OneCloud,Aliyun,Huawei
  56. // - storage_types: 存储类型,多个用逗号分隔,如:local,rbd,nfs,cephfs
  57. // - host_id: 主机ID,过滤关联指定主机的存储
  58. // - ak: 用户登录cloudpods后获取的access key
  59. // - sk: 用户登录cloudpods后获取的secret key
  60. func (c *CloudpodsStoragesTool) GetTool() mcp.Tool {
  61. return mcp.NewTool(
  62. "cloudpods_list_storages",
  63. mcp.WithDescription("查询Cloudpods块存储列表,获取存储资源信息"),
  64. mcp.WithString("limit", mcp.Description("返回结果数量限制,默认为20")),
  65. mcp.WithString("offset", mcp.Description("返回结果偏移量,默认为0")),
  66. mcp.WithString("search", mcp.Description("搜索关键词,可以按存储名称搜索")),
  67. mcp.WithString("cloudregion_ids", mcp.Description("云区域ID,多个用逗号分隔")),
  68. mcp.WithString("zone_ids", mcp.Description("可用区ID,多个用逗号分隔")),
  69. mcp.WithString("providers", mcp.Description("云平台提供商,多个用逗号分隔,如:OneCloud,Aliyun,Huawei")),
  70. mcp.WithString("storage_types", mcp.Description("存储类型,多个用逗号分隔,如:local,rbd,nfs,cephfs")),
  71. mcp.WithString("host_id", mcp.Description("主机ID,过滤关联指定主机的存储")),
  72. mcp.WithString("ak", mcp.Description("用户登录cloudpods后获取的access key")),
  73. mcp.WithString("sk", mcp.Description("用户登录cloudpods后获取的secret key")),
  74. )
  75. }
  76. // Handle 处理查询块存储列表的请求
  77. //
  78. // 参数:
  79. // - ctx: 控制生命周期的上下文
  80. // - req: 包含查询参数的请求对象
  81. //
  82. // 返回值:
  83. // - *mcp.CallToolResult: 包含块存储列表的响应对象
  84. // - error: 可能的错误信息
  85. func (c *CloudpodsStoragesTool) Handle(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  86. // 获取可选参数:返回结果数量限制,如果指定则转换为整数
  87. limit := 20
  88. if limitStr := req.GetString("limit", ""); limitStr != "" {
  89. if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 {
  90. limit = parsedLimit
  91. }
  92. }
  93. // 获取可选参数:结果偏移量,如果指定则转换为整数
  94. offset := 0
  95. if offsetStr := req.GetString("offset", ""); offsetStr != "" {
  96. if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 {
  97. offset = parsedOffset
  98. }
  99. }
  100. // 获取可选参数:搜索关键词
  101. search := req.GetString("search", "")
  102. // 获取可选参数:云区域ID列表
  103. var cloudregionIds []string
  104. if cloudregionIdsStr := req.GetString("cloudregion_ids", ""); cloudregionIdsStr != "" {
  105. cloudregionIds = strings.Split(cloudregionIdsStr, ",")
  106. for i, id := range cloudregionIds {
  107. cloudregionIds[i] = strings.TrimSpace(id)
  108. }
  109. }
  110. // 获取可选参数:可用区ID列表
  111. var zoneIds []string
  112. if zoneIdsStr := req.GetString("zone_ids", ""); zoneIdsStr != "" {
  113. zoneIds = strings.Split(zoneIdsStr, ",")
  114. for i, id := range zoneIds {
  115. zoneIds[i] = strings.TrimSpace(id)
  116. }
  117. }
  118. // 获取可选参数:云平台提供商列表
  119. var providers []string
  120. if providersStr := req.GetString("providers", ""); providersStr != "" {
  121. providers = strings.Split(providersStr, ",")
  122. for i, provider := range providers {
  123. providers[i] = strings.TrimSpace(provider)
  124. }
  125. }
  126. // 获取可选参数:存储类型列表
  127. var storageTypes []string
  128. if storageTypesStr := req.GetString("storage_types", ""); storageTypesStr != "" {
  129. storageTypes = strings.Split(storageTypesStr, ",")
  130. for i, storageType := range storageTypes {
  131. storageTypes[i] = strings.TrimSpace(storageType)
  132. }
  133. }
  134. // 获取可选参数:主机ID
  135. hostId := req.GetString("host_id", "")
  136. // 获取可选参数:访问凭证
  137. ak := req.GetString("ak", "")
  138. sk := req.GetString("sk", "")
  139. // 调用适配器查询块存储列表
  140. storagesResponse, err := c.adapter.ListStorages(ctx, limit, offset, search, cloudregionIds, zoneIds, providers, storageTypes, hostId, ak, sk)
  141. if err != nil {
  142. log.Errorf("Fail to query storage: %s", err)
  143. return nil, fmt.Errorf("fail to query storage: %w", err)
  144. }
  145. // 格式化查询结果
  146. formattedResult := c.formatStoragesResult(storagesResponse, limit, offset, search, cloudregionIds, zoneIds, providers, storageTypes, hostId)
  147. // 将结果序列化为JSON格式
  148. resultJSON, err := json.MarshalIndent(formattedResult, "", " ")
  149. if err != nil {
  150. log.Errorf("Fail to serialize result: %s", err)
  151. return nil, fmt.Errorf("fail to serialize result: %w", err)
  152. }
  153. return mcp.NewToolResultText(string(resultJSON)), nil
  154. }
  155. // GetName 返回工具的名称标识符
  156. //
  157. // 返回值:
  158. // - string: 工具名称字符串,用于唯一标识该工具
  159. func (c *CloudpodsStoragesTool) GetName() string {
  160. return "cloudpods_list_storages"
  161. }
  162. // formatStoragesResult 格式化块存储列表的响应结果
  163. //
  164. // 参数:
  165. // - response: 原始响应数据
  166. // - limit: 查询限制
  167. // - offset: 查询偏移量
  168. // - search: 搜索关键词
  169. // - cloudregionIds: 云区域ID列表
  170. // - zoneIds: 可用区ID列表
  171. // - providers: 云平台提供商列表
  172. // - storageTypes: 存储类型列表
  173. // - hostId: 主机ID
  174. //
  175. // 返回值:
  176. // - map[string]interface{}: 包含块存储列表的格式化结果
  177. func (c *CloudpodsStoragesTool) formatStoragesResult(
  178. response *models.StorageListResponse,
  179. limit, offset int,
  180. search string,
  181. cloudregionIds, zoneIds, providers, storageTypes []string,
  182. hostId string,
  183. ) map[string]interface{} {
  184. // 初始化格式化结果结构
  185. formatted := map[string]interface{}{
  186. "query_info": map[string]interface{}{
  187. "limit": limit,
  188. "offset": offset,
  189. "search": search,
  190. "cloudregion_ids": cloudregionIds,
  191. "zone_ids": zoneIds,
  192. "providers": providers,
  193. "storage_types": storageTypes,
  194. "host_id": hostId,
  195. "total": response.Total,
  196. "count": len(response.Storages),
  197. },
  198. "storages": make([]map[string]interface{}, 0, len(response.Storages)),
  199. }
  200. // 遍历块存储列表,构造每个块存储的详细信息
  201. for _, storage := range response.Storages {
  202. capacityGB := float64(storage.Capacity) / 1024
  203. usedCapacityGB := float64(storage.UsedCapacity) / 1024
  204. freeCapacityGB := float64(storage.FreeCapacity) / 1024
  205. actualUsedGB := float64(storage.ActualCapacityUsed) / 1024
  206. storageInfo := map[string]interface{}{
  207. "id": storage.Id,
  208. "name": storage.Name,
  209. "description": storage.Description,
  210. "status": storage.Status,
  211. "enabled": storage.Enabled,
  212. "storage_type": storage.StorageType,
  213. "medium_type": storage.MediumType,
  214. "provider": storage.Provider,
  215. "brand": storage.Brand,
  216. "cloud_env": storage.CloudEnv,
  217. "cloudregion": storage.Cloudregion,
  218. "cloudregion_id": storage.CloudregionId,
  219. "zone": storage.Zone,
  220. "zone_id": storage.ZoneId,
  221. "zone_ext_id": storage.ZoneExtId,
  222. "capacity_mb": storage.Capacity,
  223. "capacity_gb": fmt.Sprintf("%.2f GB", capacityGB),
  224. "used_capacity_mb": storage.UsedCapacity,
  225. "used_capacity_gb": fmt.Sprintf("%.2f GB", usedCapacityGB),
  226. "free_capacity_mb": storage.FreeCapacity,
  227. "free_capacity_gb": fmt.Sprintf("%.2f GB", freeCapacityGB),
  228. "actual_capacity_used": storage.ActualCapacityUsed,
  229. "actual_used_gb": fmt.Sprintf("%.2f GB", actualUsedGB),
  230. "virtual_capacity": storage.VirtualCapacity,
  231. "waste_capacity": storage.WasteCapacity,
  232. "reserved": storage.Reserved,
  233. "commit_bound": storage.CommitBound,
  234. "commit_rate": storage.CommitRate,
  235. "cmtbound": storage.Cmtbound,
  236. "is_sys_disk_store": storage.IsSysDiskStore,
  237. "is_public": storage.IsPublic,
  238. "is_emulated": storage.IsEmulated,
  239. "disk_count": storage.DiskCount,
  240. "host_count": storage.HostCount,
  241. "snapshot_count": storage.SnapshotCount,
  242. "master_host": storage.MasterHost,
  243. "master_host_name": storage.MasterHostName,
  244. "storagecache_id": storage.StoragecacheId,
  245. "account": storage.Account,
  246. "account_id": storage.AccountId,
  247. "account_status": storage.AccountStatus,
  248. "account_health_status": storage.AccountHealthStatus,
  249. "account_read_only": storage.AccountReadOnly,
  250. "manager": storage.Manager,
  251. "manager_id": storage.ManagerId,
  252. "manager_domain": storage.ManagerDomain,
  253. "manager_domain_id": storage.ManagerDomainId,
  254. "manager_project": storage.ManagerProject,
  255. "manager_project_id": storage.ManagerProjectId,
  256. "external_id": storage.ExternalId,
  257. "source": storage.Source,
  258. "region": storage.Region,
  259. "region_id": storage.RegionId,
  260. "region_ext_id": storage.RegionExtId,
  261. "region_external_id": storage.RegionExternalId,
  262. "environment": storage.Environment,
  263. "domain_id": storage.DomainId,
  264. "domain_src": storage.DomainSrc,
  265. "project_domain": storage.ProjectDomain,
  266. "public_scope": storage.PublicScope,
  267. "public_src": storage.PublicSrc,
  268. "shared_domains": storage.SharedDomains,
  269. "shared_projects": storage.SharedProjects,
  270. "schedtags": storage.Schedtags,
  271. "hosts": storage.Hosts,
  272. "storage_conf": storage.StorageConf,
  273. "metadata": storage.Metadata,
  274. "progress": storage.Progress,
  275. "can_delete": storage.CanDelete,
  276. "can_update": storage.CanUpdate,
  277. "update_version": storage.UpdateVersion,
  278. "created_at": storage.CreatedAt,
  279. "updated_at": storage.UpdatedAt,
  280. "imported_at": storage.ImportedAt,
  281. }
  282. formatted["storages"] = append(formatted["storages"].([]map[string]interface{}), storageInfo)
  283. }
  284. // 构造摘要信息
  285. formatted["summary"] = map[string]interface{}{
  286. "total_storages": response.Total, // 总存储数量
  287. "returned_count": len(response.Storages), // 当前返回的存储数量
  288. "has_more": response.Total > int64(offset+len(response.Storages)), // 是否还有更多数据
  289. "next_offset": offset + len(response.Storages), // 下一页的偏移量
  290. }
  291. return formatted
  292. }