mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-08-29 03:51:54 +08:00
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:
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
})
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -37,4 +37,4 @@ delete:
|
||||
schema:
|
||||
$ref: '../schemas/instance.yaml#/InstanceResponse'
|
||||
tags:
|
||||
- instance
|
||||
- instance
|
||||
|
||||
@@ -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: 实例列表信息
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -153,7 +153,6 @@ func InitHandlers(app *appsrv.Application) {
|
||||
models.LoadbalancernetworkManager,
|
||||
models.GuestdiskManager,
|
||||
models.GroupnetworkManager,
|
||||
models.GroupguestManager,
|
||||
models.StoragecachedimageManager,
|
||||
models.CloudproviderRegionManager,
|
||||
models.DBInstanceNetworkManager,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -173,4 +173,7 @@ const (
|
||||
ACT_SUBIMAGE_UPDATE = "更新子镜像"
|
||||
|
||||
ACT_PREPARE = "同步硬件配置"
|
||||
|
||||
ACT_INSTANCE_GROUP_BIND = "绑定主机组"
|
||||
ACT_INSTANCE_GROUP_UNBIND = "解绑主机组"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user