resource_operation_adapter.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  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 adapters
  15. import (
  16. "context"
  17. "fmt"
  18. "strconv"
  19. "strings"
  20. "time"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
  23. "yunion.io/x/onecloud/pkg/mcclient/modules/monitor"
  24. "yunion.io/x/onecloud/pkg/mcp-server/models"
  25. )
  26. // StartServer 启动 Cloudpods 中的服务器
  27. func (a *CloudpodsAdapter) StartServer(ctx context.Context, serverId string, req models.ServerStartRequest, ak string, sk string) (*models.ServerOperationResponse, error) {
  28. // 获取 Cloudpods 会话
  29. session, err := a.getSession(ctx, ak, sk)
  30. if err != nil {
  31. return nil, err
  32. }
  33. // 构造启动参数
  34. params := jsonutils.NewDict()
  35. // 如果需要自动续费预付费实例,则设置相应参数
  36. if req.AutoPrepaid {
  37. params.Set("auto_prepaid", jsonutils.NewBool(true))
  38. }
  39. // 如果指定了 QEMU 版本,则设置相应参数
  40. if req.QemuVersion != "" {
  41. params.Set("qemu_version", jsonutils.NewString(req.QemuVersion))
  42. }
  43. // 调用 Cloudpods API 启动服务器
  44. result, err := compute.Servers.PerformAction(session, serverId, "start", params)
  45. if err != nil {
  46. return nil, fmt.Errorf("failed to start server: %w", err)
  47. }
  48. // 构造响应数据
  49. response := &models.ServerOperationResponse{
  50. Operation: "start",
  51. }
  52. // 尝试将结果解析到响应结构体中
  53. if err := result.Unmarshal(response); err != nil {
  54. // 如果解析失败,则尝试获取任务 ID
  55. taskId, _ := result.GetString("task_id")
  56. response.TaskId = taskId
  57. // 如果任务 ID 不为空,则认为操作成功
  58. response.Success = taskId != ""
  59. }
  60. return response, nil
  61. }
  62. // StopServer 停止 Cloudpods 中的服务器
  63. func (a *CloudpodsAdapter) StopServer(ctx context.Context, serverId string, req models.ServerStopRequest, ak string, sk string) (*models.ServerOperationResponse, error) {
  64. // 获取 Cloudpods 会话
  65. session, err := a.getSession(ctx, ak, sk)
  66. if err != nil {
  67. return nil, err
  68. }
  69. // 构造停止参数
  70. params := jsonutils.NewDict()
  71. // 如果需要强制停止,则设置相应参数
  72. if req.IsForce {
  73. params.Set("is_force", jsonutils.NewBool(true))
  74. }
  75. // 如果需要停止计费,则设置相应参数
  76. if req.StopCharging {
  77. params.Set("stop_charging", jsonutils.NewBool(true))
  78. }
  79. // 如果设置了超时时间,则设置相应参数
  80. if req.TimeoutSecs > 0 {
  81. params.Set("timeout_secs", jsonutils.NewInt(req.TimeoutSecs))
  82. }
  83. // 调用 Cloudpods API 停止服务器
  84. result, err := compute.Servers.PerformAction(session, serverId, "stop", params)
  85. if err != nil {
  86. return nil, fmt.Errorf("failed to stop server: %w", err)
  87. }
  88. // 构造响应数据
  89. response := &models.ServerOperationResponse{
  90. Operation: "stop",
  91. }
  92. // 尝试将结果解析到响应结构体中
  93. if err := result.Unmarshal(response); err != nil {
  94. // 如果解析失败,则尝试获取任务 ID
  95. taskId, _ := result.GetString("task_id")
  96. response.TaskId = taskId
  97. // 如果任务 ID 不为空,则认为操作成功
  98. response.Success = taskId != ""
  99. }
  100. return response, nil
  101. }
  102. // RestartServer 重启 Cloudpods 中的服务器
  103. func (a *CloudpodsAdapter) RestartServer(ctx context.Context, serverId string, req models.ServerRestartRequest, ak string, sk string) (*models.ServerOperationResponse, error) {
  104. // 获取 Cloudpods 会话
  105. session, err := a.getSession(ctx, ak, sk)
  106. if err != nil {
  107. return nil, err
  108. }
  109. // 构造重启参数
  110. params := jsonutils.NewDict()
  111. // 如果需要强制重启,则设置相应参数
  112. if req.IsForce {
  113. params.Set("is_force", jsonutils.NewBool(true))
  114. }
  115. // 调用 Cloudpods API 重启服务器
  116. result, err := compute.Servers.PerformAction(session, serverId, "restart", params)
  117. if err != nil {
  118. return nil, fmt.Errorf("failed to restart server: %w", err)
  119. }
  120. // 构造响应数据
  121. response := &models.ServerOperationResponse{
  122. Operation: "restart",
  123. }
  124. // 尝试将结果解析到响应结构体中
  125. if err := result.Unmarshal(response); err != nil {
  126. // 如果解析失败,则尝试获取任务 ID
  127. taskId, _ := result.GetString("task_id")
  128. response.TaskId = taskId
  129. // 如果任务 ID 不为空,则认为操作成功
  130. response.Success = taskId != ""
  131. }
  132. return response, nil
  133. }
  134. // ResetServerPassword 重置 Cloudpods 中服务器的密码
  135. func (a *CloudpodsAdapter) ResetServerPassword(ctx context.Context, serverId string, req models.ServerResetPasswordRequest, ak string, sk string) (*models.ServerOperationResponse, error) {
  136. // 获取 Cloudpods 会话
  137. session, err := a.getSession(ctx, ak, sk)
  138. if err != nil {
  139. return nil, err
  140. }
  141. // 构造密码重置参数
  142. params := jsonutils.NewDict()
  143. // 设置新密码
  144. params.Set("password", jsonutils.NewString(req.Password))
  145. if req.ResetPassword {
  146. params.Set("reset_password", jsonutils.NewBool(true))
  147. }
  148. if req.AutoStart {
  149. params.Set("auto_start", jsonutils.NewBool(true))
  150. }
  151. if req.Username != "" {
  152. params.Set("username", jsonutils.NewString(req.Username))
  153. }
  154. // 调用 Cloudpods API 重置服务器密码
  155. result, err := compute.Servers.PerformAction(session, serverId, "reset-password", params)
  156. if err != nil {
  157. return nil, fmt.Errorf("failed to reset server password: %w", err)
  158. }
  159. // 构造响应数据
  160. response := &models.ServerOperationResponse{
  161. Operation: "reset-password",
  162. }
  163. // 尝试将结果解析到响应结构体中
  164. if err := result.Unmarshal(response); err != nil {
  165. // 如果解析失败,则尝试获取任务 ID
  166. taskId, _ := result.GetString("task_id")
  167. response.TaskId = taskId
  168. // 如果任务 ID 不为空,则认为操作成功
  169. response.Success = taskId != ""
  170. }
  171. return response, nil
  172. }
  173. // DeleteServer 删除 Cloudpods 中的服务器
  174. func (a *CloudpodsAdapter) DeleteServer(ctx context.Context, serverId string, req models.ServerDeleteRequest, ak string, sk string) (*models.ServerOperationResponse, error) {
  175. // 获取 Cloudpods 会话
  176. session, err := a.getSession(ctx, ak, sk)
  177. if err != nil {
  178. return nil, err
  179. }
  180. // 构造删除参数
  181. params := jsonutils.NewDict()
  182. // 如果需要覆盖待删除状态,则设置相应参数
  183. if req.OverridePendingDelete {
  184. params.Set("override_pending_delete", jsonutils.NewBool(true))
  185. }
  186. // 如果需要彻底删除,则设置相应参数
  187. if req.Purge {
  188. params.Set("purge", jsonutils.NewBool(true))
  189. }
  190. // 如果需要删除快照,则设置相应参数
  191. if req.DeleteSnapshots {
  192. params.Set("delete_snapshots", jsonutils.NewBool(true))
  193. }
  194. // 如果需要删除弹性 IP,则设置相应参数
  195. if req.DeleteEip {
  196. params.Set("delete_eip", jsonutils.NewBool(true))
  197. }
  198. // 如果需要删除磁盘,则设置相应参数
  199. if req.DeleteDisks {
  200. params.Set("delete_disks", jsonutils.NewBool(true))
  201. }
  202. // 调用 Cloudpods API 删除服务器
  203. result, err := compute.Servers.Delete(session, serverId, params)
  204. if err != nil {
  205. return nil, fmt.Errorf("failed to delete server: %w", err)
  206. }
  207. // 构造响应数据
  208. response := &models.ServerOperationResponse{
  209. Operation: "delete",
  210. }
  211. // 尝试将结果解析到响应结构体中
  212. if err := result.Unmarshal(response); err != nil {
  213. // 如果解析失败,则尝试获取任务 ID
  214. taskId, _ := result.GetString("task_id")
  215. response.TaskId = taskId
  216. // 如果任务 ID 不为空,则认为操作成功
  217. response.Success = taskId != ""
  218. }
  219. return response, nil
  220. }
  221. // CreateServer 在 Cloudpods 中创建服务器
  222. func (a *CloudpodsAdapter) CreateServer(ctx context.Context, req models.CreateServerRequest, ak string, sk string) (*models.CreateServerResponse, error) {
  223. // 获取 Cloudpods 会话
  224. session, err := a.getSession(ctx, ak, sk)
  225. if err != nil {
  226. return nil, err
  227. }
  228. // 构造创建服务器的参数
  229. params := jsonutils.NewDict()
  230. // 设置服务器名称
  231. params.Set("name", jsonutils.NewString(req.Name))
  232. // 设置 CPU 核心数
  233. params.Set("vcpu_count", jsonutils.NewInt(req.VcpuCount))
  234. // 设置内存大小
  235. params.Set("vmem_size", jsonutils.NewInt(req.VmemSize))
  236. // 如果创建数量大于1,则设置相应参数
  237. if req.Count > 1 {
  238. params.Set("count", jsonutils.NewInt(int64(req.Count)))
  239. }
  240. // 如果需要自动启动,则设置相应参数
  241. if req.AutoStart {
  242. params.Set("auto_start", jsonutils.NewBool(req.AutoStart))
  243. }
  244. // 如果设置了密码,则设置相应参数
  245. if req.Password != "" {
  246. params.Set("password", jsonutils.NewString(req.Password))
  247. }
  248. // 如果设置了计费类型,则设置相应参数
  249. if req.BillingType != "" {
  250. params.Set("billing_type", jsonutils.NewString(req.BillingType))
  251. }
  252. // 如果设置了计费时长,则设置相应参数
  253. if req.Duration != "" {
  254. params.Set("duration", jsonutils.NewString(req.Duration))
  255. }
  256. // 如果设置了描述,则设置相应参数
  257. if req.Description != "" {
  258. params.Set("description", jsonutils.NewString(req.Description))
  259. }
  260. // 如果设置了主机名,则设置相应参数
  261. if req.Hostname != "" {
  262. params.Set("hostname", jsonutils.NewString(req.Hostname))
  263. }
  264. // 如果设置了虚拟化类型,则设置相应参数
  265. if req.Hypervisor != "" {
  266. params.Set("hypervisor", jsonutils.NewString(req.Hypervisor))
  267. }
  268. // 如果设置了用户数据,则设置相应参数
  269. if req.UserData != "" {
  270. params.Set("user_data", jsonutils.NewString(req.UserData))
  271. }
  272. // 如果设置了密钥对 ID,则设置相应参数
  273. if req.KeypairId != "" {
  274. params.Set("keypair_id", jsonutils.NewString(req.KeypairId))
  275. }
  276. // 如果设置了项目 ID,则设置相应参数
  277. if req.ProjectId != "" {
  278. params.Set("project_id", jsonutils.NewString(req.ProjectId))
  279. }
  280. // 如果设置了可用区 ID,则设置相应参数
  281. if req.ZoneId != "" {
  282. params.Set("prefer_zone_id", jsonutils.NewString(req.ZoneId))
  283. }
  284. // 如果设置了区域 ID,则设置相应参数
  285. if req.RegionId != "" {
  286. params.Set("prefer_region_id", jsonutils.NewString(req.RegionId))
  287. }
  288. // 如果需要禁用删除,则设置相应参数
  289. if req.DisableDelete {
  290. params.Set("disable_delete", jsonutils.NewBool(req.DisableDelete))
  291. }
  292. // 如果设置了启动顺序,则设置相应参数
  293. if req.BootOrder != "" {
  294. params.Set("boot_order", jsonutils.NewString(req.BootOrder))
  295. }
  296. // 如果设置了元数据,则设置相应参数
  297. if len(req.Metadata) > 0 {
  298. metaDict := jsonutils.NewDict()
  299. for k, v := range req.Metadata {
  300. metaDict.Set(k, jsonutils.NewString(v))
  301. }
  302. params.Set("__meta__", metaDict)
  303. }
  304. // 构造磁盘参数
  305. disks := jsonutils.NewArray()
  306. // 如果设置了镜像 ID,则构造系统磁盘参数
  307. if req.ImageId != "" {
  308. diskDict := jsonutils.NewDict()
  309. diskDict.Set("image_id", jsonutils.NewString(req.ImageId))
  310. diskDict.Set("disk_type", jsonutils.NewString("sys"))
  311. if req.DiskSize > 0 {
  312. diskDict.Set("size", jsonutils.NewInt(req.DiskSize))
  313. }
  314. disks.Add(diskDict)
  315. }
  316. // 构造数据磁盘参数
  317. for _, disk := range req.DataDisks {
  318. diskDict := jsonutils.NewDict()
  319. if disk.ImageId != "" {
  320. diskDict.Set("image_id", jsonutils.NewString(disk.ImageId))
  321. }
  322. if disk.Size > 0 {
  323. diskDict.Set("size", jsonutils.NewInt(disk.Size))
  324. }
  325. diskDict.Set("disk_type", jsonutils.NewString(disk.DiskType))
  326. disks.Add(diskDict)
  327. }
  328. // 如果有磁盘参数,则设置相应参数
  329. if disks.Length() > 0 {
  330. params.Set("disks", disks)
  331. }
  332. // 如果设置了网络 ID,则构造网络参数
  333. if req.NetworkId != "" {
  334. networks := jsonutils.NewArray()
  335. netDict := jsonutils.NewDict()
  336. netDict.Set("network", jsonutils.NewString(req.NetworkId))
  337. networks.Add(netDict)
  338. params.Set("nets", networks)
  339. }
  340. // 如果设置了安全组 ID,则设置相应参数
  341. if req.SecgroupId != "" {
  342. params.Set("secgrp_id", jsonutils.NewString(req.SecgroupId))
  343. }
  344. // 如果设置了安全组列表,则设置相应参数
  345. if len(req.Secgroups) > 0 {
  346. secgroups := jsonutils.NewArray()
  347. for _, sg := range req.Secgroups {
  348. secgroups.Add(jsonutils.NewString(sg))
  349. }
  350. params.Set("secgroups", secgroups)
  351. }
  352. // 如果设置了服务器规格 ID,则设置相应参数
  353. if req.ServerskuId != "" {
  354. params.Set("instance_type", jsonutils.NewString(req.ServerskuId))
  355. }
  356. // 调用 Cloudpods API 创建服务器
  357. result, err := compute.Servers.Create(session, params)
  358. if err != nil {
  359. return nil, fmt.Errorf("failed to create server: %w", err)
  360. }
  361. // 构造响应数据
  362. response := &models.CreateServerResponse{}
  363. if err := result.Unmarshal(response); err != nil {
  364. return nil, fmt.Errorf("failed to unmarshal create server response: %w", err)
  365. }
  366. return response, nil
  367. }
  368. // GetServerMonitor 获取 Cloudpods 中服务器的监控数据
  369. func (a *CloudpodsAdapter) GetServerMonitor(ctx context.Context, serverId string, startTime, endTime int64, metrics []string, ak string, sk string) (*models.MonitorResponse, error) {
  370. session, err := a.getSession(ctx, ak, sk)
  371. if err != nil {
  372. return nil, err
  373. }
  374. params := jsonutils.NewDict()
  375. metricQuery := jsonutils.NewArray()
  376. for _, metric := range metrics {
  377. modelDict := jsonutils.NewDict()
  378. modelDict.Set("database", jsonutils.NewString("telegraf"))
  379. modelDict.Set("measurement", jsonutils.NewString("vm_cpu"))
  380. switch metric {
  381. case "cpu_usage":
  382. modelDict.Set("measurement", jsonutils.NewString("vm_cpu"))
  383. case "mem_usage":
  384. modelDict.Set("measurement", jsonutils.NewString("vm_mem"))
  385. case "disk_usage":
  386. modelDict.Set("measurement", jsonutils.NewString("vm_disk"))
  387. case "net_bps_rx", "net_bps_tx":
  388. modelDict.Set("measurement", jsonutils.NewString("vm_netio"))
  389. }
  390. tagsArray := jsonutils.NewArray()
  391. tagDict := jsonutils.NewDict()
  392. tagDict.Set("key", jsonutils.NewString("vm_id"))
  393. tagDict.Set("operator", jsonutils.NewString("="))
  394. tagDict.Set("value", jsonutils.NewString(serverId))
  395. tagsArray.Add(tagDict)
  396. modelDict.Set("tags", tagsArray)
  397. queryDict := jsonutils.NewDict()
  398. queryDict.Set("model", modelDict)
  399. if startTime > 0 {
  400. queryDict.Set("from", jsonutils.NewString(fmt.Sprintf("%d", startTime)))
  401. }
  402. if endTime > 0 {
  403. queryDict.Set("to", jsonutils.NewString(fmt.Sprintf("%d", endTime)))
  404. }
  405. metricQuery.Add(queryDict)
  406. }
  407. params.Set("metric_query", metricQuery)
  408. params.Set("scope", jsonutils.NewString("system"))
  409. params.Set("interval", jsonutils.NewString("60s"))
  410. result, err := monitor.UnifiedMonitorManager.PerformAction(session, "query", "", params)
  411. if err != nil {
  412. return nil, fmt.Errorf("failed to get server monitor data: %w", err)
  413. }
  414. response := &models.MonitorResponse{
  415. Status: 200,
  416. Data: models.MonitorResponseData{
  417. Metrics: []models.MetricData{},
  418. },
  419. }
  420. unifiedmonitor, err := result.Get("unifiedmonitor")
  421. if err != nil {
  422. return nil, fmt.Errorf("failed to get unifiedmonitor data: %w", err)
  423. }
  424. series, err := unifiedmonitor.Get("Series")
  425. if err != nil {
  426. return nil, fmt.Errorf("failed to get series data: %w", err)
  427. }
  428. seriesArray, ok := series.(*jsonutils.JSONArray)
  429. if !ok {
  430. return nil, fmt.Errorf("invalid series data format")
  431. }
  432. for i := 0; i < seriesArray.Length(); i++ {
  433. seriesObj, err := seriesArray.GetAt(i)
  434. if err != nil {
  435. continue
  436. }
  437. name, _ := seriesObj.GetString("name")
  438. metricData := models.MetricData{
  439. Metric: name,
  440. Unit: "%",
  441. Values: []models.MetricValue{},
  442. }
  443. if strings.Contains(name, "net_bps") {
  444. metricData.Unit = "bps"
  445. } else if strings.Contains(name, "disk_io") {
  446. metricData.Unit = "iops"
  447. }
  448. points, err := seriesObj.Get("points")
  449. if err != nil {
  450. continue
  451. }
  452. pointsArray, ok := points.(*jsonutils.JSONArray)
  453. if !ok {
  454. continue
  455. }
  456. for j := 0; j < pointsArray.Length(); j++ {
  457. pointObj, err := pointsArray.GetAt(j)
  458. if err != nil {
  459. continue
  460. }
  461. pointArray, ok := pointObj.(*jsonutils.JSONArray)
  462. if !ok || pointArray.Length() < 2 {
  463. continue
  464. }
  465. timestamp, err := pointArray.GetAt(0)
  466. if err != nil {
  467. continue
  468. }
  469. value, err := pointArray.GetAt(1)
  470. if err != nil {
  471. continue
  472. }
  473. timestampStr, _ := timestamp.GetString()
  474. valueStr, _ := value.GetString()
  475. timestampInt, _ := strconv.ParseInt(timestampStr, 10, 64)
  476. valueFloat, _ := strconv.ParseFloat(valueStr, 64)
  477. metricData.Values = append(metricData.Values, models.MetricValue{
  478. Timestamp: timestampInt,
  479. Value: valueFloat,
  480. })
  481. }
  482. response.Data.Metrics = append(response.Data.Metrics, metricData)
  483. }
  484. return response, nil
  485. }
  486. // GetServerStats 获取 Cloudpods 中服务器的实时统计数据
  487. func (a *CloudpodsAdapter) GetServerStats(ctx context.Context, serverId string, ak string, sk string) (*models.ServerStatsResponse, error) {
  488. session, err := a.getSession(ctx, ak, sk)
  489. if err != nil {
  490. return nil, err
  491. }
  492. params := jsonutils.NewDict()
  493. result, err := compute.Servers.GetSpecific(session, serverId, "stats", params)
  494. if err != nil {
  495. return nil, fmt.Errorf("failed to get server stats: %w", err)
  496. }
  497. statsData := models.ServerStatsData{}
  498. cpuUsed, _ := result.Float("cpu_used")
  499. statsData.CPUUsage = cpuUsed * 100
  500. memSize, _ := result.Int("mem_size")
  501. memUsed, _ := result.Int("mem_used")
  502. if memSize > 0 {
  503. statsData.MemUsage = float64(memUsed) / float64(memSize) * 100
  504. }
  505. diskSize, _ := result.Int("disk_size")
  506. diskUsed, _ := result.Int("disk_used")
  507. if diskSize > 0 {
  508. statsData.DiskUsage = float64(diskUsed) / float64(diskSize) * 100
  509. }
  510. netInRate, _ := result.Float("net_in_rate")
  511. netOutRate, _ := result.Float("net_out_rate")
  512. statsData.NetBpsRx = int64(netInRate)
  513. statsData.NetBpsTx = int64(netOutRate)
  514. statsData.UpdatedAt = time.Now().Format("2006-01-02 15:04:05")
  515. response := &models.ServerStatsResponse{
  516. Status: 200,
  517. Data: statsData,
  518. }
  519. return response, nil
  520. }