instance.go 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224
  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 openstack
  15. import (
  16. "context"
  17. "fmt"
  18. "net/url"
  19. "time"
  20. "gopkg.in/fatih/set.v0"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/log"
  23. "yunion.io/x/pkg/errors"
  24. "yunion.io/x/pkg/util/billing"
  25. billing_api "yunion.io/x/cloudmux/pkg/apis/billing"
  26. api "yunion.io/x/cloudmux/pkg/apis/compute"
  27. "yunion.io/x/cloudmux/pkg/cloudprovider"
  28. "yunion.io/x/cloudmux/pkg/multicloud"
  29. )
  30. const (
  31. INSTANCE_STATUS_ACTIVE = "ACTIVE" //The server is active.
  32. INSTANCE_STATUS_BUILD = "BUILD" //The server has not finished the original build process.
  33. INSTANCE_STATUS_DELETED = "DELETED" //The server is permanently deleted.
  34. INSTANCE_STATUS_ERROR = "ERROR" //The server is in error.
  35. INSTANCE_STATUS_HARD_REBOOT = "HARD_REBOOT" //The server is hard rebooting. This is equivalent to pulling the power plug on a physical server, plugging it back in, and rebooting it.
  36. INSTANCE_STATUS_MIGRATING = "MIGRATING" //The server is being migrated to a new host.
  37. INSTANCE_STATUS_PASSWORD = "PASSWORD" //The password is being reset on the server.
  38. INSTANCE_STATUS_PAUSED = "PAUSED" //In a paused state, the state of the server is stored in RAM.A paused server continues to run in frozen state.
  39. INSTANCE_STATUS_REBOOT = "REBOOT" //The server is in a soft reboot state. A reboot command was passed to the operating system.
  40. INSTANCE_STATUS_REBUILD = "REBUILD" //The server is currently being rebuilt from an image.
  41. INSTANCE_STATUS_RESCUE = "RESCUE" //The server is in rescue mode. A rescue image is running with the original server image attached.
  42. INSTANCE_STATUS_RESIZE = "RESIZE" //Server is performing the differential copy of data that changed during its initial copy. Server is down for this stage.
  43. INSTANCE_STATUS_REVERT_RESIZE = "REVERT_RESIZE" //The resize or migration of a server failed for some reason. The destination server is being cleaned up and the original source server is restarting.
  44. INSTANCE_STATUS_SHELVED = "SHELVED" // The server is in shelved state. Depending on the shelve offload time, the server will be automatically shelved offloaded.
  45. INSTANCE_STATUS_SHELVED_OFFLOADED = "SHELVED_OFFLOADED" // The shelved server is offloaded (removed from the compute host) and it needs unshelved action to be used again.
  46. INSTANCE_STATUS_SHUTOFF = "SHUTOFF" //The server is powered off and the disk image still persists.
  47. INSTANCE_STATUS_SOFT_DELETED = "SOFT_DELETED" //The server is marked as deleted but the disk images are still available to restore.
  48. INSTANCE_STATUS_SUSPENDED = "SUSPENDED" //The server is suspended, either by request or necessity. This status appears for only the XenServer/XCP, KVM, and ESXi hypervisors. Administrative users can suspend an instance if it is infrequently used or to perform system maintenance. When you suspend an instance, its VM state is stored on disk, all memory is written to disk, and the virtual machine is stopped. Suspending an instance is similar to placing a device in hibernation; memory and vCPUs become available to create other instances.
  49. INSTANCE_STATUS_UNKNOWN = "UNKNOWN" //The state of the server is unknown. Contact your cloud provider.
  50. INSTANCE_STATUS_VERIFY_RESIZE = "VERIFY_RESIZE" //System is awaiting confirmation that the server is operational after a move or resize.
  51. )
  52. type SPrivate struct {
  53. MacAddr string `json:"OS-EXT-IPS-MAC:mac_addr,omitempty"`
  54. Addr string `json:"OS-EXT-IPS:type,omitempty"`
  55. Version int
  56. }
  57. type SecurityGroup struct {
  58. Id string
  59. Name string
  60. Description string
  61. }
  62. type ExtraSpecs struct {
  63. CpuPolicy string `json:"hw:cpu_policy,omitempty"`
  64. MemPageSize string `json:"hw:mem_page_size,omitempty"`
  65. }
  66. type Resource struct {
  67. Id string
  68. Links []Link
  69. }
  70. type VolumesAttached struct {
  71. Id string
  72. DeleteOnTermination bool
  73. }
  74. type SFault struct {
  75. Message string
  76. Code int
  77. Details string
  78. }
  79. type SInstance struct {
  80. multicloud.SInstanceBase
  81. OpenStackTags
  82. host *SHypervisor
  83. imageObj *SImage
  84. DiskConfig string `json:"OS-DCF:diskConfig,omitempty"`
  85. AvailabilityZone string `json:"OS-EXT-AZ:availability_zone,omitempty"`
  86. Host string `json:"OS-EXT-SRV-ATTR:host,omitempty"`
  87. Hostname string `json:"OS-EXT-SRV-ATTR:hostname,omitempty"`
  88. HypervisorHostname string `json:"OS-EXT-SRV-ATTR:hypervisor_hostname,omitempty"`
  89. InstanceName string `json:"OS-EXT-SRV-ATTR:instance_name,omitempty"`
  90. KernelId string `json:"OS-EXT-SRV-ATTR:kernel_id,omitempty"`
  91. LaunchIndex int `json:"OS-EXT-SRV-ATTR:launch_index,omitempty"`
  92. RamdiskId string `json:"OS-EXT-SRV-ATTR:ramdisk_id,omitempty"`
  93. ReservationId string `json:"OS-EXT-SRV-ATTR:reservation_id,omitempty"`
  94. RootDeviceName string `json:"OS-EXT-SRV-ATTR:root_device_name,omitempty"`
  95. UserData string `json:"OS-EXT-SRV-ATTR:user_data,omitempty"`
  96. PowerState int `json:"OS-EXT-STS:power_state,omitempty"`
  97. TaskState string `json:"OS-EXT-STS:task_state,omitempty"`
  98. VmState string `json:"OS-EXT-STS:vm_state,omitempty"`
  99. LaunchedAt time.Time `json:"OS-SRV-USG:launched_at,omitempty"`
  100. TerminatedAt string `json:"OS-SRV-USG:terminated_at,omitempty"`
  101. AccessIPv4 string
  102. AccessIPv6 string
  103. Addresses map[string][]SInstanceNic
  104. ConfigDrive string
  105. Created time.Time
  106. Description string
  107. Flavor SFlavor
  108. HostId string
  109. HostStatus string
  110. Id string
  111. Image jsonutils.JSONObject `json:"image"` //有可能是字符串
  112. KeyName string
  113. Links []Link
  114. Locked bool
  115. Name string
  116. VolumesAttached []VolumesAttached `json:"os-extended-volumes:volumes_attached,omitempty"`
  117. Progress int
  118. SecurityGroups []SecurityGroup
  119. Status string
  120. Tags []string
  121. TenantId string
  122. TrustedImageCertificates []string
  123. Updated time.Time
  124. UserId string
  125. Fault SFault
  126. }
  127. func (region *SRegion) GetSecurityGroupsByInstance(instanceId string) ([]SecurityGroup, error) {
  128. resource := fmt.Sprintf("/servers/%s/os-security-groups", instanceId)
  129. resp, err := region.ecsGet(resource)
  130. if err != nil {
  131. return nil, errors.Wrap(err, "ecsGet")
  132. }
  133. secgroups := []SecurityGroup{}
  134. err = resp.Unmarshal(&secgroups, "security_groups")
  135. if err != nil {
  136. return nil, errors.Wrap(err, "resp.Unmarshal")
  137. }
  138. return secgroups, nil
  139. }
  140. func (region *SRegion) GetInstances(host string) ([]SInstance, error) {
  141. instances := []SInstance{}
  142. resource := "/servers/detail"
  143. query := url.Values{}
  144. query.Set("all_tenants", "True")
  145. for {
  146. resp, err := region.ecsList(resource, query)
  147. if err != nil {
  148. return nil, errors.Wrap(err, "ecsList")
  149. }
  150. part := struct {
  151. Servers []SInstance
  152. ServersLinks SNextLinks
  153. }{}
  154. err = resp.Unmarshal(&part)
  155. if err != nil {
  156. return nil, errors.Wrap(err, "resp.Unmarshal")
  157. }
  158. for i := range part.Servers {
  159. if len(host) == 0 || part.Servers[i].Host == host || part.Servers[i].HypervisorHostname == host {
  160. instances = append(instances, part.Servers[i])
  161. }
  162. }
  163. marker := part.ServersLinks.GetNextMark()
  164. if len(marker) == 0 {
  165. break
  166. }
  167. query.Set("marker", marker)
  168. }
  169. return instances, nil
  170. }
  171. func (region *SRegion) GetInstance(instanceId string) (*SInstance, error) {
  172. resource := "/servers/" + instanceId
  173. resp, err := region.ecsGet(resource)
  174. if err != nil {
  175. return nil, errors.Wrap(err, "ecsGet")
  176. }
  177. instance := &SInstance{}
  178. err = resp.Unmarshal(instance, "server")
  179. if err != nil {
  180. return nil, errors.Wrap(err, "resp.Unmarsha")
  181. }
  182. return instance, nil
  183. }
  184. func (instance *SInstance) GetSecurityGroupIds() ([]string, error) {
  185. secgroupIds := []string{}
  186. secgroups, err := instance.host.zone.region.GetSecurityGroupsByInstance(instance.Id)
  187. if err != nil {
  188. return nil, err
  189. }
  190. for _, secgroup := range secgroups {
  191. secgroupIds = append(secgroupIds, secgroup.Id)
  192. }
  193. return secgroupIds, nil
  194. }
  195. func (instance *SInstance) GetIHost() cloudprovider.ICloudHost {
  196. return instance.host
  197. }
  198. func (instance *SInstance) GetId() string {
  199. return instance.Id
  200. }
  201. func (instance *SInstance) GetName() string {
  202. return instance.Name
  203. }
  204. func (instance *SInstance) GetHostname() string {
  205. return instance.Hostname
  206. }
  207. func (instance *SInstance) GetGlobalId() string {
  208. return instance.Id
  209. }
  210. func (instance *SInstance) IsEmulated() bool {
  211. return false
  212. }
  213. func (instance *SInstance) fetchFlavor() error {
  214. if len(instance.Flavor.Id) > 0 && instance.Flavor.Vcpus == 0 {
  215. flavor, err := instance.host.zone.region.GetFlavor(instance.Flavor.Id)
  216. if err != nil {
  217. return err
  218. }
  219. instance.Flavor = *flavor
  220. }
  221. return nil
  222. }
  223. func (instance *SInstance) GetInstanceType() string {
  224. err := instance.fetchFlavor()
  225. if err != nil {
  226. return ""
  227. }
  228. return instance.Flavor.GetName()
  229. }
  230. func (instance *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
  231. disks := []SDisk{}
  232. hasSysDisk := false
  233. for i := 0; i < len(instance.VolumesAttached); i++ {
  234. disk, err := instance.host.zone.region.GetDisk(instance.VolumesAttached[i].Id)
  235. if err != nil {
  236. return nil, errors.Wrapf(err, "GetDisk(%s)", instance.VolumesAttached[i].Id)
  237. }
  238. disks = append(disks, *disk)
  239. if disk.GetDiskType() == api.DISK_TYPE_SYS {
  240. hasSysDisk = true
  241. }
  242. }
  243. idisks := []cloudprovider.ICloudDisk{}
  244. for i := 0; i < len(disks); i++ {
  245. store, err := instance.host.zone.getStorageByCategory(disks[i].VolumeType, disks[i].Host)
  246. if err != nil {
  247. return nil, errors.Wrapf(err, "getStorageByCategory(%s.%s)", disks[i].Id, disks[i].VolumeType)
  248. }
  249. disks[i].storage = store
  250. idisks = append(idisks, &disks[i])
  251. }
  252. if !hasSysDisk {
  253. store := &SNovaStorage{zone: instance.host.zone, host: instance.host}
  254. disk := &SNovaDisk{storage: store, instanceId: instance.Id, region: instance.host.zone.region}
  255. idisks = append([]cloudprovider.ICloudDisk{disk}, idisks...)
  256. }
  257. return idisks, nil
  258. }
  259. func (instance *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
  260. nics, err := instance.host.zone.region.GetInstancePorts(instance.Id)
  261. if err != nil {
  262. return nil, errors.Wrap(err, "GetInstancePorts")
  263. }
  264. inics := []cloudprovider.ICloudNic{}
  265. for i := range nics {
  266. nics[i].region = instance.host.zone.region
  267. inics = append(inics, &nics[i])
  268. }
  269. return inics, nil
  270. }
  271. func (instance *SInstance) GetVcpuCount() int {
  272. err := instance.fetchFlavor()
  273. if err != nil {
  274. return 0
  275. }
  276. return instance.Flavor.Vcpus
  277. }
  278. func (instance *SInstance) GetVmemSizeMB() int {
  279. err := instance.fetchFlavor()
  280. if err != nil {
  281. return 0
  282. }
  283. return instance.Flavor.RAM
  284. }
  285. func (instance *SInstance) GetBootOrder() string {
  286. return "dcn"
  287. }
  288. func (instance *SInstance) GetVga() string {
  289. return "std"
  290. }
  291. func (instance *SInstance) GetVdi() string {
  292. return "vnc"
  293. }
  294. func (instance *SInstance) getImage() *SImage {
  295. if instance.imageObj == nil && instance.Image != nil {
  296. imageId, _ := instance.Image.GetString("id")
  297. if len(imageId) == 0 {
  298. imageId, _ = instance.Image.GetString()
  299. }
  300. if len(imageId) > 0 {
  301. image, _ := instance.host.zone.region.GetImage(imageId)
  302. if image != nil {
  303. instance.imageObj = image
  304. }
  305. }
  306. }
  307. return instance.imageObj
  308. }
  309. func (instance *SInstance) GetOsType() cloudprovider.TOsType {
  310. img := instance.getImage()
  311. if img != nil {
  312. return img.GetOsType()
  313. }
  314. return cloudprovider.OsTypeLinux
  315. }
  316. func (instance *SInstance) GetFullOsName() string {
  317. img := instance.getImage()
  318. if img != nil {
  319. return img.GetFullOsName()
  320. }
  321. return ""
  322. }
  323. func (instance *SInstance) GetBios() cloudprovider.TBiosType {
  324. img := instance.getImage()
  325. if img != nil {
  326. return img.GetBios()
  327. }
  328. return "BIOS"
  329. }
  330. func (instance *SInstance) GetOsDist() string {
  331. img := instance.getImage()
  332. if img != nil {
  333. return img.GetOsDist()
  334. }
  335. return ""
  336. }
  337. func (instance *SInstance) GetOsVersion() string {
  338. img := instance.getImage()
  339. if img != nil {
  340. return img.GetOsVersion()
  341. }
  342. return ""
  343. }
  344. func (instance *SInstance) GetOsLang() string {
  345. img := instance.getImage()
  346. if img != nil {
  347. return img.GetOsLang()
  348. }
  349. return ""
  350. }
  351. func (instance *SInstance) GetOsArch() string {
  352. img := instance.getImage()
  353. if img != nil {
  354. return img.GetOsArch()
  355. }
  356. return ""
  357. }
  358. func (instance *SInstance) GetMachine() string {
  359. return "pc"
  360. }
  361. func (instance *SInstance) GetStatus() string {
  362. switch instance.Status {
  363. case INSTANCE_STATUS_ACTIVE, INSTANCE_STATUS_RESCUE:
  364. return api.VM_RUNNING
  365. case INSTANCE_STATUS_BUILD, INSTANCE_STATUS_PASSWORD:
  366. return api.VM_DEPLOYING
  367. case INSTANCE_STATUS_DELETED:
  368. return api.VM_DELETING
  369. case INSTANCE_STATUS_HARD_REBOOT, INSTANCE_STATUS_REBOOT:
  370. return api.VM_STARTING
  371. case INSTANCE_STATUS_MIGRATING:
  372. return api.VM_MIGRATING
  373. case INSTANCE_STATUS_PAUSED, INSTANCE_STATUS_SUSPENDED:
  374. return api.VM_SUSPEND
  375. case INSTANCE_STATUS_RESIZE:
  376. return api.VM_CHANGE_FLAVOR
  377. case INSTANCE_STATUS_VERIFY_RESIZE:
  378. // API请求更改配置后,状态先回变更到 INSTANCE_STATUS_RESIZE 等待一会变成此状态
  379. // 到达此状态后需要再次发送确认请求,变更才会生效
  380. // 此状态不能和INSTANCE_STATUS_RESIZE返回一样,避免在INSTANCE_STATUS_RESIZE状态下发送确认请求,导致更改配置失败
  381. return api.VM_SYNC_CONFIG
  382. case INSTANCE_STATUS_SHELVED, INSTANCE_STATUS_SHELVED_OFFLOADED, INSTANCE_STATUS_SHUTOFF, INSTANCE_STATUS_SOFT_DELETED:
  383. return api.VM_READY
  384. default:
  385. return api.VM_UNKNOWN
  386. }
  387. }
  388. func (instance *SInstance) Refresh() error {
  389. _instance, err := instance.host.zone.region.GetInstance(instance.Id)
  390. if err != nil {
  391. return err
  392. }
  393. instance.Addresses = nil
  394. instance.VolumesAttached = nil
  395. instance.SecurityGroups = nil
  396. instance.Tags = nil
  397. return jsonutils.Update(instance, _instance)
  398. }
  399. func (instance *SInstance) UpdateVM(ctx context.Context, input cloudprovider.SInstanceUpdateOptions) error {
  400. if instance.Name != input.NAME {
  401. params := map[string]map[string]string{
  402. "server": {
  403. "name": input.NAME,
  404. },
  405. }
  406. resource := "/servers/" + instance.Id
  407. _, err := instance.host.zone.region.ecsUpdate(resource, params)
  408. return err
  409. }
  410. return nil
  411. }
  412. func (instance *SInstance) GetHypervisor() string {
  413. return api.HYPERVISOR_OPENSTACK
  414. }
  415. func (self *SInstance) SetTags(tags map[string]string, replace bool) error {
  416. oldTags, err := self.GetTags()
  417. if err != nil {
  418. return errors.Wrapf(err, "GetTags")
  419. }
  420. added, removed := map[string]string{}, map[string]string{}
  421. for k, v := range tags {
  422. oldValue, ok := oldTags[k]
  423. if !ok {
  424. added[k] = v
  425. } else if oldValue != v {
  426. removed[k] = oldValue
  427. added[k] = v
  428. }
  429. }
  430. if replace {
  431. for k, v := range oldTags {
  432. newValue, ok := tags[k]
  433. if !ok {
  434. removed[k] = v
  435. } else if v != newValue {
  436. added[k] = newValue
  437. removed[k] = v
  438. }
  439. }
  440. }
  441. for k := range removed {
  442. err = self.host.zone.region.DeleteTags(self.Id, k)
  443. if err != nil {
  444. return errors.Wrapf(err, "DeleteTags %s", k)
  445. }
  446. }
  447. if len(added) > 0 {
  448. return self.host.zone.region.CreateTags(self.Id, added)
  449. }
  450. return nil
  451. }
  452. func (self *SRegion) DeleteTags(instanceId string, key string) error {
  453. resource := fmt.Sprintf("/servers/%s/metadata/%s", instanceId, key)
  454. _, err := self.ecsDelete(resource)
  455. return err
  456. }
  457. func (self *SRegion) CreateTags(instanceId string, tags map[string]string) error {
  458. params := map[string]interface{}{
  459. "metadata": tags,
  460. }
  461. resource := fmt.Sprintf("/servers/%s/metadata", instanceId)
  462. _, err := self.ecsPost(resource, params)
  463. return err
  464. }
  465. func (instance *SInstance) StartVM(ctx context.Context) error {
  466. err := instance.host.zone.region.StartVM(instance.Id)
  467. if err != nil {
  468. return errors.Wrapf(err, "StartVM(%s)", instance.Id)
  469. }
  470. return cloudprovider.WaitStatus(instance, api.VM_RUNNING, 10*time.Second, 8*time.Minute)
  471. }
  472. func (instance *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
  473. err := instance.host.zone.region.StopVM(instance.Id, opts.IsForce)
  474. if err != nil {
  475. return errors.Wrapf(err, "StopVM(%s)", instance.Id)
  476. }
  477. return cloudprovider.WaitStatus(instance, api.VM_READY, 10*time.Second, 8*time.Minute)
  478. }
  479. func (region *SRegion) GetInstanceVNCUrl(instanceId string, origin bool) (*cloudprovider.ServerVncOutput, error) {
  480. params := map[string]map[string]string{
  481. "remote_console": {
  482. "protocol": "vnc",
  483. "type": "novnc",
  484. },
  485. }
  486. resource := fmt.Sprintf("/servers/%s/remote-consoles", instanceId)
  487. resp, err := region.ecsPost(resource, params)
  488. if err != nil {
  489. return nil, errors.Wrap(err, "ecsPost")
  490. }
  491. ret := &cloudprovider.ServerVncOutput{
  492. Protocol: "openstack",
  493. InstanceId: instanceId,
  494. Hypervisor: api.HYPERVISOR_OPENSTACK,
  495. }
  496. ret.Url, err = resp.GetString("remote_console", "url")
  497. if err != nil {
  498. return nil, errors.Wrapf(err, "remote_console")
  499. }
  500. if origin {
  501. return ret, nil
  502. }
  503. token := string([]byte(ret.Url)[len(ret.Url)-36:])
  504. vncUrl, _ := url.Parse(ret.Url)
  505. ret.Url = fmt.Sprintf("ws://%s?token=%s", vncUrl.Host, token)
  506. ret.Protocol = "vnc"
  507. return ret, nil
  508. }
  509. func (region *SRegion) GetInstanceVNC(instanceId string, origin bool) (*cloudprovider.ServerVncOutput, error) {
  510. params := map[string]map[string]string{
  511. "os-getVNCConsole": {
  512. "type": "novnc",
  513. },
  514. }
  515. resource := fmt.Sprintf("/servers/%s/action", instanceId)
  516. resp, err := region.ecsPost(resource, params)
  517. if err != nil {
  518. return nil, errors.Wrap(err, "ecsPost")
  519. }
  520. ret := &cloudprovider.ServerVncOutput{
  521. Protocol: "openstack",
  522. InstanceId: instanceId,
  523. Hypervisor: api.HYPERVISOR_OPENSTACK,
  524. }
  525. ret.Url, err = resp.GetString("console", "url")
  526. if err != nil {
  527. return nil, errors.Wrapf(err, "remote_console")
  528. }
  529. if origin {
  530. return ret, nil
  531. }
  532. token := string([]byte(ret.Url)[len(ret.Url)-36:])
  533. vncUrl, _ := url.Parse(ret.Url)
  534. ret.Url = fmt.Sprintf("ws://%s?token=%s", vncUrl.Host, token)
  535. ret.Protocol = "vnc"
  536. return ret, nil
  537. }
  538. func (instance *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
  539. origin := false
  540. if input != nil {
  541. origin = input.Origin
  542. }
  543. ret, err := instance.host.zone.region.GetInstanceVNCUrl(instance.Id, origin)
  544. if err == nil {
  545. return ret, nil
  546. }
  547. return instance.host.zone.region.GetInstanceVNC(instance.Id, origin)
  548. }
  549. func (instance *SInstance) DeployVM(ctx context.Context, opts *cloudprovider.SInstanceDeployOptions) error {
  550. return instance.host.zone.region.DeployVM(instance.Id, opts)
  551. }
  552. func (instance *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
  553. return instance.Id, instance.host.zone.region.ReplaceSystemDisk(instance.Id, desc.ImageId, desc.Password, desc.PublicKey, desc.SysSizeGB)
  554. }
  555. func (instance *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error {
  556. if (len(config.InstanceType) > 0 && instance.GetInstanceType() != config.InstanceType) || instance.GetVcpuCount() != config.Cpu || instance.GetVmemSizeMB() != config.MemoryMB {
  557. flavor, err := instance.host.zone.region.syncFlavor(config.InstanceType, config.Cpu, config.MemoryMB, 40)
  558. if err != nil {
  559. return errors.Wrapf(err, "syncFlavor(%s)", config.InstanceType)
  560. }
  561. // When resizing, instances must change flavor!
  562. if flavor.Name == instance.Flavor.OriginalName {
  563. return nil
  564. }
  565. return instance.host.zone.region.ChangeConfig(instance, flavor.Id)
  566. }
  567. return nil
  568. }
  569. func (region *SRegion) ChangeConfig(instance *SInstance, flavorId string) error {
  570. params := map[string]map[string]string{
  571. "resize": {
  572. "flavorRef": flavorId,
  573. },
  574. }
  575. resource := fmt.Sprintf("/servers/%s/action", instance.Id)
  576. _, err := region.ecsPost(resource, params)
  577. if err != nil {
  578. return errors.Wrap(err, "ecsPost")
  579. }
  580. err = cloudprovider.WaitStatus(instance, api.VM_SYNC_CONFIG, time.Second*3, time.Minute*4)
  581. if err != nil {
  582. return errors.Wrap(err, "WaitStatsAfterChangeConfig")
  583. }
  584. return region.instanceOperation(instance.Id, "confirmResize")
  585. }
  586. func (instance *SInstance) AttachDisk(ctx context.Context, diskId string) error {
  587. return instance.host.zone.region.AttachDisk(instance.Id, diskId)
  588. }
  589. func (instance *SInstance) DetachDisk(ctx context.Context, diskId string) error {
  590. return instance.host.zone.region.DetachDisk(instance.Id, diskId)
  591. }
  592. func (region *SRegion) instanceOperation(instanceId, operate string) error {
  593. params := map[string]string{operate: ""}
  594. resource := fmt.Sprintf("/servers/%s/action", instanceId)
  595. _, err := region.ecsPost(resource, params)
  596. return err
  597. }
  598. func (region *SRegion) doStopVM(instanceId string, isForce bool) error {
  599. return region.instanceOperation(instanceId, "os-stop")
  600. }
  601. func (region *SRegion) doDeleteVM(instanceId string) error {
  602. return region.instanceOperation(instanceId, "forceDelete")
  603. }
  604. func (region *SRegion) StartVM(instanceId string) error {
  605. return region.instanceOperation(instanceId, "os-start")
  606. }
  607. func (region *SRegion) StopVM(instanceId string, isForce bool) error {
  608. return region.doStopVM(instanceId, isForce)
  609. }
  610. func (region *SRegion) DeleteVM(instanceId string) error {
  611. instance, err := region.GetInstance(instanceId)
  612. if err != nil {
  613. if errors.Cause(err) == cloudprovider.ErrNotFound {
  614. return nil
  615. }
  616. return errors.Wrapf(err, "GetInstance(%s)", instanceId)
  617. }
  618. status := instance.GetStatus()
  619. log.Debugf("Instance status on delete is %s", status)
  620. if status != api.VM_READY {
  621. log.Warningf("DeleteVM: vm status is %s expect %s", status, api.VM_READY)
  622. }
  623. return region.doDeleteVM(instanceId)
  624. }
  625. func (region *SRegion) DeployVM(instanceId string, opts *cloudprovider.SInstanceDeployOptions) error {
  626. if len(opts.Password) > 0 {
  627. params := map[string]map[string]string{
  628. "changePassword": {
  629. "adminPass": opts.Password,
  630. },
  631. }
  632. resource := fmt.Sprintf("/servers/%s/action", instanceId)
  633. _, err := region.ecsPost(resource, params)
  634. return err
  635. }
  636. return nil
  637. }
  638. func (instance *SInstance) DeleteVM(ctx context.Context) error {
  639. err := instance.host.zone.region.DeleteVM(instance.Id)
  640. if err != nil {
  641. return errors.Wrapf(err, "instance.host.zone.region.DeleteVM(%s)", instance.Id)
  642. }
  643. return cloudprovider.WaitDeleted(instance, time.Second*5, time.Minute*10)
  644. }
  645. func (region *SRegion) ReplaceSystemDisk(instanceId string, imageId string, passwd string, publicKey string, sysDiskSizeGB int) error {
  646. params := map[string]map[string]string{
  647. "rebuild": {
  648. "imageRef": imageId,
  649. },
  650. }
  651. if len(publicKey) > 0 {
  652. keypairName, err := region.syncKeypair(instanceId, publicKey)
  653. if err != nil {
  654. return err
  655. }
  656. params["rebuild"]["key_name"] = keypairName
  657. }
  658. if len(passwd) > 0 {
  659. params["rebuild"]["adminPass"] = passwd
  660. }
  661. resource := fmt.Sprintf("/servers/%s/action", instanceId)
  662. _, err := region.ecsPost(resource, params)
  663. return err
  664. }
  665. func (region *SRegion) DetachDisk(instanceId string, diskId string) error {
  666. resource := fmt.Sprintf("/servers/%s/os-volume_attachments/%s", instanceId, diskId)
  667. _, err := region.ecsDelete(resource)
  668. if err != nil {
  669. return errors.Wrap(err, "ecsDelete")
  670. }
  671. status := ""
  672. startTime := time.Now()
  673. for time.Now().Sub(startTime) < time.Minute*10 {
  674. disk, err := region.GetDisk(diskId)
  675. if err != nil {
  676. return errors.Wrapf(err, "GetDisk(%s)", diskId)
  677. }
  678. status = disk.Status
  679. log.Debugf("status %s expect %s", status, DISK_STATUS_AVAILABLE)
  680. if status == DISK_STATUS_AVAILABLE {
  681. return nil
  682. }
  683. time.Sleep(time.Second * 15)
  684. }
  685. return fmt.Errorf("timeout for waitting detach disk, current status: %s", status)
  686. }
  687. func (region *SRegion) AttachDisk(instanceId string, diskId string) error {
  688. params := map[string]map[string]string{
  689. "volumeAttachment": {
  690. "volumeId": diskId,
  691. },
  692. }
  693. resource := fmt.Sprintf("/servers/%s/os-volume_attachments", instanceId)
  694. _, err := region.ecsPost(resource, params)
  695. if err != nil {
  696. return errors.Wrap(err, "ecsPost")
  697. }
  698. status := ""
  699. startTime := time.Now()
  700. for time.Now().Sub(startTime) < time.Minute*10 {
  701. disk, err := region.GetDisk(diskId)
  702. if err != nil {
  703. return errors.Wrapf(err, "GetDisk(%s)", diskId)
  704. }
  705. status = disk.Status
  706. log.Debugf("status %s expect %s", status, DISK_STATUS_IN_USE)
  707. if status == DISK_STATUS_IN_USE {
  708. return nil
  709. }
  710. time.Sleep(time.Second * 15)
  711. }
  712. return fmt.Errorf("timeout for waitting attach disk, current status: %s", status)
  713. }
  714. func (region *SRegion) MigrateVM(instanceId string, hostName string) error {
  715. params := jsonutils.NewDict()
  716. migrate := jsonutils.NewDict()
  717. migrate.Add(jsonutils.JSONNull, "host")
  718. if hostName != "" {
  719. migrate.Add(jsonutils.NewString(hostName), "host")
  720. }
  721. params.Add(migrate, "migrate")
  722. resource := fmt.Sprintf("/servers/%s/action", instanceId)
  723. _, err := region.ecsPost(resource, params)
  724. if err != nil {
  725. return errors.Wrapf(err, "On Requst Migrate instance:%s", instanceId)
  726. }
  727. return nil
  728. }
  729. func (region *SRegion) LiveMigrateVM(instanceId string, hostName string) error {
  730. params := jsonutils.NewDict()
  731. osMigrateLive := jsonutils.NewDict()
  732. osMigrateLive.Add(jsonutils.NewString("auto"), "block_migration")
  733. osMigrateLive.Add(jsonutils.JSONNull, "host")
  734. if hostName != "" {
  735. osMigrateLive.Add(jsonutils.NewString(hostName), "host")
  736. }
  737. params.Add(osMigrateLive, "os-migrateLive")
  738. resource := fmt.Sprintf("/servers/%s/action", instanceId)
  739. _, err := region.ecsPost(resource, params)
  740. if err != nil {
  741. return errors.Wrapf(err, "On Requst LiveMigrate instance:%s", instanceId)
  742. }
  743. return nil
  744. }
  745. // 仅live-migration
  746. func (region *SRegion) ListServerMigration(instanceId string) error {
  747. resource := fmt.Sprintf("/servers/%s/migrations", instanceId)
  748. _, err := region.ecsGet(resource)
  749. if err != nil {
  750. return errors.Wrapf(err, "ListServerMigration")
  751. }
  752. return nil
  753. }
  754. // 仅live-migration
  755. func (region *SRegion) DeleteMigration(instanceId string, migrationId string) error {
  756. resource := fmt.Sprintf("/servers/%s/migrations/%s", instanceId, migrationId)
  757. _, err := region.ecsDelete(resource)
  758. if err != nil {
  759. return errors.Wrapf(err, "On Requst delete LiveMigrate:%s", migrationId)
  760. }
  761. return nil
  762. }
  763. // 仅live-migration
  764. func (region *SRegion) ForceCompleteMigration(instanceId string, migrationId string) error {
  765. params := jsonutils.NewDict()
  766. params.Add(jsonutils.JSONNull, "force_complete")
  767. resource := fmt.Sprintf("/servers/%s/migrations/%s/action", instanceId, migrationId)
  768. _, err := region.ecsPost(resource, params)
  769. if err != nil {
  770. return errors.Wrapf(err, "On Requst delete LiveMigrate:%s", migrationId)
  771. }
  772. return nil
  773. }
  774. func (region *SRegion) GetMigrations(instanceId string, migrationType string) (jsonutils.JSONObject, error) {
  775. query := url.Values{}
  776. query.Set("instance_uuid", instanceId)
  777. query.Set("migration_type", migrationType)
  778. resource := "/os-migrations"
  779. migrations, err := region.ecsList(resource, query)
  780. if err != nil {
  781. return nil, errors.Wrapf(err, "On Get instance :%s Migration,migration_type:%s", instanceId, migrationType)
  782. }
  783. return migrations, nil
  784. }
  785. func (self *SRegion) AssignSecurityGroup(instanceId, projectId, secgroupId string) error {
  786. if secgroupId == SECGROUP_NOT_SUPPORT {
  787. return fmt.Errorf("Security groups are not supported. Security group components are not installed")
  788. }
  789. secgroup, err := self.GetSecurityGroup(secgroupId)
  790. if err != nil {
  791. return errors.Wrapf(err, "GetSecurityGroup(%s)", secgroupId)
  792. }
  793. params := map[string]map[string]string{
  794. "addSecurityGroup": {
  795. "name": secgroup.Name,
  796. },
  797. }
  798. resource := fmt.Sprintf("/servers/%s/action", instanceId)
  799. _, err = self.ecsDo(projectId, resource, params)
  800. return err
  801. }
  802. func (instance *SInstance) RevokeSecurityGroup(secgroupId string) error {
  803. // 若OpenStack不支持安全组,则忽略解绑安全组
  804. if secgroupId == SECGROUP_NOT_SUPPORT {
  805. return nil
  806. }
  807. secgroup, err := instance.host.zone.region.GetSecurityGroup(secgroupId)
  808. if err != nil {
  809. return errors.Wrapf(err, "GetSecurityGroup(%s)", secgroupId)
  810. }
  811. params := map[string]map[string]string{
  812. "removeSecurityGroup": {
  813. "name": secgroup.Name,
  814. },
  815. }
  816. resource := fmt.Sprintf("/servers/%s/action", instance.Id)
  817. _, err = instance.host.zone.region.ecsDo(instance.GetProjectId(), resource, params)
  818. return err
  819. }
  820. func (instance *SInstance) SetSecurityGroups(secgroupIds []string) error {
  821. secgroups, err := instance.host.zone.region.GetSecurityGroupsByInstance(instance.Id)
  822. if err != nil {
  823. return errors.Wrapf(err, "GetSecurityGroupsByInstance(%s)", instance.Id)
  824. }
  825. local := set.New(set.ThreadSafe)
  826. for _, secgroup := range secgroups {
  827. local.Add(secgroup.Id)
  828. }
  829. newG := set.New(set.ThreadSafe)
  830. for _, secgroupId := range secgroupIds {
  831. newG.Add(secgroupId)
  832. }
  833. for _, del := range set.Difference(local, newG).List() {
  834. secgroupId := del.(string)
  835. err := instance.RevokeSecurityGroup(secgroupId)
  836. if err != nil {
  837. return errors.Wrapf(err, "RevokeSecurityGroup(%s)", secgroupId)
  838. }
  839. }
  840. for _, add := range set.Difference(newG, local).List() {
  841. secgroupId := add.(string)
  842. err := instance.host.zone.region.AssignSecurityGroup(instance.Id, instance.GetProjectId(), secgroupId)
  843. if err != nil {
  844. return errors.Wrapf(err, "AssignSecurityGroup(%s)", secgroupId)
  845. }
  846. }
  847. return nil
  848. }
  849. func (instance *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
  850. for networkName, address := range instance.Addresses {
  851. for i := 0; i < len(address); i++ {
  852. if instance.Addresses[networkName][i].Type == "floating" {
  853. return instance.host.zone.region.GetEipByIp(instance.Addresses[networkName][i].Addr)
  854. }
  855. }
  856. }
  857. return nil, nil
  858. }
  859. func (instance *SInstance) GetBillingType() string {
  860. return billing_api.BILLING_TYPE_POSTPAID
  861. }
  862. func (instance *SInstance) GetCreatedAt() time.Time {
  863. return instance.Created
  864. }
  865. func (instance *SInstance) GetExpiredAt() time.Time {
  866. return time.Time{}
  867. }
  868. func (instance *SInstance) UpdateUserData(userData string) error {
  869. return cloudprovider.ErrNotSupported
  870. }
  871. func (instance *SInstance) Renew(bc billing.SBillingCycle) error {
  872. return cloudprovider.ErrNotSupported
  873. }
  874. func (region *SRegion) RenewInstances(instanceId []string, bc billing.SBillingCycle) error {
  875. return cloudprovider.ErrNotSupported
  876. }
  877. func (instance *SInstance) GetProjectId() string {
  878. return instance.TenantId
  879. }
  880. func (self *SInstance) GetError() error {
  881. if self.Status == INSTANCE_STATUS_ERROR && len(self.Fault.Message) > 0 {
  882. return errors.Error(self.Fault.Message)
  883. }
  884. return nil
  885. }
  886. func (instance *SInstance) MigrateVM(hostId string) error {
  887. hostName := ""
  888. if hostId != "" {
  889. iHost, err := instance.host.zone.region.GetIHostById(hostId)
  890. if err != nil {
  891. return errors.Wrapf(err, "GetIHostById(%s)", hostId)
  892. }
  893. hostName = iHost.GetName()
  894. }
  895. previousHostName := instance.Host
  896. err := instance.host.zone.region.MigrateVM(instance.Id, hostName)
  897. if err != nil {
  898. return errors.Wrap(err, "MigrateVm")
  899. }
  900. err = cloudprovider.WaitMultiStatus(instance, []string{api.VM_SYNC_CONFIG, api.VM_READY, api.VM_UNKNOWN}, time.Second*10, time.Hour*3)
  901. if err != nil {
  902. return errors.Wrap(err, "WaitMultiStatus")
  903. }
  904. if instance.GetStatus() == api.VM_UNKNOWN {
  905. return errors.Wrap(errors.ErrInvalidStatus, "GetStatus")
  906. }
  907. if instance.GetStatus() == api.VM_READY {
  908. if instance.Host == previousHostName {
  909. return errors.Wrap(fmt.Errorf("instance not migrated"), "Check host after migration")
  910. }
  911. return nil
  912. }
  913. return instance.host.zone.region.instanceOperation(instance.Id, "confirmResize")
  914. }
  915. func (instance *SInstance) LiveMigrateVM(hostId string) error {
  916. hostName := ""
  917. if hostId != "" {
  918. iHost, err := instance.host.zone.region.GetIHostById(hostId)
  919. if err != nil {
  920. return errors.Wrapf(err, "GetIHostById(%s)", hostId)
  921. }
  922. hostName = iHost.GetName()
  923. }
  924. previousHostName := instance.Host
  925. err := instance.host.zone.region.LiveMigrateVM(instance.Id, hostName)
  926. if err != nil {
  927. return errors.Wrap(err, "LiveMIgrateVm")
  928. }
  929. err = cloudprovider.WaitMultiStatus(instance, []string{api.VM_SYNC_CONFIG, api.VM_RUNNING, api.VM_UNKNOWN}, time.Second*10, time.Hour*3)
  930. if err != nil {
  931. return errors.Wrap(err, "WaitMultiStatus")
  932. }
  933. if instance.GetStatus() == api.VM_UNKNOWN {
  934. return errors.Wrap(errors.ErrInvalidStatus, "GetStatus")
  935. }
  936. if instance.GetStatus() == api.VM_RUNNING {
  937. if instance.Host == previousHostName {
  938. return errors.Wrap(fmt.Errorf("instance not migrated"), "Check host after migration")
  939. }
  940. return nil
  941. }
  942. return instance.host.zone.region.instanceOperation(instance.Id, "confirmResize")
  943. }
  944. func (instance *SInstance) GetIHostId() string {
  945. err := instance.host.zone.fetchHosts()
  946. if err != nil {
  947. return ""
  948. }
  949. for _, host := range instance.host.zone.hosts {
  950. if instance.HypervisorHostname == host.HypervisorHostname {
  951. return host.GetGlobalId()
  952. }
  953. }
  954. return ""
  955. }
  956. func (region *SRegion) GetInstanceMetadata(instanceId string) (map[string]string, error) {
  957. resource := fmt.Sprintf("/servers/%s/metadata", instanceId)
  958. resp, err := region.ecsList(resource, nil)
  959. if err != nil {
  960. return nil, errors.Wrap(err, "ecsList")
  961. }
  962. result := struct {
  963. Metadata map[string]string
  964. }{}
  965. err = resp.Unmarshal(&result)
  966. if err != nil {
  967. return nil, errors.Wrap(err, "resp.Unmarshal")
  968. }
  969. return result.Metadata, nil
  970. }
  971. func (zone *SZone) CreateVM(hypervisor string, opts *cloudprovider.SManagedVMCreateConfig) (*SInstance, error) {
  972. region := zone.region
  973. network, err := region.GetNetwork(opts.ExternalNetworkId)
  974. if err != nil {
  975. return nil, err
  976. }
  977. secgroups := []map[string]string{}
  978. for _, secgroupId := range opts.ExternalSecgroupIds {
  979. if secgroupId != SECGROUP_NOT_SUPPORT {
  980. secgroups = append(secgroups, map[string]string{"name": secgroupId})
  981. }
  982. }
  983. image, err := region.GetImage(opts.ExternalImageId)
  984. if err != nil {
  985. return nil, errors.Wrapf(err, "GetImage(%s)", opts.ExternalImageId)
  986. }
  987. sysDiskSizeGB := image.Size / 1024 / 1024 / 1024
  988. if opts.SysDisk.SizeGB < sysDiskSizeGB {
  989. opts.SysDisk.SizeGB = sysDiskSizeGB
  990. }
  991. if opts.SysDisk.SizeGB < image.GetMinOsDiskSizeGb() {
  992. opts.SysDisk.SizeGB = image.GetMinOsDiskSizeGb()
  993. }
  994. BlockDeviceMappingV2 := []map[string]interface{}{}
  995. diskIds := []string{}
  996. defer func() {
  997. for _, diskId := range diskIds {
  998. err = region.DeleteDisk(diskId)
  999. if err != nil {
  1000. log.Errorf("clean disk %s error: %v", diskId, err)
  1001. }
  1002. }
  1003. }()
  1004. if opts.SysDisk.StorageType != api.STORAGE_OPENSTACK_NOVA { //新建volume
  1005. istorage, err := zone.GetIStorageById(opts.SysDisk.StorageExternalId)
  1006. if err != nil {
  1007. return nil, errors.Wrapf(err, "GetIStorageById(%s)", opts.SysDisk.StorageExternalId)
  1008. }
  1009. _sysDisk, err := region.CreateDisk(opts.ExternalImageId, istorage.GetName(), "", opts.SysDisk.SizeGB, opts.SysDisk.Name, opts.ProjectId)
  1010. if err != nil {
  1011. return nil, errors.Wrapf(err, "CreateDisk %s", opts.SysDisk.Name)
  1012. }
  1013. diskIds = append(diskIds, _sysDisk.GetGlobalId())
  1014. BlockDeviceMappingV2 = append(BlockDeviceMappingV2, map[string]interface{}{
  1015. "boot_index": 0,
  1016. "uuid": _sysDisk.GetGlobalId(),
  1017. "source_type": "volume",
  1018. "destination_type": "volume",
  1019. "delete_on_termination": true,
  1020. })
  1021. } else {
  1022. BlockDeviceMappingV2 = append(BlockDeviceMappingV2, map[string]interface{}{
  1023. "boot_index": 0,
  1024. "uuid": image.Id,
  1025. "source_type": "image",
  1026. "destination_type": "local",
  1027. "delete_on_termination": true,
  1028. })
  1029. }
  1030. var _disk *SDisk
  1031. for index, disk := range opts.DataDisks {
  1032. istorage, err := zone.GetIStorageById(disk.StorageExternalId)
  1033. if err != nil {
  1034. return nil, errors.Wrapf(err, "GetIStorageById(%s)", disk.StorageExternalId)
  1035. }
  1036. _disk, err = region.CreateDisk("", istorage.GetName(), "", disk.SizeGB, disk.Name, opts.ProjectId)
  1037. if err != nil {
  1038. return nil, errors.Wrapf(err, "CreateDisk %s", disk.Name)
  1039. }
  1040. diskIds = append(diskIds, _disk.Id)
  1041. mapping := map[string]interface{}{
  1042. "source_type": "volume",
  1043. "destination_type": "volume",
  1044. "delete_on_termination": true,
  1045. "boot_index": index + 1,
  1046. "uuid": _disk.Id,
  1047. }
  1048. BlockDeviceMappingV2 = append(BlockDeviceMappingV2, mapping)
  1049. }
  1050. az := zone.ZoneName
  1051. if len(hypervisor) > 0 {
  1052. az = fmt.Sprintf("%s:%s", zone.ZoneName, hypervisor)
  1053. }
  1054. net := map[string]string{
  1055. "uuid": network.NetworkId,
  1056. }
  1057. if len(opts.IpAddr) > 0 {
  1058. net["fixed_ip"] = opts.IpAddr
  1059. }
  1060. params := map[string]map[string]interface{}{
  1061. "server": {
  1062. "name": opts.Name,
  1063. "adminPass": opts.Password,
  1064. "availability_zone": az,
  1065. "networks": []map[string]string{net},
  1066. "security_groups": secgroups,
  1067. "user_data": opts.UserData,
  1068. "imageRef": opts.ExternalImageId,
  1069. "block_device_mapping_v2": BlockDeviceMappingV2,
  1070. },
  1071. }
  1072. if len(opts.IpAddr) > 0 {
  1073. params["server"]["accessIPv4"] = opts.IpAddr
  1074. }
  1075. flavor, err := region.syncFlavor(opts.InstanceType, opts.Cpu, opts.MemoryMB, opts.SysDisk.SizeGB)
  1076. if err != nil {
  1077. return nil, err
  1078. }
  1079. params["server"]["flavorRef"] = flavor.Id
  1080. if len(opts.PublicKey) > 0 {
  1081. keypairName, err := region.syncKeypair(opts.Name, opts.PublicKey)
  1082. if err != nil {
  1083. return nil, err
  1084. }
  1085. params["server"]["key_name"] = keypairName
  1086. }
  1087. resp, err := region.ecsCreate(opts.ProjectId, "/servers", params)
  1088. if err != nil {
  1089. return nil, errors.Wrap(err, "ecsCreate")
  1090. }
  1091. diskIds = []string{}
  1092. instance := &SInstance{}
  1093. err = resp.Unmarshal(instance, "server")
  1094. if err != nil {
  1095. return nil, errors.Wrap(err, "resp.Unmarshal")
  1096. }
  1097. return instance, nil
  1098. }