feat & fix: Add batch operator and fix some bugs for instance group

Feature:
1. Add apis: list server by group, list group by server, bind
servers, unbind servers, bind groups, unbind groups.
2. Add climc command and docs corresponding to the above newly added apis
3. Add log about binding and unbinding operator.
4. Support enable and disable instance group.
Fix:
1. Set capacity zeor if there are not enough resource aboue forced
instance group in one host.
2. Change the stage(real delete => pending delete) that guest leave all groups.
This commit is contained in:
Rain
2019-10-17 19:19:38 +08:00
committed by 郑雨
parent 29502b9e58
commit 8e55bee9b3
27 changed files with 686 additions and 54 deletions
+33
View File
@@ -191,4 +191,37 @@ func init() {
printObject(result)
return nil
})
type GroupBindGuestsOptions struct {
ID string `help:"ID"`
Guest []string `help:"ID or Name of Guest"`
}
R(&GroupBindGuestsOptions{}, "instance-group-bind-guests", "bind instancegroup to guests",
func(s *mcclient.ClientSession, opts *GroupBindGuestsOptions) error {
params, err := options.StructToParams(opts)
if err != nil {
return err
}
sp, err := modules.InstanceGroup.PerformAction(s, opts.ID, "bind-guests", params)
if err != nil {
return err
}
printObject(sp)
return nil
})
R(&GroupBindGuestsOptions{}, "instance-group-unbind-guests", "bind instancegroup to guests",
func(s *mcclient.ClientSession, opts *GroupBindGuestsOptions) error {
params, err := options.StructToParams(opts)
if err != nil {
return err
}
sp, err := modules.InstanceGroup.PerformAction(s, opts.ID, "unbind-guests", params)
if err != nil {
return err
}
printObject(sp)
return nil
})
}
+3 -2
View File
@@ -29,6 +29,7 @@ func init() {
ServiceType string `help:"Service Type"`
ParentId string `help:"Parent ID"`
ZoneId string `help:"Zone ID"`
Guest string `help:"Guest ID or Name"`
}
R(&InstanceGroupListOptions{}, "instance-group-list", "List instance group", func(s *mcclient.ClientSession,
@@ -61,9 +62,9 @@ func init() {
})
type InstanceGroupCreateOptions struct {
NAME string `help:"name of instance group"`
ZONEID string `help:"zone id" json:"zone_id"`
NAME string `help:"name of instance group"`
ZoneId string `help:"zone id" json:"zone_id"`
ServiceType string `help:"service type"`
ParentId string `help:"parent id"`
SchedStrategy string `help:"scheduler strategy"`
+21 -11
View File
@@ -1071,28 +1071,38 @@ func init() {
return nil
})
type ServerGroupOptions struct {
ID string `help:"ID or name of VM"`
GROUPID string `help:"ID or name of instance group"`
type ServerGroupsOptions struct {
ID string `help:"ID or name of VM"`
Group []string `help:"ids or names of group"`
}
R(&ServerGroupOptions{}, "server-join-group", "Join a group", func(s *mcclient.ClientSession,
opts *ServerGroupOptions) error {
ret, err := modules.GroupGuest.Attach(s, opts.ID, opts.GROUPID, jsonutils.JSONNull)
R(&ServerGroupsOptions{}, "server-join-groups", "Join multiple groups", func(s *mcclient.ClientSession,
opts *ServerGroupsOptions) error {
params, err := options.StructToParams(opts)
if err != nil {
return err
}
printObject(ret)
server, err := modules.Servers.PerformAction(s, opts.ID, "bind-groups", params)
if err != nil {
return err
}
printObject(server)
return nil
})
R(&ServerGroupOptions{}, "server-leave-group", "Leave a group", func(s *mcclient.ClientSession,
opts *ServerGroupOptions) error {
ret, err := modules.GroupGuest.Detach(s, opts.ID, opts.GROUPID, jsonutils.JSONNull)
R(&ServerGroupsOptions{}, "server-leave-groups", "Leave multiple groups", func(s *mcclient.ClientSession,
opts *ServerGroupsOptions) error {
params, err := options.StructToParams(opts)
if err != nil {
return err
}
printObject(ret)
server, err := modules.Servers.PerformAction(s, opts.ID, "unbind-groups", params)
if err != nil {
return err
}
printObject(server)
return nil
})
+12
View File
@@ -242,6 +242,10 @@ paths:
$ref: "./instance/vnc.yaml"
/servers/{instanceId}/io-throttle:
$ref: "./instance/io-throttle.yaml"
/servers/{instanceId}/bind-groups:
$ref: "./instance/bind-groups.yaml"
/servers/{instanceId}/unbind-groups:
$ref: "./instance/unbind-groups.yaml"
/images:
$ref: "./image/images.yaml"
@@ -255,6 +259,14 @@ paths:
/guestimages/{imageId}:
$ref: "./image/guest-image.yaml"
/instancegroups:
$ref: "./instancegroup/instancegroups.yaml"
/instancegroups/{instancegroupId}:
$ref: "./instancegroup/instancegroup.yaml"
/instancegroups/{instancegroupId}/bind-guests:
$ref: "./instancegroup/bind-guest.yaml"
/instancegroups/{instancegroupId}/unbind-guests:
$ref: "./instancegroup/unbind-guest.yaml"
/natgateways:
$ref: "./natgateway/natgateways.yaml"
+16
View File
@@ -0,0 +1,16 @@
post:
summary: 批量绑定主机组
parameters:
- $ref: '../parameters/instance.yaml#/instanceId'
- in: body
name: groups
required: true
schema:
$ref: '../schemas/instance.yaml#/InstanceBindGroups'
responses:
200:
description: 实例信息
schema:
$ref: '../schemas/instance.yaml#/InstanceResponse'
tags:
- instance
+1 -1
View File
@@ -37,4 +37,4 @@ delete:
schema:
$ref: '../schemas/instance.yaml#/InstanceResponse'
tags:
- instance
- instance
+1
View File
@@ -24,6 +24,7 @@ get:
- $ref: '../parameters/instance.yaml#/secgroup'
- $ref: '../parameters/instance.yaml#/disk'
- $ref: '../parameters/instance.yaml#/usable_server_for_eip'
- $ref: '../parameters/instance.yaml#/group'
responses:
200:
description: 实例列表信息
+16
View File
@@ -0,0 +1,16 @@
post:
summary: 批量解绑主机组
parameters:
- $ref: '../parameters/instance.yaml#/instanceId'
- in: body
name: groups
required: true
schema:
$ref: '../schemas/instance.yaml#/InstanceBindGroups'
responses:
200:
description: 实例信息
schema:
$ref: '../schemas/instance.yaml#/InstanceResponse'
tags:
- instance
+16
View File
@@ -0,0 +1,16 @@
post:
summary: 批量绑定主机
parameters:
- $ref: '../parameters/instancegroup.yaml#/instancegroupId'
- in: body
name: guests
required: true
schema:
$ref: '../schemas/instancegroup.yaml#/InstanceGroupBindGuests'
responses:
200:
description: 主机组
schema:
$ref: '../schemas/instancegroup.yaml#/InstanceGroupResponse'
tags:
- instancegroup
+22
View File
@@ -0,0 +1,22 @@
get:
summary: 获取指定实例组的信息
parameters:
- $ref: "../parameters/instancegroup.yaml#/instancegroupId"
responses:
200:
description: 实例组的信息
schema:
$ref: "../schemas/instancegroup.yaml#/InstanceGroupResponse"
tags:
- instancegroup
delete:
summary: 删除指定的实例组
parameters:
- $ref: '../parameters/instancegroup.yaml#/instancegroupId'
responses:
200:
schema:
$ref: '../schemas/instancegroup.yaml#/InstanceGroupResponse'
tags:
- instancegroup
+31
View File
@@ -0,0 +1,31 @@
get:
summary: 按指定条件列出主机组
parameters:
- $ref: "../parameters/common.yaml#/offset"
- $ref: "../parameters/common.yaml#/limit"
- $ref: "../parameters/instancegroup.yaml#/granularity"
- $ref: "../parameters/instancegroup.yaml#/guest"
responses:
200:
description: 实例组列表信息
schema:
$ref: "../schemas/instancegroup.yaml#/InstanceGroupListResponse"
tags:
- instancegroup
post:
summary: 创建主机组
parameters:
- in: body
name: instancegroup
required: true
schema:
$ref: '../schemas/instancegroup.yaml#/InstanceGroupCreate'
responses:
200:
description: 新建主机组的信息
schema:
$ref: '../schemas/instancegroup.yaml#/InstanceGroupResponse'
tags:
- instancegroup
+16
View File
@@ -0,0 +1,16 @@
post:
summary: 批量解绑主机
parameters:
- $ref: '../parameters/instancegroup.yaml#/instancegroupId'
- in: body
name: guests
required: true
schema:
$ref: '../schemas/instancegroup.yaml#/InstanceGroupBindGuests'
responses:
200:
description: 主机组
schema:
$ref: '../schemas/instancegroup.yaml#/InstanceGroupResponse'
tags:
- instancegroup
+6 -1
View File
@@ -64,4 +64,9 @@ usable_server_for_eip:
name: usable_server_for_eip
in: query
type: string
description: 过滤此EIP可绑定的server列表(实例和弹性IP属于同一个账号同一个区域,若是私有云则弹性IP和实例需要在同一个可用区)
description: 过滤此EIP可绑定的server列表(实例和弹性IP属于同一个账号同一个区域,若是私有云则弹性IP和实例需要在同一个可用区)
group:
name: group
in: query
type: string
description: 主机组的ID或者Name
+13
View File
@@ -0,0 +1,13 @@
instancegroupId:
name: instancegroupId
required: true
in: path
description: 实例组的ID
granularity:
name: granularity
in: query
description: 粒度
guest:
name: guest
in: query
description: 主机的ID或者Name
+21
View File
@@ -660,6 +660,12 @@ InstanceCreate:
guest_image_id:
type: string
example: 如果使用主机镜像创建主机,需要额外指定主机镜像的 ID,来检验 disks 的 Image ID 是否正确
groups:
type: array
description: 主机组列表
items:
type: string
description: 主机组的ID或者Name
InstanceDisk:
@@ -1112,3 +1118,18 @@ InstanceVncResponse:
server:
type: object
$ref: '#/InstanceVnc'
InstanceBindGroups:
type: object
required:
- group.0
- group.1
properties:
group.0:
type: string
example: vv100455-79ef-43a3-8fc3-23adc74b8b87
description: 主机组的ID或者Name
group.1:
type: string
example: dd100399-79ef-43a3-8fc3-23adc74b8b87
description: 主机组的ID或者Name
+149
View File
@@ -0,0 +1,149 @@
CanDelete:
type: string
example: true
description: 可否删除
CanUpdate:
type: string
example: false
description: 可否更新
CreatedAt:
type: string
example: 2019-05-30T08:46:12.000000Z
description: 资源创建时间
UpdatedAt:
type: string
example: 2019-05-30T08:46:12.000000Z
description: 资源最近一次更新时间
InstanceGroup:
type: object
description: 实例组
properties:
id:
type: string
example: ea27c0ce-5870-49f3-8d57-f53e63f40361
description: 实例组ID
readOnly: true
name:
type: string
description: 实例名称
example: mysql-group
granularity:
type: integer
description: 粒度
example: 1
force_dispersion:
type: boolean
description: 是否强制
example: false
can_delete:
$ref: '#/CanDelete'
can_update:
$ref: '#/CanUpdate'
created_at:
$ref: '#/CreatedAt'
updated_at:
$ref: '#/UpdatedAt'
is_emulated:
type: boolean
example: false
description: 是否是虚拟出来的实例
status:
type: string
example: init
description: 状态
update_version:
type: integer
example: 2
description: 资源被更新的次数
InstanceGroupCreate:
type: object
properties:
instancegroup:
type: object
required:
- name
- granularity
- force_dispersion
properties:
name:
type: string
example: mysql-group
description: 实例组的名称
granularity:
type: integer
description: 粒度
example: 1
force_dispersion:
type: boolean
description: 是否强制
example: false
InstanceGroupListResponse:
type: object
properties:
limit:
type: integer
example: 20
total:
type: integer
example: 12
instancegroups:
type: array
items:
$ref: '#/InstanceGroup'
InstanceGroupResponse:
type: object
properties:
instancegroup:
type: object
$ref: '#/InstanceGroup'
InstanceGroupGuest:
type: object
properties:
guest_id:
type: string
example: f7749379-34b1-4219-8835-257615cf34f6
description: 主机ID
group_id:
type: string
example: f7749379-34b1-4219-8835-257615cf34f6
description: 实例组ID
InstanceGroupGuestResponse:
type: object
properties:
instancegroupGuest:
type: object
$ref: '#/InstanceGroupGuest'
InstanceGroupGuestListResponse:
type: object
properties:
total:
type: integer
example: 20
instancegroupGuests:
type: array
items:
$ref: '#/InstanceGroupGuest'
InstanceGroupBindGuests:
type: object
required:
- guest.0
- guest.1
properties:
guest.0:
type: string
example: vv100455-79ef-43a3-8fc3-23adc74b8b87
description: 主机的ID或者Name
guest.1:
type: string
example: dd100399-79ef-43a3-8fc3-23adc74b8b87
description: 主机的ID或者Name
+10
View File
@@ -18,6 +18,7 @@ import (
"context"
"yunion.io/x/jsonutils"
"yunion.io/x/sqlchemy"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/mcclient"
@@ -86,6 +87,10 @@ func (self *SGroupguest) Detach(ctx context.Context, userCred mcclient.TokenCred
func (self *SGroupguestManager) FetchByGuestId(guestId string) ([]SGroupguest, error) {
q := self.Query().Equals("guest_id", guestId)
return self.fetchByQuery(q)
}
func (self *SGroupguestManager) fetchByQuery(q *sqlchemy.SQuery) ([]SGroupguest, error) {
joints := make([]SGroupguest, 0, 1)
err := db.FetchModelObjects(self, q, &joints)
if err != nil {
@@ -94,6 +99,11 @@ func (self *SGroupguestManager) FetchByGuestId(guestId string) ([]SGroupguest, e
return joints, err
}
func (self *SGroupguestManager) FetchByGroupId(groupId string) ([]SGroupguest, error) {
q := self.Query().Equals("group_id", groupId)
return self.fetchByQuery(q)
}
func (self *SGroupguestManager) Attach(ctx context.Context, groupId, guestId string) (*SGroupguest, error) {
joint := &SGroupguest{}
+161 -1
View File
@@ -15,9 +15,19 @@
package models
import (
"context"
"database/sql"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/tristate"
"yunion.io/x/pkg/util/sets"
"yunion.io/x/sqlchemy"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/logclient"
)
const (
@@ -50,15 +60,69 @@ type SGroup struct {
ServiceType string `width:"36" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"` // Column(VARCHAR(36, charset='ascii'), nullable=True)
ParentId string `width:"36" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"` // Column(VARCHAR(36, charset='ascii'), nullable=True)
ZoneId string `width:"36" charset:"ascii" nullable:"true" list:"user" update:"user" create:"required"` // Column(VARCHAR(36, charset='ascii'), nullable=True)
ZoneId string `width:"36" charset:"ascii" nullable:"true" list:"user" update:"user" create:"optional"` // Column(VARCHAR(36, charset='ascii'), nullable=True)
SchedStrategy string `width:"16" charset:"ascii" nullable:"true" default:"" list:"user" update:"user" create:"optional"` // Column(VARCHAR(16, charset='ascii'), nullable=True, default='')
// the upper limit number of guests with this group in a host
Granularity int `nullable:"false" list:"user" get:"user" create:"optional" update:"user" default:"1"`
ForceDispersion tristate.TriState `list:"user" get:"user" create:"optional" update:"user" default:"true"`
Enabled tristate.TriState `nullable:"false" default:"true" create:"optional" list:"user" update:"user"`
}
func (sm *SGroupManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential,
query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
guestFilter := jsonutils.GetAnyString(query, []string{"guest", "guest_id"})
if len(guestFilter) != 0 {
guestObj, err := GuestManager.FetchByIdOrName(userCred, guestFilter)
if err != nil {
return nil, err
}
ggSub := GroupguestManager.Query("group_id").Equals("guest_id", guestObj.GetId()).SubQuery()
q = q.Join(ggSub, sqlchemy.Equals(ggSub.Field("group_id"), q.Field("id")))
}
return q, nil
}
func (sp *SGroup) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject) *jsonutils.JSONDict {
extra := sp.SVirtualResourceBase.GetCustomizeColumns(ctx, userCred, query)
ret, _ := sp.getMoreDetails(ctx, userCred, extra)
return ret
}
func (sp *SGroup) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
extra, err := sp.SVirtualResourceBase.GetExtraDetails(ctx, userCred, query)
if err != nil {
return nil, err
}
return sp.getMoreDetails(ctx, userCred, extra)
}
func (sp *SGroup) getMoreDetails(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject) (*jsonutils.JSONDict, error) {
ret := query.(*jsonutils.JSONDict)
ret.Add(jsonutils.JSONTrue, "enabled")
q := GroupguestManager.Query().Equals("group_id", sp.Id)
count, _ := q.CountWithError()
ret.Add(jsonutils.NewInt(int64(count)), "guest_count")
return ret, nil
}
func (s *SGroup) ValidateDeleteCondition(ctx context.Context) error {
q := GroupguestManager.Query().Equals("group_id", s.Id)
count, err := q.CountWithError()
if err != nil {
return errors.Wrapf(err, "fail to check that if there are any guest in this group %s", s.Name)
}
if count > 0 {
return httperrors.NewUnsupportOperationError("请在解绑所有主机后重试")
}
return nil
}
func (group *SGroup) GetNetworks() ([]SGroupnetwork, error) {
q := GroupnetworkManager.Query().Equals("group_id", group.Id)
groupnets := make([]SGroupnetwork, 0)
err := db.FetchModelObjects(GroupnetworkManager, q, &groupnets)
@@ -67,3 +131,99 @@ func (group *SGroup) GetNetworks() ([]SGroupnetwork, error) {
}
return groupnets, nil
}
func (group *SGroup) AllowPerformBindGuests(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool {
return group.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, group, "bind-guests") && group.Enabled.IsTrue()
}
func (group *SGroup) PerformBindGuests(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
guestIdSet, err := group.checkGuests(ctx, userCred, query, data)
if err != nil {
return nil, err
}
groupGuests, err := GroupguestManager.FetchByGroupId(group.Id)
if err != nil {
logclient.AddActionLogWithContext(ctx, group, logclient.ACT_VM_ASSOCIATE, nil, userCred, false)
return nil, err
}
for i := range groupGuests {
guestId := groupGuests[i].GuestId
if guestIdSet.Has(guestId) {
guestIdSet.Delete(guestId)
}
}
for _, guestId := range guestIdSet.UnsortedList() {
_, err := GroupguestManager.Attach(ctx, group.Id, guestId)
if err != nil {
logclient.AddActionLogWithContext(ctx, group, logclient.ACT_VM_ASSOCIATE, nil, userCred, false)
return nil, errors.Wrapf(err, "fail to attch guest %s to group %s", guestId, group.Id)
}
}
logclient.AddActionLogWithContext(ctx, group, logclient.ACT_VM_ASSOCIATE, nil, userCred, true)
return nil, nil
}
func (group *SGroup) AllowPerformUnbindGuests(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject) bool {
return group.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, group, "unbind-guests") && group.Enabled.IsTrue()
}
func (group *SGroup) PerformUnbindGuests(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
guestIdSet, err := group.checkGuests(ctx, userCred, query, data)
if err != nil {
return nil, err
}
groupGuests, err := GroupguestManager.FetchByGroupId(group.Id)
if err != nil {
logclient.AddActionLogWithContext(ctx, group, logclient.ACT_VM_DISSOCIATE, nil, userCred, false)
return nil, err
}
for i := range groupGuests {
joint := groupGuests[i]
if !guestIdSet.Has(joint.GuestId) {
continue
}
err := joint.Detach(ctx, userCred)
if err != nil {
logclient.AddActionLogWithContext(ctx, group, logclient.ACT_VM_DISSOCIATE, nil, userCred, false)
return nil, errors.Wrapf(err, "fail to detach guest %s to group %s", joint.GuestId, group.Id)
}
}
logclient.AddActionLogWithContext(ctx, group, logclient.ACT_VM_DISSOCIATE, nil, userCred, true)
return nil, nil
}
func (group *SGroup) checkGuests(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, data jsonutils.JSONObject) (sets.String, error) {
guestIdArr := jsonutils.GetArrayOfPrefix(data, "guest")
if len(guestIdArr) == 0 {
return nil, httperrors.NewMissingParameterError("guest.0 guest.1 ... ")
}
guestIdSet := sets.NewString()
for i := range guestIdArr {
guestIdStr, _ := guestIdArr[i].GetString()
guest, err := GuestManager.FetchByIdOrName(userCred, guestIdStr)
if err == sql.ErrNoRows {
return nil, httperrors.NewInputParameterError("no such guest %s", guestIdStr)
}
if err != nil {
return nil, errors.Wrapf(err, "fail to fetch guest by id or name %s", guestIdStr)
}
guestIdSet.Insert(guest.GetId())
}
return guestIdSet, nil
}
+97
View File
@@ -30,6 +30,7 @@ import (
"yunion.io/x/pkg/tristate"
"yunion.io/x/pkg/util/fileutils"
"yunion.io/x/pkg/util/regutils"
"yunion.io/x/pkg/util/sets"
"yunion.io/x/pkg/utils"
"yunion.io/x/sqlchemy"
@@ -4155,3 +4156,99 @@ func (guest *SGuest) StartDeleteGuestSnapshots(ctx context.Context, userCred mcc
task.ScheduleRun(nil)
return nil
}
func (self *SGuest) AllowPerformBindGroups(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject) bool {
return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "bind-groups")
}
func (self *SGuest) PerformBindGroups(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
groupIdSet, err := self.checkGroups(ctx, userCred, query, data)
if err != nil {
return nil, err
}
groupGuests, err := GroupguestManager.FetchByGuestId(self.Id)
if err != nil {
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_INSTANCE_GROUP_BIND, nil, userCred, false)
return nil, err
}
for i := range groupGuests {
groupId := groupGuests[i].GroupId
if groupIdSet.Has(groupId) {
groupIdSet.Delete(groupId)
}
}
for _, groupId := range groupIdSet.UnsortedList() {
_, err := GroupguestManager.Attach(ctx, groupId, self.Id)
if err != nil {
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_INSTANCE_GROUP_BIND, nil, userCred, false)
return nil, errors.Wrapf(err, "fail to attch group %s to guest %s", groupId, self.Id)
}
}
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_INSTANCE_GROUP_BIND, nil, userCred, true)
return nil, nil
}
func (self *SGuest) AllowPerformUnbindGroups(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject) bool {
return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "unbind-groups")
}
func (self *SGuest) PerformUnbindGroups(ctx context.Context, userCred mcclient.TokenCredential,
query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
groupIdSet, err := self.checkGroups(ctx, userCred, query, data)
if err != nil {
return nil, err
}
groupGuests, err := GroupguestManager.FetchByGuestId(self.Id)
if err != nil {
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_INSTANCE_GROUP_UNBIND, nil, userCred, false)
return nil, err
}
for i := range groupGuests {
joint := groupGuests[i]
if !groupIdSet.Has(joint.GroupId) {
continue
}
err := joint.Detach(ctx, userCred)
if err != nil {
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_INSTANCE_GROUP_UNBIND, nil, userCred, false)
return nil, errors.Wrapf(err, "fail to detach group %s to guest %s", joint.GroupId, self.Id)
}
}
logclient.AddActionLogWithContext(ctx, self, logclient.ACT_INSTANCE_GROUP_UNBIND, nil, userCred, true)
return nil, nil
}
func (self *SGuest) checkGroups(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject,
data jsonutils.JSONObject) (sets.String, error) {
groupIdArr := jsonutils.GetArrayOfPrefix(data, "group")
if len(groupIdArr) == 0 {
return nil, httperrors.NewMissingParameterError("group.0 group.1 ... ")
}
groupIdSet := sets.NewString()
for i := range groupIdArr {
groupIdStr, _ := groupIdArr[i].GetString()
group, err := GroupManager.FetchByIdOrName(userCred, groupIdStr)
if err == sql.ErrNoRows {
return nil, httperrors.NewInputParameterError("no such group %s", groupIdStr)
}
if err != nil {
return nil, errors.Wrapf(err, "fail to fetch group by id or name %s", groupIdStr)
}
groupIdSet.Insert(group.GetId())
}
return groupIdSet, nil
}
+21
View File
@@ -406,6 +406,17 @@ func (manager *SGuestManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQ
q = q.Filter(cond(q.Field("id"), sgq))
}
groupFilter := jsonutils.GetAnyString(queryDict, []string{"group", "group_id"})
if len(groupFilter) != 0 {
groupObj, err := GroupManager.FetchByIdOrName(userCred, groupFilter)
if err != nil {
return nil, httperrors.NewNotFoundError("group %s not found", groupFilter)
}
queryDict.Add(jsonutils.NewString(groupObj.GetId()), "group")
ggSub := GroupguestManager.Query("guest_id").Equals("group_id", groupObj.GetId()).SubQuery()
q = q.Join(ggSub, sqlchemy.Equals(ggSub.Field("guest_id"), q.Field("id")))
}
orderByDisk, _ := queryDict.GetString("order_by_disk")
if orderByDisk == "asc" || orderByDisk == "desc" {
guestdisks := GuestdiskManager.Query().SubQuery()
@@ -1477,6 +1488,16 @@ func (self *SGuest) getExtBandwidth() int {
func (self *SGuest) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict {
extra := self.SVirtualResourceBase.GetCustomizeColumns(ctx, userCred, query)
fields := stringutils2.NewSortedStrings(jsonutils.GetQueryStringArray(query, "field"))
if query.Contains("group") {
groupId, _ := query.GetString("group")
q := GroupguestManager.Query().Equals("group_id", groupId).Equals("guest_id", self.Id)
var groupGuest SGroupguest
err := q.First(&groupGuest)
if err == nil {
extra.Add(jsonutils.NewTimeString(groupGuest.CreatedAt), "attach_time")
}
}
return self.moreExtraInfo(extra, fields)
}
+7 -3
View File
@@ -4375,7 +4375,7 @@ func (host *SHost) IsMaintaining() bool {
return utils.IsInStringArray(host.Status, []string{api.HOST_START_MAINTAIN, api.HOST_MAINTAINING, api.HOST_MAINTAIN_FAILE})
}
// InstanceGroups returns the group of guest in host and their frequency of occurrence
// InstanceGroups returns the enabled group of guest in host and their frequency of occurrence
func (host *SHost) InstanceGroups() ([]SGroup, map[string]int, error) {
q := GuestManager.Query("id")
guestQ := q.Filter(sqlchemy.OR(sqlchemy.Equals(q.Field("host_id"), host.Id),
@@ -4401,12 +4401,16 @@ func (host *SHost) InstanceGroups() ([]SGroup, map[string]int, error) {
return []SGroup{}, make(map[string]int), nil
}
groups := make([]SGroup, 0, len(groupIds))
q = GroupManager.Query().In("id", groupIds)
q = GroupManager.Query().In("id", groupIds).IsTrue("enabled")
err = db.FetchModelObjects(GroupManager, q, &groups)
if err != nil {
return nil, nil, err
}
return groups, groupSet, nil
retSet := make(map[string]int)
for i := range groups {
retSet[groups[i].GetId()] = groupSet[groups[i].GetId()]
}
return groups, retSet, nil
}
func (host *SHost) setIpmiIp(userCred mcclient.TokenCredential, ipAddr string) error {
-1
View File
@@ -153,7 +153,6 @@ func InitHandlers(app *appsrv.Application) {
models.LoadbalancernetworkManager,
models.GuestdiskManager,
models.GroupnetworkManager,
models.GroupguestManager,
models.StoragecachedimageManager,
models.CloudproviderRegionManager,
models.DBInstanceNetworkManager,
+2 -1
View File
@@ -167,6 +167,8 @@ func (self *GuestDeleteTask) doClearSecurityGroupComplete(ctx context.Context, g
func (self *GuestDeleteTask) OnSyncConfigComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
guest := obj.(*models.SGuest)
// try to leave all groups
guest.LeaveAllGroups(ctx, self.UserCred)
isPurge := jsonutils.QueryBoolean(self.Params, "purge", false)
overridePendingDelete := jsonutils.QueryBoolean(self.Params, "override_pending_delete", false)
@@ -276,7 +278,6 @@ func (self *GuestDeleteTask) OnGuestDeleteCompleteFailed(ctx context.Context, ob
func (self *GuestDeleteTask) OnGuestDeleteComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
guest := obj.(*models.SGuest)
guest.LeaveAllGroups(ctx, self.UserCred)
guest.DetachAllNetworks(ctx, self.UserCred)
guest.EjectIso(self.UserCred)
guest.DeleteEip(ctx, self.UserCred)
-31
View File
@@ -1,31 +0,0 @@
// 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 modules
import "yunion.io/x/onecloud/pkg/mcclient/modulebase"
var (
GroupGuest modulebase.JointResourceManager
)
func init() {
GroupGuest = NewJointComputeManager(
"groupguest",
"groupguests",
[]string{"Guest_ID", "Group_ID", "Tag"},
[]string{},
&Servers,
&InstanceGroup)
}
+1
View File
@@ -49,6 +49,7 @@ type ServerListOptions struct {
Vpc string `help:"Vpc id or name"`
UsableServerForEip string `help:"Eip id or name"`
WithoutUserMeta *bool `help:"Show Servers without user metadata"`
Group string `help:"Instance Group ID or Name"`
ResourceType string `help:"Resource type" choices:"shared|prepaid|dedicated"`
@@ -64,7 +64,7 @@ func (p *SForcedGroupPredicate) Execute(u *core.Unit, c core.Candidater) (bool,
schedDate := u.SchedData()
instanceGroups := c.Getter().InstanceGroups()
minFree := math.MaxInt16
minFree := math.MaxInt32
for _, id := range schedDate.InstanceGroupIds {
detail := schedDate.InstanceGroupsDetail[id]
// SForcedGroupPredicate only deal with group whose ForceDispersion is ture
@@ -76,7 +76,8 @@ func (p *SForcedGroupPredicate) Execute(u *core.Unit, c core.Candidater) (bool,
free, _ = c.Getter().GetFreeGroupCount(id)
if free < 1 {
h.AppendPredicateFailMsg(fmt.Sprintf(
"the number of guests with same group %s in this host has reached the upper limit", id))
"the number of guests with same instance group '%s' in this host has reached the upper limit",
instanceGroups[id].GetName()))
break
}
} else {
@@ -86,6 +87,10 @@ func (p *SForcedGroupPredicate) Execute(u *core.Unit, c core.Candidater) (bool,
minFree = free
}
}
// show that minFree shoule be zero
if minFree == math.MaxInt32 {
minFree = 0
}
// chose the min capacity of groups
h.SetCapacity(int64(minFree))
return h.GetResult()
+3
View File
@@ -173,4 +173,7 @@ const (
ACT_SUBIMAGE_UPDATE = "更新子镜像"
ACT_PREPARE = "同步硬件配置"
ACT_INSTANCE_GROUP_BIND = "绑定主机组"
ACT_INSTANCE_GROUP_UNBIND = "解绑主机组"
)