mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/yunionio/cloudpods.git
synced 2026-09-20 08:03:53 +08:00
Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eed954c67f | ||
|
|
353a212672 | ||
|
|
a23fdd9c3a | ||
|
|
4f30a56981 | ||
|
|
67f228c520 | ||
|
|
61a166b873 | ||
|
|
821b6e31f5 | ||
|
|
ffd9b638bd | ||
|
|
b02f9b8dee | ||
|
|
afa9b2bd4b | ||
|
|
2a1b5eda54 | ||
|
|
498363de17 | ||
|
|
60c987846f | ||
|
|
f03fc0f434 | ||
|
|
1fe13965a4 | ||
|
|
5d53daefff | ||
|
|
367462b1d1 | ||
|
|
dc22730323 | ||
|
|
94442d23d7 | ||
|
|
c736464391 | ||
|
|
1fc55a1e43 | ||
|
|
dbf0be4364 | ||
|
|
436d467793 | ||
|
|
02b47c7cb0 | ||
|
|
862120661e | ||
|
|
58a4d2b9bc | ||
|
|
462d944947 | ||
|
|
c2766a24f3 | ||
|
|
74bc8d20cf | ||
|
|
a7e11e26e5 | ||
|
|
202170cf1d | ||
|
|
73a9d2cfa0 | ||
|
|
1321bb5675 | ||
|
|
17eaed67ea | ||
|
|
6ffaacb26c | ||
|
|
ae3deea643 | ||
|
|
51d22691f2 | ||
|
|
9876b0b26a | ||
|
|
6b0c3a2cd5 | ||
|
|
cbb791cc0b | ||
|
|
c0cff6e33c | ||
|
|
a195d99ade | ||
|
|
d36bda5226 | ||
|
|
2d76337894 | ||
|
|
5df0a085a8 | ||
|
|
15da787731 | ||
|
|
4bfbba43d1 | ||
|
|
e5fd6dafa7 | ||
|
|
fe04233e8f | ||
|
|
5c050d70b1 | ||
|
|
90b3e1c1bd | ||
|
|
ac3ed49d9a | ||
|
|
5b718bfb63 | ||
|
|
51dc7ada1c | ||
|
|
1e476f9d48 | ||
|
|
39d8acd535 | ||
|
|
a02d717ee9 | ||
|
|
074fd501bb | ||
|
|
c7818df1b5 |
9
Makefile
9
Makefile
@@ -47,11 +47,6 @@ PKGS := go list ./...
|
||||
CGO_CFLAGS_ENV = $(shell go env CGO_CFLAGS)
|
||||
CGO_LDFLAGS_ENV = $(shell go env CGO_LDFLAGS)
|
||||
|
||||
ifdef LIBQEMUIO_PATH
|
||||
X_CGO_CFLAGS := ${CGO_CFLAGS_ENV} -I${LIBQEMUIO_PATH}/src -I${LIBQEMUIO_PATH}/src/include
|
||||
X_CGO_LDFLAGS := ${CGO_LDFLAGS_ENV} -laio -lqemuio -lpthread -lgnutls -lnettle -L ${LIBQEMUIO_PATH}/src
|
||||
endif
|
||||
|
||||
export GOOS ?= linux
|
||||
export GO111MODULE:=on
|
||||
export CGO_CFLAGS = ${X_CGO_CFLAGS}
|
||||
@@ -63,7 +58,7 @@ ifeq ($(UNAME), Linux)
|
||||
XARGS_FLAGS = --no-run-if-empty
|
||||
endif
|
||||
|
||||
cmdTargets:=$(filter-out cmd/host-image,$(wildcard cmd/*))
|
||||
cmdTargets:=$(wildcard cmd/*)
|
||||
rpmTargets:=$(foreach b,$(patsubst cmd/%,%,$(cmdTargets)),$(if $(shell [ -f "$(CURDIR)/build/$(b)/vars" ] && echo 1),rpm/$(b)))
|
||||
debTargets:=$(foreach b,$(patsubst cmd/%,%,$(cmdTargets)),$(if $(shell [ -f "$(CURDIR)/build/$(b)/vars" ] && echo 1),deb/$(b)))
|
||||
|
||||
@@ -282,7 +277,7 @@ dep:
|
||||
@echo "$$depDeprecated"
|
||||
@$(MAKE) mod
|
||||
|
||||
RELEASE_BRANCH:=release/3.11
|
||||
RELEASE_BRANCH:=release/3.11.10
|
||||
GOPROXY ?= direct
|
||||
|
||||
mod:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/host-base:v0.5.0
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/host-base:v0.7.0
|
||||
|
||||
MAINTAINER "Yaoqi Wan wanyaoqi@yunionyun.com"
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/onecloud-base:v0.3.5-1
|
||||
|
||||
RUN mkdir -p /opt/yunion/bin
|
||||
|
||||
ENV TZ UTC
|
||||
|
||||
ADD ./_output/bin/host-image /opt/yunion/bin/host-image
|
||||
ADD ./_output/bin/.host-image.bin /opt/yunion/bin/.host-image.bin
|
||||
ADD ./_output/bin/bundles/host-image /opt/yunion/bin/bundles/host-image
|
||||
RUN mkdir -p /opt/yunion/bin
|
||||
ADD ./_output/alpine-build/bin/host-image /opt/yunion/bin/host-image
|
||||
|
||||
@@ -7,11 +7,11 @@ COPY . /root/go/src/yunion.io/x/onecloud
|
||||
WORKDIR /root/go/src/yunion.io/x/onecloud
|
||||
RUN make cmd/host
|
||||
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/host-base:v0.4.0
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/host-base:v0.7.0
|
||||
|
||||
MAINTAINER "Yaoqi Wan wanyaoqi@yunionyun.com"
|
||||
|
||||
ENV TZ Asia/Shanghai
|
||||
ENV TZ UTC
|
||||
|
||||
RUN mkdir -p /opt/yunion/bin
|
||||
COPY --from=build /root/go/src/yunion.io/x/onecloud/_output/bin/host /opt/yunion/bin/host
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
border-bottom: 1px solid #d7d7d7;
|
||||
}
|
||||
</style>
|
||||
{{$match_tags_str := .match_tags_str}}
|
||||
<body>
|
||||
<h3 class="title">报警提醒</h3>
|
||||
<table border="0" cellspacing="0" cellpadding="0" class="table">
|
||||
@@ -110,6 +111,9 @@
|
||||
<td>平台</td>
|
||||
<td>指标</td>
|
||||
<td>触发值</td>
|
||||
{{if gt (len $match_tags_str) 1}}
|
||||
<td>标签</td>
|
||||
{{end}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -127,6 +131,9 @@
|
||||
</td>
|
||||
<td>{{ $Matche.metric }}</td>
|
||||
<td>{{ $Matche.value_str }}</td>
|
||||
{{if gt (len $match_tags_str) 1}}
|
||||
<td>{{ index $match_tags_str $i }}</td>
|
||||
{{end}}
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
border-bottom: 1px solid #d7d7d7;
|
||||
}
|
||||
</style>
|
||||
{{$match_tags_str := .match_tags_str}}
|
||||
<body>
|
||||
<h3 class="title">Alert</h3>
|
||||
<table border="0" cellspacing="0" cellpadding="0" class="table">
|
||||
@@ -110,6 +111,9 @@
|
||||
<td>Brand</td>
|
||||
<td>Metric</td>
|
||||
<td>Trigger value</td>
|
||||
{{if gt (len $match_tags_str) 1}}
|
||||
<td>Tags</td>
|
||||
{{end}}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -127,6 +131,9 @@
|
||||
</td>
|
||||
<td>{{ $Matche.metric }}</td>
|
||||
<td>{{ $Matche.value_str }}</td>
|
||||
{{if gt (len $match_tags_str) 1}}
|
||||
<td>{{ index $match_tags_str $i }}</td>
|
||||
{{end}}
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
|
||||
@@ -138,6 +138,7 @@ func init() {
|
||||
AutoSnapshot string `help:"enable/disable auto snapshot of disk" choices:"enable|disable"`
|
||||
DiskType string `help:"Disk type" choices:"data|volume|sys"`
|
||||
IsSsd *bool `help:"mark disk as ssd" negative:"no-is-ssd"`
|
||||
AutoReset *bool `help:"Enable auto reset disk after geust shutdown"`
|
||||
}
|
||||
R(&DiskUpdateOptions{}, "disk-update", "Update property of a virtual disk", func(s *mcclient.ClientSession, args *DiskUpdateOptions) error {
|
||||
params := jsonutils.NewDict()
|
||||
@@ -171,6 +172,10 @@ func init() {
|
||||
params.Add(jsonutils.JSONFalse, "is_ssd")
|
||||
}
|
||||
}
|
||||
if args.AutoReset != nil {
|
||||
params.Add(jsonutils.NewBool(*args.AutoReset), "auto_reset")
|
||||
}
|
||||
|
||||
if params.Size() == 0 {
|
||||
return InvalidUpdateError()
|
||||
}
|
||||
|
||||
@@ -26,4 +26,5 @@ func init() {
|
||||
cmd.Update(&compute.DeviceUpdateOptions{})
|
||||
cmd.Show(&compute.DeviceShowOptions{})
|
||||
cmd.BatchDelete(&compute.DeviceDeleteOptions{})
|
||||
cmd.BatchPerform("purge", &compute.DevicePurgeOptions{})
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ func init() {
|
||||
cmd.BatchPut(new(options.ServerUpdateOptions))
|
||||
cmd.GetMetadata(new(options.ServerIdOptions))
|
||||
cmd.Perform("clone", new(options.ServerCloneOptions))
|
||||
cmd.Perform("change-billing-type", new(options.ServerChangeBillingTypeOptions))
|
||||
cmd.BatchPerform("start", new(options.ServerStartOptions))
|
||||
cmd.BatchPerform("syncstatus", new(options.ServerIdsOptions))
|
||||
cmd.BatchPerform("sync", new(options.ServerIdsOptions))
|
||||
|
||||
@@ -211,7 +211,13 @@ func init() {
|
||||
}
|
||||
key := strings.TrimSpace(c[:pos])
|
||||
value := strings.TrimSpace(c[pos+1:])
|
||||
config.Add(jsonutils.NewString(value), "config", "default", key)
|
||||
var v jsonutils.JSONObject
|
||||
if value == "true" || value == "false" {
|
||||
v = jsonutils.NewBool(value == "true")
|
||||
} else {
|
||||
v = jsonutils.NewString(value)
|
||||
}
|
||||
config.Add(v, "config", "default", key)
|
||||
}
|
||||
nconf, err := modules.ServicesV3.PerformAction(s, args.SERVICE, "config", config)
|
||||
if err != nil {
|
||||
|
||||
@@ -47,7 +47,7 @@ type BaseActionListOptions struct {
|
||||
|
||||
type ActionListOptions struct {
|
||||
BaseActionListOptions
|
||||
Service []string `help:"service name`
|
||||
Service []string `help:"service name"`
|
||||
Id string `help:"" metavar:"OBJ_ID"`
|
||||
Type []string `help:"Type of relevant object" metavar:"OBJ_TYPE"`
|
||||
}
|
||||
|
||||
6
go.mod
6
go.mod
@@ -87,14 +87,14 @@ require (
|
||||
k8s.io/client-go v0.19.3
|
||||
k8s.io/cluster-bootstrap v0.19.3
|
||||
moul.io/http2curl/v2 v2.3.0
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20250113015525-b483c92621d7
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20250325104241-0718ac214f29
|
||||
yunion.io/x/executor v0.0.0-20241205080005-48f5b1212256
|
||||
yunion.io/x/jsonutils v1.0.1-0.20240930100528-1671a2d0d22f
|
||||
yunion.io/x/log v1.0.1-0.20240305175729-7cf2d6cd5a91
|
||||
yunion.io/x/ovsdb v0.0.0-20230306173834-f164f413a900
|
||||
yunion.io/x/pkg v1.10.4-0.20250123070256-9247ce856f07
|
||||
yunion.io/x/pkg v1.10.4-0.20250324235849-27051af0af08
|
||||
yunion.io/x/s3cli v0.0.0-20241221171442-1c11599d28e1
|
||||
yunion.io/x/sqlchemy v1.1.3-0.20240926163039-d41512b264e1
|
||||
yunion.io/x/sqlchemy v1.1.3-0.20250307104108-2f8e0af883e9
|
||||
yunion.io/x/structarg v0.0.0-20231017124457-df4d5009457c
|
||||
)
|
||||
|
||||
|
||||
12
go.sum
12
go.sum
@@ -1274,8 +1274,8 @@ sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20250113015525-b483c92621d7 h1:f3j4AHOANU1rDyHmXpPdmVklpL1NFODhcYXDXtt5gOE=
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20250113015525-b483c92621d7/go.mod h1:KQ/jWx7bZlmjCE711KEWuvHW/dzpdr/UTlBjjutkj0Y=
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20250325104241-0718ac214f29 h1:9cR9HujwH+N7hlQNwi+NYmXozYo5qLJgzchx9VymcTQ=
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20250325104241-0718ac214f29/go.mod h1:FXxAEbdNfWXX9gjME3K2nJhkydHY5EKEUZb+RLEzVwQ=
|
||||
yunion.io/x/executor v0.0.0-20241205080005-48f5b1212256 h1:kLKQ6zbgPDQflRwoHFAjxNChcbhXIFgsUVLkJwiXu/8=
|
||||
yunion.io/x/executor v0.0.0-20241205080005-48f5b1212256/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws=
|
||||
yunion.io/x/jsonutils v0.0.0-20190625054549-a964e1e8a051/go.mod h1:4N0/RVzsYL3kH3WE/H1BjUQdFiWu50JGCFQuuy+Z634=
|
||||
@@ -1289,11 +1289,11 @@ yunion.io/x/ovsdb v0.0.0-20230306173834-f164f413a900 h1:Hu/4ERvoWaN6aiFs4h4/yvVB
|
||||
yunion.io/x/ovsdb v0.0.0-20230306173834-f164f413a900/go.mod h1:0vLkNEhlmA64HViPBAnSTUMrx5QP1CLsxXmxDKQ80tc=
|
||||
yunion.io/x/pkg v0.0.0-20190620104149-945c25821dbf/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
|
||||
yunion.io/x/pkg v0.0.0-20190628082551-f4033ba2ea30/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
|
||||
yunion.io/x/pkg v1.10.4-0.20250123070256-9247ce856f07 h1:XZsL8+YpgVBHMcRwdpZ4cOfSZCc+KsgUCTdV3KeK8FI=
|
||||
yunion.io/x/pkg v1.10.4-0.20250123070256-9247ce856f07/go.mod h1:0Bwxqd9MA3ACi119/l02FprY/o9gHahmYC2bsSbnVpM=
|
||||
yunion.io/x/pkg v1.10.4-0.20250324235849-27051af0af08 h1:GBX/Bb72xWWp++tClxj7PjuhZnRhzgvmLIbD3RyEUZc=
|
||||
yunion.io/x/pkg v1.10.4-0.20250324235849-27051af0af08/go.mod h1:0Bwxqd9MA3ACi119/l02FprY/o9gHahmYC2bsSbnVpM=
|
||||
yunion.io/x/s3cli v0.0.0-20241221171442-1c11599d28e1 h1:1KJ3YYinydPHpDEQRXdr/T8SYcKZ5Er+m489H+PnaQ4=
|
||||
yunion.io/x/s3cli v0.0.0-20241221171442-1c11599d28e1/go.mod h1:0iFKpOs1y4lbCxeOmq3Xx/0AcQoewVPwj62eRluioEo=
|
||||
yunion.io/x/sqlchemy v1.1.3-0.20240926163039-d41512b264e1 h1:HWPqY1I5JSmM6Sks6FyK9hnq/MjL7FDghM6M8DXHob0=
|
||||
yunion.io/x/sqlchemy v1.1.3-0.20240926163039-d41512b264e1/go.mod h1:vCIZpqhZ5Jzaq3tFyrti/vv8BijQKtkzSgNT/uH4H5A=
|
||||
yunion.io/x/sqlchemy v1.1.3-0.20250307104108-2f8e0af883e9 h1:KU8BikBBUJlSkA1Ib01htqaDdzt20K+ezNkTZD8Rxjk=
|
||||
yunion.io/x/sqlchemy v1.1.3-0.20250307104108-2f8e0af883e9/go.mod h1:vCIZpqhZ5Jzaq3tFyrti/vv8BijQKtkzSgNT/uH4H5A=
|
||||
yunion.io/x/structarg v0.0.0-20231017124457-df4d5009457c h1:QuLab2kSRECZRxo4Lo2KcYn6XjQFDGaZ1+x0pYDVVwQ=
|
||||
yunion.io/x/structarg v0.0.0-20231017124457-df4d5009457c/go.mod h1:EP6NSv2C0zzqBDTKumv8hPWLb3XvgMZDHQRfyuOrQng=
|
||||
|
||||
@@ -58,7 +58,7 @@ func StartService() {
|
||||
// log.Infof("Modules: %s", jsonutils.Marshal(jmods).PrettyString())
|
||||
|
||||
if !options.Options.DisableReporting {
|
||||
cron := cronman.InitCronJobManager(true, 1)
|
||||
cron := cronman.InitCronJobManager(true, 1, opts.TimeZone)
|
||||
rand.Seed(time.Now().Unix())
|
||||
cron.AddJobEveryFewDays("AutoReport", 1, rand.Intn(23), rand.Intn(59), 0, report.Report, true)
|
||||
go cron.Start()
|
||||
|
||||
@@ -172,6 +172,10 @@ type DiskConfig struct {
|
||||
// requried: false
|
||||
Fs string `json:"fs"`
|
||||
|
||||
// 关机后自动重置磁盘
|
||||
// required: false
|
||||
AutoReset bool `json:"auto_reset"`
|
||||
|
||||
// 磁盘存储格式
|
||||
// enum: qcow2, raw, docker, iso, vmdk, vmdkflatver1, vmdkflatver2, vmdkflat, vmdksparse, vmdksparsever1, vmdksparsever2, vmdksepsparse vhd
|
||||
// requried: false
|
||||
|
||||
@@ -338,7 +338,7 @@ type CloudaccountDetail struct {
|
||||
SyncIntervalSeconds int `json:"sync_interval_seconds"`
|
||||
|
||||
// 同步状态
|
||||
SyncStatus2 string `json:"sync_stauts2"`
|
||||
SyncStatus2 string `json:"sync_status2"`
|
||||
|
||||
// 云账号环境类型
|
||||
// public: 公有云
|
||||
@@ -565,7 +565,7 @@ type SyncRangeInput struct {
|
||||
|
||||
// 按资源类型同步,可输入多个
|
||||
// enmu: project, compute, network, eip, loadbalancer, objectstore, rds, cache, event, cloudid, dnszone, public_ip, intervpcnetwork, saml_auth, quota, nat, nas, waf, mongodb, es, kafka, app, cdn, container, ipv6_gateway, tablestore, modelarts, vpcpeer, misc
|
||||
Resources []string `json:"resources" choices:"project|compute|network|eip|loadbalancer|objectstore|rds|cache|event|cloudid|dnszone|public_ip|intervpcnetwork|saml_auth|quota|nat|nas|waf|mongodb|es|kafka|app|cdn|container|ipv6_gateway|tablestore|modelarts|vpcpeer|misc"`
|
||||
Resources []string `json:"resources" choices:"project|compute|network|eip|loadbalancer|objectstore|rds|cache|event|cloudid|dnszone|public_ip|intervpcnetwork|saml_auth|quota|nat|nas|waf|mongodb|es|kafka|app|cdn|container|ipv6_gateway|tablestore|modelarts|vpcpeer|misc|image"`
|
||||
}
|
||||
|
||||
type iRes interface {
|
||||
|
||||
@@ -261,6 +261,8 @@ type DiskUpdateInput struct {
|
||||
|
||||
// 磁盘类型
|
||||
DiskType string `json:"disk_type"`
|
||||
// 关机自动重置
|
||||
AutoReset *bool `json:"auto_reset"`
|
||||
}
|
||||
|
||||
type DiskSaveInput struct {
|
||||
|
||||
@@ -178,6 +178,10 @@ const (
|
||||
VM_QGA_SET_NETWORK = "qga_set_network"
|
||||
VM_QGA_SET_NETWORK_FAILED = "qga_set_network_failed"
|
||||
|
||||
// 更改计费模式
|
||||
VM_CHANGE_BILLING_TYPE = "change_billing_type"
|
||||
VM_CHANGE_BILLING_TYPE_FAILED = "change_billing_type_failed"
|
||||
|
||||
SHUTDOWN_STOP = "stop"
|
||||
SHUTDOWN_TERMINATE = "terminate"
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ type GuestDiskDetails struct {
|
||||
// 磁盘类型
|
||||
// example: data
|
||||
DiskType string `json:"disk_type"`
|
||||
// 关机自动重置
|
||||
AutoReset bool `json:"auto_reset"`
|
||||
// 介质类型
|
||||
// example: ssd
|
||||
MediumType string `json:"medium_type"`
|
||||
@@ -91,6 +93,7 @@ type GuestdiskJsonDesc struct {
|
||||
Dev string `json:"dev"`
|
||||
IsSSD bool `json:"is_ssd"`
|
||||
NumQueues uint8 `json:"num_queues"`
|
||||
AutoReset bool `json:"auto_reset"`
|
||||
|
||||
// esxi
|
||||
ImageInfo struct {
|
||||
|
||||
@@ -359,6 +359,7 @@ type GuestDiskInfo struct {
|
||||
Driver string `json:"driver"`
|
||||
CacheMode string `json:"cache_mode"`
|
||||
AioMode string `json:"aio_mode"`
|
||||
AutoReset bool `json:"auto_reset"`
|
||||
MediumType string `json:"medium_type"`
|
||||
StorageType string `json:"storage_type"`
|
||||
Iops int `json:"iops"`
|
||||
@@ -624,6 +625,7 @@ type ServerStopInput struct {
|
||||
TimeoutSecs int `json:"timeout_secs"`
|
||||
|
||||
// 是否关机停止计费, 若平台不支持停止计费,此参数无作用
|
||||
// 若包年包月机器关机设置此参数,则先转换计费模式到按量计费,再关机不收费
|
||||
// 目前仅阿里云,腾讯云此参数生效
|
||||
StopCharging bool `json:"stop_charging"`
|
||||
}
|
||||
@@ -978,6 +980,13 @@ type ServerChangeDiskStorageInternalInput struct {
|
||||
CloneDiskCount int `json:"disk_count"`
|
||||
}
|
||||
|
||||
type ServerCopyDiskToStorageInput struct {
|
||||
KeepOriginDisk bool `json:"keep_origin_disk"`
|
||||
GuestRunning bool `json:"guest_running"`
|
||||
CompletedDiskCount int `json:"completed_disk_count"`
|
||||
CloneDiskCount int `json:"disk_count"`
|
||||
}
|
||||
|
||||
type ServerSetExtraOptionInput struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
@@ -1247,6 +1256,8 @@ type GuestPerformStartInput struct {
|
||||
// 指定启动虚拟机的Qemu版本,可选值:2.12.1, 4.2.0
|
||||
// 仅适用于KVM虚拟机
|
||||
QemuVersion string `json:"qemu_version"`
|
||||
// 按量机器自动转换为包年包月
|
||||
AutoPrepaid bool `json:"auto_prepaid"`
|
||||
}
|
||||
|
||||
type ServerSetOSInfoInput struct {
|
||||
@@ -1332,6 +1343,10 @@ func (conf ServerChangeConfigSettings) MemChanged() bool {
|
||||
return conf.VmemSize != conf.Old.VmemSize
|
||||
}
|
||||
|
||||
func (conf ServerChangeConfigSettings) InstanceTypeChanged() bool {
|
||||
return len(conf.InstanceType) > 0 && conf.InstanceType != conf.Old.InstanceType
|
||||
}
|
||||
|
||||
func (conf ServerChangeConfigSettings) AddedMem() int {
|
||||
addMem := conf.VmemSize - conf.Old.VmemSize
|
||||
if addMem < 0 {
|
||||
@@ -1350,3 +1365,10 @@ func (conf ServerChangeConfigSettings) AddedDisk() int {
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
type ServerChangeBillingTypeInput struct {
|
||||
// 仅在虚拟机开机或关机状态下调用
|
||||
// enmu: [postpaid, prepaid]
|
||||
// required: true
|
||||
BillingType string `json:"billing_type"`
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrMsgIsolatedDeviceUsedByServer = "Isolated device used by server"
|
||||
)
|
||||
|
||||
type IsolateDeviceDetails struct {
|
||||
apis.StandaloneResourceDetails
|
||||
HostResourceInfo
|
||||
|
||||
@@ -80,6 +80,9 @@ type StorageCreateInput struct {
|
||||
// enable ceph messenger v2
|
||||
EnableMessengerV2 *bool `json:"enable_messenger_v2"`
|
||||
|
||||
// rbd storage auto cache glance images
|
||||
AutoCacheImages *bool `json:"auto_cache_images"`
|
||||
|
||||
// swagger:ignore
|
||||
MonHost string
|
||||
|
||||
@@ -246,6 +249,8 @@ type StorageUpdateInput struct {
|
||||
|
||||
// enable ceph messenger v2
|
||||
EnableMessengerV2 *bool `json:"enable_messenger_v2"`
|
||||
// rbd storage auto cache glance images
|
||||
AutoCacheImages *bool `json:"auto_cache_images"`
|
||||
|
||||
RbdTimeoutInput
|
||||
|
||||
@@ -259,6 +264,18 @@ type StorageUpdateInput struct {
|
||||
MasterHost string
|
||||
}
|
||||
|
||||
type RbdStorageConf struct {
|
||||
RadosMonOpTimeout int `json:"rados_mon_op_timeout"`
|
||||
RadosOsdOpTimeout int `json:"rados_osd_op_timeout"`
|
||||
ClientMountTimeout int `json:"client_mount_timeout"`
|
||||
|
||||
MonHost string `json:"mon_host"`
|
||||
Pool string `json:"pool"`
|
||||
Key string `json:"key"`
|
||||
EnableMessengerV2 bool `json:"enable_messenger_v2"`
|
||||
AutoCacheImages bool `json:"auto_cache_images"`
|
||||
}
|
||||
|
||||
type StorageSetCmtBoundInput struct {
|
||||
Cmtbound *float32
|
||||
}
|
||||
|
||||
@@ -181,6 +181,7 @@ var (
|
||||
|
||||
SHARED_FILE_STORAGE = []string{STORAGE_NFS, STORAGE_GPFS}
|
||||
FIEL_STORAGE = []string{STORAGE_LOCAL, STORAGE_NFS, STORAGE_GPFS, STORAGE_LVM, STORAGE_CLVM, STORAGE_SLVM}
|
||||
LVM_STORAGE = []string{STORAGE_LVM, STORAGE_CLVM, STORAGE_SLVM}
|
||||
|
||||
// supported shared storage types
|
||||
SHARED_STORAGE = []string{STORAGE_NFS, STORAGE_GPFS, STORAGE_RBD, STORAGE_CLVM, STORAGE_SLVM}
|
||||
@@ -272,4 +273,7 @@ type StorageListInput struct {
|
||||
|
||||
// filter storages of baremetal host
|
||||
IsBaremetal *bool `json:"is_baremetal"`
|
||||
|
||||
// filter by storage type
|
||||
StorageType string `json:"storage_type"`
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ const (
|
||||
IMAGE_STATUS_DELETED = "deleted"
|
||||
IMAGE_STATUS_PENDING_DELETE = "pending_delete"
|
||||
|
||||
IMAGE_STATUS_UNKNOWN = "unkown"
|
||||
|
||||
IMAGE_STATUS_SYNC_CLASS_METADATA_FAILEd = "sync_class_metadata_failed"
|
||||
|
||||
ImageTypeTemplate = TImageType("image")
|
||||
|
||||
@@ -168,6 +168,8 @@ type ModelBaseListInput struct {
|
||||
ExportKeys string `json:"export_keys" help:"Export field keys"`
|
||||
// 返回结果携带delete_fail_reason和update_fail_reason字段
|
||||
ShowFailReason *bool `json:"show_fail_reason"`
|
||||
// 是否返回状态统计信息,默认为False
|
||||
SummaryStats *bool `json:"summary_stats"`
|
||||
}
|
||||
|
||||
func (o ModelBaseListInput) GetExportKeys() string {
|
||||
|
||||
@@ -45,7 +45,7 @@ type AlertRecordListInput struct {
|
||||
type AlertRecordDetails struct {
|
||||
SAlertRecord
|
||||
|
||||
apis.StatusStandaloneResourceDetails
|
||||
apis.StandaloneAnonResourceDetails
|
||||
apis.ScopedResourceBaseInfo
|
||||
|
||||
ResNum int64 `json:"res_num"`
|
||||
|
||||
@@ -39,9 +39,10 @@ type MonitorResourceListInput struct {
|
||||
apis.EnabledResourceBaseListInput
|
||||
compute.ManagedResourceListInput
|
||||
|
||||
ResId []string `json:"res_id"`
|
||||
ResType string `json:"res_type"`
|
||||
OnlyResId bool `json:"only_res_id"`
|
||||
ResId []string `json:"res_id"`
|
||||
ResType string `json:"res_type"`
|
||||
OnlyResId bool `json:"only_res_id"`
|
||||
AlertStates []string `json:"alert_states"`
|
||||
|
||||
ResName string `json:"res_name"`
|
||||
}
|
||||
|
||||
@@ -19,10 +19,12 @@ type NotificationTemplateCreateInput struct {
|
||||
}
|
||||
|
||||
type NotificationTemplateConfig struct {
|
||||
Title string `json:"title"`
|
||||
Name string `json:"name"`
|
||||
ResourceName string `json:"resource_name"`
|
||||
Matches []*EvalMatch `json:"matches"`
|
||||
Title string `json:"title"`
|
||||
Name string `json:"name"`
|
||||
ResourceName string `json:"resource_name"`
|
||||
Matches []*EvalMatch `json:"matches"`
|
||||
MatchTags []map[string]string `json:"match_tags"`
|
||||
MatchTagsStr []string `json:"match_tags_str"`
|
||||
// PrevAlertState AlertStateType `json:"prev_alert_state"`
|
||||
// State AlertStateType `json:"state"`
|
||||
NoDataFound bool `json:"no_data"`
|
||||
|
||||
@@ -128,11 +128,12 @@ type TimeSeriesSlice []*TimeSeries
|
||||
|
||||
type TimeSeries struct {
|
||||
// RawName is used to frontend displaying the curve name
|
||||
RawName string `json:"raw_name"`
|
||||
Columns []string `json:"columns"`
|
||||
Name string `json:"name"`
|
||||
Points TimeSeriesPoints `json:"points"`
|
||||
Tags map[string]string `json:"tags,omitempty"`
|
||||
RawName string `json:"raw_name"`
|
||||
Columns []string `json:"columns"`
|
||||
Name string `json:"name"`
|
||||
Points TimeSeriesPoints `json:"points"`
|
||||
Tags map[string]string `json:"tags,omitempty"`
|
||||
CloudTags map[string]string `json:"cloud_tags,omitempty"`
|
||||
}
|
||||
|
||||
type TimePoint []interface{}
|
||||
|
||||
@@ -29,11 +29,11 @@ type SAlert struct {
|
||||
apis.SStatusStandaloneResourceBase
|
||||
SMonitorScopedResource
|
||||
// Frequency is evaluate period
|
||||
Frequency int64 `json:"frequency"`
|
||||
Frequency int64 `json:"frequency"`
|
||||
Settings *AlertSetting `json:"settings"`
|
||||
Level string `json:"level"`
|
||||
Message string `json:"message"`
|
||||
UsedBy string `json:"used_by"`
|
||||
Level string `json:"level"`
|
||||
Message string `json:"message"`
|
||||
UsedBy string `json:"used_by"`
|
||||
// Silenced bool
|
||||
ExecutionError string `json:"execution_error"`
|
||||
// If an alert rule has a configured `For` and the query violates the configured threshold
|
||||
|
||||
@@ -78,6 +78,8 @@ type Application struct {
|
||||
isTLS bool
|
||||
|
||||
enableProfiling bool
|
||||
|
||||
allowTLS1x bool
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -139,6 +141,12 @@ func (app *Application) SetDefaultTimeout(to time.Duration) *Application {
|
||||
return app
|
||||
}
|
||||
|
||||
func (app *Application) AllowTLS1x() *Application {
|
||||
log.Infof("Allow TLS1.0&1.1")
|
||||
app.allowTLS1x = true
|
||||
return app
|
||||
}
|
||||
|
||||
func SplitPath(path string) []string {
|
||||
ret := make([]string, 0)
|
||||
for _, seg := range strings.Split(path, "/") {
|
||||
@@ -361,6 +369,7 @@ func (app *Application) defaultHandle(w http.ResponseWriter, r *http.Request, ri
|
||||
w.Header().Set("Server", "Yunion AppServer/Go/2018.4")
|
||||
w.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
||||
w.Header().Set("X-XSS-Protection", "1; mode=block")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
if app.isTLS {
|
||||
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
|
||||
}
|
||||
@@ -469,6 +478,10 @@ func timeoutHandle(h http.Handler) http.HandlerFunc {
|
||||
}
|
||||
|
||||
func (app *Application) initServer(addr string) *http.Server {
|
||||
return InitHTTPServer(app, addr)
|
||||
}
|
||||
|
||||
func InitHTTPServer(app *Application, addr string) *http.Server {
|
||||
/* db := AppContextDB(app.context)
|
||||
if db != nil {
|
||||
db.SetMaxIdleConns(app.connMax + 1)
|
||||
@@ -478,7 +491,18 @@ func (app *Application) initServer(addr string) *http.Server {
|
||||
|
||||
cipherSuites := []uint16{}
|
||||
for _, suite := range tls.CipherSuites() {
|
||||
cipherSuites = append(cipherSuites, suite.ID)
|
||||
if !strings.HasSuffix(suite.Name, "_SHA") {
|
||||
cipherSuites = append(cipherSuites, suite.ID)
|
||||
}
|
||||
}
|
||||
|
||||
minTLSVer := uint16(tls.VersionTLS12)
|
||||
if app.allowTLS1x {
|
||||
minTLSVer = tls.VersionTLS10
|
||||
}
|
||||
tlsConf := &tls.Config{
|
||||
CipherSuites: cipherSuites,
|
||||
MinVersion: minTLSVer,
|
||||
}
|
||||
|
||||
s := &http.Server{
|
||||
@@ -493,9 +517,7 @@ func (app *Application) initServer(addr string) *http.Server {
|
||||
// issue like: https://github.com/megaease/easegress/issues/481
|
||||
ErrorLog: olog.New(io.Discard, "", olog.LstdFlags),
|
||||
|
||||
TLSConfig: &tls.Config{
|
||||
CipherSuites: cipherSuites,
|
||||
},
|
||||
TLSConfig: tlsConf,
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -603,22 +625,6 @@ func (app *Application) listenAndServeInternal(s *http.Server, certFile, keyFile
|
||||
}
|
||||
}
|
||||
|
||||
func isJsonContentType(r *http.Request) bool {
|
||||
contType := strings.ToLower(r.Header.Get("Content-Type"))
|
||||
if strings.HasPrefix(contType, "application/json") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isFormContentType(r *http.Request) bool {
|
||||
contType := strings.ToLower(r.Header.Get("Content-Type"))
|
||||
if strings.HasPrefix(contType, "application/json") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type TContentType string
|
||||
|
||||
const (
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"yunion.io/x/pkg/util/version"
|
||||
)
|
||||
|
||||
@@ -85,3 +87,26 @@ func profSymbol(_ context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
func profTrace(_ context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
pprof.Trace(w, r)
|
||||
}
|
||||
|
||||
func AddMiscHandlersToMuxRouter(app *Application, root *mux.Router, enableProfiling bool) {
|
||||
adapterF := func(appHandleFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request)) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
appHandleFunc(app.GetContext(), w, r)
|
||||
}
|
||||
}
|
||||
root.HandleFunc("/version", adapterF(VersionHandler))
|
||||
root.HandleFunc("/stats", adapterF(StatisticHandler))
|
||||
root.HandleFunc("/ping", adapterF(PingHandler))
|
||||
root.HandleFunc("/worker_stats", adapterF(WorkerStatsHandler))
|
||||
if enableProfiling {
|
||||
pp := "/debug/pprof"
|
||||
ppPath := func(sufix string) string {
|
||||
return fmt.Sprintf("%s/%s", pp, sufix)
|
||||
}
|
||||
root.HandleFunc(ppPath(""), pprof.Index)
|
||||
root.HandleFunc(ppPath("cmdline"), pprof.Cmdline)
|
||||
root.HandleFunc(ppPath("profile"), pprof.Profile)
|
||||
root.HandleFunc(ppPath("symbol"), pprof.Symbol)
|
||||
root.HandleFunc(ppPath("trace"), pprof.Trace)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,15 +292,14 @@ func (wm *SWorkerManager) scheduleWithLock() {
|
||||
log.Debugf("no enough worker, add new worker %s", worker)
|
||||
}
|
||||
go worker.run()
|
||||
} else if queueSize > 10 {
|
||||
log.Warningf("[%s] BUSY activeWork %d detachedWork %d max %d queue: %d", wm, wm.ActiveWorkerCount(), wm.DetachedWorkerCount(), wm.workerCount, wm.queue.Size())
|
||||
} else if queueSize > 50 {
|
||||
w := wm.activeWorker.list.Front()
|
||||
for w != nil {
|
||||
if w != nil {
|
||||
worker := w.Value.(*SWorker)
|
||||
log.Warningf("work [%s]%s stucking for a while", worker.task.start, worker.task.task.Dump())
|
||||
w = w.Next()
|
||||
}
|
||||
} else if queueSize > 10 {
|
||||
log.Warningf("[%s] BUSY activeWork %d detachedWork %d max %d queue: %d", wm, wm.ActiveWorkerCount(), wm.DetachedWorkerCount(), wm.workerCount, wm.queue.Size())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ func (s *BaremetalService) StartService() {
|
||||
|
||||
s.startAgent(app)
|
||||
|
||||
cron := cronman.InitCronJobManager(false, o.Options.CronJobWorkerCount)
|
||||
cron := cronman.InitCronJobManager(false, o.Options.CronJobWorkerCount, o.Options.TimeZone)
|
||||
cron.AddJobAtIntervals("BaremetalCronJobs", 10*time.Second, baremetal.DoCronJobs)
|
||||
cron.Start()
|
||||
defer cron.Stop()
|
||||
|
||||
@@ -40,6 +40,9 @@ func InitApp(options *common_options.BaseOptions, dbAccess bool) *appsrv.Applica
|
||||
if options.EnableAppProfiling {
|
||||
app.EnableProfiling()
|
||||
}
|
||||
if options.AllowTLS1x {
|
||||
app.AllowTLS1x()
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
|
||||
@@ -124,6 +124,8 @@ func ParseDiskConfig(diskStr string, idx int) (*compute.DiskConfig, error) {
|
||||
diskConfig.Mountpoint = p
|
||||
} else if p == "autoextend" {
|
||||
diskConfig.SizeMb = -1
|
||||
} else if p == "autoreset" {
|
||||
diskConfig.AutoReset = true
|
||||
} else if utils.IsInStringArray(p, compute.STORAGE_TYPES) {
|
||||
diskConfig.Backend = p
|
||||
} else if len(p) > 0 {
|
||||
|
||||
@@ -140,15 +140,22 @@ type SCronJobManager struct {
|
||||
running bool
|
||||
workers *appsrv.SWorkerManager
|
||||
dataLock *sync.Mutex
|
||||
timezone *time.Location
|
||||
}
|
||||
|
||||
func InitCronJobManager(isDbWorker bool, workerCount int) *SCronJobManager {
|
||||
func InitCronJobManager(isDbWorker bool, workerCount int, timezone string) *SCronJobManager {
|
||||
if manager == nil {
|
||||
tz, err := time.LoadLocation(timezone)
|
||||
if err != nil {
|
||||
log.Errorf("InitCronJobManager failed")
|
||||
tz = time.UTC
|
||||
}
|
||||
manager = &SCronJobManager{
|
||||
jobs: make([]*SCronJob, 0),
|
||||
workers: appsrv.NewWorkerManager("CronJobWorkers", workerCount, 1024, isDbWorker),
|
||||
dataLock: new(sync.Mutex),
|
||||
add: make(chan struct{}),
|
||||
timezone: tz,
|
||||
}
|
||||
}
|
||||
return manager
|
||||
@@ -311,7 +318,7 @@ func (self *SCronJobManager) AddJobEveryFewHour(name string, hour, min, sec int,
|
||||
}
|
||||
|
||||
func (self *SCronJobManager) addJob(newJob *SCronJob) {
|
||||
now := time.Now()
|
||||
now := time.Now().In(self.timezone)
|
||||
newJob.Next = newJob.Timer.Next(now)
|
||||
if newJob.StartRun {
|
||||
newJob.runJob(true, now)
|
||||
@@ -375,7 +382,7 @@ func (self *SCronJobManager) Stop() {
|
||||
}
|
||||
|
||||
func (self *SCronJobManager) init() {
|
||||
now := time.Now()
|
||||
now := time.Now().In(self.timezone)
|
||||
self.next(now)
|
||||
heap.Init(&self.jobs)
|
||||
for i := 0; i < len(self.jobs); i += 1 {
|
||||
@@ -388,7 +395,7 @@ func (self *SCronJobManager) init() {
|
||||
|
||||
func (self *SCronJobManager) run(ctx context.Context) {
|
||||
var timer *time.Timer
|
||||
var now = time.Now()
|
||||
var now = time.Now().In(self.timezone)
|
||||
for {
|
||||
self.dataLock.Lock()
|
||||
if len(self.jobs) == 0 || self.jobs[0].Next.IsZero() {
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
)
|
||||
|
||||
func TestSCronJobManager_AddRemoveJobs(t *testing.T) {
|
||||
manager := InitCronJobManager(false, 4)
|
||||
manager := InitCronJobManager(false, 4, "")
|
||||
testFunc := func(ctx context.Context, userCred mcclient.TokenCredential, isStart bool) {}
|
||||
manager.AddJobAtIntervals("Test1", time.Second*100, testFunc)
|
||||
|
||||
|
||||
@@ -1980,6 +1980,9 @@ func deleteItem(manager IModelManager, model IModel, ctx context.Context, userCr
|
||||
|
||||
// 删除后钩子
|
||||
model.PostDelete(ctx, userCred)
|
||||
if err := model.GetModelManager().GetExtraHook().AfterPostDelete(ctx, userCred, model, query); err != nil {
|
||||
logclient.AddActionLogWithContext(ctx, model, logclient.ACT_POST_DELETE_HOOK, err, userCred, false)
|
||||
}
|
||||
|
||||
// 避免设置删除状态没有正常返回
|
||||
jsonutils.Update(details, model)
|
||||
|
||||
@@ -152,6 +152,7 @@ type IModelManager interface {
|
||||
|
||||
type IModelManagerExtraHook interface {
|
||||
AfterPostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, model IModel, query jsonutils.JSONObject, data jsonutils.JSONObject) error
|
||||
AfterPostDelete(ctx context.Context, userCred mcclient.TokenCredential, model IModel, query jsonutils.JSONObject) error
|
||||
}
|
||||
|
||||
type IModel interface {
|
||||
|
||||
@@ -876,3 +876,7 @@ func NewEmptyExtraHook() *SEmptyExtraHook {
|
||||
func (e SEmptyExtraHook) AfterPostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, model IModel, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e SEmptyExtraHook) AfterPostDelete(ctx context.Context, userCred mcclient.TokenCredential, model IModel, query jsonutils.JSONObject) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func NewNameValidator(ctx context.Context, manager IModelManager, ownerId mcclie
|
||||
}
|
||||
|
||||
if strings.ContainsAny(name, forbiddenNameChars) {
|
||||
return errors.Wrapf(errors.ErrInvalidFormat, "name should not contains any of %s", forbiddenNameChars)
|
||||
return errors.Wrapf(errors.ErrInvalidFormat, "name should not contains any of %q", forbiddenNameChars)
|
||||
}
|
||||
|
||||
uniq, err := isNameUnique(ctx, manager, ownerId, name, uniqValues)
|
||||
@@ -113,7 +113,7 @@ func alterNameValidator(ctx context.Context, model IModel, name string) error {
|
||||
}
|
||||
|
||||
if strings.ContainsAny(name, forbiddenNameChars) {
|
||||
return errors.Wrapf(errors.ErrInvalidFormat, "name should not contains any of %s", forbiddenNameChars)
|
||||
return errors.Wrapf(errors.ErrInvalidFormat, "name should not contains any of %q", forbiddenNameChars)
|
||||
}
|
||||
|
||||
uniq, err := isAlterNameUnique(ctx, model, name)
|
||||
|
||||
@@ -265,7 +265,10 @@ func genMsgViaLang(ctx context.Context, p sNotifyParams) ([]npk.SNotifyMessage,
|
||||
topic = p.event
|
||||
}
|
||||
msg.Topic = topic
|
||||
body, _ := getContent(langSuffix, p.event, "content", p.channel, p.data)
|
||||
body, err := getContent(langSuffix, p.event, "content", p.channel, p.data)
|
||||
if err != nil {
|
||||
log.Errorf("get content error: %s", err)
|
||||
}
|
||||
if len(body) == 0 {
|
||||
body, _ = p.data.GetString()
|
||||
}
|
||||
|
||||
@@ -119,6 +119,7 @@ type BaseOptions struct {
|
||||
PlatformNames map[string]string `help:"identity name of this platform by language"`
|
||||
|
||||
EnableAppProfiling bool `help:"enable profiling API" default:"false"`
|
||||
AllowTLS1x bool `help:"allow obsolete insecure TLS V1.0&1.1" default:"false" json:"allow_tls1x"`
|
||||
|
||||
EnableChangeOwnerAutoRename bool `help:"Allows renaming when changing names" default:"false"`
|
||||
EnableDefaultPolicy bool `help:"Enable defualt policies" default:"true"`
|
||||
|
||||
@@ -55,7 +55,7 @@ func StartService() {
|
||||
defer cloudcommon.CloseDB()
|
||||
|
||||
if !opts.IsSlaveNode {
|
||||
cron := cronman.InitCronJobManager(true, options.Options.CronJobWorkerCount)
|
||||
cron := cronman.InitCronJobManager(true, options.Options.CronJobWorkerCount, options.Options.TimeZone)
|
||||
cron.AddJobAtIntervalsWithStartRun("SyncCloudprovider", time.Duration(opts.CloudproviderSyncIntervalMinutes)*time.Minute, models.CloudproviderManager.SyncCloudproviders, true)
|
||||
cron.AddJobAtIntervalsWithStartRun("CloudeventSyncTask", time.Duration(opts.CloudeventSyncIntervalHours)*time.Hour, models.CloudproviderManager.SyncCloudeventTask, true)
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ func StartService() {
|
||||
}
|
||||
|
||||
if !opts.IsSlaveNode {
|
||||
cron := cronman.InitCronJobManager(true, options.Options.CronJobWorkerCount)
|
||||
cron := cronman.InitCronJobManager(true, options.Options.CronJobWorkerCount, options.Options.TimeZone)
|
||||
cron.AddJobAtIntervalsWithStartRun("SyncCloudaccountResources", time.Duration(opts.CloudIdResourceSyncIntervalHours)*time.Hour, models.CloudaccountManager.SyncCloudaccountResources, true)
|
||||
cron.AddJobAtIntervalsWithStartRun("SyncCloudproviderResources", time.Duration(opts.CloudIdResourceSyncIntervalHours)*time.Hour, models.CloudproviderManager.SyncCloudproviderResources, true)
|
||||
|
||||
|
||||
@@ -987,7 +987,7 @@ func (res *SResources) CollectMetrics(ctx context.Context, userCred mcclient.Tok
|
||||
log.Errorf("Get influxdb %s service url: %v", options.Options.SessionEndpointType, err)
|
||||
return
|
||||
}
|
||||
if err := influxdb.SendMetrics(urls, "meter_db", metrics, true); err != nil {
|
||||
if err := influxdb.SendMetrics(urls, "meter_db", metrics, false); err != nil {
|
||||
log.Errorf("SendMetrics to meter_db: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func StartService() {
|
||||
|
||||
res := resources.NewResources()
|
||||
if !opts.IsSlaveNode {
|
||||
cron := cronman.InitCronJobManager(true, options.Options.CronJobWorkerCount)
|
||||
cron := cronman.InitCronJobManager(true, options.Options.CronJobWorkerCount, options.Options.TimeZone)
|
||||
cron.AddJobAtIntervalsWithStartRun("InitResources", time.Duration(opts.ResourcesSyncInterval)*time.Minute, res.Init, true)
|
||||
cron.AddJobAtIntervals("IncrementResources", time.Duration(opts.ResourcesSyncInterval)*time.Minute, res.IncrementSync)
|
||||
cron.AddJobAtIntervals("DecrementResources", time.Duration(opts.ResourcesSyncInterval)*time.Minute, res.DecrementSync)
|
||||
|
||||
@@ -103,8 +103,8 @@ func (self *SAliyunGuestDriver) IsAllowSaveImageOnRunning() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *SAliyunGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
func (self *SAliyunGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
func (self *SAliyunGuestDriver) GetDeployStatus() ([]string, error) {
|
||||
|
||||
@@ -179,7 +179,7 @@ func (self *SAwsGuestDriver) GetRebuildRootStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
func (self *SAwsGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SAwsGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ func (self *SAzureGuestDriver) IsRebuildRootSupportChangeUEFI() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *SAzureGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SAzureGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ func (self *SBaremetalGuestDriver) GetRebuildRootStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_ADMIN}, nil
|
||||
}
|
||||
|
||||
func (self *SBaremetalGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SBaremetalGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return nil, httperrors.NewUnsupportOperationError("Cannot change config for baremtal")
|
||||
}
|
||||
|
||||
|
||||
@@ -125,6 +125,10 @@ func (drv *SBaseGuestDriver) RequestDetachDisk(ctx context.Context, guest *model
|
||||
return nil
|
||||
}
|
||||
|
||||
func (drv *SBaseGuestDriver) RequestChangeBillingType(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
|
||||
return errors.Wrapf(cloudprovider.ErrNotImplemented, "RequestChangeBillingType")
|
||||
}
|
||||
|
||||
func (drv *SBaseGuestDriver) RequestAttachDisk(ctx context.Context, guest *models.SGuest, disk *models.SDisk, task taskman.ITask) error {
|
||||
task.ScheduleRun(nil)
|
||||
return nil
|
||||
@@ -170,8 +174,8 @@ func (drv *SBaseGuestDriver) IsRebuildRootSupportChangeUEFI() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (drv *SBaseGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
return []string{}, fmt.Errorf("This Guest driver dose not implement GetChangeConfigStatus")
|
||||
func (drv *SBaseGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{}, fmt.Errorf("This Guest driver dose not implement GetChangeInstanceTypeStatus")
|
||||
}
|
||||
|
||||
func (drv *SBaseGuestDriver) ValidateDetachDisk(ctx context.Context, userCred mcclient.TokenCredential, guest *models.SGuest, disk *models.SDisk) error {
|
||||
|
||||
@@ -80,7 +80,7 @@ func (self *SCloudpodsGuestDriver) GetRebuildRootStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
func (self *SCloudpodsGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SCloudpodsGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ func (self *SCtyunGuestDriver) GetAttachDiskStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
func (self *SCtyunGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SCtyunGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ func (self *SEcloudGuestDriver) GetAttachDiskStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
func (self *SEcloudGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SEcloudGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ func (self *SESXiGuestDriver) GetAttachDiskStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
func (self *SESXiGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SESXiGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ func (self *SGoogleGuestDriver) GetRebuildRootStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
func (self *SGoogleGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SGoogleGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ func (self *SH3CGuestDriver) GetRebuildRootStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
func (self *SH3CGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SH3CGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ func (self *SHCSGuestDriver) GetRebuildRootStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
func (self *SHCSGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SHCSGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ func (self *SHCSOPGuestDriver) GetRebuildRootStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
func (self *SHCSOPGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SHCSOPGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ func (self *SHuaweiGuestDriver) GetRebuildRootStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
func (self *SHuaweiGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SHuaweiGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ func (self *SHCSOGuestDriver) GetRebuildRootStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
func (self *SHCSOGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SHCSOGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ func (self *SInCloudSphereGuestDriver) GetAttachDiskStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
func (self *SInCloudSphereGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SInCloudSphereGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -443,9 +443,11 @@ func (self *SKVMGuestDriver) RequestChangeVmConfig(ctx context.Context, guest *m
|
||||
body := jsonutils.NewDict()
|
||||
if vcpuCount > int64(guest.VcpuCount) {
|
||||
body.Set("add_cpu", jsonutils.NewInt(addCpu))
|
||||
body.Set("total_cpu", jsonutils.NewInt(int64(guest.VcpuCount)))
|
||||
}
|
||||
if vmemSize > int64(guest.VmemSize) {
|
||||
body.Set("add_mem", jsonutils.NewInt(addMem))
|
||||
body.Set("total_mem", jsonutils.NewInt(int64(guest.VmemSize)))
|
||||
}
|
||||
if taskParams.Contains("cpu_numa_pin") {
|
||||
cpuNumaPin, _ := taskParams.Get("cpu_numa_pin")
|
||||
@@ -606,7 +608,7 @@ func (self *SKVMGuestDriver) GetRebuildRootStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
func (self *SKVMGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SKVMGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -475,6 +475,14 @@ func (drv *SManagedVirtualizedGuestDriver) RequestStartOnHost(ctx context.Contex
|
||||
|
||||
result := jsonutils.NewDict()
|
||||
if ivm.GetStatus() != api.VM_RUNNING {
|
||||
|
||||
if guest.BillingType == billing_api.BILLING_TYPE_POSTPAID && jsonutils.QueryBoolean(task.GetParams(), "auto_prepaid", false) {
|
||||
err = ivm.ChangeBillingType(billing_api.BILLING_TYPE_PREPAID)
|
||||
if err != nil && errors.Cause(err) != cloudprovider.ErrNotImplemented {
|
||||
logclient.AddSimpleActionLog(guest, logclient.ACT_VM_CHANGE_BILLING_TYPE, errors.Wrapf(err, billing_api.BILLING_TYPE_PREPAID), userCred, false)
|
||||
}
|
||||
}
|
||||
|
||||
err := ivm.StartVM(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "StartVM")
|
||||
@@ -936,6 +944,15 @@ func (drv *SManagedVirtualizedGuestDriver) RequestStopOnHost(ctx context.Context
|
||||
if ivm.GetStatus() != api.VM_READY {
|
||||
opts := &cloudprovider.ServerStopOptions{}
|
||||
task.GetParams().Unmarshal(opts)
|
||||
|
||||
// 包年包月实例关机不收费,先转按量付费再关机
|
||||
if opts.StopCharging && guest.BillingType == billing_api.BILLING_TYPE_PREPAID {
|
||||
err = ivm.ChangeBillingType(billing_api.BILLING_TYPE_POSTPAID)
|
||||
if err != nil && errors.Cause(err) != cloudprovider.ErrNotImplemented {
|
||||
logclient.AddSimpleActionLog(guest, logclient.ACT_VM_CHANGE_BILLING_TYPE, errors.Wrapf(err, billing_api.BILLING_TYPE_POSTPAID), task.GetUserCred(), false)
|
||||
}
|
||||
}
|
||||
|
||||
err = ivm.StopVM(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "ivm.StopVM")
|
||||
@@ -952,6 +969,51 @@ func (drv *SManagedVirtualizedGuestDriver) RequestStopOnHost(ctx context.Context
|
||||
return nil
|
||||
}
|
||||
|
||||
func (drv *SManagedVirtualizedGuestDriver) RequestChangeBillingType(ctx context.Context, guest *models.SGuest, task taskman.ITask) error {
|
||||
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
|
||||
ivm, err := guest.GetIVM(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "guest.GetIVM")
|
||||
}
|
||||
billType := ""
|
||||
switch guest.BillingType {
|
||||
case billing_api.BILLING_TYPE_POSTPAID:
|
||||
billType = billing_api.BILLING_TYPE_PREPAID
|
||||
case billing_api.BILLING_TYPE_PREPAID:
|
||||
billType = billing_api.BILLING_TYPE_POSTPAID
|
||||
}
|
||||
err = ivm.ChangeBillingType(billType)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "ChangeBillingType")
|
||||
}
|
||||
err = cloudprovider.Wait(time.Second*5, time.Minute*3, func() (bool, error) {
|
||||
err = ivm.Refresh()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if ivm.GetBillingType() != billType {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Wait vm billing type changed")
|
||||
}
|
||||
_, err = db.Update(guest, func() error {
|
||||
guest.BillingType = ivm.GetBillingType()
|
||||
guest.Status = ivm.GetStatus()
|
||||
guest.ExpiredAt = time.Time{}
|
||||
if guest.BillingType == billing_api.BILLING_TYPE_PREPAID {
|
||||
guest.AutoRenew = ivm.IsAutoRenew()
|
||||
guest.ExpiredAt = ivm.GetExpiredAt()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return nil, err
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (drv *SManagedVirtualizedGuestDriver) RequestSyncstatusOnHost(ctx context.Context, guest *models.SGuest, host *models.SHost, userCred mcclient.TokenCredential, task taskman.ITask) error {
|
||||
taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) {
|
||||
ihost, err := host.GetIHost(ctx)
|
||||
|
||||
@@ -131,7 +131,7 @@ func (self *SNutanixGuestDriver) GetAttachDiskStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
func (self *SNutanixGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SNutanixGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ func (self *SOpenStackGuestDriver) GetRebuildRootStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING, api.VM_REBUILD_ROOT_FAIL}, nil
|
||||
}
|
||||
|
||||
func (self *SOpenStackGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SOpenStackGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ func (self *SProxmoxGuestDriver) GetAttachDiskStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
func (self *SProxmoxGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SProxmoxGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ func (self *SQcloudGuestDriver) GetRebuildRootStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
func (self *SQcloudGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SQcloudGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ func (self *SUCloudGuestDriver) GetAttachDiskStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
func (self *SUCloudGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SUCloudGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ func (self *SVolcengineGuestDriver) GetRebuildRootStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
func (self *SVolcengineGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SVolcengineGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ func (self *SZStackGuestDriver) GetRebuildRootStatus() ([]string, error) {
|
||||
return []string{api.VM_READY}, nil
|
||||
}
|
||||
|
||||
func (self *SZStackGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) {
|
||||
func (self *SZStackGuestDriver) GetChangeInstanceTypeStatus() ([]string, error) {
|
||||
return []string{api.VM_READY, api.VM_RUNNING}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
api "yunion.io/x/onecloud/pkg/apis/billing"
|
||||
billing_api "yunion.io/x/onecloud/pkg/apis/billing"
|
||||
notifyapi "yunion.io/x/onecloud/pkg/apis/notify"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
|
||||
@@ -247,7 +248,21 @@ func fetchExpiredModels(manager db.IModelManager, advanceDay int) ([]IBillingMod
|
||||
upLimit := time.Now().AddDate(0, 0, advanceDay+1)
|
||||
downLimit := time.Now().AddDate(0, 0, advanceDay)
|
||||
v := reflect.MakeSlice(reflect.SliceOf(manager.TableSpec().DataType()), 0, 0)
|
||||
q := manager.Query().LE("expired_at", upLimit).GE("expired_at", downLimit)
|
||||
q := manager.Query()
|
||||
q = q.Filter(
|
||||
sqlchemy.OR(
|
||||
sqlchemy.AND(
|
||||
sqlchemy.Equals(q.Field("billing_type"), billing_api.BILLING_TYPE_POSTPAID),
|
||||
sqlchemy.LE(q.Field("expired_at"), upLimit),
|
||||
sqlchemy.GE(q.Field("expired_at"), downLimit),
|
||||
),
|
||||
// 跳过自动续费实例
|
||||
sqlchemy.AND(
|
||||
sqlchemy.Equals(q.Field("billing_type"), billing_api.BILLING_TYPE_PREPAID),
|
||||
sqlchemy.LE(q.Field("expired_at"), upLimit),
|
||||
sqlchemy.GE(q.Field("expired_at"), downLimit),
|
||||
sqlchemy.IsFalse(q.Field("auto_renew")),
|
||||
)))
|
||||
|
||||
vp := reflect.New(v.Type())
|
||||
vp.Elem().Set(v)
|
||||
|
||||
@@ -866,8 +866,10 @@ func syncZoneStorages(
|
||||
newCacheIds = append(newCacheIds, cachePair)
|
||||
}
|
||||
}
|
||||
if !remoteStorages[i].DisableSync() {
|
||||
syncStorageDisks(ctx, userCred, syncResults, provider, driver, &localStorages[i], remoteStorages[i], syncRange)
|
||||
if syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_COMPUTE) {
|
||||
if !remoteStorages[i].DisableSync() {
|
||||
syncStorageDisks(ctx, userCred, syncResults, provider, driver, &localStorages[i], remoteStorages[i], syncRange)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -974,9 +976,11 @@ func syncZoneHosts(
|
||||
}
|
||||
|
||||
newCachePairs = syncHostStorages(ctx, userCred, syncResults, provider, &localHosts[i], remoteHosts[i], storageCachePairs, syncRange.Xor)
|
||||
syncHostNics(ctx, userCred, syncResults, provider, &localHosts[i], remoteHosts[i])
|
||||
// syncHostWires(ctx, userCred, syncResults, provider, &localHosts[i], remoteHosts[i])
|
||||
syncHostVMs(ctx, userCred, syncResults, provider, driver, &localHosts[i], remoteHosts[i], syncRange)
|
||||
|
||||
if syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_COMPUTE) {
|
||||
syncHostNics(ctx, userCred, syncResults, provider, &localHosts[i], remoteHosts[i])
|
||||
syncHostVMs(ctx, userCred, syncResults, provider, driver, &localHosts[i], remoteHosts[i], syncRange)
|
||||
}
|
||||
}()
|
||||
}
|
||||
return newCachePairs
|
||||
@@ -1596,6 +1600,9 @@ func syncWafIPSets(
|
||||
return remoteRegion.GetICloudWafIPSets()
|
||||
}()
|
||||
if err != nil {
|
||||
if errors.Cause(err) == cloudprovider.ErrNotImplemented || errors.Cause(err) == cloudprovider.ErrNotSupported {
|
||||
return nil
|
||||
}
|
||||
msg := fmt.Sprintf("GetICloudWafIPSets for region %s failed %s", remoteRegion.GetName(), err)
|
||||
log.Errorf(msg)
|
||||
return err
|
||||
@@ -1630,6 +1637,9 @@ func syncWafRegexSets(
|
||||
return remoteRegion.GetICloudWafRegexSets()
|
||||
}()
|
||||
if err != nil {
|
||||
if errors.Cause(err) == cloudprovider.ErrNotImplemented || errors.Cause(err) == cloudprovider.ErrNotSupported {
|
||||
return nil
|
||||
}
|
||||
msg := fmt.Sprintf("GetICloudWafRegexSets for region %s failed %s", remoteRegion.GetName(), err)
|
||||
log.Errorf(msg)
|
||||
return err
|
||||
@@ -1950,6 +1960,9 @@ func syncWafRules(ctx context.Context, userCred mcclient.TokenCredential, syncRe
|
||||
return remoteWafs.GetRules()
|
||||
}()
|
||||
if err != nil {
|
||||
if errors.Cause(err) == cloudprovider.ErrNotImplemented || errors.Cause(err) == cloudprovider.ErrNotSupported {
|
||||
return nil
|
||||
}
|
||||
msg := fmt.Sprintf("GetRules for waf instance %s failed %s", localWaf.Name, err)
|
||||
log.Errorf(msg)
|
||||
return err
|
||||
@@ -2162,12 +2175,13 @@ func syncPublicCloudProviderInfo(
|
||||
if cloudprovider.IsSupportCompute(driver) {
|
||||
if syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_NETWORK) ||
|
||||
syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_NAT) ||
|
||||
syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_IMAGE) ||
|
||||
syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_EIP) {
|
||||
// 需要先同步vpc,避免私有云eip找不到network
|
||||
if !(driver.GetFactory().IsPublicCloud() && !syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_NETWORK)) && syncRange.IsNotSkipSyncResource(VpcManager) {
|
||||
syncRegionVPCs(ctx, userCred, syncResults, provider, localRegion, remoteRegion, syncRange)
|
||||
}
|
||||
if syncRange.IsNotSkipSyncResource(ElasticipManager) {
|
||||
if syncRange.IsNotSkipSyncResource(ElasticipManager) && syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_EIP) {
|
||||
syncRegionEips(ctx, userCred, syncResults, provider, localRegion, remoteRegion, syncRange)
|
||||
}
|
||||
|
||||
@@ -2177,7 +2191,7 @@ func syncPublicCloudProviderInfo(
|
||||
|
||||
}
|
||||
|
||||
if syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_COMPUTE) {
|
||||
if syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_COMPUTE) || syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_IMAGE) {
|
||||
|
||||
for j := 0; j < len(localZones); j += 1 {
|
||||
|
||||
@@ -2311,7 +2325,7 @@ func syncPublicCloudProviderInfo(
|
||||
}
|
||||
}
|
||||
|
||||
if cloudprovider.IsSupportCompute(driver) && syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_COMPUTE) {
|
||||
if cloudprovider.IsSupportCompute(driver) && (syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_COMPUTE) || syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_IMAGE)) {
|
||||
log.Debugf("storageCachePairs count %d", len(storageCachePairs))
|
||||
for i := range storageCachePairs {
|
||||
// always sync private cloud cached images
|
||||
@@ -2397,8 +2411,10 @@ func syncOnPremiseCloudProviderStorage(ctx context.Context, userCred mcclient.To
|
||||
storageCachePairs = append(storageCachePairs, cachePair)
|
||||
}
|
||||
}
|
||||
if !remoteStorages[i].DisableSync() {
|
||||
syncStorageDisks(ctx, userCred, syncResults, provider, driver, &localStorages[i], remoteStorages[i], syncRange)
|
||||
if cloudprovider.IsSupportCompute(driver) && syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_COMPUTE) {
|
||||
if !remoteStorages[i].DisableSync() {
|
||||
syncStorageDisks(ctx, userCred, syncResults, provider, driver, &localStorages[i], remoteStorages[i], syncRange)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -2431,63 +2447,72 @@ func syncOnPremiseCloudProviderInfo(
|
||||
}
|
||||
|
||||
var storageCachePairs []sStoragecacheSyncPair
|
||||
if cloudprovider.IsSupportCompute(driver) && syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_COMPUTE) {
|
||||
remoteVpcs, err := iregion.GetIVpcs()
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("GetIVpcs for provider %s failed %s", provider.GetName(), err)
|
||||
log.Errorf(msg)
|
||||
return err
|
||||
}
|
||||
if cloudprovider.IsSupportCompute(driver) &&
|
||||
(syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_COMPUTE) ||
|
||||
syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_IMAGE) ||
|
||||
syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_NETWORK)) {
|
||||
|
||||
zone, err := getZoneForOnPremiseCloudRegion(ctx, userCred, iregion)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Can't get zone for Premise cloud region %s error: %v", iregion.GetName(), err)
|
||||
log.Errorf(msg)
|
||||
return errors.Wrap(err, "getZoneForOnPremiseCloudRegion")
|
||||
}
|
||||
{
|
||||
// sync wires
|
||||
localVpc := VpcManager.FetchDefaultVpc()
|
||||
syncVpcWires(ctx, userCred, syncResults, provider, localVpc, remoteVpcs[0], zone, syncRange)
|
||||
|
||||
if syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_COMPUTE) || syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_NETWORK) {
|
||||
remoteVpcs, err := iregion.GetIVpcs()
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("GetIVpcs for provider %s failed %s", provider.GetName(), err)
|
||||
log.Errorf(msg)
|
||||
return err
|
||||
}
|
||||
{
|
||||
// sync wires
|
||||
localVpc := VpcManager.FetchDefaultVpc()
|
||||
syncVpcWires(ctx, userCred, syncResults, provider, localVpc, remoteVpcs[0], zone, syncRange)
|
||||
}
|
||||
}
|
||||
|
||||
storageCachePairs = syncOnPremiseCloudProviderStorage(ctx, userCred, syncResults, provider, iregion, driver, zone, syncRange)
|
||||
ihosts, err := func() ([]cloudprovider.ICloudHost, error) {
|
||||
defer syncResults.AddRequestCost(HostManager)()
|
||||
return iregion.GetIHosts()
|
||||
}()
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("GetIHosts for provider %s failed %s", provider.GetName(), err)
|
||||
log.Errorf(msg)
|
||||
return err
|
||||
}
|
||||
|
||||
localHosts, remoteHosts, result := func() ([]SHost, []cloudprovider.ICloudHost, compare.SyncResult) {
|
||||
defer syncResults.AddSqlCost(HostManager)()
|
||||
return HostManager.SyncHosts(ctx, userCred, provider, zone, nil, ihosts, syncRange.Xor)
|
||||
}()
|
||||
|
||||
syncResults.Add(HostManager, result)
|
||||
|
||||
msg := result.Result()
|
||||
notes := fmt.Sprintf("SyncHosts for provider %s result: %s", provider.Name, msg)
|
||||
log.Infof(notes)
|
||||
provider.SyncError(result, notes, userCred)
|
||||
|
||||
for i := 0; i < len(localHosts); i += 1 {
|
||||
if len(syncRange.Host) > 0 && !utils.IsInStringArray(localHosts[i].Id, syncRange.Host) {
|
||||
continue
|
||||
if syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_COMPUTE) {
|
||||
ihosts, err := func() ([]cloudprovider.ICloudHost, error) {
|
||||
defer syncResults.AddRequestCost(HostManager)()
|
||||
return iregion.GetIHosts()
|
||||
}()
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("GetIHosts for provider %s failed %s", provider.GetName(), err)
|
||||
log.Errorf(msg)
|
||||
return err
|
||||
}
|
||||
newCachePairs := syncHostStorages(ctx, userCred, syncResults, provider, &localHosts[i], remoteHosts[i], storageCachePairs, syncRange.Xor)
|
||||
if len(newCachePairs) > 0 {
|
||||
storageCachePairs = append(storageCachePairs, newCachePairs...)
|
||||
|
||||
localHosts, remoteHosts, result := func() ([]SHost, []cloudprovider.ICloudHost, compare.SyncResult) {
|
||||
defer syncResults.AddSqlCost(HostManager)()
|
||||
return HostManager.SyncHosts(ctx, userCred, provider, zone, nil, ihosts, syncRange.Xor)
|
||||
}()
|
||||
|
||||
syncResults.Add(HostManager, result)
|
||||
|
||||
msg := result.Result()
|
||||
notes := fmt.Sprintf("SyncHosts for provider %s result: %s", provider.Name, msg)
|
||||
log.Infof(notes)
|
||||
provider.SyncError(result, notes, userCred)
|
||||
|
||||
for i := 0; i < len(localHosts); i += 1 {
|
||||
if len(syncRange.Host) > 0 && !utils.IsInStringArray(localHosts[i].Id, syncRange.Host) {
|
||||
continue
|
||||
}
|
||||
newCachePairs := syncHostStorages(ctx, userCred, syncResults, provider, &localHosts[i], remoteHosts[i], storageCachePairs, syncRange.Xor)
|
||||
if len(newCachePairs) > 0 {
|
||||
storageCachePairs = append(storageCachePairs, newCachePairs...)
|
||||
}
|
||||
syncHostNics(ctx, userCred, syncResults, provider, &localHosts[i], remoteHosts[i])
|
||||
syncHostVMs(ctx, userCred, syncResults, provider, driver, &localHosts[i], remoteHosts[i], syncRange)
|
||||
}
|
||||
syncHostNics(ctx, userCred, syncResults, provider, &localHosts[i], remoteHosts[i])
|
||||
// syncOnPremiseHostWires(ctx, userCred, syncResults, provider, &localHosts[i], remoteHosts[i])
|
||||
syncHostVMs(ctx, userCred, syncResults, provider, driver, &localHosts[i], remoteHosts[i], syncRange)
|
||||
}
|
||||
}
|
||||
|
||||
if cloudprovider.IsSupportCompute(driver) && syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_COMPUTE) {
|
||||
if cloudprovider.IsSupportCompute(driver) && (syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_COMPUTE) || syncRange.NeedSyncResource(cloudprovider.CLOUD_CAPABILITY_IMAGE)) {
|
||||
log.Debugf("storageCachePairs count %d", len(storageCachePairs))
|
||||
for i := range storageCachePairs {
|
||||
// alway sync on-premise cached images
|
||||
@@ -2509,34 +2534,6 @@ func syncOnPremiseCloudProviderInfo(
|
||||
return nil
|
||||
}
|
||||
|
||||
/*func syncOnPremiseHostWires(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, provider *SCloudprovider, localHost *SHost, remoteHost cloudprovider.ICloudHost) {
|
||||
log.Infof("start to sync OnPremeseHostWires")
|
||||
if provider.Provider != api.CLOUD_PROVIDER_VMWARE {
|
||||
return
|
||||
}
|
||||
func() {
|
||||
defer func() {
|
||||
if syncResults != nil {
|
||||
syncResults.AddSqlCost(NetInterfaceManager)()
|
||||
}
|
||||
}()
|
||||
result := localHost.SyncEsxiHostWires(ctx, userCred, remoteHost)
|
||||
if syncResults != nil {
|
||||
syncResults.Add(NetInterfaceManager, result)
|
||||
}
|
||||
|
||||
msg := result.Result()
|
||||
notes := fmt.Sprintf("SyncEsxiHostWires for host %s result: %s", localHost.Name, msg)
|
||||
if result.IsError() {
|
||||
log.Errorf(notes)
|
||||
return
|
||||
} else {
|
||||
log.Infof(notes)
|
||||
}
|
||||
db.OpsLog.LogEvent(provider, db.ACT_SYNC_HOST_COMPLETE, msg, userCred)
|
||||
}()
|
||||
}*/
|
||||
|
||||
func syncHostNics(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, provider *SCloudprovider, localHost *SHost, remoteHost cloudprovider.ICloudHost) {
|
||||
defer func() {
|
||||
if syncResults != nil {
|
||||
|
||||
@@ -130,6 +130,8 @@ type SDisk struct {
|
||||
Preallocation string `width:"12" default:"off" charset:"ascii" nullable:"true" list:"user" update:"admin" json:"preallocation"`
|
||||
// # is persistent
|
||||
Nonpersistent bool `default:"false" list:"user" json:"nonpersistent"`
|
||||
// auto reset disk after guest shutdown
|
||||
AutoReset bool `default:"false" list:"user" update:"user" json:"auto_reset"`
|
||||
|
||||
// 是否标记为SSD磁盘
|
||||
IsSsd bool `nullable:"false" default:"false" list:"user" update:"user" create:"optional"`
|
||||
@@ -430,6 +432,12 @@ func (self *SDisk) ValidateUpdateData(ctx context.Context, userCred mcclient.Tok
|
||||
}
|
||||
}
|
||||
|
||||
if input.AutoReset != nil && *input.AutoReset != self.AutoReset {
|
||||
if guest := self.GetGuest(); guest != nil && guest.Status != api.VM_READY {
|
||||
return input, httperrors.NewBadRequestError("Can't set disk auto_reset on guest status %s", guest.Status)
|
||||
}
|
||||
}
|
||||
|
||||
storage, _ := self.GetStorage()
|
||||
if storage == nil {
|
||||
return input, httperrors.NewNotFoundError("failed to find storage for disk %s", self.Name)
|
||||
@@ -1384,15 +1392,22 @@ func (self *SDisk) GetCloudprovider() *SCloudprovider {
|
||||
}
|
||||
|
||||
func (self *SDisk) GetPathAtHost(host *SHost) string {
|
||||
usedStorageId := self.StorageId
|
||||
hostStorage := host.GetHoststorageOfId(self.StorageId)
|
||||
if hostStorage != nil {
|
||||
return path.Join(hostStorage.MountPoint, self.Id)
|
||||
} else if len(self.BackupStorageId) > 0 {
|
||||
if hostStorage == nil {
|
||||
hostStorage = host.GetHoststorageOfId(self.BackupStorageId)
|
||||
if hostStorage != nil {
|
||||
return path.Join(hostStorage.MountPoint, self.Id)
|
||||
usedStorageId = self.BackupStorageId
|
||||
}
|
||||
}
|
||||
if hostStorage != nil {
|
||||
storage := StorageManager.FetchStorageById(usedStorageId)
|
||||
hostpath := path.Join(hostStorage.MountPoint, self.Id)
|
||||
if storage != nil && utils.IsInStringArray(storage.StorageType, api.LVM_STORAGE) {
|
||||
hostpath = path.Join("/dev", hostpath)
|
||||
}
|
||||
return hostpath
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,25 @@ type SDnsZone struct {
|
||||
ProductType string `width:"32" charset:"ascii" nullable:"false" list:"domain" create:"domain_optional"`
|
||||
}
|
||||
|
||||
func (self *SDnsZone) GetUniqValues() jsonutils.JSONObject {
|
||||
return jsonutils.Marshal(map[string]string{"manager_id": self.ManagerId})
|
||||
}
|
||||
|
||||
func (manager *SDnsZoneManager) FetchUniqValues(ctx context.Context, data jsonutils.JSONObject) jsonutils.JSONObject {
|
||||
managerId, _ := data.GetString("manager_id")
|
||||
return jsonutils.Marshal(map[string]string{"manager_id": managerId})
|
||||
}
|
||||
|
||||
func (manager *SDnsZoneManager) FilterByUniqValues(q *sqlchemy.SQuery, values jsonutils.JSONObject) *sqlchemy.SQuery {
|
||||
managerId, _ := values.GetString("manager_id")
|
||||
if len(managerId) > 0 {
|
||||
q = q.Equals("manager_id", managerId)
|
||||
} else {
|
||||
q = q.IsNullOrEmpty("manager_id")
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
// 创建
|
||||
func (manager *SDnsZoneManager) ValidateCreateData(
|
||||
ctx context.Context,
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/gotypes"
|
||||
"yunion.io/x/pkg/tristate"
|
||||
"yunion.io/x/pkg/util/billing"
|
||||
"yunion.io/x/pkg/util/httputils"
|
||||
@@ -1665,6 +1666,9 @@ func (self *SGuest) PerformRebuildRoot(
|
||||
}
|
||||
|
||||
diskCat := self.CategorizeDisks()
|
||||
if gotypes.IsNil(diskCat.Root) {
|
||||
return nil, httperrors.NewInputParameterError("no root disk is found")
|
||||
}
|
||||
if img.MinDiskMB == 0 || img.Status != imageapi.IMAGE_STATUS_ACTIVE {
|
||||
return nil, httperrors.NewInputParameterError("invlid image")
|
||||
}
|
||||
@@ -2965,15 +2969,7 @@ func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.T
|
||||
return nil, httperrors.NewBadRequestError("Guest have backup not allow to change config")
|
||||
}
|
||||
|
||||
changeStatus, err := self.GetDriver().GetChangeConfigStatus(self)
|
||||
if err != nil {
|
||||
return nil, httperrors.NewInputParameterError("%v", err)
|
||||
}
|
||||
if !utils.IsInStringArray(self.Status, changeStatus) {
|
||||
return nil, httperrors.NewInvalidStatusError("Cannot change config in %s for %s, requires %s", self.Status, self.GetHypervisor(), changeStatus)
|
||||
}
|
||||
|
||||
_, err = self.GetHost()
|
||||
_, err := self.GetHost()
|
||||
if err != nil {
|
||||
return nil, httperrors.NewInvalidStatusError("no valid host")
|
||||
}
|
||||
@@ -2983,6 +2979,16 @@ func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.T
|
||||
return nil, errors.Wrap(err, "ValidateGuestChangeConfigInput")
|
||||
}
|
||||
|
||||
if confs.CpuChanged() || confs.MemChanged() || confs.InstanceTypeChanged() {
|
||||
changeStatus, err := self.GetDriver().GetChangeInstanceTypeStatus()
|
||||
if err != nil {
|
||||
return nil, httperrors.NewInputParameterError("%v", err)
|
||||
}
|
||||
if !utils.IsInStringArray(self.Status, changeStatus) {
|
||||
return nil, httperrors.NewInvalidStatusError("Cannot change config in %s for %s, requires %s", self.Status, self.GetHypervisor(), changeStatus)
|
||||
}
|
||||
}
|
||||
|
||||
if self.PowerStates == api.VM_POWER_STATES_ON && (confs.CpuChanged() || confs.MemChanged()) {
|
||||
confs, err = self.GetDriver().ValidateGuestHotChangeConfigInput(ctx, self, confs)
|
||||
if err != nil {
|
||||
@@ -2990,6 +2996,16 @@ func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.T
|
||||
}
|
||||
}
|
||||
|
||||
if len(confs.Create) > 0 {
|
||||
attachStatus, err := self.GetDriver().GetAttachDiskStatus()
|
||||
if err != nil {
|
||||
return nil, httperrors.NewInputParameterError("%v", err)
|
||||
}
|
||||
if !utils.IsInStringArray(self.Status, attachStatus) {
|
||||
return nil, httperrors.NewInvalidStatusError("Cannot attach disk in %s for %s, requires %s", self.Status, self.GetHypervisor(), attachStatus)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("%s", jsonutils.Marshal(confs).String())
|
||||
|
||||
pendingUsage := &SQuota{}
|
||||
@@ -5793,7 +5809,21 @@ func (self *SGuest) PerformChangeDiskStorage(ctx context.Context, userCred mccli
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create a disk on target storage from source disk
|
||||
{
|
||||
copyInput := api.ServerCopyDiskToStorageInput{
|
||||
KeepOriginDisk: input.KeepOriginDisk,
|
||||
GuestRunning: self.Status == api.VM_RUNNING,
|
||||
}
|
||||
err := self.CopyDiskToStorage(ctx, userCred, srcDisk, storage, copyInput, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "CopyDiskToStorage")
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (guest *SGuest) CopyDiskToStorage(ctx context.Context, userCred mcclient.TokenCredential, srcDisk *SDisk, storage *SStorage, input api.ServerCopyDiskToStorageInput, parentTaskId string) error {
|
||||
diskConf := &api.DiskConfig{
|
||||
Index: -1,
|
||||
ImageId: srcDisk.TemplateId,
|
||||
@@ -5802,19 +5832,39 @@ func (self *SGuest) PerformChangeDiskStorage(ctx context.Context, userCred mccli
|
||||
DiskType: srcDisk.DiskType,
|
||||
}
|
||||
|
||||
targetDisk, err := self.CreateDiskOnStorage(ctx, userCred, storage, diskConf, nil, true, true)
|
||||
targetDisk, err := guest.CreateDiskOnStorage(ctx, userCred, storage, diskConf, nil, true, true)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Create target disk on storage %s", storage.GetName())
|
||||
return errors.Wrapf(err, "Create target disk on storage %s", storage.GetName())
|
||||
}
|
||||
|
||||
{
|
||||
err := db.InheritFromTo(ctx, userCred, srcDisk, targetDisk)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Inherit class metadata from src %s to target %s", srcDisk.GetName(), targetDisk.GetName())
|
||||
}
|
||||
}
|
||||
|
||||
internalInput := &api.ServerChangeDiskStorageInternalInput{
|
||||
ServerChangeDiskStorageInput: *input,
|
||||
StorageId: srcDisk.StorageId,
|
||||
TargetDiskId: targetDisk.GetId(),
|
||||
GuestRunning: self.Status == api.VM_RUNNING,
|
||||
ServerChangeDiskStorageInput: api.ServerChangeDiskStorageInput{
|
||||
DiskId: srcDisk.Id,
|
||||
TargetStorageId: storage.Id,
|
||||
KeepOriginDisk: input.KeepOriginDisk,
|
||||
},
|
||||
StorageId: srcDisk.StorageId,
|
||||
TargetDiskId: targetDisk.GetId(),
|
||||
GuestRunning: input.GuestRunning,
|
||||
CloneDiskCount: input.CloneDiskCount,
|
||||
CompletedDiskCount: input.CompletedDiskCount,
|
||||
}
|
||||
|
||||
return nil, self.StartChangeDiskStorageTask(ctx, userCred, internalInput, "")
|
||||
{
|
||||
err := guest.StartChangeDiskStorageTask(ctx, userCred, internalInput, parentTaskId)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "StartChangeDiskStorageTask")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SGuest) StartChangeDiskStorageTask(ctx context.Context, userCred mcclient.TokenCredential, input *api.ServerChangeDiskStorageInternalInput, parentTaskId string) error {
|
||||
@@ -6238,3 +6288,29 @@ func (g *SGuest) PerformSetRootDiskMatcher(ctx context.Context, userCred mcclien
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (g *SGuest) PerformChangeBillingType(ctx context.Context, userCred mcclient.TokenCredential, _ jsonutils.JSONObject, input *api.ServerChangeBillingTypeInput) (jsonutils.JSONObject, error) {
|
||||
if !utils.IsInStringArray(g.Status, []string{api.VM_RUNNING, api.VM_READY}) {
|
||||
return nil, httperrors.NewServerStatusError("Cannot change guest billing type in status %s", g.Status)
|
||||
}
|
||||
if len(input.BillingType) == 0 {
|
||||
return nil, httperrors.NewMissingParameterError("billing_type")
|
||||
}
|
||||
if !utils.IsInStringArray(input.BillingType, []string{billing_api.BILLING_TYPE_POSTPAID, billing_api.BILLING_TYPE_PREPAID}) {
|
||||
return nil, httperrors.NewInputParameterError("invalid billing_type %s", input.BillingType)
|
||||
}
|
||||
if g.BillingType == input.BillingType {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, g.StartChangeBillingTypeTask(ctx, userCred, "")
|
||||
}
|
||||
|
||||
func (self *SGuest) StartChangeBillingTypeTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
|
||||
self.SetStatus(ctx, userCred, api.VM_CHANGE_BILLING_TYPE, "")
|
||||
kwargs := jsonutils.NewDict()
|
||||
task, err := taskman.TaskManager.NewTask(ctx, "GuestChangeBillingTypeTask", self, userCred, kwargs, parentTaskId, "", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return task.ScheduleRun(nil)
|
||||
}
|
||||
|
||||
@@ -379,6 +379,7 @@ func fetchGuestDisksInfo(guestIds []string) map[string][]api.GuestDiskInfo {
|
||||
guestdisks.Field("driver"),
|
||||
guestdisks.Field("cache_mode"),
|
||||
guestdisks.Field("aio_mode"),
|
||||
disks.Field("auto_reset"),
|
||||
storages.Field("medium_type"),
|
||||
storages.Field("storage_type"),
|
||||
guestdisks.Field("iops"),
|
||||
|
||||
@@ -147,6 +147,7 @@ func (manager *SGuestdiskManager) FetchCustomizeColumns(
|
||||
rows[i].Status = disk.Status
|
||||
rows[i].DiskSize = disk.DiskSize
|
||||
rows[i].DiskType = disk.DiskType
|
||||
rows[i].AutoReset = disk.AutoReset
|
||||
storage, _ := disk.GetStorage()
|
||||
if storage != nil {
|
||||
rows[i].StorageType = storage.StorageType
|
||||
@@ -228,6 +229,7 @@ func (self *SGuestdisk) GetDiskJsonDescAtHost(ctx context.Context, host *SHost,
|
||||
desc.Index = self.Index
|
||||
bootIndex := self.BootIndex
|
||||
desc.BootIndex = &bootIndex
|
||||
desc.AutoReset = disk.AutoReset
|
||||
|
||||
if len(disk.SnapshotId) > 0 {
|
||||
needMerge := disk.GetMetadata(ctx, "merge_snapshot", nil)
|
||||
|
||||
@@ -112,6 +112,7 @@ type IGuestDriver interface {
|
||||
RequestStartOnHost(ctx context.Context, guest *SGuest, host *SHost, userCred mcclient.TokenCredential, task taskman.ITask) error
|
||||
|
||||
RequestStopOnHost(ctx context.Context, guest *SGuest, host *SHost, task taskman.ITask, syncStatus bool) error
|
||||
RequestChangeBillingType(ctx context.Context, guest *SGuest, task taskman.ITask) error
|
||||
|
||||
StartDeleteGuestTask(ctx context.Context, userCred mcclient.TokenCredential, guest *SGuest, params *jsonutils.JSONDict, parentTaskId string) error
|
||||
|
||||
@@ -140,7 +141,7 @@ type IGuestDriver interface {
|
||||
GetAttachDiskStatus() ([]string, error)
|
||||
GetRebuildRootStatus() ([]string, error)
|
||||
IsAllowSaveImageOnRunning() bool
|
||||
GetChangeConfigStatus(guest *SGuest) ([]string, error)
|
||||
GetChangeInstanceTypeStatus() ([]string, error)
|
||||
GetDeployStatus() ([]string, error)
|
||||
ValidateResizeDisk(guest *SGuest, disk *SDisk, storage *SStorage) error
|
||||
CanKeepDetachDisk() bool
|
||||
|
||||
@@ -137,7 +137,7 @@ type SHost struct {
|
||||
// 预留CPU大小
|
||||
CpuReserved int `nullable:"true" default:"0" list:"domain" update:"domain" create:"domain_optional"`
|
||||
// CPU超分比
|
||||
CpuCmtbound float32 `nullable:"true" default:"8" list:"domain" create:"domain_optional"`
|
||||
CpuCmtbound float32 `nullable:"true" list:"domain" create:"domain_optional"`
|
||||
// CPUMicrocode
|
||||
CpuMicrocode string `width:"64" charset:"ascii" nullable:"true" get:"domain" update:"domain" create:"domain_optional"`
|
||||
// CPU架构
|
||||
@@ -148,7 +148,7 @@ type SHost struct {
|
||||
// 预留内存大小
|
||||
MemReserved int `nullable:"true" default:"0" list:"domain" update:"domain" create:"domain_optional"`
|
||||
// 内存超分比
|
||||
MemCmtbound float32 `nullable:"true" default:"1" list:"domain" create:"domain_optional"`
|
||||
MemCmtbound float32 `nullable:"true" list:"domain" create:"domain_optional"`
|
||||
// 页大小
|
||||
PageSizeKB int `nullable:"false" default:"4" list:"domain" update:"domain" create:"domain_optional"`
|
||||
EnableNumaAllocate bool `nullable:"true" default:"false" list:"domain" update:"domain" create:"domain_optional"`
|
||||
|
||||
@@ -948,7 +948,7 @@ func (self *SIsolatedDevice) PerformPurge(ctx context.Context, userCred mcclient
|
||||
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)
|
||||
return httperrors.NewBadRequestError("%s: %s", api.ErrMsgIsolatedDeviceUsedByServer, self.GuestId)
|
||||
}
|
||||
iGuest, err := GuestManager.FetchById(self.GuestId)
|
||||
if err != nil {
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/gotypes"
|
||||
"yunion.io/x/pkg/tristate"
|
||||
"yunion.io/x/pkg/util/compare"
|
||||
"yunion.io/x/pkg/util/rbacscope"
|
||||
@@ -88,7 +89,7 @@ type SStorage struct {
|
||||
// example: ssd
|
||||
MediumType string `width:"32" charset:"ascii" nullable:"false" list:"user" update:"domain" create:"domain_required"`
|
||||
// 超售比
|
||||
Cmtbound float32 `nullable:"true" default:"1" list:"domain"`
|
||||
Cmtbound float32 `nullable:"true" list:"domain"`
|
||||
// 存储配置信息
|
||||
StorageConf jsonutils.JSONObject `nullable:"true" get:"domain" list:"domain" update:"domain"`
|
||||
|
||||
@@ -117,11 +118,14 @@ func (self *SStorage) ValidateUpdateData(ctx context.Context, userCred mcclient.
|
||||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
input.StorageConf = jsonutils.NewDict()
|
||||
|
||||
if gotypes.IsNil(input.StorageConf) {
|
||||
input.StorageConf = jsonutils.NewDict()
|
||||
}
|
||||
if self.StorageConf != nil {
|
||||
confs, _ := self.StorageConf.GetMap()
|
||||
for k, v := range confs {
|
||||
if !input.StorageConf.Contains(k) {
|
||||
if input.StorageConf.Contains(k) {
|
||||
continue
|
||||
}
|
||||
input.StorageConf.Set(k, v)
|
||||
@@ -734,9 +738,9 @@ func (self *SStorage) GetUsedCapacity(isReady tristate.TriState) int64 {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SStorage) GetOvercommitBound() float32 {
|
||||
if self.Cmtbound > 0 {
|
||||
return self.Cmtbound
|
||||
func (storage *SStorage) GetOvercommitBound() float32 {
|
||||
if storage.Cmtbound > 0 {
|
||||
return storage.Cmtbound
|
||||
} else {
|
||||
return options.Options.DefaultStorageOvercommitBound
|
||||
}
|
||||
@@ -1484,6 +1488,7 @@ func (self *SStorage) createDisk(ctx context.Context, name string, diskConfig *a
|
||||
disk.Iops = diskConfig.Iops
|
||||
disk.Throughput = diskConfig.Throughput
|
||||
disk.Preallocation = diskConfig.Preallocation
|
||||
disk.AutoReset = diskConfig.AutoReset
|
||||
|
||||
if self.MediumType == api.DISK_TYPE_SSD {
|
||||
disk.IsSsd = true
|
||||
@@ -1712,6 +1717,10 @@ func (manager *SStorageManager) ListItemFilter(
|
||||
q = q.Filter(sqlchemy.In(q.Field("storage_type"), api.STORAGE_LOCAL_TYPES))
|
||||
}
|
||||
|
||||
if len(query.StorageType) > 0 {
|
||||
q = q.Equals("storage_type", query.StorageType)
|
||||
}
|
||||
|
||||
if len(query.SchedtagId) > 0 {
|
||||
schedTag, err := SchedtagManager.FetchByIdOrName(ctx, nil, query.SchedtagId)
|
||||
if err != nil {
|
||||
|
||||
@@ -106,8 +106,7 @@ type ComputeOptions struct {
|
||||
RetentionDaysLimit int `default:"49" help:"Days of snapshot retention, default 49 days"`
|
||||
TimePointsLimit int `default:"1" help:"time point of every days, default 1 point"`
|
||||
|
||||
ServerStatusSyncIntervalMinutes int `default:"5" help:"Interval to sync server status, defualt is 5 minutes"`
|
||||
CloudAccountBatchSyncSize int `default:"10" help:"How many cloud account syncing in a batch"`
|
||||
CloudAccountBatchSyncSize int `default:"10" help:"How many cloud account syncing in a batch"`
|
||||
|
||||
ServerSkuSyncIntervalMinutes int `default:"60" help:"Interval to sync public cloud server skus, defualt is 1 hour"`
|
||||
SkuBatchSync int `default:"5" help:"How many skus can be sync in a batch"`
|
||||
@@ -223,6 +222,15 @@ type ComputeOptions struct {
|
||||
esxi.EsxiOptions
|
||||
|
||||
NetworkAlwaysManualConfig bool `help:"always manually configure network settings" default:"false"`
|
||||
|
||||
ComputeEEOptions
|
||||
}
|
||||
|
||||
type ComputeEEOptions struct {
|
||||
// 快速同步资源状态时间周期
|
||||
ServerStatusSyncIntervalMinutes int `default:"5" help:"Interval to sync server status, defualt is 5 minutes"`
|
||||
// 跳过新增资源同步时间范围
|
||||
SkipServerStatusSyncTimeRange string `help:"Skip server status sync time range example: 08:00-18:00"`
|
||||
}
|
||||
|
||||
type SCapabilityOptions struct {
|
||||
|
||||
@@ -146,7 +146,7 @@ func StartServiceWithJobs(jobs func(cron *cronman.SCronJobManager)) {
|
||||
cronFunc := func() {
|
||||
cachesync.StartTenantCacheSync(opts.TenantCacheExpireSeconds)
|
||||
|
||||
cron := cronman.InitCronJobManager(true, options.Options.CronJobWorkerCount)
|
||||
cron := cronman.InitCronJobManager(true, options.Options.CronJobWorkerCount, options.Options.TimeZone)
|
||||
cron.AddJobAtIntervals("CleanPendingDeleteServers", time.Duration(opts.PendingDeleteCheckSeconds)*time.Second, models.GuestManager.CleanPendingDeleteServers)
|
||||
cron.AddJobAtIntervals("CleanPendingDeleteDisks", time.Duration(opts.PendingDeleteCheckSeconds)*time.Second, models.DiskManager.CleanPendingDeleteDisks)
|
||||
if opts.PrepaidExpireCheck {
|
||||
|
||||
@@ -91,6 +91,10 @@ func (self *SRbdStorageDriver) ValidateCreateData(ctx context.Context, userCred
|
||||
if input.EnableMessengerV2 != nil {
|
||||
enableMessengerV2 = *input.EnableMessengerV2
|
||||
}
|
||||
autoCacheImages := false
|
||||
if input.AutoCacheImages != nil {
|
||||
autoCacheImages = *input.AutoCacheImages
|
||||
}
|
||||
input.StorageConf.Update(
|
||||
jsonutils.Marshal(map[string]interface{}{
|
||||
"mon_host": input.MonHost,
|
||||
@@ -100,6 +104,7 @@ func (self *SRbdStorageDriver) ValidateCreateData(ctx context.Context, userCred
|
||||
"rados_osd_op_timeout": input.RadosOsdOpTimeout,
|
||||
"client_mount_timeout": input.ClientMountTimeout,
|
||||
"enable_messenger_v2": enableMessengerV2,
|
||||
"auto_cache_images": autoCacheImages,
|
||||
}))
|
||||
return nil
|
||||
}
|
||||
@@ -120,6 +125,11 @@ func (self *SRbdStorageDriver) ValidateUpdateData(ctx context.Context, userCred
|
||||
input.UpdateStorageConf = true
|
||||
}
|
||||
|
||||
if input.AutoCacheImages != nil {
|
||||
input.StorageConf.Set("auto_cache_images", jsonutils.NewBool(*input.AutoCacheImages))
|
||||
input.UpdateStorageConf = true
|
||||
}
|
||||
|
||||
if len(input.RbdKey) > 0 {
|
||||
input.StorageConf.Set("key", jsonutils.NewString(strings.Trim(input.RbdKey, " ")))
|
||||
input.UpdateStorageConf = true
|
||||
|
||||
56
pkg/compute/tasks/guest_change_billing_type_task.go
Normal file
56
pkg/compute/tasks/guest_change_billing_type_task.go
Normal file
@@ -0,0 +1,56 @@
|
||||
// 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 tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/compute/models"
|
||||
"yunion.io/x/onecloud/pkg/util/logclient"
|
||||
)
|
||||
|
||||
type GuestChangeBillingTypeTask struct {
|
||||
SGuestBaseTask
|
||||
}
|
||||
|
||||
func init() {
|
||||
taskman.RegisterTask(GuestChangeBillingTypeTask{})
|
||||
}
|
||||
|
||||
func (self *GuestChangeBillingTypeTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) {
|
||||
guest := obj.(*models.SGuest)
|
||||
self.SetStage("OnGuestChangeBillingTypeTaskComplete", nil)
|
||||
drv := guest.GetDriver()
|
||||
err := drv.RequestChangeBillingType(ctx, guest, self)
|
||||
if err != nil {
|
||||
self.OnGuestChangeBillingTypeTaskCompleteFailed(ctx, guest, jsonutils.NewString(err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
func (self *GuestChangeBillingTypeTask) OnGuestChangeBillingTypeTaskComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
|
||||
logclient.AddActionLogWithStartable(self, guest, logclient.ACT_VM_CHANGE_BILLING_TYPE, guest.BillingType, self.UserCred, false)
|
||||
self.SetStageComplete(ctx, nil)
|
||||
}
|
||||
|
||||
func (self *GuestChangeBillingTypeTask) OnGuestChangeBillingTypeTaskCompleteFailed(ctx context.Context, guest *models.SGuest, reason jsonutils.JSONObject) {
|
||||
guest.SetStatus(ctx, self.GetUserCred(), api.VM_CHANGE_BILLING_TYPE_FAILED, "")
|
||||
logclient.AddActionLogWithStartable(self, guest, logclient.ACT_VM_CHANGE_BILLING_TYPE, reason.String(), self.UserCred, false)
|
||||
self.SetStageFailed(ctx, reason)
|
||||
}
|
||||
@@ -381,36 +381,17 @@ func (t *GuestChangeDisksStorageTask) CreateTargetDisk(ctx context.Context, gues
|
||||
return
|
||||
}
|
||||
|
||||
// create a disk on target storage from source disk
|
||||
diskConf := &api.DiskConfig{
|
||||
Index: -1,
|
||||
ImageId: srcDisk.TemplateId,
|
||||
SizeMb: srcDisk.DiskSize,
|
||||
Fs: srcDisk.FsFormat,
|
||||
DiskType: srcDisk.DiskType,
|
||||
}
|
||||
|
||||
targetDisk, err := guest.CreateDiskOnStorage(ctx, t.UserCred, storage, diskConf, nil, true, true)
|
||||
if err != nil {
|
||||
t.TaskFailed(ctx, guest, jsonutils.NewString(fmt.Sprintf("Create target disk on storage %s: %s", storage.GetName(), err)))
|
||||
return
|
||||
}
|
||||
|
||||
internalInput := &api.ServerChangeDiskStorageInternalInput{
|
||||
ServerChangeDiskStorageInput: api.ServerChangeDiskStorageInput{
|
||||
DiskId: srcDisk.Id,
|
||||
TargetStorageId: storage.Id,
|
||||
KeepOriginDisk: input.KeepOriginDisk,
|
||||
},
|
||||
StorageId: srcDisk.StorageId,
|
||||
TargetDiskId: targetDisk.GetId(),
|
||||
GuestRunning: input.GuestRunning,
|
||||
CloneDiskCount: input.DiskCount,
|
||||
CompletedDiskCount: input.DiskCount - len(input.Disks) - 1,
|
||||
}
|
||||
|
||||
if err := guest.StartChangeDiskStorageTask(ctx, t.UserCred, internalInput, t.Id); err != nil {
|
||||
t.TaskFailed(ctx, guest, jsonutils.NewString(err.Error()))
|
||||
return
|
||||
{
|
||||
copyInput := api.ServerCopyDiskToStorageInput{
|
||||
KeepOriginDisk: input.KeepOriginDisk,
|
||||
GuestRunning: input.GuestRunning,
|
||||
CloneDiskCount: input.DiskCount,
|
||||
CompletedDiskCount: input.DiskCount - len(input.Disks) - 1,
|
||||
}
|
||||
err := guest.CopyDiskToStorage(ctx, t.UserCred, srcDisk, storage, copyInput, t.GetTaskId())
|
||||
if err != nil {
|
||||
t.TaskFailed(ctx, guest, jsonutils.NewString(fmt.Sprintf("CopyDiskToStorage fail: %s", err)))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ func (self *SecurityGroupRuleUpdateTask) OnInit(ctx context.Context, obj db.ISta
|
||||
}
|
||||
|
||||
if len(rule.ExternalId) == 0 {
|
||||
self.taskFailed(ctx, secgroup, errors.Wrapf(err, "empty external id"))
|
||||
self.taskFailed(ctx, secgroup, errors.Errorf("empty external id"))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/cronman"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/devtool/options"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules/ansible"
|
||||
@@ -111,7 +112,7 @@ func AddOneCronjob(item *SCronjob, s *mcclient.ClientSession) error {
|
||||
}
|
||||
|
||||
func InitializeCronjobs(ctx context.Context) error {
|
||||
DevToolCronManager = cronman.InitCronJobManager(true, 8)
|
||||
DevToolCronManager = cronman.InitCronJobManager(true, 8, options.Options.TimeZone)
|
||||
DevToolCronManager.Start()
|
||||
Session := auth.GetAdminSession(ctx, "")
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ func (ea *SEsxiAgent) Start() error {
|
||||
ea.AgentStorage = storageman.NewAgentStorage(&storageman.SStorageManager{LocalStorageImagecacheManager: ea.CacheManager},
|
||||
ea, options.Options.AgentTempPath)
|
||||
|
||||
cronManager := cronman.InitCronJobManager(false, options.Options.CronJobWorkerCount)
|
||||
cronManager := cronman.InitCronJobManager(false, options.Options.CronJobWorkerCount, options.Options.TimeZone)
|
||||
err = cronManager.AddJobEveryFewDays(
|
||||
"CleanRecycleDiskFiles", 1, 3, 0, 0, storageman.CleanRecycleDiskfiles, false)
|
||||
if err != nil {
|
||||
|
||||
@@ -20,43 +20,36 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pierrec/lz4/v4"
|
||||
|
||||
execlient "yunion.io/x/executor/client"
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/appctx"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
app_common "yunion.io/x/onecloud/pkg/cloudcommon/app"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
|
||||
common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
"yunion.io/x/onecloud/pkg/util/procutils"
|
||||
"yunion.io/x/onecloud/pkg/util/qemuimg"
|
||||
"yunion.io/x/onecloud/pkg/util/seclib2"
|
||||
)
|
||||
|
||||
type SHostImageOptions struct {
|
||||
common_options.CommonOptions
|
||||
LocalImagePath []string `help:"Local Image Paths"`
|
||||
LVMVolumeGroups []string `help:"LVM Volume Groups(vgs)"`
|
||||
SnapshotDirSuffix string `help:"Snapshot dir name equal diskId concat snapshot dir suffix" default:"_snap"`
|
||||
CommonConfigFile string `help:"common config file for container"`
|
||||
StreamChunkSize int `help:"Download stream chunk size KB" default:"4096"`
|
||||
Lz4ChecksumOff bool `help:"Turn off lz4 checksum option"`
|
||||
common_options.HostCommonOptions
|
||||
LocalImagePath []string `help:"Local Image Paths"`
|
||||
LVMVolumeGroups []string `help:"LVM Volume Groups(vgs)"`
|
||||
SnapshotDirSuffix string `help:"Snapshot dir name equal diskId concat snapshot dir suffix" default:"_snap"`
|
||||
HostImageNbdPidDir string `help:"Host-image nbd pid files dir " default:"/var/run/onecloud/host-image"`
|
||||
CommonConfigFile string `help:"common config file for container"`
|
||||
}
|
||||
|
||||
var (
|
||||
HostImageOptions SHostImageOptions
|
||||
streamingWorkerMan *appsrv.SWorkerManager
|
||||
HostImageOptions SHostImageOptions
|
||||
nbdExportManager *SNbdExportManager
|
||||
)
|
||||
|
||||
func init() {
|
||||
streamingWorkerMan = appsrv.NewWorkerManager("streaming_worker", 20, 1024, false)
|
||||
}
|
||||
|
||||
func StartService() {
|
||||
consts.SetServiceType("host-image")
|
||||
common_options.ParseOptions(&HostImageOptions, os.Args, "host.conf", "host-image")
|
||||
@@ -68,6 +61,19 @@ func StartService() {
|
||||
HostImageOptions.CommonOptions = *commonCfg
|
||||
HostImageOptions.BaseOptions.BaseOptions = baseOpt
|
||||
}
|
||||
log.Infof("exec socket path: %s", HostImageOptions.ExecutorSocketPath)
|
||||
if HostImageOptions.EnableRemoteExecutor {
|
||||
execlient.Init(HostImageOptions.ExecutorSocketPath)
|
||||
execlient.SetTimeoutSeconds(HostImageOptions.ExecutorConnectTimeoutSeconds)
|
||||
procutils.SetRemoteExecutor()
|
||||
}
|
||||
|
||||
nbdExportManager = NewNbdExportManager()
|
||||
output, err := procutils.NewCommand("mkdir", "-p", HostImageOptions.HostImageNbdPidDir).Output()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create path %s: %s %s", HostImageOptions.HostImageNbdPidDir, output, err)
|
||||
}
|
||||
|
||||
HostImageOptions.EnableSsl = false
|
||||
HostImageOptions.Port += 40000
|
||||
app_common.InitAuth(&HostImageOptions.CommonOptions, func() {
|
||||
@@ -79,27 +85,23 @@ func StartService() {
|
||||
}
|
||||
|
||||
func initHandlers(app *appsrv.Application, prefix string) {
|
||||
app.AddHandler("GET", fmt.Sprintf("%s/disks/<sid>", prefix), getImage).
|
||||
SetProcessNoTimeout().SetWorkerManager(streamingWorkerMan)
|
||||
app.AddHandler("GET", fmt.Sprintf("%s/snapshots/<diskId>/<sid>", prefix), getImage).
|
||||
SetProcessNoTimeout().SetWorkerManager(streamingWorkerMan)
|
||||
app.AddHandler("POST", fmt.Sprintf("%s/disks/<sid>/nbd-export", prefix), auth.Authenticate(imageNbdExport))
|
||||
app.AddHandler("POST", fmt.Sprintf("%s/snapshots/<diskId>/<sid>/nbd-export", prefix), auth.Authenticate(imageNbdExport))
|
||||
|
||||
app.AddHandler("HEAD", fmt.Sprintf("%s/disks/<sid>", prefix), getImageMeta)
|
||||
app.AddHandler("HEAD", fmt.Sprintf("%s/snapshots/<diskId>/<sid>", prefix), getImageMeta)
|
||||
app.AddHandler("POST", fmt.Sprintf("%s/disks/<sid>", prefix), closeImage)
|
||||
app.AddHandler("POST", fmt.Sprintf("%s/snapshots/<diskId>/<sid>", prefix), closeImage)
|
||||
app.AddHandler("POST", fmt.Sprintf("%s/disks/<sid>/nbd-close", prefix), auth.Authenticate(imageNbdClose))
|
||||
app.AddHandler("POST", fmt.Sprintf("%s/snapshots/<diskId>/<sid>/nbd-close", prefix), auth.Authenticate(imageNbdClose))
|
||||
}
|
||||
|
||||
func getDiskPath(diskId string) string {
|
||||
for _, imagePath := range HostImageOptions.LocalImagePath {
|
||||
diskPath := path.Join(imagePath, diskId)
|
||||
if _, err := os.Stat(diskPath); !os.IsNotExist(err) {
|
||||
if _, err := procutils.RemoteStat(diskPath); err == nil {
|
||||
return diskPath
|
||||
}
|
||||
}
|
||||
for _, vg := range HostImageOptions.LVMVolumeGroups {
|
||||
diskPath := path.Join("/dev", vg, diskId)
|
||||
if _, err := os.Stat(diskPath); !os.IsNotExist(err) {
|
||||
if _, err := procutils.RemoteStat(diskPath); err == nil {
|
||||
return diskPath
|
||||
}
|
||||
}
|
||||
@@ -110,244 +112,79 @@ func getSnapshotPath(diskId, snapshotId string) string {
|
||||
for _, imagePath := range HostImageOptions.LocalImagePath {
|
||||
diskPath := path.Join(imagePath, "snapshots",
|
||||
diskId+HostImageOptions.SnapshotDirSuffix, snapshotId)
|
||||
if _, err := os.Stat(diskPath); !os.IsNotExist(err) {
|
||||
if _, err := procutils.RemoteStat(diskPath); err == nil {
|
||||
return diskPath
|
||||
}
|
||||
}
|
||||
for _, vg := range HostImageOptions.LVMVolumeGroups {
|
||||
diskPath := path.Join("/dev", vg, "snap_"+snapshotId)
|
||||
if _, err := os.Stat(diskPath); !os.IsNotExist(err) {
|
||||
if _, err := procutils.RemoteStat(diskPath); err == nil {
|
||||
return diskPath
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func inputCheck(ctx context.Context) (string, error) {
|
||||
var params = appctx.AppContextParams(ctx)
|
||||
func inputCheck(ctx context.Context, w http.ResponseWriter, r *http.Request) (string, string, error) {
|
||||
params, _, body := appsrv.FetchEnv(ctx, w, r)
|
||||
var sid = params["<sid>"]
|
||||
var imagePath string
|
||||
var remoteDiskId string
|
||||
|
||||
remoteDiskId, _ = body.GetString("disk_id")
|
||||
if remoteDiskId == "" {
|
||||
return "", "", httperrors.NewMissingParameterError("disk_id")
|
||||
}
|
||||
|
||||
if diskId, ok := params["<diskId>"]; ok {
|
||||
imagePath = getSnapshotPath(diskId, sid)
|
||||
} else {
|
||||
imagePath = getDiskPath(sid)
|
||||
}
|
||||
if len(imagePath) == 0 {
|
||||
return "", httperrors.NewNotFoundError("Disk not found")
|
||||
return "", "", httperrors.NewNotFoundError("Disk not found")
|
||||
}
|
||||
return imagePath, nil
|
||||
return imagePath, remoteDiskId, nil
|
||||
}
|
||||
|
||||
func parseRange(reqRange string) (int64, int64, error) {
|
||||
if !strings.HasPrefix(reqRange, "bytes=") {
|
||||
return 0, 0, httperrors.NewInputParameterError("Invalid range header")
|
||||
}
|
||||
reqRange = reqRange[len("bytes="):]
|
||||
ranges := strings.Split(reqRange, "-")
|
||||
if len(ranges) != 2 {
|
||||
return 0, 0, httperrors.NewInputParameterError("Invalid range header")
|
||||
}
|
||||
startPos, err := strconv.ParseInt(ranges[0], 10, 0)
|
||||
if err != nil {
|
||||
return 0, 0, httperrors.NewInputParameterError("Invalid range header")
|
||||
}
|
||||
endPos, err := strconv.ParseInt(ranges[1], 10, 0)
|
||||
if err != nil {
|
||||
return 0, 0, httperrors.NewInputParameterError("Invalid range header")
|
||||
}
|
||||
return startPos, endPos, nil
|
||||
}
|
||||
|
||||
func closeImage(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
imagePath, err := inputCheck(ctx)
|
||||
func imageNbdExport(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
imagePath, targetDiskId, err := inputCheck(ctx, w, r)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var f IImage
|
||||
if r.Header.Get("X-Read-File") == "true" {
|
||||
f = &SFile{}
|
||||
} else {
|
||||
f = &SQcow2Image{}
|
||||
imageInfo := qemuimg.SImageInfo{
|
||||
Path: imagePath,
|
||||
}
|
||||
err = f.Load(imagePath, true, false)
|
||||
encryptKey := r.Header.Get("X-Encrypt-Key")
|
||||
if len(encryptKey) > 0 {
|
||||
imageInfo.Password = encryptKey
|
||||
imageInfo.EncryptAlg = seclib2.TSymEncAlg(r.Header.Get("X-Encrypt-Alg"))
|
||||
}
|
||||
|
||||
nbdPort, err := nbdExportManager.QemuNbdStartExport(imageInfo, targetDiskId)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
f.Close()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
log.Infof("Image %s request nbd export with port %d", targetDiskId, nbdPort)
|
||||
|
||||
ret := jsonutils.NewDict()
|
||||
ret.Set("nbd_port", jsonutils.NewInt(int64(nbdPort)))
|
||||
appsrv.SendJSON(w, ret)
|
||||
}
|
||||
|
||||
func getImage(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
imagePath, err := inputCheck(ctx)
|
||||
func imageNbdClose(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
_, targetDiskId, err := inputCheck(ctx, w, r)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
f IImage
|
||||
startPos, endPos int64
|
||||
rateLimit int64 = -1
|
||||
encryptInfo *apis.SEncryptInfo
|
||||
)
|
||||
|
||||
if r.Header.Get("X-Read-File") == "true" {
|
||||
f = &SFile{}
|
||||
} else {
|
||||
f = &SQcow2Image{}
|
||||
}
|
||||
|
||||
err = f.Load(imagePath, true, true)
|
||||
if err != nil {
|
||||
encryptKey := r.Header.Get("X-Encrypt-Key")
|
||||
if len(encryptKey) > 0 {
|
||||
encryptInfo = new(apis.SEncryptInfo)
|
||||
encryptInfo.Key = encryptKey
|
||||
encryptInfo.Alg = seclib2.TSymEncAlg(r.Header.Get("X-Encrypt-Alg"))
|
||||
}
|
||||
|
||||
if err = f.Open(imagePath, true, encryptInfo); err != nil {
|
||||
log.Errorf("Open image error: %s", err)
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
endPos = f.Length() - 1
|
||||
reqRange := r.Header.Get("Range")
|
||||
if len(reqRange) > 0 {
|
||||
startPos, endPos, err = parseRange(reqRange)
|
||||
if err != nil {
|
||||
log.Errorf("Parse range error: %s", err)
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
strRateLimit := r.Header.Get("X-Rate-Limit-Mbps")
|
||||
if len(strRateLimit) > 0 {
|
||||
rateLimit, err = strconv.ParseInt(strRateLimit, 10, 0)
|
||||
if err != nil {
|
||||
log.Errorf("Parse ratelimit error: %s", err)
|
||||
httperrors.InvalidInputError(ctx, w, "Invaild rate limit header")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
streamHeader(w, f, startPos, endPos)
|
||||
startStream(w, f, startPos, endPos, rateLimit)
|
||||
}
|
||||
|
||||
func streamHeader(w http.ResponseWriter, f IImage, startPos, endPos int64) {
|
||||
var statusCode = http.StatusOK
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
if startPos > 0 || endPos < f.Length()-1 {
|
||||
statusCode = http.StatusPartialContent
|
||||
w.Header().Set("Content-Range",
|
||||
fmt.Sprintf("bytes %d-%d/%d", startPos, endPos, f.Length()))
|
||||
}
|
||||
w.WriteHeader(statusCode)
|
||||
}
|
||||
|
||||
func startStream(w http.ResponseWriter, f IImage, startPos, endPos, rateLimit int64) {
|
||||
var CHUNK_SIZE int64 = int64(HostImageOptions.StreamChunkSize * 1024)
|
||||
var readSize int64 = CHUNK_SIZE
|
||||
var sendBytes int64
|
||||
var lz4Writer = lz4.NewWriter(w)
|
||||
var startTime = time.Now()
|
||||
|
||||
opts := []lz4.Option{
|
||||
lz4.BlockSizeOption(lz4.Block4Mb),
|
||||
lz4.ConcurrencyOption(-1),
|
||||
lz4.CompressionLevelOption(lz4.Fast),
|
||||
}
|
||||
if HostImageOptions.Lz4ChecksumOff {
|
||||
log.Infof("Turn off lz4 checksum")
|
||||
opts = append(opts,
|
||||
lz4.BlockChecksumOption(false),
|
||||
lz4.ChecksumOption(false),
|
||||
)
|
||||
}
|
||||
if err := lz4Writer.Apply(opts...); err != nil {
|
||||
log.Errorf("lz4Writer.Apply options error: %v", err)
|
||||
goto fail
|
||||
}
|
||||
|
||||
for startPos < endPos {
|
||||
if endPos-startPos < CHUNK_SIZE {
|
||||
readSize = endPos - startPos + 1
|
||||
}
|
||||
buf, err := f.Read(startPos, readSize)
|
||||
if err != nil {
|
||||
log.Errorf("Read image error: %s", err)
|
||||
goto fail
|
||||
}
|
||||
startPos += readSize
|
||||
wSize, err := lz4Writer.Write(buf)
|
||||
if err != nil {
|
||||
log.Errorf("lz4Write error: %s", err)
|
||||
goto fail
|
||||
}
|
||||
sendBytes += int64(wSize)
|
||||
if rateLimit > 0 {
|
||||
tmDelta := time.Now().Sub(startTime)
|
||||
tms := tmDelta.Seconds()
|
||||
vtmDelta := float64(sendBytes*8) / float64(1024.0*1024.0*rateLimit)
|
||||
if vtmDelta > tms {
|
||||
time.Sleep(time.Duration(vtmDelta - tms))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
if err := lz4Writer.Close(); err != nil {
|
||||
log.Errorf("lz4 Close error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func getImageMeta(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
||||
imagePath, err := inputCheck(ctx)
|
||||
err = nbdExportManager.QemuNbdCloseExport(targetDiskId)
|
||||
if err != nil {
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
f IImage
|
||||
encryptInfo *apis.SEncryptInfo
|
||||
)
|
||||
|
||||
if r.Header.Get("X-Read-File") == "true" {
|
||||
f = &SFile{}
|
||||
} else {
|
||||
f = &SQcow2Image{}
|
||||
}
|
||||
|
||||
log.Infof("open image %s", imagePath)
|
||||
err = f.Load(imagePath, true, true)
|
||||
if err != nil {
|
||||
encryptKey := r.Header.Get("X-Encrypt-Key")
|
||||
if len(encryptKey) > 0 {
|
||||
encryptInfo = new(apis.SEncryptInfo)
|
||||
encryptInfo.Key = encryptKey
|
||||
encryptInfo.Alg = seclib2.TSymEncAlg(r.Header.Get("X-Encrypt-Alg"))
|
||||
}
|
||||
|
||||
if err = f.Open(imagePath, true, encryptInfo); err != nil {
|
||||
log.Errorf("Open image error: %s", err)
|
||||
httperrors.GeneralServerError(ctx, w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", f.Length()))
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Accept-Ranges", "bytes")
|
||||
w.WriteHeader(200)
|
||||
log.Infof("Image %s request nbd close export with port", targetDiskId)
|
||||
appsrv.SendStruct(w, map[string]string{"result": "ok"})
|
||||
}
|
||||
|
||||
@@ -1,266 +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 hostimage
|
||||
|
||||
/*
|
||||
#cgo pkg-config: glib-2.0 zlib
|
||||
|
||||
#include "libqemuio.h"
|
||||
#include "qemu/osdep.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
"yunion.io/x/onecloud/pkg/util/qemuimg"
|
||||
)
|
||||
|
||||
var qemuBlkCache sync.Map
|
||||
|
||||
type QemuioBlkDev struct {
|
||||
imagePath string
|
||||
readonly bool
|
||||
encrypted bool
|
||||
refCount int32
|
||||
|
||||
blk *C.struct_QemuioBlk
|
||||
}
|
||||
|
||||
func init() {
|
||||
C.qemuio_init()
|
||||
}
|
||||
|
||||
func (qb *QemuioBlkDev) ReadQcow2(offset int64, count int64) ([]byte, int64) {
|
||||
if qb.blk == nil || offset < 0 || count < 0 {
|
||||
return nil, -1
|
||||
}
|
||||
var b = make([]byte, count)
|
||||
var total = C.int64_t(0)
|
||||
ret := C.read_qcow2(qb.blk, unsafe.Pointer(&b[0]), C.int64_t(offset), C.int64_t(count), &total)
|
||||
if ret < 0 {
|
||||
return nil, int64(ret)
|
||||
} else {
|
||||
return b, int64(total)
|
||||
}
|
||||
}
|
||||
|
||||
func OpenQcow2(disk *qemuimg.SImageInfo, readonly bool) *QemuioBlkDev {
|
||||
key := fmt.Sprintf("%s_%v", disk.Path, readonly)
|
||||
if blk, ok := qemuBlkCache.Load(key); ok {
|
||||
qb := blk.(*QemuioBlkDev)
|
||||
atomic.AddInt32(&qb.refCount, 1)
|
||||
return qb
|
||||
}
|
||||
|
||||
qb := &QemuioBlkDev{
|
||||
imagePath: disk.Path,
|
||||
readonly: readonly,
|
||||
}
|
||||
diskPath := C.CString(disk.Path)
|
||||
imageOpts := C.CString(disk.ImageOptions())
|
||||
|
||||
// sec options
|
||||
var secretOpts *C.char
|
||||
secOpt := disk.SecretOptions()
|
||||
if len(secOpt) > 0 {
|
||||
secretOpts = C.CString(secOpt)
|
||||
qb.encrypted = true
|
||||
}
|
||||
|
||||
// qemu io open image
|
||||
blk := C.open_qcow2(diskPath, imageOpts, secretOpts, C.bool(readonly))
|
||||
|
||||
C.free(unsafe.Pointer(diskPath))
|
||||
C.free(unsafe.Pointer(imageOpts))
|
||||
|
||||
if secretOpts != nil {
|
||||
C.free(unsafe.Pointer(secretOpts))
|
||||
}
|
||||
|
||||
if blk == nil {
|
||||
// failed open qemu image
|
||||
return nil
|
||||
}
|
||||
|
||||
qb.blk = blk
|
||||
qb.refCount = 1
|
||||
qemuBlkCache.Store(key, qb)
|
||||
return qb
|
||||
}
|
||||
|
||||
func LoadQcow2(imagePath string, readonly bool) *QemuioBlkDev {
|
||||
key := fmt.Sprintf("%s_%v", imagePath, readonly)
|
||||
if blk, ok := qemuBlkCache.Load(key); ok {
|
||||
return blk.(*QemuioBlkDev)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qb *QemuioBlkDev) Qcow2GetLength() int64 {
|
||||
if qb.blk == nil {
|
||||
return -1
|
||||
} else {
|
||||
return int64(C.qcow2_get_length(qb.blk))
|
||||
}
|
||||
}
|
||||
|
||||
func (qb *QemuioBlkDev) CloseQcow2() {
|
||||
if qb.blk != nil && atomic.AddInt32(&qb.refCount, -1) == 0 {
|
||||
var secId *C.char
|
||||
if qb.encrypted {
|
||||
secId = C.CString(filepath.Base(qb.imagePath))
|
||||
}
|
||||
|
||||
C.close_qcow2(qb.blk, secId)
|
||||
qemuBlkCache.Delete(fmt.Sprintf("%s_%v", qb.imagePath, qb.readonly))
|
||||
|
||||
if secId != nil {
|
||||
C.free(unsafe.Pointer(secId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type IImage interface {
|
||||
// Open image file and its backing file (if have)
|
||||
Open(imagePath string, readonly bool, encryptInfo *apis.SEncryptInfo) error
|
||||
|
||||
// load opend qcow2 img form qemu blk cache
|
||||
Load(imagePath string, readonly, reference bool) error
|
||||
|
||||
// Close may not really close image file handle, just reudce ref count
|
||||
Close()
|
||||
|
||||
// If return number < 0 indicate read failed
|
||||
Read(offset, count int64) ([]byte, error)
|
||||
|
||||
// Get image file length, not file actual length, it's image virtual size
|
||||
Length() int64
|
||||
}
|
||||
|
||||
type SQcow2Image struct {
|
||||
fd *QemuioBlkDev
|
||||
}
|
||||
|
||||
func (img *SQcow2Image) newQemuImage(imagePath string, encryptInfo *apis.SEncryptInfo) *qemuimg.SImageInfo {
|
||||
info := &qemuimg.SImageInfo{
|
||||
Path: imagePath,
|
||||
}
|
||||
|
||||
if encryptInfo != nil {
|
||||
info.SetSecId(filepath.Base(imagePath))
|
||||
info.Password = encryptInfo.Key
|
||||
info.EncryptAlg = encryptInfo.Alg
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func (img *SQcow2Image) Open(imagePath string, readonly bool, encryptInfo *apis.SEncryptInfo) error {
|
||||
disk := img.newQemuImage(imagePath, encryptInfo)
|
||||
fd := OpenQcow2(disk, readonly)
|
||||
if fd == nil {
|
||||
return fmt.Errorf("open image %s failed", imagePath)
|
||||
} else {
|
||||
img.fd = fd
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (img *SQcow2Image) Load(imagePath string, readonly, reference bool) error {
|
||||
fd := LoadQcow2(imagePath, readonly)
|
||||
if fd == nil {
|
||||
return fmt.Errorf("image %s readonly: %v not found", imagePath, readonly)
|
||||
} else {
|
||||
atomic.AddInt32(&fd.refCount, 1)
|
||||
img.fd = fd
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (img *SQcow2Image) Read(offset, count int64) ([]byte, error) {
|
||||
buf, ret := img.fd.ReadQcow2(offset, count)
|
||||
if ret >= 0 {
|
||||
return buf[:ret], nil
|
||||
} else {
|
||||
return nil, errors.Errorf("failed read data %d", ret)
|
||||
}
|
||||
}
|
||||
|
||||
func (img *SQcow2Image) Close() {
|
||||
img.fd.CloseQcow2()
|
||||
}
|
||||
|
||||
func (img *SQcow2Image) Length() int64 {
|
||||
return img.fd.Qcow2GetLength()
|
||||
}
|
||||
|
||||
type SFile struct {
|
||||
fd *os.File
|
||||
}
|
||||
|
||||
func (f *SFile) Open(imagePath string, readonly bool, encryptInfo *apis.SEncryptInfo) error {
|
||||
var mode = os.O_RDWR
|
||||
if readonly {
|
||||
mode = os.O_RDONLY
|
||||
}
|
||||
fd, err := os.OpenFile(imagePath, mode, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
f.fd = fd
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (f *SFile) Load(imagePath string, readonly, reference bool) error {
|
||||
return fmt.Errorf("File don't support load")
|
||||
}
|
||||
|
||||
func (f *SFile) Read(offset, count int64) ([]byte, error) {
|
||||
if _, err := f.fd.Seek(offset, io.SeekStart); err != nil {
|
||||
log.Errorf("seek file %s", err)
|
||||
return nil, errors.Wrap(err, "seek")
|
||||
}
|
||||
buf := make([]byte, count)
|
||||
r := bufio.NewReader(f.fd)
|
||||
n, err := r.Read(buf)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "read")
|
||||
}
|
||||
return buf[:n], nil
|
||||
}
|
||||
|
||||
func (f *SFile) Close() {
|
||||
f.fd.Close()
|
||||
}
|
||||
|
||||
func (f *SFile) Length() int64 {
|
||||
stat, e := f.fd.Stat()
|
||||
if e != nil {
|
||||
return -1
|
||||
}
|
||||
return stat.Size()
|
||||
}
|
||||
162
pkg/hostimage/nbd.go
Normal file
162
pkg/hostimage/nbd.go
Normal file
@@ -0,0 +1,162 @@
|
||||
// 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 hostimage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/version"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/util/fileutils2"
|
||||
"yunion.io/x/onecloud/pkg/util/netutils2"
|
||||
"yunion.io/x/onecloud/pkg/util/procutils"
|
||||
"yunion.io/x/onecloud/pkg/util/qemuimg"
|
||||
"yunion.io/x/onecloud/pkg/util/qemutils"
|
||||
)
|
||||
|
||||
var EXPORT_NBD_BASE_PORT = 7777
|
||||
var LAST_USED_NBD_SERVER_PORT = 0
|
||||
|
||||
type SNbdExportManager struct {
|
||||
portsLock *sync.Mutex
|
||||
}
|
||||
|
||||
func NewNbdExportManager() *SNbdExportManager {
|
||||
return &SNbdExportManager{
|
||||
portsLock: new(sync.Mutex),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *SNbdExportManager) GetFreePortByBase(basePort int) int {
|
||||
var port = 1
|
||||
for {
|
||||
if netutils2.IsTcpPortUsed("0.0.0.0", basePort+port) {
|
||||
port += 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return port + basePort
|
||||
}
|
||||
|
||||
func (m *SNbdExportManager) GetNBDServerFreePort() int {
|
||||
basePort := EXPORT_NBD_BASE_PORT + LAST_USED_NBD_SERVER_PORT
|
||||
var port = 1
|
||||
for {
|
||||
if netutils2.IsTcpPortUsed("0.0.0.0", basePort+port) {
|
||||
port += 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
LAST_USED_NBD_SERVER_PORT = port
|
||||
if LAST_USED_NBD_SERVER_PORT > 1000 {
|
||||
LAST_USED_NBD_SERVER_PORT = 0
|
||||
}
|
||||
return port + basePort
|
||||
}
|
||||
|
||||
func (m *SNbdExportManager) getQemuNbdVersion() (string, error) {
|
||||
output, err := procutils.NewRemoteCommandAsFarAsPossible(qemutils.GetQemuNbd(), "--version").Output()
|
||||
if err != nil {
|
||||
log.Errorf("qemu-nbd version failed %s %s", output, err.Error())
|
||||
return "", errors.Wrapf(err, "qemu-nbd version failed %s", output)
|
||||
}
|
||||
lines := strings.Split(strings.TrimSpace(string(output)), "\n")
|
||||
if len(lines) > 0 {
|
||||
parts := strings.Split(lines[0], " ")
|
||||
return parts[1], nil
|
||||
}
|
||||
return "", errors.Error("empty version output")
|
||||
}
|
||||
|
||||
func (m *SNbdExportManager) QemuNbdStartExport(imageInfo qemuimg.SImageInfo, diskId string) (int, error) {
|
||||
m.portsLock.Lock()
|
||||
defer m.portsLock.Unlock()
|
||||
|
||||
nbdPort := m.GetNBDServerFreePort()
|
||||
pidFilePath := path.Join(HostImageOptions.HostImageNbdPidDir, fmt.Sprintf("nbd_%s.pid", diskId))
|
||||
|
||||
nbdVer, err := m.getQemuNbdVersion()
|
||||
if err != nil {
|
||||
return -1, errors.Wrap(err, "getQemuNbdVersion")
|
||||
}
|
||||
var cmd []string
|
||||
if imageInfo.Encrypted() {
|
||||
cmd = []string{
|
||||
qemutils.GetQemuNbd(),
|
||||
"--read-only", "--persistent", "-x", diskId, "-p", strconv.Itoa(nbdPort),
|
||||
"--object", imageInfo.SecretOptions(),
|
||||
"--image-opts", imageInfo.ImageOptions(),
|
||||
}
|
||||
} else {
|
||||
cmd = []string{
|
||||
qemutils.GetQemuNbd(),
|
||||
"--read-only", "--persistent", "-x", diskId, "-p", strconv.Itoa(nbdPort),
|
||||
imageInfo.Path,
|
||||
}
|
||||
}
|
||||
cmd = append(cmd, "--pid-file", pidFilePath)
|
||||
if version.GE(nbdVer, "4.0.0") {
|
||||
cmd = append(cmd, "--fork")
|
||||
}
|
||||
cmdStr := strings.Join(cmd, " ")
|
||||
err = procutils.NewRemoteCommandAsFarAsPossible("sh", "-c", cmdStr).Run()
|
||||
if err != nil {
|
||||
log.Errorf("qemu-nbd connect failed %s %s", err.Error())
|
||||
return -1, errors.Wrapf(err, "qemu-nbd connect failed")
|
||||
}
|
||||
return nbdPort, nil
|
||||
}
|
||||
|
||||
func (m *SNbdExportManager) QemuNbdCloseExport(diskId string) error {
|
||||
pidFilePath := path.Join(HostImageOptions.HostImageNbdPidDir, fmt.Sprintf("nbd_%s.pid", diskId))
|
||||
if !m.nbdProcessExist(diskId) {
|
||||
if fileutils2.Exists(pidFilePath) {
|
||||
if err := os.Remove(pidFilePath); err != nil {
|
||||
log.Errorf("failed remove nbd pid file %s", pidFilePath)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if fileutils2.Exists(pidFilePath) {
|
||||
pid, err := fileutils2.FileGetIntContent(pidFilePath)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed get pid of qemu-nbd process %s", pidFilePath)
|
||||
}
|
||||
out, err := procutils.NewRemoteCommandAsFarAsPossible("kill", "-9", strconv.Itoa(pid)).Output()
|
||||
if err != nil {
|
||||
log.Errorf("failed kill nbd export process %s %s", err, out)
|
||||
return errors.Wrapf(err, "kill nbd export failed: %s", out)
|
||||
}
|
||||
|
||||
if err := os.Remove(pidFilePath); err != nil {
|
||||
log.Errorf("failed remove nbd pid file %s", pidFilePath)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SNbdExportManager) nbdProcessExist(diskId string) bool {
|
||||
return procutils.NewRemoteCommandAsFarAsPossible("sh", "-c",
|
||||
fmt.Sprintf("ps -ef | grep [q]emu-nbd | grep %s", diskId)).Run() == nil
|
||||
}
|
||||
@@ -417,7 +417,7 @@ func FormatPartition(path, fs, uuid string) error {
|
||||
// #case fs == "ntfs":
|
||||
// # cmd = []string{"/sbin/mkfs.ntfs"}
|
||||
case fs == "xfs":
|
||||
cmd = []string{"mkfs.xfs", "-f", "-m", "crc=0", "-i", "projid32bit=0", "-n", "ftype=0"}
|
||||
cmd = []string{"mkfs.xfs", "-f", "-m", "crc=0", "-i", "projid32bit=0", "-n", "ftype=1"}
|
||||
cmdUuid = []string{"xfs_admin", "-U", uuid}
|
||||
}
|
||||
|
||||
|
||||
@@ -620,7 +620,15 @@ func (d *QemuBaseDriver) startCmds(
|
||||
|
||||
if sysutils.IsKvmSupport() {
|
||||
cmd += __("-enable-kvm")
|
||||
cmd += __("-cpu host")
|
||||
isCPUIntel := sysutils.IsProcessorIntel()
|
||||
isCPUAMD := sysutils.IsProcessorAmd()
|
||||
if isCPUIntel {
|
||||
cmd += __("-cpu host,vendor=GenuineIntel")
|
||||
} else if isCPUAMD {
|
||||
cmd += __("-cpu host,vendor=AuthenticAMD")
|
||||
} else {
|
||||
cmd += __("-cpu host")
|
||||
}
|
||||
} else {
|
||||
cmd += __("-cpu max")
|
||||
}
|
||||
|
||||
@@ -397,6 +397,13 @@ func NewUsbController(masterbus string, port int) *UsbController {
|
||||
return uc
|
||||
}
|
||||
|
||||
func (m *SGuestMem) GuestNumaNodeCount() int {
|
||||
if m.Mem == nil {
|
||||
return 0
|
||||
}
|
||||
return len(m.Mem.Mems) + 1
|
||||
}
|
||||
|
||||
func NewMemDesc(objType, id string, nodeId *uint16, cpus *string) *SMemDesc {
|
||||
md := &SMemDesc{
|
||||
Object: NewObject(objType, id),
|
||||
|
||||
@@ -140,7 +140,7 @@ func (m *SGuestManager) GuestCreateFromCloudpods(
|
||||
iDisk := storage.CreateDisk(diskId)
|
||||
diskUrl := fmt.Sprintf("http://%s:48885/disks/%s",
|
||||
createConfig.CloudpodsAccessInfo.HostIp, createConfig.CloudpodsAccessInfo.OriginDisksId[i])
|
||||
if err = iDisk.CreateFromImageFuse(ctx, diskUrl, 0, nil); err != nil {
|
||||
if err = iDisk.CreateFromRemoteHostImage(ctx, diskUrl, 0, nil); err != nil {
|
||||
log.Errorf("failed create disk %s from fuse %s", diskUrl, err)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -589,6 +589,14 @@ func guestHotplugCpuMem(ctx context.Context, userCred mcclient.TokenCredential,
|
||||
AddCpuCount: addCpuCount,
|
||||
AddMemSize: addMemSize,
|
||||
}
|
||||
totalMemSize, err := body.Int("total_mem")
|
||||
if err == nil {
|
||||
input.TotalMemSize = &totalMemSize
|
||||
}
|
||||
totalCpuCount, err := body.Int("total_cpu")
|
||||
if err == nil {
|
||||
input.TotalCpuCount = &totalCpuCount
|
||||
}
|
||||
|
||||
if body.Contains("cpu_numa_pin") {
|
||||
cpuNumaPin := make([]schedapi.SCpuNumaPin, 0)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user