guest_convert.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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 models
  15. import (
  16. "context"
  17. "fmt"
  18. "yunion.io/x/jsonutils"
  19. "yunion.io/x/pkg/errors"
  20. "yunion.io/x/pkg/util/netutils"
  21. "yunion.io/x/pkg/utils"
  22. "yunion.io/x/sqlchemy"
  23. api "yunion.io/x/onecloud/pkg/apis/compute"
  24. "yunion.io/x/onecloud/pkg/cloudcommon/db"
  25. "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
  26. "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas"
  27. "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
  28. "yunion.io/x/onecloud/pkg/compute/options"
  29. "yunion.io/x/onecloud/pkg/httperrors"
  30. "yunion.io/x/onecloud/pkg/mcclient"
  31. "yunion.io/x/onecloud/pkg/mcclient/auth"
  32. "yunion.io/x/onecloud/pkg/mcclient/modules/scheduler"
  33. )
  34. func (self *SGuest) PerformConvert(
  35. ctx context.Context, userCred mcclient.TokenCredential,
  36. query jsonutils.JSONObject, data *api.ConvertToKvmInput,
  37. ) (jsonutils.JSONObject, error) {
  38. if data.TargetHypervisor != api.HYPERVISOR_KVM {
  39. return nil, httperrors.NewBadRequestError("not support target hypervisor %s", data.TargetHypervisor)
  40. }
  41. return self.PerformConvertToKvm(ctx, userCred, query, data)
  42. }
  43. func (self *SGuest) PerformConvertToKvm(
  44. ctx context.Context, userCred mcclient.TokenCredential,
  45. query jsonutils.JSONObject, data *api.ConvertToKvmInput,
  46. ) (jsonutils.JSONObject, error) {
  47. if len(self.GetMetadata(ctx, api.SERVER_META_CONVERTED_SERVER, userCred)) > 0 {
  48. return nil, httperrors.NewBadRequestError("guest has been converted")
  49. }
  50. drv, err := self.GetDriver()
  51. if err != nil {
  52. return nil, err
  53. }
  54. if drv.GetProvider() == api.CLOUD_PROVIDER_ONECLOUD && drv.GetHypervisor() == api.HYPERVISOR_ESXI {
  55. return self.ConvertEsxiToKvm(ctx, userCred, data)
  56. }
  57. if drv.GetProvider() == api.CLOUD_PROVIDER_CLOUDPODS && drv.GetHypervisor() == api.HYPERVISOR_DEFAULT {
  58. return self.ConvertCloudpodsToKvm(ctx, userCred, data)
  59. }
  60. return nil, httperrors.NewBadRequestError("not support %s", self.Hypervisor)
  61. }
  62. func (self *SGuest) ConvertCloudpodsToKvm(ctx context.Context, userCred mcclient.TokenCredential, data *api.ConvertToKvmInput) (jsonutils.JSONObject, error) {
  63. preferHost := data.PreferHost
  64. if len(preferHost) > 0 {
  65. iHost, err := HostManager.FetchByIdOrName(ctx, userCred, preferHost)
  66. if err != nil {
  67. return nil, err
  68. }
  69. host := iHost.(*SHost)
  70. if host.HostType != api.HOST_TYPE_HYPERVISOR {
  71. return nil, httperrors.NewBadRequestError("host %s is not kvm host", preferHost)
  72. }
  73. preferHost = host.GetId()
  74. }
  75. if self.Status != api.VM_READY {
  76. return nil, httperrors.NewBadRequestError("guest status must be ready")
  77. }
  78. newGuest, createInput, err := self.createConvertedServer(ctx, userCred, data)
  79. if err != nil {
  80. return nil, errors.Wrap(err, "create converted server")
  81. }
  82. return nil, self.StartConvertToKvmTask(ctx, userCred, "GuestConvertCloudpodsToKvmTask", preferHost, newGuest, createInput, data)
  83. }
  84. func (self *SGuest) ConvertEsxiToKvm(ctx context.Context, userCred mcclient.TokenCredential, data *api.ConvertToKvmInput) (jsonutils.JSONObject, error) {
  85. preferHost := data.PreferHost
  86. if len(preferHost) > 0 {
  87. iHost, err := HostManager.FetchByIdOrName(ctx, userCred, preferHost)
  88. if err != nil {
  89. return nil, err
  90. }
  91. host := iHost.(*SHost)
  92. if host.HostType != api.HOST_TYPE_HYPERVISOR {
  93. return nil, httperrors.NewBadRequestError("host %s is not kvm host", preferHost)
  94. }
  95. preferHost = host.GetId()
  96. }
  97. if self.Status != api.VM_READY {
  98. return nil, httperrors.NewBadRequestError("guest status must be ready")
  99. }
  100. nets, err := self.GetNetworks("")
  101. if err != nil {
  102. return nil, errors.Wrap(err, "GetNetworks")
  103. }
  104. if len(nets) == 0 {
  105. syncIps := self.GetMetadata(ctx, "sync_ips", userCred)
  106. if len(syncIps) > 0 {
  107. return nil, errors.Wrap(httperrors.ErrInvalidStatus, "VMware network not configured properly")
  108. }
  109. }
  110. newGuest, createInput, err := self.createConvertedServer(ctx, userCred, data)
  111. if err != nil {
  112. return nil, errors.Wrap(err, "create converted server")
  113. }
  114. if data.Networks != nil && len(data.Networks) != len(createInput.Networks) {
  115. return nil, httperrors.NewInputParameterError("input network configs length must equal guestnetworks length")
  116. }
  117. for i := 0; i < len(createInput.Networks); i++ {
  118. createInput.Networks[i].Network = ""
  119. createInput.Networks[i].Wire = ""
  120. if data.Networks != nil {
  121. createInput.Networks[i].Network = data.Networks[i].Network
  122. createInput.Networks[i].Address = data.Networks[i].Address
  123. if data.Networks[i].Schedtags != nil {
  124. createInput.Networks[i].Schedtags = data.Networks[i].Schedtags
  125. }
  126. }
  127. }
  128. return nil, self.StartConvertToKvmTask(ctx, userCred, "GuestConvertEsxiToKvmTask", preferHost, newGuest, createInput, data)
  129. }
  130. func (self *SGuest) StartConvertToKvmTask(
  131. ctx context.Context, userCred mcclient.TokenCredential, taskName, preferHostId string,
  132. newGuest *SGuest, createInput *api.ServerCreateInput, data *api.ConvertToKvmInput,
  133. ) error {
  134. params := jsonutils.NewDict()
  135. if len(preferHostId) > 0 {
  136. params.Set("prefer_host_id", jsonutils.NewString(preferHostId))
  137. }
  138. params.Set("target_guest_id", jsonutils.NewString(newGuest.Id))
  139. params.Set("input", jsonutils.Marshal(createInput))
  140. params.Set("deploy_telegraf", jsonutils.NewBool(data.DeployTelegraf))
  141. task, err := taskman.TaskManager.NewTask(ctx, taskName, self, userCred,
  142. params, "", "", nil)
  143. if err != nil {
  144. return err
  145. } else {
  146. self.SetStatus(ctx, userCred, api.VM_CONVERTING, "esxi guest convert to kvm")
  147. task.ScheduleRun(nil)
  148. return nil
  149. }
  150. }
  151. func (self *SGuest) createConvertedServer(ctx context.Context, userCred mcclient.TokenCredential, data *api.ConvertToKvmInput) (*SGuest, *api.ServerCreateInput, error) {
  152. // set guest pending usage
  153. pendingUsage, pendingRegionUsage, err := self.getGuestUsage(1)
  154. keys, err := self.GetQuotaKeys()
  155. if err != nil {
  156. return nil, nil, errors.Wrap(err, "GetQuotaKeys")
  157. }
  158. pendingUsage.SetKeys(keys)
  159. err = quotas.CheckSetPendingQuota(ctx, userCred, &pendingUsage)
  160. if err != nil {
  161. return nil, nil, httperrors.NewOutOfQuotaError("Check set pending quota error %s", err)
  162. }
  163. regionKeys, err := self.GetRegionalQuotaKeys()
  164. if err != nil {
  165. quotas.CancelPendingUsage(ctx, userCred, &pendingUsage, &pendingUsage, false)
  166. return nil, nil, errors.Wrap(err, "GetRegionalQuotaKeys")
  167. }
  168. pendingRegionUsage.SetKeys(regionKeys)
  169. err = quotas.CheckSetPendingQuota(ctx, userCred, &pendingRegionUsage)
  170. if err != nil {
  171. quotas.CancelPendingUsage(ctx, userCred, &pendingUsage, &pendingUsage, false)
  172. return nil, nil, errors.Wrap(err, "CheckSetPendingQuota")
  173. }
  174. // generate guest create params
  175. createInput := self.ToCreateInput(ctx, userCred)
  176. createInput.Hypervisor = api.HYPERVISOR_KVM
  177. createInput.PreferHost = data.PreferHost
  178. createInput.GenerateName = fmt.Sprintf("%s-%s", self.Name, api.HYPERVISOR_KVM)
  179. createInput.Hostname = self.Name
  180. if self.Hostname != "" {
  181. createInput.Hostname = self.Hostname
  182. }
  183. if self.Hypervisor == api.HYPERVISOR_ESXI {
  184. // change drivers so as to bootable in KVM
  185. for i := range createInput.Disks {
  186. if !utils.IsInStringArray(createInput.Disks[i].Driver, []string{api.DISK_DRIVER_VIRTIO, api.DISK_DRIVER_PVSCSI, api.DISK_DRIVER_IDE}) {
  187. createInput.Disks[i].Driver = api.DISK_DRIVER_IDE
  188. }
  189. createInput.Disks[i].Format = ""
  190. createInput.Disks[i].Backend = ""
  191. createInput.Disks[i].Medium = ""
  192. }
  193. gns, err := self.GetNetworks("")
  194. if err != nil {
  195. return nil, nil, errors.Wrap(err, "GetNetworks")
  196. }
  197. for i := range createInput.Networks {
  198. if createInput.Networks[i].Driver != "e1000" && createInput.Networks[i].Driver != "vmxnet3" {
  199. createInput.Networks[i].Driver = "e1000"
  200. }
  201. createInput.Networks[i].Network = ""
  202. createInput.Networks[i].Wire = ""
  203. createInput.Networks[i].Mac = gns[i].MacAddr
  204. createInput.Networks[i].Address = gns[i].IpAddr
  205. }
  206. createInput.Vdi = api.VM_VDI_PROTOCOL_VNC
  207. } else {
  208. createInput.Disks[0].ImageId = ""
  209. }
  210. if data.Networks != nil && len(data.Networks) != len(createInput.Networks) {
  211. return nil, nil, httperrors.NewInputParameterError("input network configs length must equal guestnetworks length")
  212. }
  213. for i := 0; i < len(createInput.Networks); i++ {
  214. createInput.Networks[i].Network = ""
  215. createInput.Networks[i].Wire = ""
  216. if data.Networks != nil {
  217. if data.Networks[i].Schedtags != nil {
  218. createInput.Networks[i].Schedtags = data.Networks[i].Schedtags
  219. }
  220. createInput.Networks[i].Address = data.Networks[i].Address
  221. createInput.Networks[i].Network = data.Networks[i].Network
  222. }
  223. }
  224. schedDesc := self.ToSchedDesc()
  225. schedDesc.PreferHost = data.PreferHost
  226. for i := range schedDesc.Disks {
  227. schedDesc.Disks[i].Backend = ""
  228. schedDesc.Disks[i].Medium = ""
  229. schedDesc.Disks[i].Storage = ""
  230. }
  231. schedDesc.Networks = data.Networks
  232. schedDesc.Hypervisor = api.HYPERVISOR_KVM
  233. s := auth.GetAdminSession(ctx, options.Options.Region)
  234. succ, res, err := scheduler.SchedManager.DoScheduleForecast(s, schedDesc, 1)
  235. if err != nil {
  236. return nil, nil, errors.Wrap(err, "Do schedule migrate forecast")
  237. }
  238. if !succ {
  239. return nil, nil, httperrors.NewInsufficientResourceError("%s", res.String())
  240. }
  241. lockman.LockClass(ctx, GuestManager, userCred.GetProjectId())
  242. defer lockman.ReleaseClass(ctx, GuestManager, userCred.GetProjectId())
  243. newGuest, err := db.DoCreate(GuestManager, ctx, userCred, nil,
  244. jsonutils.Marshal(createInput), self.GetOwnerId())
  245. quotas.CancelPendingUsage(ctx, userCred, &pendingUsage, &pendingUsage, true)
  246. if err != nil {
  247. return nil, nil, errors.Wrap(err, "db.DoCreate")
  248. }
  249. return newGuest.(*SGuest), createInput, nil
  250. }
  251. func (manager *SGuestManager) PerformBatchConvertPrecheck(
  252. ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *api.BatchConvertToKvmCheckInput,
  253. ) (jsonutils.JSONObject, error) {
  254. if len(data.GuestIds) == 0 {
  255. return nil, httperrors.NewInputParameterError("missing guest ids")
  256. }
  257. guests := make([]SGuest, 0)
  258. q := GuestManager.Query().In("id", data.GuestIds)
  259. err := db.FetchModelObjects(GuestManager, q, &guests)
  260. if err != nil {
  261. return nil, httperrors.NewInternalServerError("%v", err)
  262. }
  263. if len(guests) != len(data.GuestIds) {
  264. return nil, httperrors.NewBadRequestError("Check input guests is exist")
  265. }
  266. res := jsonutils.NewDict()
  267. for i := 0; i < len(guests); i++ {
  268. gns, err := guests[i].GetNetworks("")
  269. if err != nil {
  270. return nil, errors.Wrapf(err, "Get guest networks %s", err)
  271. }
  272. for j := 0; j < len(gns); j++ {
  273. if gns[j].IpAddr != "" {
  274. cnt, err := NetworkManager.checkIpHasOneCloudNetworks(gns[j].IpAddr)
  275. if err != nil {
  276. return nil, err
  277. }
  278. if cnt <= 0 {
  279. reason := fmt.Sprintf("kvm networks has no addr %s for guest %s convert", gns[j].IpAddr, guests[i].GetName())
  280. res.Set("reason", jsonutils.NewString(reason))
  281. res.Set("network_failed", jsonutils.JSONTrue)
  282. return res, nil
  283. }
  284. }
  285. }
  286. }
  287. return res, nil
  288. }
  289. func (manager *SNetworkManager) checkIpHasOneCloudNetworks(ipAddr string) (int, error) {
  290. ip4Addr, err := netutils.NewIPV4Addr(ipAddr)
  291. if err != nil {
  292. return -1, err
  293. }
  294. q := manager.Query()
  295. // filter onecloud wire
  296. wireQ := WireManager.Query().IsNullOrEmpty("manager_id").SubQuery()
  297. ipStart := sqlchemy.INET_ATON(q.Field("guest_ip_start"))
  298. ipEnd := sqlchemy.INET_ATON(q.Field("guest_ip_end"))
  299. ipCondtion := sqlchemy.AND(
  300. sqlchemy.GE(ipEnd, uint32(ip4Addr)),
  301. sqlchemy.LE(ipStart, uint32(ip4Addr)),
  302. )
  303. q = q.Filter(ipCondtion)
  304. q = q.Join(wireQ, sqlchemy.Equals(q.Field("wire_id"), wireQ.Field("id")))
  305. return q.CountWithError()
  306. }