diff --git a/pkg/cloudcommon/db/db_dispatcher.go b/pkg/cloudcommon/db/db_dispatcher.go index 9837f0bb67..67b19d2013 100644 --- a/pkg/cloudcommon/db/db_dispatcher.go +++ b/pkg/cloudcommon/db/db_dispatcher.go @@ -955,10 +955,6 @@ func objectPerformAction(dispatcher *DBModelDispatcher, modelValue reflect.Value } func updateItem(manager IModelManager, item IModel, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if !item.AllowUpdateItem(ctx, userCred) { - return nil, httperrors.NewForbiddenError(fmt.Sprintf("Not allow to update item")) - } - var err error err = item.ValidateUpdateCondition(ctx) @@ -1030,6 +1026,10 @@ func (dispatcher *DBModelDispatcher) Update(ctx context.Context, idStr string, q return nil, httperrors.NewGeneralError(err) } + if !model.AllowUpdateItem(ctx, userCred) { + return nil, httperrors.NewForbiddenError(fmt.Sprintf("Not allow to update item")) + } + lockman.LockObject(ctx, model) defer lockman.ReleaseObject(ctx, model) diff --git a/pkg/cloudcommon/db/db_joint_dispatcher.go b/pkg/cloudcommon/db/db_joint_dispatcher.go index 8b287f9544..236d6104f6 100644 --- a/pkg/cloudcommon/db/db_joint_dispatcher.go +++ b/pkg/cloudcommon/db/db_joint_dispatcher.go @@ -136,7 +136,7 @@ func (dispatcher *DBJointModelDispatcher) Get(ctx context.Context, id1 string, i } else if err != nil { return nil, httperrors.NewGeneralError(err) } - if !item.AllowGetDetails(ctx, userCred, query) { + if !item.AllowGetJointDetails(ctx, userCred, query, item) { return nil, httperrors.NewForbiddenError("Not allow to get details") } return getItemDetails(dispatcher.JointModelManager(), item, ctx, userCred, query) @@ -200,6 +200,11 @@ func (dispatcher *DBJointModelDispatcher) Update(ctx context.Context, id1 string } else if err != nil { return nil, httperrors.NewGeneralError(err) } + + if !item.AllowUpdateJointItem(ctx, userCred, item) { + return nil, httperrors.NewForbiddenError(fmt.Sprintf("Not allow to update item")) + } + lockman.LockJointObject(ctx, master, slave) defer lockman.ReleaseJointObject(ctx, master, slave) return updateItem(dispatcher.JointModelManager(), item, ctx, userCred, query, data) diff --git a/pkg/cloudcommon/db/interface.go b/pkg/cloudcommon/db/interface.go index bcc4c00779..4b0a7a3ac5 100644 --- a/pkg/cloudcommon/db/interface.go +++ b/pkg/cloudcommon/db/interface.go @@ -131,6 +131,8 @@ type IJointModel interface { Slave() IStandaloneModel Detach(ctx context.Context, userCred mcclient.TokenCredential) error + AllowGetJointDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, item IJointModel) bool + AllowUpdateJointItem(ctx context.Context, userCred mcclient.TokenCredential, item IJointModel) bool } type IStandaloneModelManager interface { diff --git a/pkg/cloudcommon/db/jointbase.go b/pkg/cloudcommon/db/jointbase.go index b8c3bf4ef0..3679eea408 100644 --- a/pkg/cloudcommon/db/jointbase.go +++ b/pkg/cloudcommon/db/jointbase.go @@ -179,6 +179,16 @@ func (joint *SJointResourceBase) Slave() IStandaloneModel { return nil } +func (self *SJointResourceBase) AllowGetJointDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, item IJointModel) bool { + masterVirtual := item.Master().(IVirtualModel) + return masterVirtual.IsOwner(userCred) +} + +func (self *SJointResourceBase) AllowUpdateJointItem(ctx context.Context, userCred mcclient.TokenCredential, item IJointModel) bool { + masterVirtual := item.Master().(IVirtualModel) + return masterVirtual.IsOwner(userCred) +} + /* func (joint *SJointResourceBase) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { extra := joint.SResourceBase.GetCustomizeColumns(ctx, userCred, query) diff --git a/pkg/compute/guestdrivers/kvm.go b/pkg/compute/guestdrivers/kvm.go index 19368731b6..7aa3d7fa98 100644 --- a/pkg/compute/guestdrivers/kvm.go +++ b/pkg/compute/guestdrivers/kvm.go @@ -266,21 +266,18 @@ func (self *SKVMGuestDriver) RequestDeleteDetachedDisk(ctx context.Context, disk } func (self *SKVMGuestDriver) RequestSyncConfigOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost, task taskman.ITask) error { - if guest.Status == models.VM_RUNNING { - desc := guest.GetDriver().GetJsonDescAtHost(ctx, guest, host) - body := jsonutils.NewDict() - body.Add(desc, "desc") - if fw_only, _ := task.GetParams().Bool("fw_only"); fw_only { - body.Add(jsonutils.JSONTrue, "fw_only") - } - url := fmt.Sprintf("/servers/%s/sync", guest.Id) - header := http.Header{} - header.Add("X-Task-Id", task.GetTaskId()) - header.Add("X-Region-Version", "v2") - _, err := host.Request(task.GetUserCred(), "POST", url, header, body) - return err + desc := guest.GetDriver().GetJsonDescAtHost(ctx, guest, host) + body := jsonutils.NewDict() + body.Add(desc, "desc") + if fw_only, _ := task.GetParams().Bool("fw_only"); fw_only { + body.Add(jsonutils.JSONTrue, "fw_only") } - return nil + url := fmt.Sprintf("/servers/%s/sync", guest.Id) + header := http.Header{} + header.Add("X-Task-Id", task.GetTaskId()) + header.Add("X-Region-Version", "v2") + _, err := host.Request(task.GetUserCred(), "POST", url, header, body) + return err } func (self *SKVMGuestDriver) RqeuestSuspendOnHost(ctx context.Context, guest *models.SGuest, task taskman.ITask) error { diff --git a/pkg/compute/models/guestnetworks.go b/pkg/compute/models/guestnetworks.go index 882bbb5851..2df92532e1 100644 --- a/pkg/compute/models/guestnetworks.go +++ b/pkg/compute/models/guestnetworks.go @@ -4,6 +4,7 @@ import ( "context" "crypto/md5" "crypto/rand" + "database/sql" "fmt" "io" "regexp" @@ -19,7 +20,6 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/compute/options" - "database/sql" ) const ( @@ -292,9 +292,11 @@ func (self *SGuestnetwork) ValidateUpdateData(ctx context.Context, userCred mccl if err != nil { return nil, fmt.Errorf("fail to fetch index %s", err) } - q := GuestnetworkManager.Query().Equals("guest_id", self.GuestId) - q = q.NotEquals("network_id", self.NetworkId).Equals("idnex", index) - if q.Count() > 0 { + q := GuestnetworkManager.Query().SubQuery() + count := q.Query().Filter(sqlchemy.Equals(q.Field("guest_id"), self.GuestId)). + Filter(sqlchemy.NotEquals(q.Field("network_id"), self.NetworkId)). + Filter(sqlchemy.Equals(q.Field("index"), index)).Count() + if count > 0 { return nil, fmt.Errorf("NIC Index %d has been occupied", index) } } diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 3695163d9e..003c53f333 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -35,6 +35,7 @@ import ( "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" "yunion.io/x/onecloud/pkg/util/httputils" + "yunion.io/x/onecloud/pkg/util/logclient" ) const ( @@ -513,6 +514,16 @@ func (self *SGuest) ValidateUpdateData(ctx context.Context, userCred mcclient.To if err != nil { return nil, err } + + if vmemSize > 0 || vcpuCount > 0 { + if !utils.IsInStringArray(self.Status, []string{VM_READY}) { + return nil, httperrors.NewInvalidStatusError("Cannot modify Memory and CPU in status %s", self.Status) + } + if self.GetHypervisor() == HYPERVISOR_BAREMETAL { + return nil, httperrors.NewInputParameterError("Cannot modify memory for baremetal") + } + } + if vmemSize > 0 { data.Add(jsonutils.NewInt(int64(vmemSize)), "vmem_size") } @@ -530,14 +541,6 @@ func (self *SGuest) ValidateUpdateData(ctx context.Context, userCred mcclient.To return nil, httperrors.NewInputParameterError("name is to short") } } - /* if self.GetHypervisor() == HYPERVISOR_BAREMETAL { - return nil, httperrors.NewInputParameterError("Cannot modify memory for baremetal") - } - if ! utils.IsInStringArray(self.Status, []string {VM_READY}) { - return nil, httperrors.NewInvalidStatusError("Cannot modify Memory and CPU in status %s", self.Status) - }*/ - // return nil, httperrors.NewInputParameterError("cannot update guest vmem_size") - //} return self.SVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, data) } @@ -2109,7 +2112,7 @@ func (self *SGuest) attachIsolatedDevice(userCred mcclient.TokenCredential, dev if dev.HostId != self.HostId { return fmt.Errorf("Isolated device and guest are not located in the same host") } - _, err := self.GetModelManager().TableSpec().Update(self, func() error { + _, err := IsolatedDeviceManager.TableSpec().Update(dev, func() error { dev.GuestId = self.Id return nil }) @@ -2182,6 +2185,9 @@ func (self *SGuest) CategorizeNics() SGuestNicCategory { func (self *SGuest) StartGuestDeployTask(ctx context.Context, userCred mcclient.TokenCredential, kwargs *jsonutils.JSONDict, action string, parentTaskId string) error { self.SetStatus(userCred, VM_START_DEPLOY, "") + if kwargs == nil { + kwargs = jsonutils.NewDict() + } kwargs.Add(jsonutils.NewString(action), "deploy_action") task, err := taskman.TaskManager.NewTask(ctx, "GuestDeployTask", self, userCred, kwargs, parentTaskId, "", nil) if err != nil { @@ -2462,6 +2468,74 @@ func (self *SGuest) DetachDisk(ctx context.Context, disk *SDisk, userCred mcclie } } +func (self *SGuest) AllowPerformCreatedisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformCreatedisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + var diskIdx, diskSize = 0, 0 + disksConf := jsonutils.NewDict() + diskSizes := make(map[string]int, 0) + diskSeq := fmt.Sprintf("disk.%d", diskIdx) + for data.Contains(diskSeq) { + diskDef, _ := data.Get(diskSeq) + diskInfo, err := parseDiskInfo(ctx, userCred, diskDef) + if err != nil { + logclient.AddActionLog(self, logclient.ACT_CREATE, err.Error(), userCred, false) + return nil, httperrors.NewBadRequestError(err.Error()) + } + disksConf.Set(diskSeq, jsonutils.Marshal(diskInfo)) + if _, ok := diskSizes[diskInfo.Backend]; !ok { + diskSizes[diskInfo.Backend] = diskInfo.Size + } else { + diskSizes[diskInfo.Backend] += diskInfo.Size + } + diskSize += diskInfo.Size + diskIdx += 1 + diskSeq = fmt.Sprintf("disk.%d", diskIdx) + } + if diskIdx == 0 { + logclient.AddActionLog(self, logclient.ACT_CREATE, "No Disk Info Provided", userCred, false) + return nil, httperrors.NewBadRequestError("No Disk Info Provided") + } + host := self.GetHost() + if host == nil { + logclient.AddActionLog(self, logclient.ACT_CREATE, "No valid host", userCred, false) + return nil, httperrors.NewBadRequestError("No valid host") + } + for backend, size := range diskSizes { + storage := host.GetLeastUsedStorage(backend) + if storage == nil { + logclient.AddActionLog(self, logclient.ACT_CREATE, "No valid storage on current host", userCred, false) + return nil, httperrors.NewBadRequestError("No valid storage on current host") + } + if storage.GetCapacity() < size { + logclient.AddActionLog(self, logclient.ACT_CREATE, "Not eough storage space on current host", userCred, false) + return nil, httperrors.NewBadRequestError("Not eough storage space on current host") + } + } + pendingUsage := &SQuota{ + Storage: diskSize, + } + err := QuotaManager.CheckSetPendingQuota(ctx, userCred, self.ProjectId, pendingUsage) + if err != nil { + logclient.AddActionLog(self, logclient.ACT_CREATE, err.Error(), userCred, false) + return nil, httperrors.NewBadRequestError(err.Error()) + } + + lockman.LockObject(ctx, host) + defer lockman.ReleaseObject(ctx, host) + + err = self.CreateDisksOnHost(ctx, userCred, host, disksConf, pendingUsage) + if err != nil { + QuotaManager.CancelPendingUsage(ctx, userCred, self.ProjectId, nil, pendingUsage) + logclient.AddActionLog(self, logclient.ACT_CREATE, err.Error(), userCred, false) + return nil, httperrors.NewBadRequestError(err.Error()) + } + err = self.StartGuestCreateDiskTask(ctx, userCred, disksConf, "") + return nil, err +} + func (self *SGuest) AllowPerformDetachdisk(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return self.IsOwner(userCred) } @@ -2508,6 +2582,196 @@ func (self *SGuest) PerformDetachdisk(ctx context.Context, userCred mcclient.Tok return nil, httperrors.NewResourceNotFoundError("Disk %s not found", diskId) } +func (self *SGuest) AllowPerformDetachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return userCred.IsSystemAdmin() +} + +func (self *SGuest) PerformDetachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Hypervisor != HYPERVISOR_KVM { + return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.Hypervisor) + } + if self.Status != VM_READY { + msg := "Only allowed to attach isolated device when guest is ready" + logclient.AddActionLog(self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false) + return nil, httperrors.NewInvalidStatusError(msg) + } + device, err := data.GetString("device") + if err != nil { + msg := "Missing isolated device" + logclient.AddActionLog(self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false) + return nil, httperrors.NewBadRequestError(msg) + } + iDev, err := IsolatedDeviceManager.FetchByIdOrName(userCred.GetProjectId(), device) + if err != nil { + msg := fmt.Sprintf("Isolated device %s not found", device) + logclient.AddActionLog(self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false) + return nil, httperrors.NewBadRequestError(msg) + } + dev := iDev.(*SIsolatedDevice) + host := self.GetHost() + lockman.LockObject(ctx, host) + defer lockman.ReleaseObject(ctx, host) + err = self.detachIsolateDevice(userCred, dev) + return nil, err +} + +func (self *SGuest) detachIsolateDevice(userCred mcclient.TokenCredential, dev *SIsolatedDevice) error { + if dev.GuestId != self.Id { + msg := "Isolated device is not attached to this guest" + logclient.AddActionLog(self, logclient.ACT_GUEST_DETACH_ISOLATED_DEVICE, msg, userCred, false) + return httperrors.NewBadRequestError(msg) + } + _, err := self.GetModelManager().TableSpec().Update(dev, func() error { + dev.GuestId = "" + return nil + }) + if err != nil { + return err + } + db.OpsLog.LogEvent(self, db.ACT_GUEST_DETACH_ISOLATED_DEVICE, dev.GetShortDesc(), userCred) + return nil +} + +func (self *SGuest) AllowPerformAttachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return userCred.IsSystemAdmin() +} + +func (self *SGuest) PerformAttachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Hypervisor != HYPERVISOR_KVM { + return nil, httperrors.NewNotAcceptableError("Not allow for hypervisor %s", self.Hypervisor) + } + if self.Status != VM_READY { + msg := "Only allowed to attach isolated device when guest is ready" + logclient.AddActionLog(self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, false) + return nil, httperrors.NewInvalidStatusError(msg) + } + device, err := data.GetString("device") + if err != nil { + msg := "Missing isolated device" + logclient.AddActionLog(self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, false) + return nil, httperrors.NewBadRequestError(msg) + } + iDev, err := IsolatedDeviceManager.FetchByIdOrName(userCred.GetProjectId(), device) + if err != nil { + msg := fmt.Sprintf("Isolated device %s not found", device) + logclient.AddActionLog(self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, false) + return nil, httperrors.NewBadRequestError(msg) + } + dev := iDev.(*SIsolatedDevice) + host := self.GetHost() + lockman.LockObject(ctx, host) + defer lockman.ReleaseObject(ctx, host) + err = self.attachIsolatedDevice(userCred, dev) + var msg string + if err != nil { + msg = err.Error() + } + logclient.AddActionLog(self, logclient.ACT_GUEST_ATTACH_ISOLATED_DEVICE, msg, userCred, err == nil) + return nil, err +} + +func (self *SGuest) AllowPerformDetachnetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformDetachnetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Status != VM_READY { + return nil, httperrors.NewInvalidStatusError("Cannot detach network in status %s", self.Status) + } + reserve := jsonutils.QueryBoolean(data, "reserve", false) + netId, err := data.GetString("net_id") + if err != nil { + return nil, httperrors.NewBadRequestError(err.Error()) + } + iNetwork, err := NetworkManager.FetchById(netId) + if err != nil { + return nil, httperrors.NewNotFoundError("Network %s not found", netId) + } + network := iNetwork.(*SNetwork) + err = self.detachNetwork(ctx, userCred, network, reserve, true) + return nil, err +} + +func (self *SGuest) AllowPerformAttachnetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformAttachnetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Status == VM_READY { + // owner_cred = self.get_owner_user_cred() >.< + netDesc, err := data.Get("net_desc") + if err != nil { + return nil, httperrors.NewBadRequestError(err.Error()) + } + conf, err := parseNetworkInfo(userCred, netDesc) + if err != nil { + return nil, err + } + err = isValidNetworkInfo(userCred, conf) + if err != nil { + return nil, httperrors.NewBadRequestError(err.Error()) + } + var inicCnt, enicCnt, ibw, ebw int + if isExitNetworkInfo(conf) { + enicCnt = 1 + ebw = conf.BwLimit + } else { + inicCnt = 1 + ibw = conf.BwLimit + } + pendingUsage := &SQuota{ + Port: inicCnt, + Eport: enicCnt, + Bw: ibw, + Ebw: ebw, + } + projectId := self.GetOwnerProjectId() + err = QuotaManager.CheckSetPendingQuota(ctx, userCred, projectId, pendingUsage) + if err != nil { + return nil, httperrors.NewOutOfQuotaError(err.Error()) + } + host := self.GetHost() + err = self.attach2NetworkDesc(ctx, userCred, host, conf, pendingUsage) + if err != nil { + QuotaManager.CancelPendingUsage(ctx, userCred, projectId, nil, pendingUsage) + return nil, httperrors.NewBadRequestError(err.Error()) + } + host.ClearSchedDescCache() + err = self.StartGuestDeployTask(ctx, userCred, nil, "deploy", "") + return nil, err + } + return nil, httperrors.NewBadRequestError("Cannot attach network in status %s", self.Status) +} + +func (self *SGuest) AllowPerformChangeBandwidth(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || userCred.IsSystemAdmin() +} + +func (self *SGuest) PerformChangeBandwidth(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if utils.IsInStringArray(self.Status, []string{VM_READY, VM_RUNNING}) { + guestnics := self.GetNetworks() + index, err := data.Int("index") + if err != nil || index > int64(len(guestnics)) { + return nil, httperrors.NewBadRequestError("Index Not fount or out of NIC index") + } + bandwidth, err := data.Int("bandwidth") + if err != nil || bandwidth <= 0 { + return nil, httperrors.NewBadRequestError("Bandwidth must be larger than 0") + } + guestnic := &guestnics[index] + if guestnic.BwLimit != int(bandwidth) { + GuestnetworkManager.TableSpec().Update(guestnic, func() error { + guestnic.BwLimit = int(bandwidth) + return nil + }) + err := self.StartSyncTask(ctx, userCred, false, "") + return nil, err + } + return nil, nil + } + return nil, httperrors.NewBadRequestError("Cannot change bandwidth in status %s", self.Status) +} + func (self *SGuest) AllowPerformChangeConfig(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return self.IsOwner(userCred) || self.IsAdmin(userCred) } @@ -3548,6 +3812,78 @@ func (self *SGuest) GetDetailsMonitor(ctx context.Context, userCred mcclient.Tok return nil, httperrors.NewInvalidStatusError("Cannot send command in status %s", self.Status) } +func (self *SGuest) AllowGetDetailsDesc(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) GetDetailsDesc(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { + host := self.GetHost() + if host == nil { + return nil, httperrors.NewInvalidStatusError("No host for server") + } + desc := self.GetDriver().GetJsonDescAtHost(ctx, self, host) + return desc, nil +} + +func (self *SGuest) AllowPerformSendkeys(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) +} + +func (self *SGuest) PerformSendkeys(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.Hypervisor != HYPERVISOR_KVM { + return nil, httperrors.NewUnsupportOperationError("Not allow for hypervisor %s", self.Hypervisor) + } + if self.Status != VM_RUNNING { + return nil, httperrors.NewInvalidStatusError("Cannot send keys in status %s", self.Status) + } + keys, err := data.GetString("keys") + if err != nil { + return nil, err + } + err = self.VerifySendKeys(keys) + if err != nil { + return nil, httperrors.NewBadRequestError(err.Error()) + } + cmd := fmt.Sprintf("sendkey %s", keys) + duration, err := data.Int("duration") + if err == nil { + cmd = fmt.Sprintf("%s %d", cmd, duration) + } + _, err = self.SendMonitorCommand(ctx, userCred, cmd) + return nil, err +} + +func (self *SGuest) VerifySendKeys(keyStr string) error { + keys := strings.Split(keyStr, "-") + for _, key := range keys { + if !self.IsLegalKey(key) { + return fmt.Errorf("Unknown key '%s'", key) + } + } + return nil +} + +func (self *SGuest) IsLegalKey(key string) bool { + singleKeys := "1234567890abcdefghijklmnopqrstuvwxyz" + legalKeys := []string{"ctrl", "ctrl_r", "alt", "alt_r", "shift", "shift_r", + "delete", "esc", "insert", "print", "spc", + "f1", "f2", "f3", "f4", "f5", "f6", + "f7", "f8", "f9", "f10", "f11", "f12", + "home", "pgup", "pgdn", "end", + "up", "down", "left", "right", + "tab", "minus", "equal", "backspace", "backslash", + "bracket_left", "bracket_right", "backslash", + "semicolon", "apostrophe", "grave_accent", "ret", + "comma", "dot", "slash", + "caps_lock", "num_lock", "scroll_lock"} + if len(key) > 1 && !utils.IsInStringArray(key, legalKeys) { + return false + } else if len(key) == 1 && !strings.Contains(singleKeys, key) { + return false + } + return true +} + func (self *SGuest) SendMonitorCommand(ctx context.Context, userCred mcclient.TokenCredential, cmd string) (jsonutils.JSONObject, error) { host := self.GetHost() url := fmt.Sprintf("%s/servers/%s/monitor", host.ManagerUri, self.Id) diff --git a/pkg/compute/models/isolated_devices.go b/pkg/compute/models/isolated_devices.go index d88332cf5c..479a81a939 100644 --- a/pkg/compute/models/isolated_devices.go +++ b/pkg/compute/models/isolated_devices.go @@ -452,10 +452,40 @@ func (self *SIsolatedDevice) GetCustomizeColumns(ctx context.Context, userCred m return extra } +func (self *SIsolatedDevice) ClearSchedDescCache() error { + if len(self.HostId) == 0 { + return nil + } + host := self.getHost() + return host.ClearSchedDescCache() +} + +func (self *SIsolatedDevice) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + err := self.SStandaloneResourceBase.Delete(ctx, userCred) + if err != nil { + return err + } + return self.ClearSchedDescCache() +} + func (self *SIsolatedDevice) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + if len(self.GuestId) > 0 { + if !jsonutils.QueryBoolean(data, "purge", false) { + return httperrors.NewBadRequestError("Isolated device used by server: %s", self.GuestId) + } + iGuest, err := GuestManager.FetchById(self.GuestId) + if err != nil { + return err + } + guest := iGuest.(*SGuest) + err = guest.detachIsolateDevice(userCred, self) + if err != nil { + return err + } + } host := self.getHost() if host != nil { db.OpsLog.LogEvent(host, db.ACT_HOST_DETACH_ISOLATED_DEVICE, self.GetShortDesc(), userCred) } - return nil + return self.RealDelete(ctx, userCred) } diff --git a/pkg/mcclient/session.go b/pkg/mcclient/session.go index 527e05c4e4..5cf2324f9e 100644 --- a/pkg/mcclient/session.go +++ b/pkg/mcclient/session.go @@ -21,6 +21,10 @@ const ( DEFAULT_API_VERSION = "v1" ) +var ( + MutilVersionService = []string{"compute"} +) + type ClientSession struct { client *Client region string @@ -72,7 +76,7 @@ func (this *ClientSession) GetServiceURL(service, endpointType string) (string, // session specific endpoint type should override the input endpointType, which is supplied by manager endpointType = this.endpointType } - if len(this.apiVersion) > 0 && this.apiVersion != DEFAULT_API_VERSION { + if utils.IsInStringArray(service, MutilVersionService) && len(this.apiVersion) > 0 && this.apiVersion != DEFAULT_API_VERSION { service = fmt.Sprintf("%s_%s", service, this.apiVersion) } url, err := this.token.GetServiceURL(service, this.region, this.zone, endpointType) @@ -87,7 +91,7 @@ func (this *ClientSession) GetServiceURLs(service, endpointType string) ([]strin // session specific endpoint type should override the input endpointType, which is supplied by manager endpointType = this.endpointType } - if len(this.apiVersion) > 0 && this.apiVersion != DEFAULT_API_VERSION { + if utils.IsInStringArray(service, MutilVersionService) && len(this.apiVersion) > 0 && this.apiVersion != DEFAULT_API_VERSION { service = fmt.Sprintf("%s_%s", service, this.apiVersion) } urls, err := this.token.GetServiceURLs(service, this.region, this.zone, endpointType) diff --git a/pkg/util/logclient/logclient.go b/pkg/util/logclient/logclient.go index e8a6ca1371..f7898ca1e7 100644 --- a/pkg/util/logclient/logclient.go +++ b/pkg/util/logclient/logclient.go @@ -3,12 +3,12 @@ package logclient import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/util/stringutils" "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" "yunion.io/x/onecloud/pkg/mcclient/modules" - "yunion.io/x/pkg/util/stringutils" ) const ( @@ -102,7 +102,6 @@ func AddActionLog(model IObject, action string, iNotes interface{}, userCred mcc } logentry.Add(jsonutils.NewString(notes), "notes") - logclientWorkerMan.Run(func() { s := auth.GetSession(userCred, "", "") _, err := modules.Actions.Create(s, logentry)