| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673 |
- // Copyright 2019 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package zstack
- import (
- "context"
- "fmt"
- "net/url"
- "strings"
- "time"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/util/billing"
- "yunion.io/x/pkg/util/imagetools"
- "yunion.io/x/pkg/util/version"
- "yunion.io/x/pkg/utils"
- api "yunion.io/x/cloudmux/pkg/apis/compute"
- "yunion.io/x/cloudmux/pkg/cloudprovider"
- "yunion.io/x/cloudmux/pkg/multicloud"
- )
- type SInstanceCdrome struct {
- }
- type SInstance struct {
- multicloud.SInstanceBase
- ZStackTags
- host *SHost
- osInfo *imagetools.ImageInfo
- ZStackBasic
- ZoneUUID string `json:"zoneUuid"`
- ClusterUUID string `json:"clusterUuid"`
- HostUUID string `json:"hostUuid"`
- LastHostUUID string `json:"lastHostUuid"`
- RootVolumeUUID string `json:"rootVolumeUuid"`
- Platform string `json:"platform"`
- InstanceOfferingUUID string `json:"instanceOfferingUuid"`
- DefaultL3NetworkUUID string `json:"defaultL3NetworkUuid"`
- Type string `json:"type"`
- HypervisorType string `json:"hypervisorType"`
- MemorySize int `json:"memorySize"`
- CPUNum int `json:"cpuNum"`
- CPUSpeed int `json:"cpuSpeed"`
- State string `json:"state"`
- InternalID string `json:"internalId"`
- VMNics []SInstanceNic `json:"vmNics"`
- AllVolumes []SDisk `json:"allVolumes"`
- VMCdRoms []SInstanceCdrome `json:"vmCdRoms"`
- ZStackTime
- }
- func (region *SRegion) GetInstance(instanceId string) (*SInstance, error) {
- instance := &SInstance{}
- err := region.client.getResource("vm-instances", instanceId, instance)
- if err != nil {
- return nil, err
- }
- if instance.State == "Destroyed" {
- return nil, cloudprovider.ErrNotFound
- }
- return instance, nil
- }
- func (region *SRegion) GetInstances(hostId string, instanceId string, nicId string) ([]SInstance, error) {
- instance := []SInstance{}
- params := url.Values{}
- params.Add("q", "type=UserVm")
- params.Add("q", "state!=Destroyed")
- if len(hostId) > 0 {
- params.Add("q", "lastHostUuid="+hostId)
- }
- if len(instanceId) > 0 {
- params.Add("q", "uuid="+instanceId)
- }
- if len(nicId) > 0 {
- params.Add("q", "vmNics.uuid="+nicId)
- }
- if SkipEsxi {
- params.Add("q", "hypervisorType!=ESX")
- }
- return instance, region.client.listAll("vm-instances", params, &instance)
- }
- func (instance *SInstance) GetSecurityGroupIds() ([]string, error) {
- ids := []string{}
- secgroups, err := instance.host.zone.region.GetSecurityGroups("", instance.UUID, "")
- if err != nil {
- return nil, err
- }
- for _, secgroup := range secgroups {
- ids = append(ids, secgroup.UUID)
- }
- return ids, nil
- }
- func (instance *SInstance) GetIHost() cloudprovider.ICloudHost {
- return instance.host
- }
- func (instance *SInstance) GetIHostId() string {
- if len(instance.LastHostUUID) > 0 {
- return instance.LastHostUUID
- }
- return instance.HostUUID
- }
- func (instance *SInstance) GetId() string {
- return instance.UUID
- }
- func (instance *SInstance) GetName() string {
- return instance.Name
- }
- func (instance *SInstance) GetHostname() string {
- return ""
- }
- func (instance *SInstance) GetGlobalId() string {
- return instance.GetId()
- }
- func (instance *SInstance) IsEmulated() bool {
- return false
- }
- func (instance *SInstance) GetInstanceType() string {
- if len(instance.InstanceOfferingUUID) > 0 {
- offer, err := instance.host.zone.region.GetInstanceOffering(instance.InstanceOfferingUUID)
- if err == nil {
- return offer.Name
- }
- }
- return instance.Type
- }
- func (instance *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) {
- idisks := []cloudprovider.ICloudDisk{}
- rootDisk, err := instance.host.zone.region.GetDiskWithStorage(instance.RootVolumeUUID)
- if err != nil {
- return nil, err
- }
- idisks = append(idisks, rootDisk)
- for i := 0; i < len(instance.AllVolumes); i++ {
- if instance.AllVolumes[i].UUID != instance.RootVolumeUUID {
- dataDisk, err := instance.host.zone.region.GetDiskWithStorage(instance.AllVolumes[i].UUID)
- if err != nil {
- return nil, err
- }
- idisks = append(idisks, dataDisk)
- }
- }
- return idisks, nil
- }
- func (instance *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) {
- iNics := []cloudprovider.ICloudNic{}
- for i := 0; i < len(instance.VMNics); i++ {
- instance.VMNics[i].instance = instance
- iNics = append(iNics, &instance.VMNics[i])
- }
- return iNics, nil
- }
- func (instance *SInstance) GetVcpuCount() int {
- return instance.CPUNum
- }
- func (instance *SInstance) GetVmemSizeMB() int {
- return instance.MemorySize / 1024 / 1024
- }
- func (instance *SInstance) GetBootOrder() string {
- return instance.host.zone.region.GetBootOrder(instance.UUID)
- }
- func (region *SRegion) GetBootOrder(instanceId string) string {
- resp, err := region.client.get("vm-instances", instanceId, "boot-orders")
- if err != nil {
- return "dcn"
- }
- orders := []string{}
- err = resp.Unmarshal(&orders, "orders")
- if err != nil {
- return "dcn"
- }
- order := ""
- for _, _order := range orders {
- switch _order {
- case "CdRom":
- order += "c"
- case "HardDisk":
- order += "d"
- default:
- log.Errorf("Unknown BootOrder %s for instance %s", _order, instanceId)
- }
- }
- return order
- }
- func (instance *SInstance) GetVga() string {
- return "std"
- }
- func (instance *SInstance) GetVdi() string {
- return "vnc"
- }
- func (instance *SInstance) getNormalizedOsInfo() *imagetools.ImageInfo {
- if instance.osInfo == nil {
- osInfo := imagetools.NormalizeImageInfo(instance.Platform, "", "", "", "")
- instance.osInfo = &osInfo
- }
- return instance.osInfo
- }
- func (instance *SInstance) GetOsType() cloudprovider.TOsType {
- return cloudprovider.TOsType(instance.getNormalizedOsInfo().OsType)
- }
- func (instance *SInstance) GetFullOsName() string {
- return instance.Platform
- }
- func (instance *SInstance) GetBios() cloudprovider.TBiosType {
- return cloudprovider.ToBiosType(instance.getNormalizedOsInfo().OsBios)
- }
- func (instance *SInstance) GetOsDist() string {
- return instance.getNormalizedOsInfo().OsDistro
- }
- func (instance *SInstance) GetOsVersion() string {
- return instance.getNormalizedOsInfo().OsVersion
- }
- func (instance *SInstance) GetOsLang() string {
- return instance.getNormalizedOsInfo().OsLang
- }
- func (instance *SInstance) GetOsArch() string {
- return instance.getNormalizedOsInfo().OsArch
- }
- func (instance *SInstance) GetMachine() string {
- return "pc"
- }
- func (instance *SInstance) GetStatus() string {
- switch instance.State {
- case "Stopped":
- return api.VM_READY
- case "Running":
- return api.VM_RUNNING
- case "Destroyed":
- return api.VM_DEALLOCATED
- default:
- log.Errorf("Unknown instance %s status %s", instance.Name, instance.State)
- return api.VM_UNKNOWN
- }
- }
- func (instance *SInstance) Refresh() error {
- new, err := instance.host.zone.region.GetInstance(instance.UUID)
- if err != nil {
- return err
- }
- return jsonutils.Update(instance, new)
- }
- func (instance *SInstance) GetHypervisor() string {
- return api.HYPERVISOR_ZSTACK
- }
- func (instance *SInstance) StartVM(ctx context.Context) error {
- err := instance.host.zone.region.StartVM(instance.UUID)
- if err != nil {
- return err
- }
- return cloudprovider.WaitStatus(instance, api.VM_RUNNING, 5*time.Second, 5*time.Minute)
- }
- func (region *SRegion) StartVM(instanceId string) error {
- params := map[string]interface{}{
- "startVmInstance": jsonutils.NewDict(),
- }
- _, err := region.client.put("vm-instances", instanceId, jsonutils.Marshal(params))
- return err
- }
- func (instance *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error {
- err := instance.host.zone.region.StopVM(instance.UUID, opts.IsForce)
- if err != nil {
- return err
- }
- return cloudprovider.WaitStatus(instance, api.VM_READY, 5*time.Second, 5*time.Minute)
- }
- func (region *SRegion) StopVM(instanceId string, isForce bool) error {
- option := "grace"
- if isForce {
- option = "cold"
- }
- params := map[string]interface{}{
- "stopVmInstance": map[string]string{
- "type": option,
- "stopHA": "true",
- },
- }
- _, err := region.client.put("vm-instances", instanceId, jsonutils.Marshal(params))
- return err
- }
- func (instance *SInstance) GetVNCInfo(input *cloudprovider.ServerVncInput) (*cloudprovider.ServerVncOutput, error) {
- info, err := instance.host.zone.region.GetInstanceConsoleInfo(instance.UUID)
- if err != nil {
- return nil, err
- }
- authURL, _ := url.Parse(instance.host.zone.region.client.authURL)
- url := fmt.Sprintf("%s://%s:5000/thirdparty/vnc_auto.html?host=%s&port=%d&token=%s&title=%s", info.Scheme, authURL.Hostname(), info.Hostname, info.Port, info.Token, instance.Name)
- if ver, _ := instance.host.zone.region.client.GetVersion(); ver != nil {
- if version.GE(ver.Version, "4.0.0") {
- url = fmt.Sprintf("%s://%s:5000/novnc?host=%s&port=%d&token=%s&title=%s&language=zh-CN&lowVersion=false", info.Scheme, authURL.Hostname(), info.Hostname, info.Port, info.Token, instance.Name)
- }
- }
- password, _ := instance.host.zone.region.GetInstanceConsolePassword(instance.UUID)
- if len(password) > 0 {
- url = url + fmt.Sprintf("&password=%s", password)
- }
- ret := &cloudprovider.ServerVncOutput{
- Url: url,
- Protocol: "zstack",
- InstanceId: instance.UUID,
- Hypervisor: api.HYPERVISOR_ZSTACK,
- }
- return ret, nil
- }
- func (instance *SInstance) UpdateVM(ctx context.Context, input cloudprovider.SInstanceUpdateOptions) error {
- params := map[string]interface{}{
- "updateVmInstance": map[string]string{
- "name": input.NAME,
- },
- }
- return instance.host.zone.region.UpdateVM(instance.UUID, jsonutils.Marshal(params))
- }
- func (region *SRegion) UpdateVM(instanceId string, params jsonutils.JSONObject) error {
- _, err := region.client.put("vm-instances", instanceId, params)
- return err
- }
- func (instance *SInstance) DeployVM(ctx context.Context, opts *cloudprovider.SInstanceDeployOptions) error {
- if len(opts.Password) > 0 {
- params := map[string]interface{}{
- "changeVmPassword": map[string]string{
- "account": opts.Username,
- "password": opts.Password,
- },
- }
- err := instance.host.zone.region.UpdateVM(instance.UUID, jsonutils.Marshal(params))
- if err != nil {
- return err
- }
- }
- if len(opts.PublicKey) > 0 {
- params := map[string]interface{}{
- "setVmSshKey": map[string]string{
- "SshKey": opts.PublicKey,
- },
- }
- err := instance.host.zone.region.UpdateVM(instance.UUID, jsonutils.Marshal(params))
- if err != nil {
- return err
- }
- }
- if opts.DeleteKeypair {
- err := instance.host.zone.region.client.delete("vm-instances", fmt.Sprintf("%s/ssh-keys", instance.UUID), "")
- if err != nil {
- return err
- }
- }
- return nil
- }
- func (instance *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) {
- return instance.host.zone.region.RebuildRoot(instance.UUID, desc.ImageId, desc.SysSizeGB)
- }
- func (region *SRegion) RebuildRoot(instanceId, imageId string, sysSizeGB int) (string, error) {
- params := map[string]interface{}{
- "changeVmImage": map[string]string{
- "imageUuid": imageId,
- },
- }
- instance := &SInstance{}
- resp, err := region.client.put("vm-instances", instanceId, jsonutils.Marshal(params))
- if err != nil {
- return "", err
- }
- err = resp.Unmarshal(instance, "inventory")
- if err != nil {
- return "", err
- }
- disk, err := region.GetDisk(instance.RootVolumeUUID)
- if err != nil {
- return "", err
- }
- if sysSizeGB > disk.GetDiskSizeMB()*1024 {
- return instance.RootVolumeUUID, region.ResizeDisk(disk.UUID, int64(sysSizeGB)*1024)
- }
- return instance.RootVolumeUUID, nil
- }
- func (instance *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error {
- offerings, err := instance.host.zone.region.GetInstanceOfferings("", "", config.Cpu, config.MemoryMB)
- if err != nil {
- return err
- }
- if len(config.InstanceType) > 0 {
- for _, offering := range offerings {
- if offering.Name == config.InstanceType {
- return instance.host.zone.region.ChangeConfig(instance.UUID, offering.UUID)
- }
- }
- offering, err := instance.host.zone.region.CreateInstanceOffering(config.InstanceType, config.Cpu, config.MemoryMB, "UserVm")
- if err != nil {
- return err
- }
- return instance.host.zone.region.ChangeConfig(instance.UUID, offering.UUID)
- }
- for _, offering := range offerings {
- log.Debugf("try instance offering %s(%s) ...", offering.Name, offering.UUID)
- err := instance.host.zone.region.ChangeConfig(instance.UUID, offering.UUID)
- if err != nil {
- log.Errorf("failed to change config for instance %s(%s) error: %v", instance.Name, instance.UUID, err)
- } else {
- return nil
- }
- }
- return fmt.Errorf("Failed to change vm config, specification not supported")
- }
- func (region *SRegion) ChangeConfig(instanceId, offeringId string) error {
- params := map[string]interface{}{
- "changeInstanceOffering": map[string]string{
- "instanceOfferingUuid": offeringId,
- },
- }
- return region.UpdateVM(instanceId, jsonutils.Marshal(params))
- }
- func (instance *SInstance) AttachDisk(ctx context.Context, diskId string) error {
- return instance.host.zone.region.AttachDisk(instance.UUID, diskId)
- }
- func (region *SRegion) AttachDisk(instanceId string, diskId string) error {
- _, err := region.client.post(fmt.Sprintf("volumes/%s/vm-instances/%s", diskId, instanceId), jsonutils.NewDict())
- return err
- }
- func (instance *SInstance) DetachDisk(ctx context.Context, diskId string) error {
- return instance.host.zone.region.DetachDisk(instance.UUID, diskId)
- }
- func (region *SRegion) DetachDisk(instanceId, diskId string) error {
- url := fmt.Sprintf("volumes/%s/vm-instances?vmUuid=%s", diskId, instanceId)
- err := region.client.delete(url, "", "")
- if err != nil && strings.Contains(err.Error(), "is not attached to any vm") {
- return nil
- }
- return err
- }
- func (instance *SInstance) DeleteVM(ctx context.Context) error {
- disks, err := instance.GetIDisks()
- if err != nil {
- return errors.Wrapf(err, "GetIDisks")
- }
- err = instance.host.zone.region.DeleteVM(instance.UUID)
- if err != nil {
- return errors.Wrapf(err, "DeleteVM")
- }
- for i := range disks {
- if disks[i].GetDiskType() != api.DISK_TYPE_SYS && disks[i].GetIsAutoDelete() {
- err = disks[i].Delete(ctx)
- if err != nil {
- log.Warningf("delete disk %s failed %s", disks[i].GetId(), err)
- }
- }
- }
- return nil
- }
- func (region *SRegion) DeleteVM(instanceId string) error {
- err := region.client.delete("vm-instances", instanceId, "Enforcing")
- if err != nil {
- return err
- }
- params := map[string]interface{}{
- "expungeVmInstance": jsonutils.NewDict(),
- }
- _, err = region.client.put("vm-instances", instanceId, jsonutils.Marshal(params))
- if err != nil {
- return errors.Wrapf(err, "expungeVmInstance")
- }
- return nil
- }
- func (instance *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) {
- eips, err := instance.host.zone.region.GetEips("", instance.UUID)
- if err != nil {
- return nil, err
- }
- if len(eips) == 0 {
- return nil, cloudprovider.ErrNotFound
- }
- if len(eips) == 1 {
- return &eips[0], nil
- }
- return nil, cloudprovider.ErrDuplicateId
- }
- func (instance *SInstance) SetSecurityGroups(secgroupIds []string) error {
- currentIds, err := instance.GetSecurityGroupIds()
- if err != nil {
- return err
- }
- for _, id := range currentIds {
- if !utils.IsInStringArray(id, secgroupIds) {
- err := instance.host.zone.region.RevokeSecurityGroup(instance.UUID, id)
- if err != nil {
- return err
- }
- }
- }
- for _, id := range secgroupIds {
- if !utils.IsInStringArray(id, currentIds) {
- err := instance.host.zone.region.AssignSecurityGroup(instance.UUID, id)
- if err != nil {
- return err
- }
- }
- }
- return nil
- }
- func (region *SRegion) AssignSecurityGroup(instanceId, secgroupId string) error {
- instance, err := region.GetInstance(instanceId)
- if err != nil {
- return err
- }
- secgroup, err := region.GetSecurityGroup(secgroupId)
- if err != nil {
- return err
- }
- if len(instance.VMNics) > 0 {
- if !utils.IsInStringArray(instance.VMNics[0].L3NetworkUUID, secgroup.AttachedL3NetworkUUIDs) {
- resource := fmt.Sprintf("security-groups/%s/l3-networks/%s", secgroupId, instance.VMNics[0].L3NetworkUUID)
- _, err := region.client.post(resource, jsonutils.NewDict())
- if err != nil {
- return err
- }
- }
- params := map[string]interface{}{
- "params": map[string]interface{}{
- "vmNicUuids": []string{instance.VMNics[0].UUID},
- },
- }
- resource := fmt.Sprintf("security-groups/%s/vm-instances/nics", secgroupId)
- _, err = region.client.post(resource, jsonutils.Marshal(params))
- return err
- }
- return nil
- }
- func (region *SRegion) RevokeSecurityGroup(instanceId, secgroupId string) error {
- instance, err := region.GetInstance(instanceId)
- if err != nil {
- return err
- }
- for _, nic := range instance.VMNics {
- resource := fmt.Sprintf("security-groups/%s/vm-instances/nics?vmNicUuids=%s", secgroupId, nic.UUID)
- err := region.client.delete(resource, "", "")
- if err != nil {
- return err
- }
- }
- return nil
- }
- func (instance *SInstance) GetBillingType() string {
- return ""
- }
- func (instance *SInstance) GetCreatedAt() time.Time {
- return instance.CreateDate
- }
- func (instance *SInstance) GetExpiredAt() time.Time {
- return time.Time{}
- }
- func (instance *SInstance) UpdateUserData(userData string) error {
- return cloudprovider.ErrNotSupported
- }
- func (instance *SInstance) Renew(bc billing.SBillingCycle) error {
- return cloudprovider.ErrNotSupported
- }
- func (instance *SInstance) GetProjectId() string {
- return ""
- }
- func (instance *SInstance) GetError() error {
- return nil
- }
- type SConsoleInfo struct {
- Scheme string `json:"scheme"`
- Hostname string `json:"hostname"`
- Port int `json:"port"`
- Token string `json:"token"`
- }
- func (region *SRegion) GetInstanceConsoleInfo(instnaceId string) (*SConsoleInfo, error) {
- params := map[string]interface{}{
- "params": map[string]string{
- "vmInstanceUuid": instnaceId,
- },
- }
- resp, err := region.client.post("consoles", jsonutils.Marshal(params))
- if err != nil {
- return nil, err
- }
- info := &SConsoleInfo{}
- err = resp.Unmarshal(info, "inventory")
- if err != nil {
- return nil, err
- }
- return info, nil
- }
- func (region *SRegion) GetInstanceConsolePassword(instnaceId string) (string, error) {
- resp, err := region.client.get("vm-instances", instnaceId, "console-passwords")
- if err != nil {
- return "", err
- }
- if resp.Contains("consolePassword") {
- return resp.GetString("consolePassword")
- }
- return "", nil
- }
|